48744f05252814691dc53614a53dcc30ce054f94
[yaffs-website] / vendor / drush / drush / includes / drush.inc
1 <?php
2
3 /**
4  * @file
5  * The drush API implementation and helpers.
6  */
7
8 use Drush\Log\Logger;
9 use Drush\Log\LogLevel;
10 use Psr\Log\LoggerInterface;
11
12 /**
13  * @name Error status definitions
14  * @{
15  * Error code definitions for interpreting the current error status.
16  * @see drush_set_error(), drush_get_error(), drush_get_error_log(), drush_cmp_error()
17  */
18
19 /** The command completed successfully. */
20 define('DRUSH_SUCCESS', 0);
21 /** The command could not be completed because the framework has specified errors that have occured. */
22 define('DRUSH_FRAMEWORK_ERROR', 1);
23 /** The command was aborted because the user chose to cancel it at some prompt.
24 This exit code is arbitrarily the same as EX_TEMPFAIL in sysexits.h, although
25 note that shell error codes are distinct from C exit codes, so this alignment
26 not meaningful. */
27 define('DRUSH_EXITCODE_USER_ABORT', 75);
28 /** The command that was executed resulted in an application error,
29 The most commom causes for this is invalid PHP or a broken SSH
30 pipe when using drush_backend_invoke in a distributed manner. */
31 define('DRUSH_APPLICATION_ERROR', 255);
32
33 /**
34  * @} End of "name Error status defintions".
35  */
36
37 /**
38  * The number of bytes in a kilobyte. Copied from Drupal.
39  */
40 define('DRUSH_KILOBYTE', 1024);
41
42 /**
43  * Default amount of time, in seconds, to cache downloads via
44  * drush_download_file(). One day is 86400 seconds.
45  */
46 define('DRUSH_CACHE_LIFETIME_DEFAULT', 86400);
47
48 /**
49  * Include a file, selecting a version specific file if available.
50  *
51  * For example, if you pass the path "/var/drush" and the name
52  * "update" when bootstrapped on a Drupal 6 site it will first check for
53  * the presence of "/var/drush/update_6.inc" in include it if exists. If this
54  * file does NOT exist it will proceed and check for "/var/drush/update.inc".
55  * If neither file exists, it will return FALSE.
56  *
57  * @param $path
58  *   The path you want to search.
59  * @param $name
60  *   The file base name you want to include (not including a version suffix
61  *   or extension).
62  * @param $version
63  *   The version suffix you want to include (could be specific to the software
64  *   or platform your are connecting to) - defaults to the current Drupal core
65  *   major version.
66  * @param $extension
67  *   The extension - defaults to ".inc".
68  *
69  * @return
70  *   TRUE if the file was found and included.
71  */
72 function drush_include($path, $name, $version = NULL, $extension = 'inc') {
73   $name = str_replace('-', '_', $name);
74   $version = ($version) ? $version : drush_drupal_major_version();
75
76   $file = sprintf("%s/%s_%s.%s", $path, $name, $version, $extension);
77   if (file_exists($file)) {
78     include_once($file);
79     return TRUE;
80   }
81   $file = sprintf("%s/%s.%s", $path, $name, $extension);
82   if (file_exists($file)) {
83     include_once($file);
84     return TRUE;
85   }
86
87   return drush_set_error('DRUSH_INCLUDE_NO_PATH', dt('Unable to include file !name!version!extension or !name!extension from !path.', array('!name' => $name, '!version' => $version, '!extension' => $extension)));
88 }
89
90 /**
91  * Provide a version-specific class instance.
92  *
93  * @param $class_name
94  *   The name of the class to instantiate.  Appends the Drupal
95  *   major version number to the end of the class name before instantiation.
96  * @param $constructor_args
97  *   An array of arguments to pass to the class constructor.
98  *
99  * Example wrapper class to instantiate a widget, called with the
100  * arguments for the WIDGET_CLASS constructor:
101  *
102  *  function drush_WIDGET_CLASS_get_class($widgetName, $widgetStyle) {
103  *    retrun drush_get_class('Widget_Class', func_get_args()));
104  *  }
105  */
106
107 function drush_get_class($class_name, $constructor_args = array(), $variations = array()) {
108   if (empty($variations)) {
109     $variations[] = drush_drupal_major_version();
110   }
111   $class_names = is_array($class_name) ? $class_name : array($class_name);
112   foreach ($class_names as $class_name) {
113     for ($i=count($variations); $i >= 0; $i--) {
114       $variant_class_name = $class_name . implode('', array_slice($variations, 0, $i));
115       if (class_exists($variant_class_name)) {
116         $reflectionClass = new ReflectionClass($variant_class_name);
117         return !empty($constructor_args) ? $reflectionClass->newInstanceArgs($constructor_args) : $reflectionClass->newInstanceArgs();
118       }
119     }
120   }
121   // Something bad happenned. TODO Exception?
122   return drush_set_error('DRUSH_GET_CLASS_ERROR', dt('Unable to load class !class', array('!class' => $class_name)));
123 }
124
125 /**
126  * Generate an .ini file. used by archive-dump."
127  *
128  * @param array $ini
129  *   A two dimensional associative array where top level are sections and
130  *   second level are key => value pairs.
131  *
132  * @return string
133  *   .ini formatted text.
134  */
135 function drush_export_ini($ini) {
136   $output = '';
137   foreach ($ini as $section => $pairs) {
138     if ($section) {
139       $output .= "[$section]\n";
140     }
141
142     foreach ($pairs as $k => $v) {
143       if ($v) {
144         $output .= "$k = \"$v\"\n";
145       }
146     }
147   }
148   return $output;
149 }
150
151 /**
152  * Generate code friendly to the Drupal .info format from a structured array.
153  * Mostly copied from http://drupalcode.org/viewvc/drupal/contributions/modules/features/features.export.inc.
154  *
155  * @param $info
156  *   An array or single value to put in a module's .info file.
157  *
158  * @param boolean $integer_keys
159  *   Use integer in keys.
160  *
161  * @param $parents
162  *   Array of parent keys (internal use only).
163  *
164  * @return
165  *   A code string ready to be written to a module's .info file.
166  */
167 function drush_export_info($info, $integer_keys = FALSE, $parents = array()) {
168   $output = '';
169   if (is_array($info)) {
170     foreach ($info as $k => $v) {
171       $child = $parents;
172       $child[] = $k;
173       $output .= drush_export_info($v, $integer_keys, $child);
174     }
175   }
176   else if (!empty($info) && count($parents)) {
177     $line = array_shift($parents);
178     foreach ($parents as $key) {
179       $line .= (!$integer_keys && is_numeric($key)) ? "[]" : "[{$key}]";
180     }
181     $line .=  " = \"{$info}\"\n";
182     return $line;
183   }
184   return $output;
185 }
186
187 /**
188  * Convert a csv string, or an array of items which
189  * may contain csv strings, into an array of items.
190  *
191  * @param $args
192  *   A simple csv string; e.g. 'a,b,c'
193  *   or a simple list of items; e.g. array('a','b','c')
194  *   or some combination; e.g. array('a,b','c') or array('a,','b,','c,')
195  *
196  * @returns array
197  *   A simple list of items (e.g. array('a','b','c')
198  */
199 function _convert_csv_to_array($args) {
200   //
201   // Step 1: implode(',',$args) converts from, say, array('a,','b,','c,') to 'a,,b,,c,'
202   // Step 2: explode(',', ...) converts to array('a','','b','','c','')
203   // Step 3: array_filter(...) removes the empty items
204   // Step 4: array_map(...) trims extra whitespace from each item
205   // (handles csv strings with extra whitespace, e.g. 'a, b, c')
206   //
207   return array_map('trim', array_filter(explode(',', is_array($args) ? implode(',',$args) : $args)));
208 }
209
210 /**
211  * Convert a nested array into a flat array.  Thows away
212  * the array keys, returning only the values.
213  *
214  * @param $args
215  *   An array that may potentially be nested.
216  *   e.g. array('a', array('b', 'c'))
217  *
218  * @returns array
219  *   A simple list of items (e.g. array('a','b','c')
220  */
221 function drush_flatten_array($a) {
222   $result = array();
223   if (!is_array($a)) {
224     return array($a);
225   }
226   foreach ($a as $value) {
227     $result = array_merge($result, drush_flatten_array($value));
228   }
229   return $result;
230 }
231
232 /**
233  * Get the available global options. Used by help command. Command files may
234  * modify this list using hook_drush_help_alter().
235  *
236  * @param boolean $brief
237  *   Return a reduced set of important options. Used by help command.
238  *
239  * @return
240  *   An associative array containing the option definition as the key,
241  *   and a descriptive array for each of the available options.  The array
242  *   elements for each item are:
243  *
244  *     - short-form: The shortcut form for specifying the key on the commandline.
245  *     - propagate: backend invoke will use drush_get_option to propagate this
246  *       option, when set, to any other Drush command that is called.
247  *     - context: The drush context where the value of this item is cached.  Used
248  *       by backend invoke to propagate values set in code.
249  *     - never-post: If TRUE, backend invoke will never POST this item's value
250  *       on STDIN; it will always be sent as a commandline option.
251  *     - never-propagate: If TRUE, backend invoke will never pass this item on
252  *       to the subcommand being executed.
253  *     - local-context-only: Backend invoke will only pass this value on for local calls.
254  *     - merge: For options such as $options['shell-aliases'] that consist of an array
255  *       of items, make a merged array that contains all of the values specified for
256  *       all of the contexts (config files) where the option is defined.  The value is stored in
257  *       the specified 'context', or in a context named after the option itself if the
258  *       context flag is not specified.
259  *       IMPORTANT: When the merge flag is used, the option value must be obtained via
260  *       drush_get_context('option') rather than drush_get_option('option').
261  *     - merge-pathlist: For options such as --include and --config, make a merged list
262  *       of options from all contexts; works like the 'merge' flag, but also handles string
263  *       values separated by the PATH_SEPARATOR.
264  *     - merge-associative: Like 'merge-pathlist', but key values are preserved.
265  *     - propagate-cli-value: Used to tell backend invoke to include the value for
266  *       this item as specified on the cli.  This can either override 'context'
267  *       (e.g., propagate --include from cli value instead of DRUSH_INCLUDE context),
268  *       or for an independent global setting (e.g. --user)
269  *     - description: The help text for this item. displayed by `drush help`.
270  */
271 function drush_get_global_options($brief = FALSE) {
272   $options['root']               = array('short-form' => 'r', 'short-has-arg' => TRUE, 'never-post' => TRUE, 'description' => "Drupal root directory to use (default: current directory).", 'example-value' => 'path');
273   $options['uri']                = array('short-form' => 'l', 'short-has-arg' => TRUE, 'never-post' => TRUE, 'description' => 'URI of the drupal site to use (only needed in multisite environments or when running on an alternate port).', 'example-value' => 'http://example.com:8888');
274   $options['verbose']            = array('short-form' => 'v', 'context' => 'DRUSH_VERBOSE', 'description' => 'Display extra information about the command.');
275   $options['debug']              = array('short-form' => 'd', 'context' => 'DRUSH_DEBUG', 'description' => 'Display even more information, including internal messages.');
276   $options['yes']                = array('short-form' => 'y', 'context' => 'DRUSH_AFFIRMATIVE', 'description' => "Assume 'yes' as answer to all prompts.");
277   $options['no']                 = array('short-form' => 'n', 'context' => 'DRUSH_NEGATIVE', 'description' => "Assume 'no' as answer to all prompts.");
278   $options['simulate']           = array('short-form' => 's', 'context' => 'DRUSH_SIMULATE', 'never-propagate' => TRUE, 'description' => "Simulate all relevant actions (don't actually change the system).");
279   $options['pipe']               = array('short-form' => 'p', 'hidden' => TRUE, 'description' => "Emit a compact representation of the command for scripting.");
280   $options['help']               = array('short-form' => 'h', 'description' => "This help system.");
281
282   if (!$brief) {
283     $options['version']            = array('description' => "Show drush version.");
284     $options['php']                = array('description' => "The absolute path to your PHP interpreter, if not 'php' in the path.", 'example-value' => '/path/to/file', 'never-propagate' => TRUE);
285     $options['interactive']        = array('short-form' => 'ia', 'description' => "Force interactive mode for commands run on multiple targets (e.g. `drush @site1,@site2 cc --ia`).", 'never-propagate' => TRUE);
286     $options['tty']                = array('hidden' => TRUE, 'description' => "Force allocation of tty for remote commands", 'never-propagate' => TRUE);
287     $options['quiet']               = array('short-form' => 'q', 'description' => 'Suppress non-error messages.');
288     $options['include']             = array('short-form' => 'i', 'short-has-arg' => TRUE, 'context' => 'DRUSH_INCLUDE', 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'merge-pathlist' => TRUE, 'description' => "A list of additional directory paths to search for drush commands.", 'example-value' => '/path/dir');
289     $options['exclude']             = array('propagate-cli-value' => TRUE, 'never-post' => TRUE, 'merge-pathlist' => TRUE, 'description' => "A list of files and directory paths to exclude from consideration when searching for drush commandfiles.", 'example-value' => '/path/dir');
290     $options['config']              = array('short-form' => 'c', 'short-has-arg' => TRUE, 'context' => 'DRUSH_CONFIG', 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'merge-pathlist' => TRUE, 'description' => "Specify an additional config file to load. See example.drushrc.php.", 'example-value' => '/path/file');
291     $options['user']                = array('short-form' => 'u', 'short-has-arg' => TRUE, 'propagate-cli-value' => TRUE, 'description' => "Specify a Drupal user to login with. May be a name or a number.", 'example-value' => 'name_or_number');
292     $options['backend']             = array('short-form' => 'b', 'never-propagate' => TRUE, 'description' => "Hide all output and return structured data.");
293     $options['choice']              = array('description' => "Provide an answer to a multiple-choice prompt.", 'example-value' => 'number');
294     $options['variables']           = array('description' => "Comma delimited list of name=value pairs. These values take precedence even over settings.php variable overrides.", 'example-value' => 'foo=bar,baz=yaz');
295     $options['search-depth']        = array('description' => "Control the depth that drush will search for alias files.", 'example-value' => 'number');
296     $options['ignored-modules']     = array('description' => "Exclude some modules from consideration when searching for drush command files.", 'example-value' => 'token,views');
297     $options['no-label']            = array('description' => "Remove the site label that drush includes in multi-site command output (e.g. `drush @site1,@site2 status`).");
298     $options['label-separator']     = array('description' => "Specify the separator to use in multi-site command output (e.g. `drush @sites pm-list --label-separator=',' --format=csv`).");
299     $options['nocolor']             = array('context' => 'DRUSH_NOCOLOR', 'propagate-cli-value' => TRUE, 'description' => "Suppress color highlighting on log messages.");
300     $options['show-passwords']      = array('description' => "Show database passwords in commands that display connection information.");
301     $options['show-invoke']         = array('description' => "Show all function names which could have been called for the current command. See drush_invoke().");
302     $options['watchdog']            = array('description' => "Control logging of Drupal's watchdog() to drush log. Recognized values are 'log', 'print', 'disabled'. Defaults to log. 'print' shows calls to admin but does not add them to the log.", 'example-value' => 'print');
303     $options['cache-default-class'] = array('description' => "A cache backend class that implements CacheInterface. Defaults to JSONCache.", 'example-value' => 'JSONCache');
304     $options['cache-class-<bin>']   = array('description' => "A cache backend class that implements CacheInterface to use for a specific cache bin.", 'example-value' => 'className');
305     $options['early']               = array('description' => "Include a file (with relative or full path) and call the drush_early_hook() function (where 'hook' is the filename). The function is called pre-bootstrap and offers an opportunity to alter the drush bootstrap environment or process (returning FALSE from the function will continue the bootstrap), or return output very rapidly (e.g. from caches). See includes/complete.inc for an example.");
306     $options['alias-path']          = array('context' => 'ALIAS_PATH', 'local-context-only' => TRUE, 'merge-pathlist' => TRUE, 'propagate-cli-value' => TRUE, 'description' => "Specifies the list of paths where drush will search for alias files.", 'example-value' => '/path/alias1:/path/alias2');
307     $options['backup-location']     = array('description' => "Specifies the directory where drush will store backups.", 'example-value' => '/path/to/dir');
308     $options['confirm-rollback']    = array('description' => 'Wait for confirmation before doing a rollback when something goes wrong.');
309     $options['complete-debug']      = array('hidden' => TRUE, 'description' => "Turn on debug mode forf completion code");
310     $options['php-options']         = array('description' => "Options to pass to `php` when running drush.  Only effective when using the drush.launcher script.", 'never-propagate' => TRUE, 'example-value' => '-d error_reporting="E_ALL"');
311     $options['halt-on-error']       = array('propagate-cli-value' => TRUE, 'description' => "Controls error handling of recoverable errors (E_RECOVERABLE_ERROR). --halt-on-error=1: Execution is halted. --halt-on-error=0: Drush execution continues");
312     $options['deferred-sanitization'] = array('hidden' => TRUE, 'description' => "Defer calculating the sanitization operations until after the database has been copied. This is done automatically if the source database is remote.");
313     $options['remote-host']         = array('hidden' => TRUE, 'description' => 'Remote site to execute drush command on. Managed by site alias.');
314     $options['remote-user']         = array('hidden' => TRUE, 'description' => 'User account to use with a remote drush command. Managed by site alias.');
315     $options['remote-os']           = array('hidden' => TRUE, 'description' => 'The operating system used on the remote host. Managed by site alias.');
316     $options['site-list']           = array('hidden' => TRUE, 'description' => 'List of sites to run commands on. Managed by site alias.');
317     $options['reserve-margin']      = array('hidden' => TRUE, 'description' => 'Remove columns from formatted opions. Managed by multi-site command handling.');
318     $options['strict']              = array('propagate' => TRUE, 'description' => 'Return an error on unrecognized options. --strict=0: Allow unrecognized options.  --strict=2: Also return an error on any "warning" log messages.  Optional.  Default is 1.');
319     $options['command-specific']    = array('hidden' => TRUE, 'merge-associative' => TRUE, 'description' => 'Command-specific options.');
320     $options['site-aliases']        = array('hidden' => TRUE, 'merge-associative' => TRUE, 'description' => 'List of site aliases.');
321     $options['shell-aliases']       = array('hidden' => TRUE, 'merge' => TRUE, 'never-propagate' => TRUE, 'description' => 'List of shell aliases.');
322     $options['path-aliases']        = array('hidden' => TRUE, 'never-propagate' => TRUE, 'description' => 'Path aliases from site alias.');
323     $options['ssh-options']         = array('never-propagate' => TRUE, 'description' => 'A string of extra options that will be passed to the ssh command', 'example-value' => '-p 100');
324     $options['editor']              = array('never-propagate' => TRUE, 'description' => 'A string of bash which launches user\'s preferred text editor. Defaults to ${VISUAL-${EDITOR-vi}}.', 'example-value' => 'vi');
325     $options['bg']                  = array('never-propagate' => TRUE, 'description' => 'Run editor in the background. Does not work with editors such as `vi` that run in the terminal. Supresses config-import at the end.');
326     $options['db-url']              = array('hidden' => TRUE, 'description' => 'A Drupal 6 style database URL. Used by various commands.', 'example-value' => 'mysql://root:pass@127.0.0.1/db');
327     $options['drush-coverage']      = array('hidden' => TRUE, 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'description' => 'File to save code coverage data into.');
328     $options['redirect-port']       = array('hidden' => TRUE, 'never-propagate' => TRUE, 'description' => 'Used by the user-login command to specify the redirect port on the local machine; it therefore would not do to pass this to the remote machines.');
329     $options['cache-clear']         = array('propagate' => TRUE, 'description' => 'If 0, Drush skips normal cache clearing; the caller should then clear if needed.', 'example-value' => '0', );
330     $options['local']               = array('propagate' => TRUE, 'description' => 'Don\'t look in global locations for commandfiles, config, and site aliases');
331
332     $command = array(
333         'options' => $options,
334         '#brief' => FALSE,
335       ) + drush_command_defaults('global-options', 'global_options', __FILE__);
336     drush_command_invoke_all_ref('drush_help_alter', $command);
337
338     $options = $command['options'];
339   }
340   return $options;
341 }
342
343 /**
344  * Exits with a message. In general, you should use drush_set_error() instead of
345  * this function. That lets drush proceed with other tasks.
346  * TODO: Exit with a correct status code.
347  */
348 function drush_die($msg = NULL, $status = NULL) {
349   die($msg ? "drush: $msg\n" : '');
350 }
351
352 /**
353  * Check to see if the provided line is a "#!/usr/bin/env drush"
354  * "shebang" script line.
355  */
356 function _drush_is_drush_shebang_line($line) {
357   return ((substr($line,0,2) == '#!') && (strstr($line, 'drush') !== FALSE));
358 }
359
360 /**
361  * Check to see if the provided script file is a "#!/usr/bin/env drush"
362  * "shebang" script line.
363  */
364 function _drush_is_drush_shebang_script($script_filename) {
365   $result = FALSE;
366
367   if (file_exists($script_filename)) {
368     $fp = fopen($script_filename, "r");
369     if ($fp !== FALSE) {
370       $line = fgets($fp);
371       $result = _drush_is_drush_shebang_line($line);
372       fclose($fp);
373     }
374   }
375
376   return $result;
377 }
378
379 /**
380  * @defgroup userinput Get input from the user.
381  * @{
382  */
383
384 /**
385  * Asks the user a basic yes/no question.
386  *
387  * @param string $msg
388  *   The question to ask.
389  * @param int $indent
390  *   The number of spaces to indent the message.
391  *
392  * @return bool
393  *   TRUE if the user enters "y" or FALSE if "n".
394  */
395 function drush_confirm($msg, $indent = 0) {
396   drush_print_prompt((string)$msg . " (y/n): ", $indent);
397
398   // Automatically accept confirmations if the --yes argument was supplied.
399   if (drush_get_context('DRUSH_AFFIRMATIVE')) {
400     drush_print("y");
401     return TRUE;
402   }
403   // Automatically cancel confirmations if the --no argument was supplied.
404   elseif (drush_get_context('DRUSH_NEGATIVE')) {
405     drush_print("n");
406     return FALSE;
407   }
408   // See http://drupal.org/node/499758 before changing this.
409   $stdin = fopen("php://stdin","r");
410
411   while ($line = fgets($stdin)) {
412     $line = trim($line);
413     if ($line == 'y') {
414       return TRUE;
415     }
416     if ($line == 'n') {
417       return FALSE;
418     }
419     drush_print_prompt((string)$msg . " (y/n): ", $indent);
420   }
421 }
422
423 /**
424  * Ask the user to select an item from a list.
425  * From a provided associative array, drush_choice will
426  * display all of the questions, numbered from 1 to N,
427  * and return the item the user selected. "0" is always
428  * cancel; entering a blank line is also interpreted
429  * as cancelling.
430  *
431  * @param $options
432  *   A list of questions to display to the user.  The
433  *   KEYS of the array are the result codes to return to the
434  *   caller; the VALUES are the messages to display on
435  *   each line. Special keys of the form '-- something --' can be
436  *   provided as separator between choices groups. Separator keys
437  *    don't alter the numbering.
438  * @param $prompt
439  *   The message to display to the user prompting for input.
440  * @param $label
441  *   Controls the display of each line.  Defaults to
442  *   '!value', which displays the value of each item
443  *   in the $options array to the user.  Use '!key' to
444  *   display the key instead.  In some instances, it may
445  *   be useful to display both the key and the value; for
446  *   example, if the key is a user id and the value is the
447  *   user name, use '!value (uid=!key)'.
448  */
449 function drush_choice($options, $prompt = 'Enter a number.', $label = '!value', $widths = array()) {
450   drush_print(dt($prompt));
451
452   // Preflight so that all rows will be padded out to the same number of columns
453   $array_pad = 0;
454   foreach ($options as $key => $option) {
455     if (is_array($option) && (count($option) > $array_pad)) {
456       $array_pad = count($option);
457     }
458   }
459
460   $rows[] = array_pad(array('[0]', ':', 'Cancel'), $array_pad + 2, '');
461   $selection_number = 0;
462   foreach ($options as $key => $option) {
463     if ((substr($key, 0, 3) == '-- ') && (substr($key, -3) == ' --')) {
464       $rows[] = array_pad(array('', '', $option), $array_pad + 2, '');
465     }
466     else {
467       $selection_number++;
468       $row = array("[$selection_number]", ':');
469       if (is_array($option)) {
470         $row = array_merge($row, $option);
471       }
472       else {
473         $row[] = dt($label, array('!number' => $selection_number, '!key' => $key, '!value' => $option));
474       }
475       $rows[] = $row;
476       $selection_list[$selection_number] = $key;
477     }
478   }
479   drush_print_table($rows, FALSE, $widths);
480   drush_print_pipe(array_keys($options));
481
482   // If the user specified --choice, then make an
483   // automatic selection.  Cancel if the choice is
484   // not an available option.
485   if (($choice = drush_get_option('choice', FALSE)) !== FALSE) {
486     // First check to see if $choice is one of the symbolic options
487     if (array_key_exists($choice, $options)) {
488       return $choice;
489     }
490     // Next handle numeric selections
491     elseif (array_key_exists($choice, $selection_list)) {
492       return $selection_list[$choice];
493     }
494     return FALSE;
495   }
496
497   // If the user specified --no, then cancel; also avoid
498   // getting hung up waiting for user input in --pipe and
499   // backend modes.  If none of these apply, then wait,
500   // for user input and return the selected result.
501   if (!drush_get_context('DRUSH_NEGATIVE') && !drush_get_context('DRUSH_AFFIRMATIVE') && !drush_get_context('DRUSH_PIPE')) {
502     while ($line = trim(fgets(STDIN))) {
503       if (array_key_exists($line, $selection_list)) {
504         return $selection_list[$line];
505       }
506     }
507   }
508   // We will allow --yes to confirm input if there is only
509   // one choice; otherwise, --yes will cancel to avoid ambiguity
510   if (drush_get_context('DRUSH_AFFIRMATIVE')  && (count($options) == 1)) {
511     return $selection_list[1];
512   }
513   return FALSE;
514 }
515
516 /**
517  * Ask the user to select multiple items from a list.
518  * This is a wrapper around drush_choice, that repeats the selection process,
519  * allowing users to toggle a number of items in a list. The number of values
520  * that can be constrained by both min and max: the user will only be allowed
521  * finalize selection once the minimum number has been selected, and the oldest
522  * selected value will "drop off" the list, if they exceed the maximum number.
523  *
524  * @param $options
525  *   Same as drush_choice() (see above).
526  * @param $defaults
527  *   This can take 3 forms:
528  *   - FALSE: (Default) All options are unselected by default.
529  *   - TRUE: All options are selected by default.
530  *   - Array of $options keys to be selected by default.
531  * @param $prompt
532  *   Same as drush_choice() (see above).
533  * @param $label
534  *   Same as drush_choice() (see above).
535  * @param $mark
536  *   Controls how selected values are marked.  Defaults to '!value (selected)'.
537  * @param $min
538  *   Constraint on minimum number of selections. Defaults to zero. When fewer
539  *   options than this are selected, no final options will be available.
540  * @param $max
541  *   Constraint on minimum number of selections. Defaults to NULL (unlimited).
542  *   If the a new selection causes this value to be exceeded, the oldest
543  *   previously selected value is automatically unselected.
544  * @param $final_options
545  *   An array of additional options in the same format as $options.
546  *   When the minimum number of selections is met, this array is merged into the
547  *   array of options. If the user selects one of these values and the
548  *   selection process will complete (the key for the final option is included
549  *   in the return value). If this is an empty array (default), then a built in
550  *   final option of "Done" will be added to the available options (in this case
551  *   no additional keys are added to the return value).
552  */
553 function drush_choice_multiple($options, $defaults = FALSE, $prompt = 'Select some numbers.', $label = '!value', $mark = '!value (selected)', $min = 0, $max = NULL, $final_options = array()) {
554   $selections = array();
555   // Load default selections.
556   if (is_array($defaults)) {
557     $selections = $defaults;
558   }
559   elseif ($defaults === TRUE) {
560     $selections = array_keys($options);
561   }
562   $complete = FALSE;
563   $final_builtin = array();
564   if (empty($final_options)) {
565     $final_builtin['done'] = dt('Done');
566   }
567   $final_options_keys = array_keys($final_options);
568   while (TRUE) {
569     $current_options = $options;
570     // Mark selections.
571     foreach ($selections as $selection) {
572       $current_options[$selection] = dt($mark, array('!key' => $selection, '!value' => $options[$selection]));
573     }
574     // Add final options, if the minimum number of selections has been reached.
575     if (count($selections) >= $min) {
576       $current_options = array_merge($current_options, $final_options, $final_builtin);
577     }
578     $toggle = drush_choice($current_options, $prompt, $label);
579     if ($toggle === FALSE) {
580       return FALSE;
581     }
582     // Don't include the built in final option in the return value.
583     if (count($selections) >= $min && empty($final_options) && $toggle == 'done') {
584       return $selections;
585     }
586     // Toggle the selected value.
587     $item = array_search($toggle, $selections);
588     if ($item === FALSE) {
589       array_unshift($selections, $toggle);
590     }
591     else {
592       unset($selections[$item]);
593     }
594     // If the user selected one of the final options, return.
595     if (count($selections) >= $min && in_array($toggle, $final_options_keys)) {
596       return $selections;
597     }
598     // If the user selected too many options, drop the oldest selection.
599     if (isset($max) && count($selections) > $max) {
600       array_pop($selections);
601     }
602   }
603 }
604
605 /**
606  * Prompt the user for input
607  *
608  * The input can be anything that fits on a single line (not only y/n),
609  * so we can't use drush_confirm()
610  *
611  * @param $prompt
612  *   The text which is displayed to the user.
613  * @param $default
614  *   The default value of the input.
615  * @param $required
616  *   If TRUE, user may continue even when no value is in the input.
617  * @param $password
618  *   If TRUE, surpress printing of the input.
619  *
620  * @see drush_confirm()
621  */
622 function drush_prompt($prompt, $default = NULL, $required = TRUE, $password = FALSE) {
623   if (isset($default)) {
624     $prompt .= " [" . $default . "]";
625   }
626   $prompt .= ": ";
627
628   drush_print_prompt($prompt);
629
630   if (drush_get_context('DRUSH_AFFIRMATIVE')) {
631     return $default;
632   }
633
634   $stdin = fopen('php://stdin', 'r');
635
636   if ($password) drush_shell_exec("stty -echo");
637
638   stream_set_blocking($stdin, TRUE);
639   while (($line = fgets($stdin)) !== FALSE) {
640     $line = trim($line);
641     if ($line === "") {
642       $line = $default;
643     }
644     if ($line || !$required) {
645       break;
646     }
647     drush_print_prompt($prompt);
648   }
649   fclose($stdin);
650   if ($password) {
651     drush_shell_exec("stty echo");
652     print "\n";
653   }
654   return $line;
655 }
656
657 /**
658  * @} End of "defgroup userinput".
659  */
660
661 /**
662  * Calls a given function, passing through all arguments unchanged.
663  *
664  * This should be used when calling possibly mutative or destructive functions
665  * (e.g. unlink() and other file system functions) so that can be suppressed
666  * if the simulation mode is enabled.
667  *
668  * Important:  Call @see drush_op_system() to execute a shell command,
669  * or @see drush_shell_exec() to execute a shell command and capture the
670  * shell output.
671  *
672  * @param $callable
673  *   The name of the function. Any additional arguments are passed along.
674  * @return
675  *   The return value of the function, or TRUE if simulation mode is enabled.
676  *
677  */
678 function drush_op($callable) {
679   $args_printed = array();
680   $args = func_get_args();
681   array_shift($args); // Skip function name
682   foreach ($args as $arg) {
683     $args_printed[] = is_scalar($arg) ? $arg : (is_array($arg) ? 'Array' : 'Object');
684   }
685
686   if (!is_array($callable)) {
687     $callable_string = $callable;
688   }
689   else {
690     if (is_object($callable[0])) {
691       $callable_string = get_class($callable[0]) . '::' . $callable[1];
692     }
693     else {
694       $callable_string = implode('::', $callable);
695     }
696   }
697
698   // Special checking for drush_op('system')
699   if ($callable == 'system') {
700     drush_log(dt("Do not call drush_op('system'); use drush_op_system instead"), LogLevel::DEBUG);
701   }
702
703   if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) {
704     drush_log(sprintf("Calling %s(%s)", $callable_string, implode(", ", $args_printed)), LogLevel::DEBUG);
705   }
706
707   if (drush_get_context('DRUSH_SIMULATE')) {
708     return TRUE;
709   }
710
711   return drush_call_user_func_array($callable, $args);
712 }
713
714 /**
715  * Mimic cufa but still call function directly. See http://drupal.org/node/329012#comment-1260752
716  */
717 function drush_call_user_func_array($function, $args = array() ) {
718   if (is_array($function)) {
719     // $callable is a method so always use CUFA.
720     return call_user_func_array($function, $args);
721   }
722
723   switch (count($args)) {
724     case 0: return $function(); break;
725     case 1: return $function($args[0]); break;
726     case 2: return $function($args[0], $args[1]); break;
727     case 3: return $function($args[0], $args[1], $args[2]); break;
728     case 4: return $function($args[0], $args[1], $args[2], $args[3]); break;
729     case 5: return $function($args[0], $args[1], $args[2], $args[3], $args[4]); break;
730     case 6: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]); break;
731     case 7: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6]); break;
732     case 8: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7]); break;
733     case 9: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], $args[8]); break;
734     default: return call_user_func_array($function,$args);
735   }
736 }
737
738 /**
739  * Download a file using wget, curl or file_get_contents, or via download cache.
740  *
741  * @param string $url
742  *   The url of the file to download.
743  * @param string $destination
744  *   The name of the file to be saved, which may include the full path.
745  *   Optional, if omitted the filename will be extracted from the url and the
746  *   file downloaded to the current working directory (Drupal root if
747  *   bootstrapped).
748  * @param integer $cache_duration
749  *   The acceptable age of a cached file. If cached file is too old, a fetch
750  *   will occur and cache will be updated. Optional, if ommitted the file will
751  *   be fetched directly.
752  *
753  * @return string
754  *   The path to the downloaded file, or FALSE if the file could not be
755  *   downloaded.
756  */
757 function drush_download_file($url, $destination = FALSE, $cache_duration = 0) {
758   // Generate destination if omitted.
759   if (!$destination) {
760     $file = basename(current(explode('?', $url, 2)));
761     $destination = getcwd() . '/' . basename($file);
762   }
763
764   // Simply copy local files to the destination
765   if (!_drush_is_url($url)) {
766     return copy($url, $destination) ? $destination : FALSE;
767   }
768
769   if ($cache_duration !== 0 && $cache_file = drush_download_file_name($url)) {
770     // Check for cached, unexpired file.
771     if (file_exists($cache_file) && filectime($cache_file) > ($_SERVER['REQUEST_TIME']-$cache_duration)) {
772       drush_log(dt('!name retrieved from cache.', array('!name' => $cache_file)));
773     }
774     else {
775       if (_drush_download_file($url, $cache_file, TRUE)) {
776         // Cache was set just by downloading file to right location.
777       }
778       elseif (file_exists($cache_file)) {
779         drush_log(dt('!name retrieved from an expired cache since refresh failed.', array('!name' => $cache_file)), LogLevel::WARNING);
780       }
781       else {
782         $cache_file = FALSE;
783       }
784     }
785
786     if ($cache_file && copy($cache_file, $destination)) {
787       // Copy cached file to the destination
788       return $destination;
789     }
790   }
791   elseif ($return = _drush_download_file($url, $destination)) {
792     drush_register_file_for_deletion($return);
793     return $return;
794   }
795
796   // Unable to retrieve from cache nor download.
797   return FALSE;
798 }
799
800 /**
801  * Helper function to determine name of cached file.
802  */
803 function drush_download_file_name($url) {
804   if ($cache_dir = drush_directory_cache('download')) {
805     $cache_name = str_replace(array(':', '/', '?', '=', '\\'), '-', $url);
806     return $cache_dir . "/" . $cache_name;
807   }
808   else {
809     return FALSE;
810   }
811 }
812
813 /**
814  * Check whether the given path is just a url or a local path
815  * @param string $url
816  * @return boolean
817  *   TRUE if the path does not contain a schema:// part.
818  */
819 function _drush_is_url($url) {
820   return parse_url($url, PHP_URL_SCHEME) !== NULL;
821 }
822
823 /**
824  * Download a file using wget, curl or file_get_contents. Does not use download
825  * cache.
826  *
827  * @param string $url
828  *   The url of the file to download.
829  * @param string $destination
830  *   The name of the file to be saved, which may include the full path.
831  * @param boolean $overwrite
832  *   Overwrite any file thats already at the destination.
833  * @return string
834  *   The path to the downloaded file, or FALSE if the file could not be
835  *   downloaded.
836  */
837 function _drush_download_file($url, $destination, $overwrite = TRUE) {
838   static $use_wget;
839   if ($use_wget === NULL) {
840     $use_wget = drush_shell_exec('wget --version');
841   }
842
843   $destination_tmp = drush_tempnam('download_file');
844   if ($use_wget) {
845     drush_shell_exec("wget -q --timeout=30 -O %s %s", $destination_tmp, $url);
846   }
847   else {
848     // Force TLS1+ as per https://github.com/drush-ops/drush/issues/894.
849     drush_shell_exec("curl --tlsv1 --fail -s -L --connect-timeout 30 -o %s %s", $destination_tmp, $url);
850   }
851   if (!drush_file_not_empty($destination_tmp) && $file = @file_get_contents($url)) {
852     @file_put_contents($destination_tmp, $file);
853   }
854   if (!drush_file_not_empty($destination_tmp)) {
855     // Download failed.
856     return FALSE;
857   }
858
859   drush_move_dir($destination_tmp, $destination, $overwrite);
860   return $destination;
861 }
862
863 /**
864  * Determines the MIME content type of the specified file.
865  *
866  * The power of this function depends on whether the PHP installation
867  * has either mime_content_type() or finfo installed -- if not, only tar,
868  * gz, zip and bzip2 types can be detected.
869  *
870  * If mime type can't be obtained, an error will be set.
871  *
872  * @return mixed
873  *   The MIME content type of the file or FALSE.
874  */
875 function drush_mime_content_type($filename) {
876   $content_type = drush_attempt_mime_content_type($filename);
877   if ($content_type) {
878     drush_log(dt('Mime type for !file is !mt', array('!file' => $filename, '!mt' => $content_type)), LogLevel::NOTICE);
879     return $content_type;
880   }
881   return drush_set_error('MIME_CONTENT_TYPE_UNKNOWN', dt('Unable to determine mime type for !file.', array('!file' => $filename)));
882 }
883
884 /**
885  * Works like drush_mime_content_type, but does not set an error
886  * if the type is unknown.
887  */
888 function drush_attempt_mime_content_type($filename) {
889   $content_type = FALSE;
890   if (class_exists('finfo')) {
891     $finfo = new finfo(FILEINFO_MIME_TYPE);
892     $content_type = $finfo->file($filename);
893     if ($content_type == 'application/octet-stream') {
894       drush_log(dt('Mime type for !file is application/octet-stream.', array('!file' => $filename)), LogLevel::DEBUG);
895       $content_type = FALSE;
896     }
897   }
898   // If apache is configured in such a way that all files are considered
899   // octet-stream (e.g with mod_mime_magic and an http conf that's serving all
900   // archives as octet-stream for other reasons) we'll detect mime types on our
901   //  own by examing the file's magic header bytes.
902   if (!$content_type) {
903     drush_log(dt('Examining !file headers.', array('!file' => $filename)), LogLevel::DEBUG);
904     if ($file = fopen($filename, 'rb')) {
905       $first = fread($file, 2);
906       fclose($file);
907
908       if ($first !== FALSE) {
909         // Interpret the two bytes as a little endian 16-bit unsigned int.
910         $data = unpack('v', $first);
911         switch ($data[1]) {
912           case 0x8b1f:
913             // First two bytes of gzip files are 0x1f, 0x8b (little-endian).
914             // See http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
915             $content_type = 'application/x-gzip';
916             break;
917
918           case 0x4b50:
919             // First two bytes of zip files are 0x50, 0x4b ('PK') (little-endian).
920             // See http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
921             $content_type = 'application/zip';
922             break;
923
924           case 0x5a42:
925             // First two bytes of bzip2 files are 0x5a, 0x42 ('BZ') (big-endian).
926             // See http://en.wikipedia.org/wiki/Bzip2#File_format
927             $content_type = 'application/x-bzip2';
928             break;
929
930           default:
931             drush_log(dt('Unable to determine mime type from header bytes 0x!hex of !file.', array('!hex' => dechex($data[1]), '!file' => $filename,), LogLevel::DEBUG));
932         }
933       }
934       else {
935         drush_log(dt('Unable to read !file.', array('!file' => $filename)), LogLevel::WARNING);
936       }
937     }
938     else {
939       drush_log(dt('Unable to open !file.', array('!file' => $filename)), LogLevel::WARNING);
940     }
941   }
942
943   // 3. Lastly if above methods didn't work, try to guess the mime type from
944   // the file extension. This is useful if the file has no identificable magic
945   // header bytes (for example tarballs).
946   if (!$content_type) {
947     drush_log(dt('Examining !file extension.', array('!file' => $filename)), LogLevel::DEBUG);
948
949     // Remove querystring from the filename, if present.
950     $filename = basename(current(explode('?', $filename, 2)));
951     $extension_mimetype = array(
952       '.tar'     => 'application/x-tar',
953       '.sql'     => 'application/octet-stream',
954     );
955     foreach ($extension_mimetype as $extension => $ct) {
956       if (substr($filename, -strlen($extension)) === $extension) {
957         $content_type = $ct;
958         break;
959       }
960     }
961   }
962   return $content_type;
963 }
964
965 /**
966  * Check whether a file is a supported tarball.
967  *
968  * @return mixed
969  *   The file content type if it's a tarball. FALSE otherwise.
970  */
971 function drush_file_is_tarball($path) {
972   $content_type = drush_attempt_mime_content_type($path);
973   $supported = array(
974     'application/x-bzip2',
975     'application/x-gzip',
976     'application/x-tar',
977     'application/x-zip',
978     'application/zip',
979   );
980   if (in_array($content_type, $supported)) {
981     return $content_type;
982   }
983   return FALSE;
984 }
985
986 /**
987  * Extract a tarball.
988  *
989  * @param string $path
990  *   Path to the archive to be extracted.
991  * @param string $destination
992  *   The destination directory the tarball should be extracted into.
993  *   Optional, if ommitted the tarball directory will be used as destination.
994  * @param boolean $listing
995  *   If TRUE, a listing of the tar contents will be returned on success.
996  * @param string $tar_extra_options
997  *   Extra options to be passed to the tar command.
998  *
999  * @return mixed
1000  *   TRUE on success, FALSE on fail. If $listing is TRUE, a file listing of the
1001  *   tarball is returned if the extraction reported success, instead of TRUE.
1002  */
1003 function drush_tarball_extract($path, $destination = FALSE, $listing = FALSE, $tar_extra_options = '') {
1004   // Check if tarball is supported.
1005   if (!($mimetype = drush_file_is_tarball($path))) {
1006     return drush_set_error('TARBALL_EXTRACT_UNKNOWN_FORMAT', dt('Unable to extract !path. Unknown archive format.', array('!path' => $path)));
1007   }
1008
1009   // Check if destination is valid.
1010   if (!$destination) {
1011     $destination = dirname($path);
1012   }
1013   if (!drush_mkdir($destination)) {
1014     // drush_mkdir already set an error.
1015     return FALSE;
1016   }
1017
1018   // Perform the extraction of a zip file.
1019   if (($mimetype == 'application/zip') || ($mimetype == 'application/x-zip')) {
1020     $return = drush_shell_cd_and_exec(dirname($path), "unzip %s -d %s", $path, $destination);
1021     if (!$return) {
1022       return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to unzip !filename.', array('!filename' => $path)));
1023     }
1024     if ($listing) {
1025       // unzip prefixes the file listing output with a header line,
1026       // and prefixes each line with a verb representing the compression type.
1027       $output = drush_shell_exec_output();
1028       // Remove the header line.
1029       array_shift($output);
1030       // Remove the prefix verb from each line.
1031       $output = array_map(create_function('$str', 'return substr($str, strpos($str, ":") + 3 + ' . strlen($destination) . ');'), $output);
1032       // Remove any remaining blank lines.
1033       $return = array_filter($output, create_function('$str', 'return $str != "";'));
1034     }
1035   }
1036   // Otherwise we have a possibly-compressed Tar file.
1037   // If we are not on Windows, then try to do "tar" in a single operation.
1038   elseif (!drush_is_windows()) {
1039     $tar = drush_get_tar_executable();
1040     $tar_compression_flag = '';
1041     if ($mimetype == 'application/x-gzip') {
1042       $tar_compression_flag = 'z';
1043     }
1044     elseif ($mimetype == 'application/x-bzip2') {
1045       $tar_compression_flag = 'j';
1046     }
1047
1048     $return = drush_shell_cd_and_exec(dirname($path), "$tar {$tar_extra_options} -C %s -x%sf %s", $destination, $tar_compression_flag, basename($path));
1049     if (!$return) {
1050       return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to untar !filename.', array('!filename' => $path)));
1051     }
1052     if ($listing) {
1053       // We use a separate tar -tf instead of -xvf above because
1054       // the output is not the same in Mac.
1055       drush_shell_cd_and_exec(dirname($path), "$tar -t%sf %s", $tar_compression_flag, basename($path));
1056       $return = drush_shell_exec_output();
1057     }
1058   }
1059   // In windows, do the extraction by its primitive steps.
1060   else {
1061     // 1. copy the source tarball to the destination directory. Rename to a
1062     // temp name in case the destination directory == dirname($path)
1063     $tmpfile = drush_tempnam(basename($path), $destination);
1064     drush_copy_dir($path, $tmpfile, FILE_EXISTS_OVERWRITE);
1065
1066     // 2. uncompress the tarball, if compressed.
1067     if (($mimetype == 'application/x-gzip') || ($mimetype == 'application/x-bzip2')) {
1068       if ($mimetype == 'application/x-gzip') {
1069         $compressed = $tmpfile . '.gz';
1070         // We used to use gzip --decompress in --stdout > out, but the output
1071         // redirection sometimes failed on Windows for some binary output.
1072         $command = 'gzip --decompress %s';
1073       }
1074       elseif ($mimetype == 'application/x-bzip2') {
1075         $compressed = $tmpfile . '.bz2';
1076         $command = 'bzip2 --decompress %s';
1077       }
1078       drush_op('rename', $tmpfile, $compressed);
1079       $return = drush_shell_cd_and_exec(dirname($compressed), $command, $compressed);
1080       if (!$return || !file_exists($tmpfile)) {
1081         return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to decompress !filename.', array('!filename' => $compressed)));
1082       }
1083     }
1084
1085     // 3. Untar.
1086     $tar = drush_get_tar_executable();
1087     $return = drush_shell_cd_and_exec(dirname($tmpfile), "$tar {$tar_extra_options} -xvf %s", basename($tmpfile));
1088     if (!$return) {
1089       return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to untar !filename.', array('!filename' => $tmpfile)));
1090     }
1091     if ($listing) {
1092       $return = drush_shell_exec_output();
1093       // Cut off the 'x ' prefix for the each line of the tar output
1094       // See http://drupal.org/node/1775520
1095       foreach($return as &$line) {
1096         if(strpos($line, "x ") === 0)
1097           $line = substr($line, 2);
1098       }
1099     }
1100
1101     // Remove the temporary file so the md5 hash is accurate.
1102     unlink($tmpfile);
1103   }
1104
1105   return $return;
1106 }
1107
1108 /**
1109  * @defgroup commandprocessing Command processing functions.
1110  * @{
1111  *
1112  * These functions manage command processing by the
1113  * main function in drush.php.
1114  */
1115
1116 /**
1117  * Determine whether or not an argument should be removed from the
1118  * DRUSH_COMMAND_ARGS context.  This method is used when a Drush
1119  * command has set the 'strict-option-handling' flag indicating
1120  * that it will pass through all commandline arguments and any
1121  * additional options (not known to Drush) to some shell command.
1122  *
1123  * Take as an example the following call to core-rsync:
1124  *
1125  *   drush --yes core-rsync -v -az --exclude-paths='.git:.svn' local-files/ @site:%files
1126  *
1127  * In this instance:
1128  *
1129  *   --yes is a global Drush option
1130  *
1131  *   -v is an rsync option.  It will make rsync run in verbose mode,
1132  *     but will not make Drush run in verbose mode due to the fact that
1133  *     core-rsync sets the 'strict-option-handling' flag.
1134  *
1135  *   --exclude-paths is a local Drush option.  It will be converted by
1136  *     Drush into --exclude='.git' and --exclude='.svn', and then passed
1137  *     on to the rsync command.
1138  *
1139  * The parameter $arg passed to this function is one of the elements
1140  * of DRUSH_COMMAND_ARGS.  It will have values such as:
1141  *   -v
1142  *   -az
1143  *   --exclude-paths='.git:.svn'
1144  *   local-files/
1145  *   @site:%files
1146  *
1147  * Our job in this function is to determine if $arg should be removed
1148  * by virtue of appearing in $removal_list.  $removal_list is an array
1149  * that will contain values such as 'exclude-paths'.  Both the key and
1150  * the value of $removal_list is the same.
1151  */
1152 function _drush_should_remove_command_arg($arg, $removal_list) {
1153   foreach ($removal_list as $candidate) {
1154     if (($arg == "-$candidate") ||
1155       ($arg == "--$candidate") ||
1156       (substr($arg,0,strlen($candidate)+3) == "--$candidate=") ) {
1157       return TRUE;
1158     }
1159   }
1160   return FALSE;
1161 }
1162
1163 /**
1164  * Redispatch the specified command using the same
1165  * options that were passed to this invocation of drush.
1166  */
1167 function drush_do_command_redispatch($command, $args = array(), $remote_host = NULL, $remote_user = NULL, $drush_path = NULL, $user_interactive = FALSE, $aditional_options = array()) {
1168   $additional_global_options = array();
1169   $command_options = drush_redispatch_get_options();
1170   $command_options = $aditional_options + $command_options;
1171   if (is_array($command)) {
1172     $command_name = $command['command'];
1173     // If we are executing a remote command that uses strict option handling,
1174     // then mark all of the options in the alias context as global, so that they
1175     // will appear before the command name.
1176     if (!empty($command['strict-option-handling'])) {
1177       foreach(drush_get_context('alias') as $alias_key => $alias_value) {
1178         if (array_key_exists($alias_key, $command_options) && !array_key_exists($alias_key, $command['options'])) {
1179           $additional_global_options[$alias_key] = $alias_value;
1180         }
1181       }
1182     }
1183   }
1184   else {
1185     $command_name = $command;
1186   }
1187   // If the path to drush was supplied, then use it to invoke the new command.
1188   if ($drush_path == NULL) {
1189     $drush_path = drush_get_option('drush-script');
1190     if (!isset($drush_path)) {
1191       $drush_folder = drush_get_option('drush');
1192       if (isset($drush)) {
1193         $drush_path = $drush_folder . '/drush';
1194       }
1195     }
1196   }
1197   $backend_options = array('drush-script' => $drush_path, 'remote-host' => $remote_host, 'remote-user' => $remote_user, 'integrate' => TRUE, 'additional-global-options' => $additional_global_options);
1198   // Set the tty if requested, if the command necessitates it,
1199   // or if the user explicitly asks for interactive mode, but
1200   // not if interactive mode is forced.  tty implies interactive
1201   if (drush_get_option('tty') || $user_interactive || !empty($command['remote-tty'])) {
1202     $backend_options['#tty'] = TRUE;
1203     $backend_options['interactive'] = TRUE;
1204   }
1205   elseif (drush_get_option('interactive')) {
1206     $backend_options['interactive'] = TRUE;
1207   }
1208
1209   // Run the command in a new process.
1210   drush_log(dt('Begin redispatch via drush_invoke_process().'));
1211   $values = drush_invoke_process('@self', $command_name, $args, $command_options, $backend_options);
1212   drush_log(dt('End redispatch via drush_invoke_process().'));
1213
1214   return $values;
1215 }
1216
1217
1218 /**
1219  * @} End of "defgroup commandprocessing".
1220  */
1221
1222 /**
1223  * @defgroup logging Logging information to be provided as output.
1224  * @{
1225  *
1226  * These functions are primarily for diagnostic purposes, but also provide an overview of tasks that were taken
1227  * by drush.
1228  */
1229
1230 /**
1231  * Add a log message to the log history.
1232  *
1233  * This function calls the callback stored in the 'DRUSH_LOG_CALLBACK' context with
1234  * the resulting entry at the end of execution.
1235  *
1236  * This allows you to replace it with custom logging implementations if needed,
1237  * such as logging to a file or logging to a database (drupal or otherwise).
1238  *
1239  * The default callback is the Drush\Log\Logger class with prints the messages
1240  * to the shell.
1241  *
1242  * @param message
1243  *   String containing the message to be logged.
1244  * @param type
1245  *   The type of message to be logged. Common types are 'warning', 'error', 'success' and 'notice'.
1246  *   A type of 'ok' or 'success' can also be supplied to flag something that worked.
1247  *   If you want your log messages to print to screen without the user entering
1248  *   a -v or --verbose flag, use type 'ok', this prints log messages out to
1249  *   STDERR, which prints to screen (unless you have redirected it). All other
1250  *   types of messages will be assumed to be notices.
1251  */
1252 function drush_log($message, $type = LogLevel::NOTICE, $error = null) {
1253   $entry = array(
1254     'type' => $type,
1255     'message' => $message,
1256     'timestamp' => microtime(TRUE),
1257     'memory' => memory_get_usage(),
1258   );
1259   $entry['error'] = $error;
1260
1261   return _drush_log($entry);
1262 }
1263
1264 /**
1265  * Future: add some sort of dependency injection to Drush.
1266  */
1267 function _drush_create_default_logger() {
1268   $drush_logger = new Logger();
1269   drush_set_context('DRUSH_LOGGER', $drush_logger);
1270   drush_set_context('DRUSH_LOG_CALLBACK', $drush_logger);
1271 }
1272
1273 /**
1274  * Call the default logger, or the user's log callback, as
1275  * appropriate.
1276  */
1277 function _drush_log($entry) {
1278   $callback = drush_get_context('DRUSH_LOG_CALLBACK');
1279
1280   if ($callback instanceof LoggerInterface) {
1281     _drush_log_to_logger($callback, $entry);
1282   }
1283   elseif ($callback) {
1284     $log =& drush_get_context('DRUSH_LOG', array());
1285     $log[] = $entry;
1286     drush_backend_packet('log', $entry);
1287     return $callback($entry);
1288   }
1289 }
1290
1291 // Maintain compatibility with extensions that hook into
1292 // DRUSH_LOG_CALLBACK (e.g. drush_ctex_bonus)
1293 function _drush_print_log($entry) {
1294   $drush_logger = drush_get_context('DRUSH_LOGGER');
1295   if ($drush_logger) {
1296     _drush_log_to_logger($drush_logger, $entry);
1297   }
1298 }
1299
1300 function _drush_log_to_logger($logger, $entry) {
1301     $context = $entry;
1302     $log_level = $entry['type'];
1303     $message = $entry['message'];
1304     unset($entry['type']);
1305     unset($entry['message']);
1306
1307     $logger->log($log_level, $message, $context);
1308 }
1309
1310 function drush_log_has_errors($types = array(LogLevel::WARNING, LogLevel::ERROR, LogLevel::FAILED)) {
1311   $log =& drush_get_context('DRUSH_LOG', array());
1312   foreach ($log as $entry) {
1313     if (in_array($entry['type'], $types)) {
1314       return TRUE;
1315     }
1316   }
1317   return FALSE;
1318 }
1319
1320 /**
1321  * Backend command callback. Add a log message to the log history.
1322  *
1323  * @param entry
1324  *   The log entry.
1325  */
1326 function drush_backend_packet_log($entry, $backend_options) {
1327   if (!$backend_options['log']) {
1328     return;
1329   }
1330   if (!is_string($entry['message'])) {
1331     $entry['message'] = implode("\n", (array)$entry['message']);
1332   }
1333   $entry['message'] = $entry['message'];
1334   if (array_key_exists('#output-label', $backend_options)) {
1335     $entry['message'] = $backend_options['#output-label'] . $entry['message'];
1336   }
1337
1338   // If integrate is FALSE, then log messages are stored in DRUSH_LOG,
1339   // but are -not- printed to the console.
1340   if ($backend_options['integrate']) {
1341     _drush_log($entry);
1342   }
1343   else {
1344     $log =& drush_get_context('DRUSH_LOG', array());
1345     $log[] = $entry;
1346     // Yes, this looks odd, but we might in fact be a backend command
1347     // that ran another backend command.
1348     drush_backend_packet('log', $entry);
1349   }
1350 }
1351
1352 /**
1353  * Retrieve the log messages from the log history
1354  *
1355  * @return
1356  *   Entire log history
1357  */
1358 function drush_get_log() {
1359   return drush_get_context('DRUSH_LOG', array());
1360 }
1361
1362 /**
1363  * Run print_r on a variable and log the output.
1364  */
1365 function dlm($object) {
1366   drush_log(print_r($object, TRUE));
1367 }
1368
1369 /**
1370  * Display the pipe output for the current request.
1371  */
1372 function drush_pipe_output() {
1373   $pipe = drush_get_context('DRUSH_PIPE_BUFFER');
1374   if (!empty($pipe)) {
1375     drush_print_r($pipe, NULL, FALSE);
1376   }
1377 }
1378
1379 // Print all timers for the request.
1380 function drush_print_timers() {
1381   global $timers;
1382   $temparray = array();
1383   foreach ((array)$timers as $name => $timerec) {
1384     // We have to use timer_read() for active timers, and check the record for others
1385     if (isset($timerec['start'])) {
1386       $temparray[$name] = timer_read($name);
1387     }
1388     else {
1389       $temparray[$name] = $timerec['time'];
1390     }
1391   }
1392   // Go no farther if there were no timers
1393   if (count($temparray) > 0) {
1394     // Put the highest cumulative times first
1395     arsort($temparray);
1396     $table = array();
1397     $table[] = array('Timer', 'Cum (sec)', 'Count', 'Avg (msec)');
1398     foreach ($temparray as $name => $time) {
1399       $cum = round($time/1000, 3);
1400       $count = $timers[$name]['count'];
1401       if ($count > 0) {
1402         $avg = round($time/$count, 3);
1403       }
1404       else {
1405         $avg = 'N/A';
1406       }
1407       $table[] = array($name, $cum, $count, $avg);
1408     }
1409     drush_print_table($table, TRUE, array(), STDERR);
1410   }
1411 }
1412
1413 /**
1414  * Turn drupal_set_message errors into drush_log errors
1415  */
1416 function _drush_log_drupal_messages() {
1417   if (function_exists('drupal_get_messages')) {
1418
1419     $messages = drupal_get_messages(NULL, TRUE);
1420
1421     if (array_key_exists('error', $messages)) {
1422       //Drupal message errors.
1423       foreach ((array) $messages['error'] as $error) {
1424         $error = strip_tags($error);
1425         $header = preg_match('/^warning: Cannot modify header information - headers already sent by /i', $error);
1426         $session = preg_match('/^warning: session_start\(\): Cannot send session /i', $error);
1427         if ($header || $session) {
1428           //These are special cases for an unavoidable warnings
1429           //that are generated by generating output before Drupal is bootstrapped.
1430           //or sending a session cookie (seems to affect d7 only?)
1431           //Simply ignore them.
1432           continue;
1433         }
1434         elseif (preg_match('/^warning:/i', $error)) {
1435           drush_log(preg_replace('/^warning: /i', '', $error), LogLevel::WARNING);
1436         }
1437         elseif (preg_match('/^notice:/i', $error)) {
1438           drush_log(preg_replace('/^notice: /i', '', $error), LogLevel::NOTICE);
1439         }
1440         elseif (preg_match('/^user warning:/i', $error)) {
1441           // This is a special case. PHP logs sql errors as 'User Warnings', not errors.
1442           drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', preg_replace('/^user warning: /i', '', $error));
1443         }
1444         else {
1445           drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', $error);
1446         }
1447       }
1448     }
1449     unset($messages['error']);
1450
1451     // Log non-error messages.
1452     foreach ($messages as $type => $items) {
1453       foreach ($items as $item) {
1454         drush_log(strip_tags($item), $type);
1455       }
1456     }
1457   }
1458 }
1459
1460 // Copy of format_size() in Drupal.
1461 function drush_format_size($size) {
1462   if ($size < DRUSH_KILOBYTE) {
1463     // format_plural() not always available.
1464     return dt('@count bytes', array('@count' => $size));
1465   }
1466   else {
1467     $size = $size / DRUSH_KILOBYTE; // Convert bytes to kilobytes.
1468     $units = array(
1469       dt('@size KB', array()),
1470       dt('@size MB', array()),
1471       dt('@size GB', array()),
1472       dt('@size TB', array()),
1473       dt('@size PB', array()),
1474       dt('@size EB', array()),
1475       dt('@size ZB', array()),
1476       dt('@size YB', array()),
1477     );
1478     foreach ($units as $unit) {
1479       if (round($size, 2) >= DRUSH_KILOBYTE) {
1480         $size = $size / DRUSH_KILOBYTE;
1481       }
1482       else {
1483         break;
1484       }
1485     }
1486     return str_replace('@size', round($size, 2), $unit);
1487   }
1488 }
1489
1490 /**
1491  * @} End of "defgroup logging".
1492  */
1493
1494 /**
1495  * @defgroup errorhandling Managing errors that occur in the Drush framework.
1496  * @{
1497  * Functions that manage the current error status of the Drush framework.
1498  *
1499  * These functions operate by maintaining a static variable that is a equal to the constant DRUSH_FRAMEWORK_ERROR if an
1500  * error has occurred.
1501  * This error code is returned at the end of program execution, and provide the shell or calling application with
1502  * more information on how to diagnose any problems that may have occurred.
1503  */
1504
1505 /**
1506  * Set an error code for the error handling system.
1507  *
1508  * @param \Drupal\Component\Render\MarkupInterface|string $error
1509  *   A text string identifying the type of error.
1510  * @param null|string $message
1511  *   Optional. Error message to be logged. If no message is specified,
1512  *   hook_drush_help will be consulted, using a key of 'error:MY_ERROR_STRING'.
1513  * @param null|string $output_label
1514  *   Optional. Label to prepend to the error message.
1515  *
1516  * @return bool
1517  *   Always returns FALSE, to allow returning false in the calling functions,
1518  *   such as <code>return drush_set_error('DRUSH_FRAMEWORK_ERROR')</code>.
1519  */
1520 function drush_set_error($error, $message = null, $output_label = "") {
1521   $error_code =& drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
1522   $error_code = DRUSH_FRAMEWORK_ERROR;
1523
1524   $error_log =& drush_get_context('DRUSH_ERROR_LOG', array());
1525
1526   if (is_numeric($error)) {
1527     $error = 'DRUSH_FRAMEWORK_ERROR';
1528   }
1529   elseif (!is_string($error)) {
1530     // Typical case: D8 TranslatableMarkup, implementing MarkupInterface.
1531     $error = "$error";
1532   }
1533
1534   $message = ($message) ? $message : drush_command_invoke_all('drush_help', 'error:' . $error);
1535
1536   if (is_array($message)) {
1537     $message = implode("\n", $message);
1538   }
1539
1540   $error_log[$error][] = $message;
1541   if (!drush_backend_packet('set_error', array('error' => $error, 'message' => $message))) {
1542     drush_log(($message) ? $output_label . $message : $output_label . $error, LogLevel::ERROR, $error);
1543   }
1544
1545   return FALSE;
1546 }
1547
1548 /**
1549  * Return the current error handling status
1550  *
1551  * @return
1552  *   The current aggregate error status
1553  */
1554 function drush_get_error() {
1555   return drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
1556 }
1557
1558 /**
1559  * Return the current list of errors that have occurred.
1560  *
1561  * @return
1562  *   An associative array of error messages indexed by the type of message.
1563  */
1564 function drush_get_error_log() {
1565   return drush_get_context('DRUSH_ERROR_LOG', array());
1566 }
1567
1568 /**
1569  * Check if a specific error status has been set.
1570  *
1571  * @param error
1572  *   A text string identifying the error that has occurred.
1573  * @return
1574  *   TRUE if the specified error has been set, FALSE if not
1575  */
1576 function drush_cmp_error($error) {
1577   $error_log = drush_get_error_log();
1578
1579   if (is_numeric($error)) {
1580     $error = 'DRUSH_FRAMEWORK_ERROR';
1581   }
1582
1583   return array_key_exists($error, $error_log);
1584 }
1585
1586 /**
1587  * Clear error context.
1588  */
1589 function drush_clear_error() {
1590   drush_set_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
1591 }
1592
1593 /**
1594  * Exit due to user declining a confirmation prompt.
1595  *
1596  * Usage:  return drush_user_abort();
1597  */
1598 function drush_user_abort($msg = NULL) {
1599   drush_set_context('DRUSH_USER_ABORT', TRUE);
1600   drush_set_context('DRUSH_EXIT_CODE', DRUSH_EXITCODE_USER_ABORT);
1601   drush_log($msg ? $msg : dt('Cancelled.'), LogLevel::CANCEL);
1602   return FALSE;
1603 }
1604
1605 /**
1606  * Turn PHP error handling off.
1607  *
1608  * This is commonly used while bootstrapping Drupal for install
1609  * or updates.
1610  *
1611  * This also records the previous error_reporting setting, in
1612  * case it wasn't recorded previously.
1613  *
1614  * @see drush_errors_off()
1615  */
1616 function drush_errors_off() {
1617   drush_get_context('DRUSH_ERROR_REPORTING', error_reporting(0));
1618   ini_set('display_errors', FALSE);
1619 }
1620
1621 /**
1622  * Turn PHP error handling on.
1623  *
1624  * We default to error_reporting() here just in
1625  * case drush_errors_on() is called before drush_errors_off() and
1626  * the context is not yet set.
1627  *
1628  * @arg $errors string
1629  *   The default error level to set in drush. This error level will be
1630  *   carried through further drush_errors_on()/off() calls even if not
1631  *   provided in later calls.
1632  *
1633  * @see error_reporting()
1634  * @see drush_errors_off()
1635  */
1636 function drush_errors_on($errors = null) {
1637   if (!isset($errors)) {
1638     $errors = error_reporting();
1639   }
1640   else {
1641     drush_set_context('DRUSH_ERROR_REPORTING', $errors);
1642   }
1643   error_reporting(drush_get_context('DRUSH_ERROR_REPORTING', $errors));
1644   ini_set('display_errors', TRUE);
1645 }
1646
1647 /**
1648  * @} End of "defgroup errorhandling".
1649  */
1650
1651 /**
1652  * Get the PHP memory_limit value in bytes.
1653  */
1654 function drush_memory_limit() {
1655   $value = trim(ini_get('memory_limit'));
1656   $last = strtolower($value[strlen($value)-1]);
1657   switch ($last) {
1658     case 'g':
1659       $value *= DRUSH_KILOBYTE;
1660     case 'm':
1661       $value *= DRUSH_KILOBYTE;
1662     case 'k':
1663       $value *= DRUSH_KILOBYTE;
1664   }
1665
1666   return $value;
1667 }
1668
1669 /**
1670  * Unset the named key anywhere in the provided
1671  * data structure.
1672  */
1673 function drush_unset_recursive(&$data, $unset_key) {
1674   if (!empty($data) && is_array($data)) {
1675     unset($data[$unset_key]);
1676     foreach ($data as $key => $value) {
1677       if (is_array($value)) {
1678         drush_unset_recursive($data[$key], $unset_key);
1679       }
1680     }
1681   }
1682 }
1683
1684 /**
1685  * Return a list of VCSs reserved files and directories.
1686  */
1687 function drush_version_control_reserved_files() {
1688   static $files = FALSE;
1689
1690   if (!$files) {
1691     // Also support VCSs that are not drush vc engines.
1692     $files = array('.git', '.gitignore', '.hg', '.hgignore', '.hgrags');
1693     $engine_info = drush_get_engines('version_control');
1694     $vcs = array_keys($engine_info['engines']);
1695     foreach ($vcs as $name) {
1696       $version_control = drush_include_engine('version_control', $name);
1697       $files = array_merge($files, $version_control->reserved_files());
1698     }
1699   }
1700
1701   return $files;
1702 }
1703
1704 /**
1705  * Generate a random alphanumeric password.  Copied from user.module.
1706  */
1707 function drush_generate_password($length = 10) {
1708   // This variable contains the list of allowable characters for the
1709   // password. Note that the number 0 and the letter 'O' have been
1710   // removed to avoid confusion between the two. The same is true
1711   // of 'I', 1, and 'l'.
1712   $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
1713
1714   // Zero-based count of characters in the allowable list:
1715   $len = strlen($allowable_characters) - 1;
1716
1717   // Declare the password as a blank string.
1718   $pass = '';
1719
1720   // Loop the number of times specified by $length.
1721   for ($i = 0; $i < $length; $i++) {
1722
1723     // Each iteration, pick a random character from the
1724     // allowable string and append it to the password:
1725     $pass .= $allowable_characters[mt_rand(0, $len)];
1726   }
1727
1728   return $pass;
1729 }
1730
1731 /**
1732  * Form an associative array from a linear array.
1733  *
1734  * This function walks through the provided array and constructs an associative
1735  * array out of it. The keys of the resulting array will be the values of the
1736  * input array. The values will be the same as the keys unless a function is
1737  * specified, in which case the output of the function is used for the values
1738  * instead.
1739  *
1740  * @param $array
1741  *   A linear array.
1742  * @param $function
1743  *   A name of a function to apply to all values before output.
1744  *
1745  * @return
1746  *   An associative array.
1747  */
1748 function drush_map_assoc($array, $function = NULL) {
1749   // array_combine() fails with empty arrays:
1750   // http://bugs.php.net/bug.php?id=34857.
1751   $array = !empty($array) ? array_combine($array, $array) : array();
1752   if (is_callable($function)) {
1753     $array = array_map($function, $array);
1754   }
1755   return $array;
1756 }
1757 /**
1758  * Clears completion caches.
1759  *
1760  * If called with no parameters the entire complete cache will be cleared.
1761  * If called with just the $type parameter the global cache for that type will
1762  * be cleared (in the site context, if any). If called with both $type and
1763  * $command parameters the command cache of that type will be cleared  (in the
1764  * site context, if any).
1765  *
1766  * This is included in drush.inc as complete.inc is only loaded conditionally.
1767  *
1768  * @param $type
1769  *   The completion type (optional).
1770  * @param $command
1771  *   The command name (optional), if command specific cache is to be cleared.
1772  *   If specifying a command, $type is not optional.
1773  */
1774 function drush_complete_cache_clear($type = NULL, $command = NULL) {
1775   require_once DRUSH_BASE_PATH . '/includes/complete.inc';
1776   if ($type) {
1777     drush_cache_clear_all(drush_complete_cache_cid($type, $command), 'complete');
1778     return;
1779   }
1780   // No type or command, so clear the entire complete cache.
1781   drush_cache_clear_all('*', 'complete', TRUE);
1782 }
1783
1784 /*
1785  * Cast a value according to the provided format
1786  *
1787  * @param mixed $value.
1788  * @param string $format
1789  *   Allowed values: auto, integer, float, bool, boolean, json, yaml.
1790  *
1791  * @return $value
1792  *  The value, re-typed as needed.
1793  */
1794 function drush_value_format($value, $format) {
1795   if ($format == 'auto') {
1796     if (is_numeric($value)) {
1797       $value = $value + 0; // http://php.net/manual/en/function.is-numeric.php#107326
1798       $format = gettype($value);
1799     }
1800     elseif (($value == 'TRUE') || ($value == 'FALSE')) {
1801       $format = 'bool';
1802     }
1803   }
1804
1805   // Now, we parse the object.
1806   switch ($format) {
1807     case 'integer':
1808       $value = (integer)$value;
1809       break;
1810     // from: http://php.net/gettype
1811     // for historical reasons "double" is returned in case of a float, and not simply "float"
1812     case 'double':
1813     case 'float':
1814       $value = (float)$value;
1815       break;
1816     case 'bool':
1817     case 'boolean':
1818       if ($value == 'TRUE') {
1819         $value = TRUE;
1820       }
1821       elseif ($value == 'FALSE') {
1822         $value = FALSE;
1823       }
1824       else {
1825         $value = (bool)$value;
1826       }
1827       break;
1828
1829     case 'json':
1830       $value = drush_json_decode($value);
1831       break;
1832
1833     case 'yaml':
1834       $value = \Symfony\Component\Yaml\Yaml::parse($value, FALSE, TRUE);
1835       break;
1836   }
1837   return $value;
1838 }