5 * The drush API implementation and helpers.
9 use Drush\Log\LogLevel;
10 use Psr\Log\LoggerInterface;
13 * @name Error status definitions
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()
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
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);
34 * @} End of "name Error status defintions".
38 * The number of bytes in a kilobyte. Copied from Drupal.
40 define('DRUSH_KILOBYTE', 1024);
43 * Default amount of time, in seconds, to cache downloads via
44 * drush_download_file(). One day is 86400 seconds.
46 define('DRUSH_CACHE_LIFETIME_DEFAULT', 86400);
49 * Include a file, selecting a version specific file if available.
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.
58 * The path you want to search.
60 * The file base name you want to include (not including a version suffix
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
67 * The extension - defaults to ".inc".
70 * TRUE if the file was found and included.
72 function drush_include($path, $name, $version = NULL, $extension = 'inc') {
73 $name = str_replace('-', '_', $name);
74 $version = ($version) ? $version : drush_drupal_major_version();
76 $file = sprintf("%s/%s_%s.%s", $path, $name, $version, $extension);
77 if (file_exists($file)) {
81 $file = sprintf("%s/%s.%s", $path, $name, $extension);
82 if (file_exists($file)) {
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)));
91 * Provide a version-specific class instance.
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.
99 * Example wrapper class to instantiate a widget, called with the
100 * arguments for the WIDGET_CLASS constructor:
102 * function drush_WIDGET_CLASS_get_class($widgetName, $widgetStyle) {
103 * retrun drush_get_class('Widget_Class', func_get_args()));
107 function drush_get_class($class_name, $constructor_args = array(), $variations = array()) {
108 if (empty($variations)) {
109 $variations[] = drush_drupal_major_version();
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();
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)));
126 * Generate an .ini file. used by archive-dump."
129 * A two dimensional associative array where top level are sections and
130 * second level are key => value pairs.
133 * .ini formatted text.
135 function drush_export_ini($ini) {
137 foreach ($ini as $section => $pairs) {
139 $output .= "[$section]\n";
142 foreach ($pairs as $k => $v) {
144 $output .= "$k = \"$v\"\n";
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.
156 * An array or single value to put in a module's .info file.
158 * @param boolean $integer_keys
159 * Use integer in keys.
162 * Array of parent keys (internal use only).
165 * A code string ready to be written to a module's .info file.
167 function drush_export_info($info, $integer_keys = FALSE, $parents = array()) {
169 if (is_array($info)) {
170 foreach ($info as $k => $v) {
173 $output .= drush_export_info($v, $integer_keys, $child);
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}]";
181 $line .= " = \"{$info}\"\n";
188 * Convert a csv string, or an array of items which
189 * may contain csv strings, into an array of items.
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,')
197 * A simple list of items (e.g. array('a','b','c')
199 function _convert_csv_to_array($args) {
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')
207 return array_map('trim', array_filter(explode(',', is_array($args) ? implode(',',$args) : $args)));
211 * Convert a nested array into a flat array. Thows away
212 * the array keys, returning only the values.
215 * An array that may potentially be nested.
216 * e.g. array('a', array('b', 'c'))
219 * A simple list of items (e.g. array('a','b','c')
221 function drush_flatten_array($a) {
226 foreach ($a as $value) {
227 $result = array_merge($result, drush_flatten_array($value));
233 * Get the available global options. Used by help command. Command files may
234 * modify this list using hook_drush_help_alter().
236 * @param boolean $brief
237 * Return a reduced set of important options. Used by help command.
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:
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`.
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.");
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 $options['ignored-directories'] = array('propagate' => TRUE, 'description' => "Exclude directories when searching for files in drush_scan_directory().", 'example-value' => 'node_modules,bower_components');
334 'options' => $options,
336 ) + drush_command_defaults('global-options', 'global_options', __FILE__);
337 drush_command_invoke_all_ref('drush_help_alter', $command);
339 $options = $command['options'];
345 * Exits with a message. In general, you should use drush_set_error() instead of
346 * this function. That lets drush proceed with other tasks.
347 * TODO: Exit with a correct status code.
349 function drush_die($msg = NULL, $status = NULL) {
350 die($msg ? "drush: $msg\n" : '');
354 * Check to see if the provided line is a "#!/usr/bin/env drush"
355 * "shebang" script line.
357 function _drush_is_drush_shebang_line($line) {
358 return ((substr($line,0,2) == '#!') && (strstr($line, 'drush') !== FALSE));
362 * Check to see if the provided script file is a "#!/usr/bin/env drush"
363 * "shebang" script line.
365 function _drush_is_drush_shebang_script($script_filename) {
368 if (file_exists($script_filename)) {
369 $fp = fopen($script_filename, "r");
372 $result = _drush_is_drush_shebang_line($line);
381 * @defgroup userinput Get input from the user.
386 * Asks the user a basic yes/no question.
389 * The question to ask.
391 * The number of spaces to indent the message.
394 * TRUE if the user enters "y" or FALSE if "n".
396 function drush_confirm($msg, $indent = 0) {
397 drush_print_prompt((string)$msg . " (y/n): ", $indent);
399 // Automatically accept confirmations if the --yes argument was supplied.
400 if (drush_get_context('DRUSH_AFFIRMATIVE')) {
404 // Automatically cancel confirmations if the --no argument was supplied.
405 elseif (drush_get_context('DRUSH_NEGATIVE')) {
409 // See http://drupal.org/node/499758 before changing this.
410 $stdin = fopen("php://stdin","r");
412 while ($line = fgets($stdin)) {
420 drush_print_prompt((string)$msg . " (y/n): ", $indent);
425 * Ask the user to select an item from a list.
426 * From a provided associative array, drush_choice will
427 * display all of the questions, numbered from 1 to N,
428 * and return the item the user selected. "0" is always
429 * cancel; entering a blank line is also interpreted
433 * A list of questions to display to the user. The
434 * KEYS of the array are the result codes to return to the
435 * caller; the VALUES are the messages to display on
436 * each line. Special keys of the form '-- something --' can be
437 * provided as separator between choices groups. Separator keys
438 * don't alter the numbering.
440 * The message to display to the user prompting for input.
442 * Controls the display of each line. Defaults to
443 * '!value', which displays the value of each item
444 * in the $options array to the user. Use '!key' to
445 * display the key instead. In some instances, it may
446 * be useful to display both the key and the value; for
447 * example, if the key is a user id and the value is the
448 * user name, use '!value (uid=!key)'.
450 function drush_choice($options, $prompt = 'Enter a number.', $label = '!value', $widths = array()) {
451 drush_print(dt($prompt));
453 // Preflight so that all rows will be padded out to the same number of columns
455 foreach ($options as $key => $option) {
456 if (is_array($option) && (count($option) > $array_pad)) {
457 $array_pad = count($option);
461 $rows[] = array_pad(array('[0]', ':', 'Cancel'), $array_pad + 2, '');
462 $selection_number = 0;
463 foreach ($options as $key => $option) {
464 if ((substr($key, 0, 3) == '-- ') && (substr($key, -3) == ' --')) {
465 $rows[] = array_pad(array('', '', $option), $array_pad + 2, '');
469 $row = array("[$selection_number]", ':');
470 if (is_array($option)) {
471 $row = array_merge($row, $option);
474 $row[] = dt($label, array('!number' => $selection_number, '!key' => $key, '!value' => $option));
477 $selection_list[$selection_number] = $key;
480 drush_print_table($rows, FALSE, $widths);
481 drush_print_pipe(array_keys($options));
483 // If the user specified --choice, then make an
484 // automatic selection. Cancel if the choice is
485 // not an available option.
486 if (($choice = drush_get_option('choice', FALSE)) !== FALSE) {
487 // First check to see if $choice is one of the symbolic options
488 if (array_key_exists($choice, $options)) {
491 // Next handle numeric selections
492 elseif (array_key_exists($choice, $selection_list)) {
493 return $selection_list[$choice];
498 // If the user specified --no, then cancel; also avoid
499 // getting hung up waiting for user input in --pipe and
500 // backend modes. If none of these apply, then wait,
501 // for user input and return the selected result.
502 if (!drush_get_context('DRUSH_NEGATIVE') && !drush_get_context('DRUSH_AFFIRMATIVE') && !drush_get_context('DRUSH_PIPE')) {
503 while ($line = trim(fgets(STDIN))) {
504 if (array_key_exists($line, $selection_list)) {
505 return $selection_list[$line];
509 // We will allow --yes to confirm input if there is only
510 // one choice; otherwise, --yes will cancel to avoid ambiguity
511 if (drush_get_context('DRUSH_AFFIRMATIVE') && (count($options) == 1)) {
512 return $selection_list[1];
518 * Ask the user to select multiple items from a list.
519 * This is a wrapper around drush_choice, that repeats the selection process,
520 * allowing users to toggle a number of items in a list. The number of values
521 * that can be constrained by both min and max: the user will only be allowed
522 * finalize selection once the minimum number has been selected, and the oldest
523 * selected value will "drop off" the list, if they exceed the maximum number.
526 * Same as drush_choice() (see above).
528 * This can take 3 forms:
529 * - FALSE: (Default) All options are unselected by default.
530 * - TRUE: All options are selected by default.
531 * - Array of $options keys to be selected by default.
533 * Same as drush_choice() (see above).
535 * Same as drush_choice() (see above).
537 * Controls how selected values are marked. Defaults to '!value (selected)'.
539 * Constraint on minimum number of selections. Defaults to zero. When fewer
540 * options than this are selected, no final options will be available.
542 * Constraint on minimum number of selections. Defaults to NULL (unlimited).
543 * If the a new selection causes this value to be exceeded, the oldest
544 * previously selected value is automatically unselected.
545 * @param $final_options
546 * An array of additional options in the same format as $options.
547 * When the minimum number of selections is met, this array is merged into the
548 * array of options. If the user selects one of these values and the
549 * selection process will complete (the key for the final option is included
550 * in the return value). If this is an empty array (default), then a built in
551 * final option of "Done" will be added to the available options (in this case
552 * no additional keys are added to the return value).
554 function drush_choice_multiple($options, $defaults = FALSE, $prompt = 'Select some numbers.', $label = '!value', $mark = '!value (selected)', $min = 0, $max = NULL, $final_options = array()) {
555 $selections = array();
556 // Load default selections.
557 if (is_array($defaults)) {
558 $selections = $defaults;
560 elseif ($defaults === TRUE) {
561 $selections = array_keys($options);
564 $final_builtin = array();
565 if (empty($final_options)) {
566 $final_builtin['done'] = dt('Done');
568 $final_options_keys = array_keys($final_options);
570 $current_options = $options;
572 foreach ($selections as $selection) {
573 $current_options[$selection] = dt($mark, array('!key' => $selection, '!value' => $options[$selection]));
575 // Add final options, if the minimum number of selections has been reached.
576 if (count($selections) >= $min) {
577 $current_options = array_merge($current_options, $final_options, $final_builtin);
579 $toggle = drush_choice($current_options, $prompt, $label);
580 if ($toggle === FALSE) {
583 // Don't include the built in final option in the return value.
584 if (count($selections) >= $min && empty($final_options) && $toggle == 'done') {
587 // Toggle the selected value.
588 $item = array_search($toggle, $selections);
589 if ($item === FALSE) {
590 array_unshift($selections, $toggle);
593 unset($selections[$item]);
595 // If the user selected one of the final options, return.
596 if (count($selections) >= $min && in_array($toggle, $final_options_keys)) {
599 // If the user selected too many options, drop the oldest selection.
600 if (isset($max) && count($selections) > $max) {
601 array_pop($selections);
607 * Prompt the user for input
609 * The input can be anything that fits on a single line (not only y/n),
610 * so we can't use drush_confirm()
613 * The text which is displayed to the user.
615 * The default value of the input.
617 * If TRUE, user may continue even when no value is in the input.
619 * If TRUE, surpress printing of the input.
621 * @see drush_confirm()
623 function drush_prompt($prompt, $default = NULL, $required = TRUE, $password = FALSE) {
624 if (isset($default)) {
625 $prompt .= " [" . $default . "]";
629 drush_print_prompt($prompt);
631 if (drush_get_context('DRUSH_AFFIRMATIVE')) {
635 $stdin = fopen('php://stdin', 'r');
637 if ($password) drush_shell_exec("stty -echo");
639 stream_set_blocking($stdin, TRUE);
640 while (($line = fgets($stdin)) !== FALSE) {
645 if ($line || !$required) {
648 drush_print_prompt($prompt);
652 drush_shell_exec("stty echo");
659 * @} End of "defgroup userinput".
663 * Calls a given function, passing through all arguments unchanged.
665 * This should be used when calling possibly mutative or destructive functions
666 * (e.g. unlink() and other file system functions) so that can be suppressed
667 * if the simulation mode is enabled.
669 * Important: Call @see drush_op_system() to execute a shell command,
670 * or @see drush_shell_exec() to execute a shell command and capture the
674 * The name of the function. Any additional arguments are passed along.
676 * The return value of the function, or TRUE if simulation mode is enabled.
679 function drush_op($callable) {
680 $args_printed = array();
681 $args = func_get_args();
682 array_shift($args); // Skip function name
683 foreach ($args as $arg) {
684 $args_printed[] = is_scalar($arg) ? $arg : (is_array($arg) ? 'Array' : 'Object');
687 if (!is_array($callable)) {
688 $callable_string = $callable;
691 if (is_object($callable[0])) {
692 $callable_string = get_class($callable[0]) . '::' . $callable[1];
695 $callable_string = implode('::', $callable);
699 // Special checking for drush_op('system')
700 if ($callable == 'system') {
701 drush_log(dt("Do not call drush_op('system'); use drush_op_system instead"), LogLevel::DEBUG);
704 if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) {
705 drush_log(sprintf("Calling %s(%s)", $callable_string, implode(", ", $args_printed)), LogLevel::DEBUG);
708 if (drush_get_context('DRUSH_SIMULATE')) {
712 return drush_call_user_func_array($callable, $args);
716 * Mimic cufa but still call function directly. See http://drupal.org/node/329012#comment-1260752
718 function drush_call_user_func_array($function, $args = array() ) {
719 if (is_array($function)) {
720 // $callable is a method so always use CUFA.
721 return call_user_func_array($function, $args);
724 switch (count($args)) {
725 case 0: return $function(); break;
726 case 1: return $function($args[0]); break;
727 case 2: return $function($args[0], $args[1]); break;
728 case 3: return $function($args[0], $args[1], $args[2]); break;
729 case 4: return $function($args[0], $args[1], $args[2], $args[3]); break;
730 case 5: return $function($args[0], $args[1], $args[2], $args[3], $args[4]); break;
731 case 6: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]); break;
732 case 7: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6]); break;
733 case 8: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7]); break;
734 case 9: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], $args[8]); break;
735 default: return call_user_func_array($function,$args);
740 * Download a file using wget, curl or file_get_contents, or via download cache.
743 * The url of the file to download.
744 * @param string $destination
745 * The name of the file to be saved, which may include the full path.
746 * Optional, if omitted the filename will be extracted from the url and the
747 * file downloaded to the current working directory (Drupal root if
749 * @param integer $cache_duration
750 * The acceptable age of a cached file. If cached file is too old, a fetch
751 * will occur and cache will be updated. Optional, if ommitted the file will
752 * be fetched directly.
755 * The path to the downloaded file, or FALSE if the file could not be
758 function drush_download_file($url, $destination = FALSE, $cache_duration = 0) {
759 // Generate destination if omitted.
761 $file = basename(current(explode('?', $url, 2)));
762 $destination = getcwd() . '/' . basename($file);
765 // Simply copy local files to the destination
766 if (!_drush_is_url($url)) {
767 return copy($url, $destination) ? $destination : FALSE;
770 if ($cache_duration !== 0 && $cache_file = drush_download_file_name($url)) {
771 // Check for cached, unexpired file.
772 if (file_exists($cache_file) && filectime($cache_file) > ($_SERVER['REQUEST_TIME']-$cache_duration)) {
773 drush_log(dt('!name retrieved from cache.', array('!name' => $cache_file)));
776 if (_drush_download_file($url, $cache_file, TRUE)) {
777 // Cache was set just by downloading file to right location.
779 elseif (file_exists($cache_file)) {
780 drush_log(dt('!name retrieved from an expired cache since refresh failed.', array('!name' => $cache_file)), LogLevel::WARNING);
787 if ($cache_file && copy($cache_file, $destination)) {
788 // Copy cached file to the destination
792 elseif ($return = _drush_download_file($url, $destination)) {
793 drush_register_file_for_deletion($return);
797 // Unable to retrieve from cache nor download.
802 * Helper function to determine name of cached file.
804 function drush_download_file_name($url) {
805 if ($cache_dir = drush_directory_cache('download')) {
806 $cache_name = str_replace(array(':', '/', '?', '=', '\\'), '-', $url);
807 return $cache_dir . "/" . $cache_name;
815 * Check whether the given path is just a url or a local path
818 * TRUE if the path does not contain a schema:// part.
820 function _drush_is_url($url) {
821 return parse_url($url, PHP_URL_SCHEME) !== NULL;
825 * Download a file using wget, curl or file_get_contents. Does not use download
829 * The url of the file to download.
830 * @param string $destination
831 * The name of the file to be saved, which may include the full path.
832 * @param boolean $overwrite
833 * Overwrite any file thats already at the destination.
835 * The path to the downloaded file, or FALSE if the file could not be
838 function _drush_download_file($url, $destination, $overwrite = TRUE) {
840 if ($use_wget === NULL) {
841 $use_wget = drush_shell_exec('wget --version');
844 $destination_tmp = drush_tempnam('download_file');
846 drush_shell_exec("wget -q --timeout=30 -O %s %s", $destination_tmp, $url);
849 // Force TLS1+ as per https://github.com/drush-ops/drush/issues/894.
850 drush_shell_exec("curl --tlsv1 --fail -s -L --connect-timeout 30 -o %s %s", $destination_tmp, $url);
852 if (!drush_file_not_empty($destination_tmp) && $file = @file_get_contents($url)) {
853 @file_put_contents($destination_tmp, $file);
855 if (!drush_file_not_empty($destination_tmp)) {
860 drush_move_dir($destination_tmp, $destination, $overwrite);
865 * Determines the MIME content type of the specified file.
867 * The power of this function depends on whether the PHP installation
868 * has either mime_content_type() or finfo installed -- if not, only tar,
869 * gz, zip and bzip2 types can be detected.
871 * If mime type can't be obtained, an error will be set.
874 * The MIME content type of the file or FALSE.
876 function drush_mime_content_type($filename) {
877 $content_type = drush_attempt_mime_content_type($filename);
879 drush_log(dt('Mime type for !file is !mt', array('!file' => $filename, '!mt' => $content_type)), LogLevel::NOTICE);
880 return $content_type;
882 return drush_set_error('MIME_CONTENT_TYPE_UNKNOWN', dt('Unable to determine mime type for !file.', array('!file' => $filename)));
886 * Works like drush_mime_content_type, but does not set an error
887 * if the type is unknown.
889 function drush_attempt_mime_content_type($filename) {
890 $content_type = FALSE;
891 if (class_exists('finfo')) {
892 $finfo = new finfo(FILEINFO_MIME_TYPE);
893 $content_type = $finfo->file($filename);
894 if ($content_type == 'application/octet-stream') {
895 drush_log(dt('Mime type for !file is application/octet-stream.', array('!file' => $filename)), LogLevel::DEBUG);
896 $content_type = FALSE;
899 // If apache is configured in such a way that all files are considered
900 // octet-stream (e.g with mod_mime_magic and an http conf that's serving all
901 // archives as octet-stream for other reasons) we'll detect mime types on our
902 // own by examing the file's magic header bytes.
903 if (!$content_type) {
904 drush_log(dt('Examining !file headers.', array('!file' => $filename)), LogLevel::DEBUG);
905 if ($file = fopen($filename, 'rb')) {
906 $first = fread($file, 2);
909 if ($first !== FALSE) {
910 // Interpret the two bytes as a little endian 16-bit unsigned int.
911 $data = unpack('v', $first);
914 // First two bytes of gzip files are 0x1f, 0x8b (little-endian).
915 // See http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
916 $content_type = 'application/x-gzip';
920 // First two bytes of zip files are 0x50, 0x4b ('PK') (little-endian).
921 // See http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
922 $content_type = 'application/zip';
926 // First two bytes of bzip2 files are 0x5a, 0x42 ('BZ') (big-endian).
927 // See http://en.wikipedia.org/wiki/Bzip2#File_format
928 $content_type = 'application/x-bzip2';
932 drush_log(dt('Unable to determine mime type from header bytes 0x!hex of !file.', array('!hex' => dechex($data[1]), '!file' => $filename,), LogLevel::DEBUG));
936 drush_log(dt('Unable to read !file.', array('!file' => $filename)), LogLevel::WARNING);
940 drush_log(dt('Unable to open !file.', array('!file' => $filename)), LogLevel::WARNING);
944 // 3. Lastly if above methods didn't work, try to guess the mime type from
945 // the file extension. This is useful if the file has no identificable magic
946 // header bytes (for example tarballs).
947 if (!$content_type) {
948 drush_log(dt('Examining !file extension.', array('!file' => $filename)), LogLevel::DEBUG);
950 // Remove querystring from the filename, if present.
951 $filename = basename(current(explode('?', $filename, 2)));
952 $extension_mimetype = array(
953 '.tar' => 'application/x-tar',
954 '.sql' => 'application/octet-stream',
956 foreach ($extension_mimetype as $extension => $ct) {
957 if (substr($filename, -strlen($extension)) === $extension) {
963 return $content_type;
967 * Check whether a file is a supported tarball.
970 * The file content type if it's a tarball. FALSE otherwise.
972 function drush_file_is_tarball($path) {
973 $content_type = drush_attempt_mime_content_type($path);
975 'application/x-bzip2',
976 'application/x-gzip',
981 if (in_array($content_type, $supported)) {
982 return $content_type;
990 * @param string $path
991 * Path to the archive to be extracted.
992 * @param string $destination
993 * The destination directory the tarball should be extracted into.
994 * Optional, if ommitted the tarball directory will be used as destination.
995 * @param boolean $listing
996 * If TRUE, a listing of the tar contents will be returned on success.
997 * @param string $tar_extra_options
998 * Extra options to be passed to the tar command.
1001 * TRUE on success, FALSE on fail. If $listing is TRUE, a file listing of the
1002 * tarball is returned if the extraction reported success, instead of TRUE.
1004 function drush_tarball_extract($path, $destination = FALSE, $listing = FALSE, $tar_extra_options = '') {
1005 // Check if tarball is supported.
1006 if (!($mimetype = drush_file_is_tarball($path))) {
1007 return drush_set_error('TARBALL_EXTRACT_UNKNOWN_FORMAT', dt('Unable to extract !path. Unknown archive format.', array('!path' => $path)));
1010 // Check if destination is valid.
1011 if (!$destination) {
1012 $destination = dirname($path);
1014 if (!drush_mkdir($destination)) {
1015 // drush_mkdir already set an error.
1019 // Perform the extraction of a zip file.
1020 if (($mimetype == 'application/zip') || ($mimetype == 'application/x-zip')) {
1021 $return = drush_shell_cd_and_exec(dirname($path), "unzip %s -d %s", $path, $destination);
1023 return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to unzip !filename.', array('!filename' => $path)));
1026 // unzip prefixes the file listing output with a header line,
1027 // and prefixes each line with a verb representing the compression type.
1028 $output = drush_shell_exec_output();
1029 // Remove the header line.
1030 array_shift($output);
1031 // Remove the prefix verb from each line.
1032 $output = array_map(
1033 function ($str) use ($destination) {
1034 return substr($str, strpos($str, ":") + 3 + strlen($destination));
1038 // Remove any remaining blank lines.
1039 $return = array_filter(
1047 // Otherwise we have a possibly-compressed Tar file.
1048 // If we are not on Windows, then try to do "tar" in a single operation.
1049 elseif (!drush_is_windows()) {
1050 $tar = drush_get_tar_executable();
1051 $tar_compression_flag = '';
1052 if ($mimetype == 'application/x-gzip') {
1053 $tar_compression_flag = 'z';
1055 elseif ($mimetype == 'application/x-bzip2') {
1056 $tar_compression_flag = 'j';
1059 $return = drush_shell_cd_and_exec(dirname($path), "$tar {$tar_extra_options} -C %s -x%sf %s", $destination, $tar_compression_flag, basename($path));
1061 return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to untar !filename.', array('!filename' => $path)));
1064 // We use a separate tar -tf instead of -xvf above because
1065 // the output is not the same in Mac.
1066 drush_shell_cd_and_exec(dirname($path), "$tar -t%sf %s", $tar_compression_flag, basename($path));
1067 $return = drush_shell_exec_output();
1070 // In windows, do the extraction by its primitive steps.
1072 // 1. copy the source tarball to the destination directory. Rename to a
1073 // temp name in case the destination directory == dirname($path)
1074 $tmpfile = drush_tempnam(basename($path), $destination);
1075 drush_copy_dir($path, $tmpfile, FILE_EXISTS_OVERWRITE);
1077 // 2. uncompress the tarball, if compressed.
1078 if (($mimetype == 'application/x-gzip') || ($mimetype == 'application/x-bzip2')) {
1079 if ($mimetype == 'application/x-gzip') {
1080 $compressed = $tmpfile . '.gz';
1081 // We used to use gzip --decompress in --stdout > out, but the output
1082 // redirection sometimes failed on Windows for some binary output.
1083 $command = 'gzip --decompress %s';
1085 elseif ($mimetype == 'application/x-bzip2') {
1086 $compressed = $tmpfile . '.bz2';
1087 $command = 'bzip2 --decompress %s';
1089 drush_op('rename', $tmpfile, $compressed);
1090 $return = drush_shell_cd_and_exec(dirname($compressed), $command, $compressed);
1091 if (!$return || !file_exists($tmpfile)) {
1092 return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to decompress !filename.', array('!filename' => $compressed)));
1097 $tar = drush_get_tar_executable();
1098 $return = drush_shell_cd_and_exec(dirname($tmpfile), "$tar {$tar_extra_options} -xvf %s", basename($tmpfile));
1100 return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to untar !filename.', array('!filename' => $tmpfile)));
1103 $return = drush_shell_exec_output();
1104 // Cut off the 'x ' prefix for the each line of the tar output
1105 // See http://drupal.org/node/1775520
1106 foreach($return as &$line) {
1107 if(strpos($line, "x ") === 0)
1108 $line = substr($line, 2);
1112 // Remove the temporary file so the md5 hash is accurate.
1120 * @defgroup commandprocessing Command processing functions.
1123 * These functions manage command processing by the
1124 * main function in drush.php.
1128 * Determine whether or not an argument should be removed from the
1129 * DRUSH_COMMAND_ARGS context. This method is used when a Drush
1130 * command has set the 'strict-option-handling' flag indicating
1131 * that it will pass through all commandline arguments and any
1132 * additional options (not known to Drush) to some shell command.
1134 * Take as an example the following call to core-rsync:
1136 * drush --yes core-rsync -v -az --exclude-paths='.git:.svn' local-files/ @site:%files
1140 * --yes is a global Drush option
1142 * -v is an rsync option. It will make rsync run in verbose mode,
1143 * but will not make Drush run in verbose mode due to the fact that
1144 * core-rsync sets the 'strict-option-handling' flag.
1146 * --exclude-paths is a local Drush option. It will be converted by
1147 * Drush into --exclude='.git' and --exclude='.svn', and then passed
1148 * on to the rsync command.
1150 * The parameter $arg passed to this function is one of the elements
1151 * of DRUSH_COMMAND_ARGS. It will have values such as:
1154 * --exclude-paths='.git:.svn'
1158 * Our job in this function is to determine if $arg should be removed
1159 * by virtue of appearing in $removal_list. $removal_list is an array
1160 * that will contain values such as 'exclude-paths'. Both the key and
1161 * the value of $removal_list is the same.
1163 function _drush_should_remove_command_arg($arg, $removal_list) {
1164 foreach ($removal_list as $candidate) {
1165 if (($arg == "-$candidate") ||
1166 ($arg == "--$candidate") ||
1167 (substr($arg,0,strlen($candidate)+3) == "--$candidate=") ) {
1175 * Redispatch the specified command using the same
1176 * options that were passed to this invocation of drush.
1178 function drush_do_command_redispatch($command, $args = array(), $remote_host = NULL, $remote_user = NULL, $drush_path = NULL, $user_interactive = FALSE, $aditional_options = array()) {
1179 $additional_global_options = array();
1180 $command_options = drush_redispatch_get_options();
1181 $command_options = $aditional_options + $command_options;
1182 if (is_array($command)) {
1183 $command_name = $command['command'];
1184 // If we are executing a remote command that uses strict option handling,
1185 // then mark all of the options in the alias context as global, so that they
1186 // will appear before the command name.
1187 if (!empty($command['strict-option-handling'])) {
1188 foreach(drush_get_context('alias') as $alias_key => $alias_value) {
1189 if (array_key_exists($alias_key, $command_options) && !array_key_exists($alias_key, $command['options'])) {
1190 $additional_global_options[$alias_key] = $alias_value;
1196 $command_name = $command;
1198 // If the path to drush was supplied, then use it to invoke the new command.
1199 if ($drush_path == NULL) {
1200 $drush_path = drush_get_option('drush-script');
1201 if (!isset($drush_path)) {
1202 $drush_folder = drush_get_option('drush');
1203 if (isset($drush)) {
1204 $drush_path = $drush_folder . '/drush';
1208 $backend_options = array('drush-script' => $drush_path, 'remote-host' => $remote_host, 'remote-user' => $remote_user, 'integrate' => TRUE, 'additional-global-options' => $additional_global_options);
1209 // Set the tty if requested, if the command necessitates it,
1210 // or if the user explicitly asks for interactive mode, but
1211 // not if interactive mode is forced. tty implies interactive
1212 if (drush_get_option('tty') || $user_interactive || !empty($command['remote-tty'])) {
1213 $backend_options['#tty'] = TRUE;
1214 $backend_options['interactive'] = TRUE;
1216 elseif (drush_get_option('interactive')) {
1217 $backend_options['interactive'] = TRUE;
1220 // Run the command in a new process.
1221 drush_log(dt('Begin redispatch via drush_invoke_process().'));
1222 $values = drush_invoke_process('@self', $command_name, $args, $command_options, $backend_options);
1223 drush_log(dt('End redispatch via drush_invoke_process().'));
1230 * @} End of "defgroup commandprocessing".
1234 * @defgroup logging Logging information to be provided as output.
1237 * These functions are primarily for diagnostic purposes, but also provide an overview of tasks that were taken
1242 * Add a log message to the log history.
1244 * This function calls the callback stored in the 'DRUSH_LOG_CALLBACK' context with
1245 * the resulting entry at the end of execution.
1247 * This allows you to replace it with custom logging implementations if needed,
1248 * such as logging to a file or logging to a database (drupal or otherwise).
1250 * The default callback is the Drush\Log\Logger class with prints the messages
1254 * String containing the message to be logged.
1256 * The type of message to be logged. Common types are 'warning', 'error', 'success' and 'notice'.
1257 * A type of 'ok' or 'success' can also be supplied to flag something that worked.
1258 * If you want your log messages to print to screen without the user entering
1259 * a -v or --verbose flag, use type 'ok', this prints log messages out to
1260 * STDERR, which prints to screen (unless you have redirected it). All other
1261 * types of messages will be assumed to be notices.
1263 function drush_log($message, $type = LogLevel::NOTICE, $error = null) {
1266 'message' => $message,
1267 'timestamp' => microtime(TRUE),
1268 'memory' => memory_get_usage(),
1270 $entry['error'] = $error;
1272 return _drush_log($entry);
1276 * Future: add some sort of dependency injection to Drush.
1278 function _drush_create_default_logger() {
1279 $drush_logger = new Logger();
1280 drush_set_context('DRUSH_LOGGER', $drush_logger);
1281 drush_set_context('DRUSH_LOG_CALLBACK', $drush_logger);
1285 * Call the default logger, or the user's log callback, as
1288 function _drush_log($entry) {
1289 $callback = drush_get_context('DRUSH_LOG_CALLBACK');
1291 if ($callback instanceof LoggerInterface) {
1292 _drush_log_to_logger($callback, $entry);
1294 elseif ($callback) {
1295 $log =& drush_get_context('DRUSH_LOG', array());
1297 drush_backend_packet('log', $entry);
1298 return $callback($entry);
1302 // Maintain compatibility with extensions that hook into
1303 // DRUSH_LOG_CALLBACK (e.g. drush_ctex_bonus)
1304 function _drush_print_log($entry) {
1305 $drush_logger = drush_get_context('DRUSH_LOGGER');
1306 if ($drush_logger) {
1307 _drush_log_to_logger($drush_logger, $entry);
1311 function _drush_log_to_logger($logger, $entry) {
1313 $log_level = $entry['type'];
1314 $message = $entry['message'];
1315 unset($entry['type']);
1316 unset($entry['message']);
1318 $logger->log($log_level, $message, $context);
1321 function drush_log_has_errors($types = array(LogLevel::WARNING, LogLevel::ERROR, LogLevel::FAILED)) {
1322 $log =& drush_get_context('DRUSH_LOG', array());
1323 foreach ($log as $entry) {
1324 if (in_array($entry['type'], $types)) {
1332 * Backend command callback. Add a log message to the log history.
1337 function drush_backend_packet_log($entry, $backend_options) {
1338 if (!$backend_options['log']) {
1341 if (!is_string($entry['message'])) {
1342 $entry['message'] = implode("\n", (array)$entry['message']);
1344 $entry['message'] = $entry['message'];
1345 if (array_key_exists('#output-label', $backend_options)) {
1346 $entry['message'] = $backend_options['#output-label'] . $entry['message'];
1349 // If integrate is FALSE, then log messages are stored in DRUSH_LOG,
1350 // but are -not- printed to the console.
1351 if ($backend_options['integrate']) {
1355 $log =& drush_get_context('DRUSH_LOG', array());
1357 // Yes, this looks odd, but we might in fact be a backend command
1358 // that ran another backend command.
1359 drush_backend_packet('log', $entry);
1364 * Retrieve the log messages from the log history
1367 * Entire log history
1369 function drush_get_log() {
1370 return drush_get_context('DRUSH_LOG', array());
1374 * Run print_r on a variable and log the output.
1376 function dlm($object) {
1377 drush_log(print_r($object, TRUE));
1381 * Display the pipe output for the current request.
1383 function drush_pipe_output() {
1384 $pipe = drush_get_context('DRUSH_PIPE_BUFFER');
1385 if (!empty($pipe)) {
1386 drush_print_r($pipe, NULL, FALSE);
1390 // Print all timers for the request.
1391 function drush_print_timers() {
1393 $temparray = array();
1394 foreach ((array)$timers as $name => $timerec) {
1395 // We have to use timer_read() for active timers, and check the record for others
1396 if (isset($timerec['start'])) {
1397 $temparray[$name] = timer_read($name);
1400 $temparray[$name] = $timerec['time'];
1403 // Go no farther if there were no timers
1404 if (count($temparray) > 0) {
1405 // Put the highest cumulative times first
1408 $table[] = array('Timer', 'Cum (sec)', 'Count', 'Avg (msec)');
1409 foreach ($temparray as $name => $time) {
1410 $cum = round($time/1000, 3);
1411 $count = $timers[$name]['count'];
1413 $avg = round($time/$count, 3);
1418 $table[] = array($name, $cum, $count, $avg);
1420 drush_print_table($table, TRUE, array(), STDERR);
1425 * Turn drupal_set_message errors into drush_log errors
1427 function _drush_log_drupal_messages() {
1428 if (function_exists('drupal_get_messages')) {
1430 $messages = drupal_get_messages(NULL, TRUE);
1432 if (array_key_exists('error', $messages)) {
1433 //Drupal message errors.
1434 foreach ((array) $messages['error'] as $error) {
1435 $error = strip_tags($error);
1436 $header = preg_match('/^warning: Cannot modify header information - headers already sent by /i', $error);
1437 $session = preg_match('/^warning: session_start\(\): Cannot send session /i', $error);
1438 if ($header || $session) {
1439 //These are special cases for an unavoidable warnings
1440 //that are generated by generating output before Drupal is bootstrapped.
1441 //or sending a session cookie (seems to affect d7 only?)
1442 //Simply ignore them.
1445 elseif (preg_match('/^warning:/i', $error)) {
1446 drush_log(preg_replace('/^warning: /i', '', $error), LogLevel::WARNING);
1448 elseif (preg_match('/^notice:/i', $error)) {
1449 drush_log(preg_replace('/^notice: /i', '', $error), LogLevel::NOTICE);
1451 elseif (preg_match('/^user warning:/i', $error)) {
1452 // This is a special case. PHP logs sql errors as 'User Warnings', not errors.
1453 drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', preg_replace('/^user warning: /i', '', $error));
1456 drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', $error);
1460 unset($messages['error']);
1462 // Log non-error messages.
1463 foreach ($messages as $type => $items) {
1464 foreach ($items as $item) {
1465 drush_log(strip_tags($item), $type);
1471 // Copy of format_size() in Drupal.
1472 function drush_format_size($size) {
1473 if ($size < DRUSH_KILOBYTE) {
1474 // format_plural() not always available.
1475 return dt('@count bytes', array('@count' => $size));
1478 $size = $size / DRUSH_KILOBYTE; // Convert bytes to kilobytes.
1480 dt('@size KB', array()),
1481 dt('@size MB', array()),
1482 dt('@size GB', array()),
1483 dt('@size TB', array()),
1484 dt('@size PB', array()),
1485 dt('@size EB', array()),
1486 dt('@size ZB', array()),
1487 dt('@size YB', array()),
1489 foreach ($units as $unit) {
1490 if (round($size, 2) >= DRUSH_KILOBYTE) {
1491 $size = $size / DRUSH_KILOBYTE;
1497 return str_replace('@size', round($size, 2), $unit);
1502 * @} End of "defgroup logging".
1506 * @defgroup errorhandling Managing errors that occur in the Drush framework.
1508 * Functions that manage the current error status of the Drush framework.
1510 * These functions operate by maintaining a static variable that is a equal to the constant DRUSH_FRAMEWORK_ERROR if an
1511 * error has occurred.
1512 * This error code is returned at the end of program execution, and provide the shell or calling application with
1513 * more information on how to diagnose any problems that may have occurred.
1517 * Set an error code for the error handling system.
1519 * @param \Drupal\Component\Render\MarkupInterface|string $error
1520 * A text string identifying the type of error.
1521 * @param null|string $message
1522 * Optional. Error message to be logged. If no message is specified,
1523 * hook_drush_help will be consulted, using a key of 'error:MY_ERROR_STRING'.
1524 * @param null|string $output_label
1525 * Optional. Label to prepend to the error message.
1528 * Always returns FALSE, to allow returning false in the calling functions,
1529 * such as <code>return drush_set_error('DRUSH_FRAMEWORK_ERROR')</code>.
1531 function drush_set_error($error, $message = null, $output_label = "") {
1532 $error_code =& drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
1533 $error_code = DRUSH_FRAMEWORK_ERROR;
1535 $error_log =& drush_get_context('DRUSH_ERROR_LOG', array());
1537 if (is_numeric($error)) {
1538 $error = 'DRUSH_FRAMEWORK_ERROR';
1540 elseif (!is_string($error)) {
1541 // Typical case: D8 TranslatableMarkup, implementing MarkupInterface.
1545 $message = ($message) ? $message : drush_command_invoke_all('drush_help', 'error:' . $error);
1547 if (is_array($message)) {
1548 $message = implode("\n", $message);
1551 $error_log[$error][] = $message;
1552 if (!drush_backend_packet('set_error', array('error' => $error, 'message' => $message))) {
1553 drush_log(($message) ? $output_label . $message : $output_label . $error, LogLevel::ERROR, $error);
1560 * Return the current error handling status
1563 * The current aggregate error status
1565 function drush_get_error() {
1566 return drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
1570 * Return the current list of errors that have occurred.
1573 * An associative array of error messages indexed by the type of message.
1575 function drush_get_error_log() {
1576 return drush_get_context('DRUSH_ERROR_LOG', array());
1580 * Check if a specific error status has been set.
1583 * A text string identifying the error that has occurred.
1585 * TRUE if the specified error has been set, FALSE if not
1587 function drush_cmp_error($error) {
1588 $error_log = drush_get_error_log();
1590 if (is_numeric($error)) {
1591 $error = 'DRUSH_FRAMEWORK_ERROR';
1594 return array_key_exists($error, $error_log);
1598 * Clear error context.
1600 function drush_clear_error() {
1601 drush_set_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
1605 * Exit due to user declining a confirmation prompt.
1607 * Usage: return drush_user_abort();
1609 function drush_user_abort($msg = NULL) {
1610 drush_set_context('DRUSH_USER_ABORT', TRUE);
1611 drush_set_context('DRUSH_EXIT_CODE', DRUSH_EXITCODE_USER_ABORT);
1612 drush_log($msg ? $msg : dt('Cancelled.'), LogLevel::CANCEL);
1617 * Turn PHP error handling off.
1619 * This is commonly used while bootstrapping Drupal for install
1622 * This also records the previous error_reporting setting, in
1623 * case it wasn't recorded previously.
1625 * @see drush_errors_off()
1627 function drush_errors_off() {
1628 drush_get_context('DRUSH_ERROR_REPORTING', error_reporting(0));
1629 ini_set('display_errors', FALSE);
1633 * Turn PHP error handling on.
1635 * We default to error_reporting() here just in
1636 * case drush_errors_on() is called before drush_errors_off() and
1637 * the context is not yet set.
1639 * @arg $errors string
1640 * The default error level to set in drush. This error level will be
1641 * carried through further drush_errors_on()/off() calls even if not
1642 * provided in later calls.
1644 * @see error_reporting()
1645 * @see drush_errors_off()
1647 function drush_errors_on($errors = null) {
1648 if (!isset($errors)) {
1649 $errors = error_reporting();
1652 drush_set_context('DRUSH_ERROR_REPORTING', $errors);
1654 error_reporting(drush_get_context('DRUSH_ERROR_REPORTING', $errors));
1655 ini_set('display_errors', TRUE);
1659 * @} End of "defgroup errorhandling".
1663 * Get the PHP memory_limit value in bytes.
1665 function drush_memory_limit() {
1666 $value = trim(ini_get('memory_limit'));
1667 $last = strtolower($value[strlen($value)-1]);
1670 $value *= DRUSH_KILOBYTE;
1672 $value *= DRUSH_KILOBYTE;
1674 $value *= DRUSH_KILOBYTE;
1681 * Unset the named key anywhere in the provided
1684 function drush_unset_recursive(&$data, $unset_key) {
1685 if (!empty($data) && is_array($data)) {
1686 unset($data[$unset_key]);
1687 foreach ($data as $key => $value) {
1688 if (is_array($value)) {
1689 drush_unset_recursive($data[$key], $unset_key);
1696 * Return a list of VCSs reserved files and directories.
1698 function drush_version_control_reserved_files() {
1699 static $files = FALSE;
1702 // Also support VCSs that are not drush vc engines.
1703 $files = array('.git', '.gitignore', '.hg', '.hgignore', '.hgrags');
1704 $engine_info = drush_get_engines('version_control');
1705 $vcs = array_keys($engine_info['engines']);
1706 foreach ($vcs as $name) {
1707 $version_control = drush_include_engine('version_control', $name);
1708 $files = array_merge($files, $version_control->reserved_files());
1716 * Generate a random alphanumeric password. Copied from user.module.
1718 function drush_generate_password($length = 10) {
1719 // This variable contains the list of allowable characters for the
1720 // password. Note that the number 0 and the letter 'O' have been
1721 // removed to avoid confusion between the two. The same is true
1722 // of 'I', 1, and 'l'.
1723 $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
1725 // Zero-based count of characters in the allowable list:
1726 $len = strlen($allowable_characters) - 1;
1728 // Declare the password as a blank string.
1731 // Loop the number of times specified by $length.
1732 for ($i = 0; $i < $length; $i++) {
1734 // Each iteration, pick a random character from the
1735 // allowable string and append it to the password:
1736 $pass .= $allowable_characters[mt_rand(0, $len)];
1743 * Form an associative array from a linear array.
1745 * This function walks through the provided array and constructs an associative
1746 * array out of it. The keys of the resulting array will be the values of the
1747 * input array. The values will be the same as the keys unless a function is
1748 * specified, in which case the output of the function is used for the values
1754 * A name of a function to apply to all values before output.
1757 * An associative array.
1759 function drush_map_assoc($array, $function = NULL) {
1760 // array_combine() fails with empty arrays:
1761 // http://bugs.php.net/bug.php?id=34857.
1762 $array = !empty($array) ? array_combine($array, $array) : array();
1763 if (is_callable($function)) {
1764 $array = array_map($function, $array);
1769 * Clears completion caches.
1771 * If called with no parameters the entire complete cache will be cleared.
1772 * If called with just the $type parameter the global cache for that type will
1773 * be cleared (in the site context, if any). If called with both $type and
1774 * $command parameters the command cache of that type will be cleared (in the
1775 * site context, if any).
1777 * This is included in drush.inc as complete.inc is only loaded conditionally.
1780 * The completion type (optional).
1782 * The command name (optional), if command specific cache is to be cleared.
1783 * If specifying a command, $type is not optional.
1785 function drush_complete_cache_clear($type = NULL, $command = NULL) {
1786 require_once DRUSH_BASE_PATH . '/includes/complete.inc';
1788 drush_cache_clear_all(drush_complete_cache_cid($type, $command), 'complete');
1791 // No type or command, so clear the entire complete cache.
1792 drush_cache_clear_all('*', 'complete', TRUE);
1796 * Cast a value according to the provided format
1798 * @param mixed $value.
1799 * @param string $format
1800 * Allowed values: auto, integer, float, bool, boolean, json, yaml.
1803 * The value, re-typed as needed.
1805 function drush_value_format($value, $format) {
1806 if ($format == 'auto') {
1807 if (is_numeric($value)) {
1808 $value = $value + 0; // http://php.net/manual/en/function.is-numeric.php#107326
1809 $format = gettype($value);
1811 elseif (($value == 'TRUE') || ($value == 'FALSE')) {
1816 // Now, we parse the object.
1819 $value = (integer)$value;
1821 // from: http://php.net/gettype
1822 // for historical reasons "double" is returned in case of a float, and not simply "float"
1825 $value = (float)$value;
1829 if ($value == 'TRUE') {
1832 elseif ($value == 'FALSE') {
1836 $value = (bool)$value;
1841 $value = drush_json_decode($value);
1845 $value = \Symfony\Component\Yaml\Yaml::parse($value, FALSE, TRUE);