command.inc

  1. 8.0.x includes/command.inc
  2. 6.x includes/command.inc
  3. 7.x includes/command.inc
  4. 3.x includes/command.inc
  5. 4.x includes/command.inc
  6. 5.x includes/command.inc
  7. master includes/command.inc

The drush command engine.

Since drush can be invoked independently of a proper Drupal installation and commands may operate across sites, a distinct command engine is needed.

It mimics the Drupal module engine in order to economize on concepts and to make developing commands as familiar as possible to traditional Drupal module developers.

Functions

Namesort descending Description
commandfiles_cache
drush_adjust_args_if_shebang_script Special checking for "shebang" script handling.
drush_append_negation_options
drush_command Entry point for commands into the drush_invoke() API
drush_commandfile_list Collect a list of all available drush command files.
drush_commands_categorize Organize commands into categories. Used by help listing and core-cli.
drush_command_defaults
drush_command_default_options Conditionally include default options based on the command used.
drush_command_get_command_specific_options Return all of the command-specific options defined in the given options set for the specified command name. Note that it is valid to use the command name alias rather than the primary command name, both in the parameter to this function, and in the…
drush_command_get_includes
drush_command_hook Determine whether a command file implements a hook.
drush_command_implements Determine which command files are implementing a hook.
drush_command_include Conditionally include files based on the command used.
drush_command_invoke_all Invoke a hook in all available command files that implement it.
drush_command_invoke_all_ref A drush_command_invoke_all() that wants the first parameter to be passed by reference.
drush_command_normalize_name
drush_command_set_command_specific
drush_command_set_command_specific_options
drush_command_translate Translates description and other keys of a command definition.
drush_dispatch Given a command record, dispatch it as if it were the original command. Executes in the currently bootstrapped site using the current option contexts. Note that drush_dispatch will not bootstrap any further than the current command has already…
drush_enforce_requirement_bootstrap_phase Check that a command is valid for the current bootstrap phase.
drush_enforce_requirement_core Check that a command is valid for the current major version of core. Handles explicit version numbers and 'plus' numbers like 7+ (compatible with 7,8 ...).
drush_enforce_requirement_drush_dependencies Check that a command has its declared drush dependencies available or have no dependencies. Drush dependencies are helpful when a command is invoking another command, or implementing its API.
drush_filename_blacklist Substrings to ignore during commandfile and site alias searching.
drush_get_commands Get a list of all implemented commands. This invokes hook_drush_command().
drush_get_command_options_extended Return the list of all of the options for the given command record including options provided by engines and additional-options.
drush_get_original_cli_args_and_options Return the original cli args and options, exactly as they appeared on the command line, and in the same order. Any command-specific options that were set will also appear in this list, appended at the very end.
drush_handle_command_output Convert the structured output array provided from the Drush command into formatted output. Output is only printed for commands that define 'default-format' &/or 'default-pipe-format'; all other commands are expected to do…
drush_invoke Invokes a Drush API call, including all hooks.
drush_invoke_process Invoke a command in a new process, targeting the site specified by the provided site alias record.
drush_is_command
drush_parse_args Parse console arguments.
drush_parse_command Matches a commands array, as returned by drush_get_arguments, with the current command table.
drush_process_bootstrap_to_first_arg Process the --bootstrap-to-first-arg option, if it is present.
drush_redispatch_get_options Get the options that were passed to the current command.
drush_shell_alias_replace Check if a shell alias exists for current request. If so, re-route to core-execute and pass alias value along with rest of CLI arguments.
drush_shift Pop an argument off of drush's argument list
drush_sitealias_command_default_options
_drush_add_commandfiles
_drush_command_set_default_options
_drush_command_translate Helper function for drush_command_translate().
_drush_find_commandfiles
_drush_flatten_options Return the array keys of $options, plus any 'short-form' representations that may appear in the option's value.
_drush_get_command_options Return the list of all of the options for the given command record by merging the 'options' and 'sub-options' records.
_drush_invoke_hooks Invoke Drush API calls, including all hooks.
_drush_prepare_command Called by drush_parse_command(). If a command is dispatched directly by drush_dispatch(), then drush_dispatch() will call this function.
_drush_verify_cli_arguments
_drush_verify_cli_options Fail with an error if the user specified options on the command line that are not documented in the current command record. Also verify that required options are present.

File

includes/command.inc
View source
  1. <?php
  2. use Drush\Log\LogLevel;
  3. use Webmozart\PathUtil\Path;
  4. use Consolidation\AnnotatedCommand\AnnotationData;
  5. use Drush\Command\DrushInputAdapter;
  6. use Drush\Command\DrushOutputAdapter;
  7. use Consolidation\AnnotatedCommand\CommandData;
  8. /**
  9. * @defgroup dispatching Command dispatching functions.
  10. * @{
  11. *
  12. * These functions handle command dispatching, and can
  13. * be used to programatically invoke drush commands in
  14. * different ways.
  15. */
  16. /**
  17. * Invokes a Drush API call, including all hooks.
  18. *
  19. * Executes the specified command with the specified arguments on the currently
  20. * bootstrapped site using the current option contexts. Note that it will not
  21. * bootstrap any further than the current command has already bootstrapped;
  22. * therefore, you should only invoke commands that have the same (or lower)
  23. * bootstrap requirements.
  24. *
  25. * Commands execute with the same options that the user provided on the
  26. * commandline. If you need to invoke another Drush command with options you
  27. * specify, use drush_invoke_process() instead.
  28. *
  29. * @param string $command
  30. * The command to invoke.
  31. * @param array $arguments
  32. * An array of argument to pass into the command.
  33. *
  34. * @return mixed|false
  35. * The return value from drush_dispatch() or FALSE on error.
  36. *
  37. * @see drush_invoke_process()
  38. */
  39. function drush_invoke($command, $arguments = array()) {
  40. // Convert a standalone argument to a single-element array.
  41. if (!is_array($arguments)) {
  42. $arguments = array($arguments);
  43. }
  44. $commands = drush_get_commands();
  45. if (array_key_exists($command, $commands)) {
  46. $command = $commands[$command];
  47. // Drush overloads the 'arguments' element, which contains the help string
  48. // for the allowed arguments for the command when fetched, and is fixed up
  49. // by _drush_prepare_command() to contain the actual commandline arguments
  50. // during dispatching.
  51. $command['arguments'] = array();
  52. return drush_dispatch($command, $arguments);
  53. }
  54. else {
  55. return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt("The drush command '!command' could not be found.", array('!command' => $command)));
  56. }
  57. }
  58. /**
  59. * Invoke a command in a new process, targeting the site specified by
  60. * the provided site alias record.
  61. *
  62. * @param array $site_alias_record
  63. * The site record to execute the command on. Use '@self' to run on the current site.
  64. * @param string $command_name
  65. * The command to invoke.
  66. * @param array $commandline_args
  67. * The arguments to pass to the command.
  68. * @param array $commandline_options
  69. * The options (e.g. --select) to provide to the command.
  70. * @param mixed $backend_options
  71. * TRUE - integrate errors
  72. * FALSE - do not integrate errors
  73. * array - @see drush_backend_invoke_concurrent
  74. * There are also several options that _only_ work when set in
  75. * this parameter. They include:
  76. * 'invoke-multiple'
  77. * If $site_alias_record represents a single site, then 'invoke-multiple'
  78. * will cause the _same_ command with the _same_ arguments and options
  79. * to be invoked concurrently (e.g. for running concurrent batch processes).
  80. * 'concurrency'
  81. * Limits the number of concurrent processes that will run at the same time.
  82. * Defaults to '4'.
  83. * 'override-simulated'
  84. * Forces the command to run, even in 'simulated' mode. Useful for
  85. * commands that do not change any state on the machine, e.g. to fetch
  86. * database information for sql-sync via sql-conf.
  87. * 'interactive'
  88. * Overrides the backend invoke process to run commands interactively.
  89. * 'fork'
  90. * Overrides the backend invoke process to run non blocking commands in
  91. * the background. Forks a new process by adding a '&' at the end of the
  92. * command. The calling process does not receive any output from the child
  93. * process. The fork option is used to spawn a process that outlives its
  94. * parent.
  95. *
  96. * @return
  97. * If the command could not be completed successfully, FALSE.
  98. * If the command was completed, this will return an associative
  99. * array containing the results of the API call.
  100. * @see drush_backend_get_result()
  101. *
  102. * Do not change the signature of this function! drush_invoke_process
  103. * is one of the key Drush APIs. See http://drupal.org/node/1152908
  104. */
  105. function drush_invoke_process($site_alias_record, $command_name, $commandline_args = array(), $commandline_options = array(), $backend_options = TRUE) {
  106. if (is_array($site_alias_record) && array_key_exists('site-list', $site_alias_record)) {
  107. list($site_alias_records, $not_found) = drush_sitealias_resolve_sitespecs($site_alias_record['site-list']);
  108. if (!empty($not_found)) {
  109. drush_log(dt("Not found: @list", array("@list" => implode(', ', $not_found))), LogLevel::WARNING);
  110. return FALSE;
  111. }
  112. $site_alias_records = drush_sitealias_simplify_names($site_alias_records);
  113. foreach ($site_alias_records as $alias_name => $alias_record) {
  114. $invocations[] = array(
  115. 'site' => $alias_record,
  116. 'command' => $command_name,
  117. 'args' => $commandline_args,
  118. );
  119. }
  120. }
  121. else {
  122. $invocations[] = array(
  123. 'site' => $site_alias_record,
  124. 'command' => $command_name,
  125. 'args' => $commandline_args);
  126. $invoke_multiple = drush_get_option_override($backend_options, 'invoke-multiple', 0);
  127. if ($invoke_multiple) {
  128. $invocations = array_fill(0, $invoke_multiple, $invocations[0]);
  129. }
  130. }
  131. return drush_backend_invoke_concurrent($invocations, $commandline_options, $backend_options);
  132. }
  133. /**
  134. * Given a command record, dispatch it as if it were
  135. * the original command. Executes in the currently
  136. * bootstrapped site using the current option contexts.
  137. * Note that drush_dispatch will not bootstrap any further than the
  138. * current command has already bootstrapped; therefore, you should only invoke
  139. * commands that have the same (or lower) bootstrap requirements.
  140. *
  141. * @param command
  142. * A full $command such as returned by drush_get_commands(),
  143. * or a string containing the name of the command record from
  144. * drush_get_commands() to call.
  145. * @param arguments
  146. * An array of argument values.
  147. *
  148. * @see drush_topic_docs_topic().
  149. */
  150. function drush_dispatch($command, $arguments = array()) {
  151. drush_set_command($command);
  152. $return = FALSE;
  153. if ($command) {
  154. // Add arguments, if this has not already been done.
  155. // (If the command was fetched from drush_parse_command,
  156. // then you cannot provide arguments to drush_dispatch.)
  157. if (empty($command['arguments'])) {
  158. _drush_prepare_command($command, $arguments);
  159. }
  160. // Merge in the options added by hooks. We need this
  161. // for validation, but this $command is just going to
  162. // get thrown away, so we'll have to do this again later.
  163. annotationcommand_adapter_add_hook_options($command);
  164. // Add command-specific options, if applicable.
  165. drush_command_default_options($command);
  166. // Test to see if any of the options in the 'cli' context
  167. // are not represented in the command structure.
  168. if ((_drush_verify_cli_options($command) === FALSE) || (_drush_verify_cli_arguments($command) === FALSE)) {
  169. return FALSE;
  170. }
  171. // Give command files an opportunity to alter the command record
  172. drush_command_invoke_all_ref('drush_command_alter', $command);
  173. // Include and validate command engines.
  174. if (drush_load_command_engines($command) === FALSE) {
  175. return FALSE;
  176. }
  177. // Do tilde expansion immediately prior to execution,
  178. // so that tildes are passed through unchanged for
  179. // remote commands and other redispatches.
  180. drush_preflight_tilde_expansion($command);
  181. // Get the arguments for this command. Add the options
  182. // on the end if this is that kind of command.
  183. $args = $command['arguments'];
  184. // Call the callback function of the active command.
  185. $return = call_user_func_array($command['callback'], $args);
  186. }
  187. // Add a final log entry, just so a timestamp appears.
  188. drush_log(dt('Command dispatch complete'), LogLevel::INFO);
  189. return $return;
  190. }
  191. /**
  192. * Entry point for commands into the drush_invoke() API
  193. *
  194. * If a command does not have a callback specified, this function will be called.
  195. *
  196. * This function will trigger $hook_drush_init, then if no errors occur,
  197. * it will call drush_invoke() with the command that was dispatch.
  198. *
  199. * If no errors have occured, it will run $hook_drush_exit.
  200. */
  201. function drush_command() {
  202. $args = func_get_args();
  203. $command = drush_get_command();
  204. foreach (drush_command_implements("drush_init") as $name) {
  205. $func = $name . '_drush_init';
  206. if (drush_get_option('show-invoke')) {
  207. drush_log(dt("Calling global init hook: !func", array('!name' => $name, '!func' => $func . '()')), LogLevel::BOOTSTRAP);
  208. }
  209. call_user_func_array($func, $args);
  210. _drush_log_drupal_messages();
  211. }
  212. if (!drush_get_error()) {
  213. $result = _drush_invoke_hooks($command, $args);
  214. }
  215. if (!drush_get_error()) {
  216. foreach (drush_command_implements('drush_exit') as $name) {
  217. $func = $name . '_drush_exit';
  218. if (drush_get_option('show-invoke')) {
  219. drush_log(dt("Calling global exit hook: !func", array('!name' => $name, '!func' => $func . '()')), LogLevel::BOOTSTRAP);
  220. }
  221. call_user_func_array($func, $args);
  222. _drush_log_drupal_messages();
  223. }
  224. }
  225. }
  226. /**
  227. * Invoke Drush API calls, including all hooks.
  228. *
  229. * This is an internal function; it is called from drush_dispatch via
  230. * drush_command, but only if the command does not specify a 'callback'
  231. * function. If a callback function is specified, it will be called
  232. * instead of drush_command + _drush_invoke_hooks.
  233. *
  234. * Executes the specified command with the specified arguments on the
  235. * currently bootstrapped site using the current option contexts.
  236. * Note that _drush_invoke_hooks will not bootstrap any further than the
  237. * current command has already bootstrapped; therefore, you should only invoke
  238. * commands that have the same (or lower) bootstrap requirements.
  239. *
  240. * Call the correct hook for all the modules that implement it.
  241. * Additionally, the ability to rollback when an error has been encountered is also provided.
  242. * If at any point during execution, the drush_get_error() function returns anything but 0,
  243. * drush_invoke() will trigger $hook_rollback for each of the hooks that implement it,
  244. * in reverse order from how they were executed. Rollbacks are also triggered any
  245. * time a hook function returns FALSE.
  246. *
  247. * This function will also trigger pre_$hook and post_$hook variants of the hook
  248. * and its rollbacks automatically.
  249. *
  250. * HOW DRUSH HOOK FUNCTIONS ARE NAMED:
  251. *
  252. * The name of the hook is composed from the name of the command and the name of
  253. * the command file that the command definition is declared in. The general
  254. * form for the hook filename is:
  255. *
  256. * drush_COMMANDFILE_COMMANDNAME
  257. *
  258. * In many cases, drush commands that are functionally part of a common collection
  259. * of similar commands will all be declared in the same file, and every command
  260. * defined in that file will start with the same command prefix. For example, the
  261. * command file "pm.drush.inc" defines commands such as "pm-enable" and "pm-disable".
  262. * In the case of "pm-enable", the command file is "pm", and and command name is
  263. * "pm-enable". When the command name starts with the same sequence of characters
  264. * as the command file, then the repeated sequence is dropped; thus, the command
  265. * hook for "pm-enable" is "drush_pm_enable", not "drush_pm_pm_enable".
  266. *
  267. * There is also a special Drupal-version-specific naming convention that may
  268. * be used. To hide a commandfile from all versions of Drupal except for the
  269. * specific one named, add a ".dVERSION" after the command prefix. For example,
  270. * the file "views.d8.drush.inc" defines a "views" commandfile that will only
  271. * load with Drupal 8. This feature is not necessary and should not be used
  272. * in contrib modules (any extension with a ".module" file), since these modules
  273. * are already version-specific.
  274. *
  275. * @param command
  276. * The drush command to execute.
  277. * @param args
  278. * An array of arguments to the command OR a single non-array argument.
  279. * @return
  280. * The return value will be passed along to the caller if --backend option is
  281. * present. A boolean FALSE indicates failure and rollback will be intitated.
  282. *
  283. * This function should not be called directly.
  284. * @see drush_invoke() and @see drush_invoke_process()
  285. */
  286. function _drush_invoke_hooks($command, $args) {
  287. $return = null;
  288. // If someone passed a standalone arg, convert it to a single-element array
  289. if (!is_array($args)) {
  290. $args = array($args);
  291. }
  292. // Include the external command file used by this command, if there is one.
  293. drush_command_include($command['command-hook']);
  294. // Generate the base name for the hook by converting all
  295. // dashes in the command name to underscores.
  296. $hook = str_replace("-", "_", $command['command-hook']);
  297. // Call the hook init function, if it exists.
  298. // If a command needs to bootstrap, it is advisable
  299. // to do so in _init; otherwise, new commandfiles
  300. // will miss out on participating in any stage that
  301. // has passed or started at the time it was discovered.
  302. $func = 'drush_' . $hook . '_init';
  303. if (function_exists($func)) {
  304. drush_log(dt("Calling drush command init function: !func", array('!func' => $func)), LogLevel::BOOTSTRAP);
  305. call_user_func_array($func, $args);
  306. _drush_log_drupal_messages();
  307. if (drush_get_error()) {
  308. drush_log(dt('The command @command could not be initialized.', array('@command' => $command['command-hook'])), LogLevel::ERROR);
  309. return FALSE;
  310. }
  311. }
  312. // We will adapt and call as many of the annotated command hooks as we can.
  313. // The following command hooks are not presently supported in the legacy dispatcher:
  314. // - Command Event: not called (requires CommandEvent object)
  315. // - Option: Equivalent functionality supported in annotationcommand_adapter.inc
  316. // - Interact: not called (We don't use SymfonyStyle in 8.x at the moment)
  317. // - Status: not called - probably not needed?
  318. // - Extract not called - probably not needed?
  319. // The hooks that are called include:
  320. // - Pre-initialize, initialize and post-initialize
  321. // - Pre-validate and validate
  322. // - Pre-command, command and post-command
  323. // - Pre-process, process and post-process
  324. // - Pre-alter, alter and post-alter
  325. $names = annotationcommand_adapter_command_names($command);
  326. // Merge in the options added by hooks (again)
  327. annotationcommand_adapter_add_hook_options($command);
  328. $annotationData = $command['annotations'];
  329. $input = new DrushInputAdapter($args, annotationcommand_adapter_get_options($command), $command['command']);
  330. $output = new DrushOutputAdapter();
  331. $commandData = new CommandData(
  332. $annotationData,
  333. $input,
  334. $output,
  335. false,
  336. false
  337. );
  338. annotationcommand_adapter_call_initialize($names, $commandData);
  339. $rollback = FALSE;
  340. $completed = array();
  341. $available_rollbacks = array();
  342. $all_available_hooks = array();
  343. // Iterate through the different hook variations
  344. $variations = array(
  345. 'pre_validate' => $hook . "_pre_validate",
  346. 'validate' => $hook . "_validate",
  347. 'pre_command' => "pre_$hook",
  348. 'command' => $hook,
  349. 'post_command' => "post_$hook"
  350. );
  351. foreach ($variations as $hook_phase => $var_hook) {
  352. $adapterHookFunction = 'annotationcommand_adapter_call_hook_' . $hook_phase;
  353. $adapterHookFunction($names, $commandData, $return);
  354. // Get the list of command files.
  355. // We re-fetch the list every time through
  356. // the loop in case one of the hook function
  357. // does something that will add additional
  358. // commandfiles to the list (i.e. bootstrapping
  359. // to a higher phase will do this).
  360. $list = drush_commandfile_list();
  361. // Make a list of function callbacks to call. If
  362. // there is a 'primary function' mentioned, make sure
  363. // that it appears first in the list, but only if
  364. // we are running the main hook ("$hook"). After that,
  365. // make sure that any callback associated with this commandfile
  366. // executes before any other hooks defined in any other
  367. // commandfiles.
  368. $callback_list = array();
  369. if (($var_hook == $hook) && ($command['primary function'])) {
  370. $callback_list[$command['primary function']] = $list[$command['commandfile']];
  371. }
  372. else {
  373. $primary_func = ($command['commandfile'] . "_" == substr($var_hook . "_",0,strlen($command['commandfile']) + 1)) ? sprintf("drush_%s", $var_hook) : sprintf("drush_%s_%s", $command['commandfile'], $var_hook);
  374. $callback_list[$primary_func] = $list[$command['commandfile']];
  375. }
  376. // We've got the callback for the primary function in the
  377. // callback list; now add all of the other callback functions.
  378. unset($list[$command['commandfile']]);
  379. foreach ($list as $commandfile => $filename) {
  380. $func = sprintf("drush_%s_%s", $commandfile, $var_hook);
  381. $callback_list[$func] = $filename;
  382. }
  383. // Run all of the functions available for this variation
  384. $accumulated_result = NULL;
  385. foreach ($callback_list as $func => $filename) {
  386. if (function_exists($func)) {
  387. $all_available_hooks[] = $func . ' [* Defined in ' . $filename . ']';
  388. $available_rollbacks[] = $func . '_rollback';
  389. $completed[] = $func;
  390. drush_log(dt("Calling hook !hook", array('!hook' => $func)), LogLevel::DEBUG);
  391. try {
  392. $result = call_user_func_array($func, $args);
  393. drush_log(dt("Returned from hook !hook", array('!hook' => $func)), LogLevel::DEBUG);
  394. }
  395. catch (Exception $e) {
  396. drush_set_error('DRUSH_EXECUTION_EXCEPTION', (string) $e);
  397. }
  398. // If there is an error, break out of the foreach
  399. // $variations and foreach $callback_list
  400. if (drush_get_error() || ($result === FALSE)) {
  401. $rollback = TRUE;
  402. break 2;
  403. }
  404. // If result values are arrays, then combine them all together.
  405. // Later results overwrite earlier results.
  406. if (isset($result) && is_array($accumulated_result) && is_array($result)) {
  407. $accumulated_result = array_merge($accumulated_result, $result);
  408. }
  409. else {
  410. $accumulated_result = $result;
  411. }
  412. _drush_log_drupal_messages();
  413. }
  414. else {
  415. $all_available_hooks[] = $func;
  416. }
  417. }
  418. // Process the result value from the 'main' callback hook only.
  419. if ($var_hook == $hook) {
  420. $return = $accumulated_result;
  421. if (isset($return)) {
  422. annotationcommand_adapter_call_hook_process_and_alter($names, $commandData, $return);
  423. drush_handle_command_output($command, $return);
  424. }
  425. }
  426. }
  427. // If no hook functions were found, print a warning.
  428. if (empty($completed)) {
  429. $default_command_hook = sprintf("drush_%s_%s", $command['commandfile'], $hook);
  430. if (($command['commandfile'] . "_" == substr($hook . "_",0,strlen($command['commandfile'])+ 1))) {
  431. $default_command_hook = sprintf("drush_%s", $hook);
  432. }
  433. $dt_args = array(
  434. '!command' => $command['command-hook'],
  435. '!default_func' => $default_command_hook,
  436. );
  437. $message = "No hook functions were found for !command. The primary hook function is !default_func(). Please implement this function. Run with --show-invoke to see all available hooks.";
  438. $return = drush_set_error('DRUSH_FUNCTION_NOT_FOUND', dt($message, $dt_args));
  439. }
  440. if (drush_get_option('show-invoke')) {
  441. // We show all available hooks up to and including the one that failed (or all, if there were no failures)
  442. drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command['command-hook'], '!available' => "\n" . implode("\n", $all_available_hooks))), LogLevel::OK);
  443. }
  444. if (drush_get_option('show-invoke') && !empty($available_rollbacks)) {
  445. drush_log(dt("Available rollback hooks for !command: !rollback", array('!command' => $command['command-hook'], '!rollback' => "\n" . implode("\n", $available_rollbacks))), LogLevel::OK);
  446. }
  447. // Something went wrong, we need to undo.
  448. if ($rollback) {
  449. if (drush_get_option('confirm-rollback', FALSE)) {
  450. // Optionally ask for confirmation, --yes and --no are ignored from here on as we are about to finish this process.
  451. drush_set_context('DRUSH_AFFIRMATIVE', FALSE);
  452. drush_set_context('DRUSH_NEGATIVE', FALSE);
  453. $rollback = drush_confirm(dt('Do you want to rollback? (manual cleanup might be required otherwise)'));
  454. }
  455. if ($rollback) {
  456. foreach (array_reverse($completed) as $func) {
  457. $rb_func = $func . '_rollback';
  458. if (function_exists($rb_func)) {
  459. call_user_func_array($rb_func, $args);
  460. _drush_log_drupal_messages();
  461. drush_log(dt("Changes made in !func have been rolled back.", array('!func' => $func)), LogLevel::DEBUG);
  462. }
  463. }
  464. }
  465. $return = FALSE;
  466. }
  467. if (isset($return)) {
  468. return $return;
  469. }
  470. }
  471. /**
  472. * Convert the structured output array provided from the Drush
  473. * command into formatted output. Output is only printed for commands
  474. * that define 'default-format' &/or 'default-pipe-format'; all
  475. * other commands are expected to do their own output.
  476. */
  477. function drush_handle_command_output($command, $structured_output) {
  478. // If the hook already called drush_backend_set_result,
  479. // then return that value. If it did not, then the return
  480. // value from the hook will be the value returned from
  481. // this routine.
  482. $return = drush_backend_get_result();
  483. if (empty($return)) {
  484. drush_backend_set_result($structured_output);
  485. }
  486. // We skip empty strings and empty arrays, but note that 'empty'
  487. // returns TRUE for the integer value '0', but we do want to print that.
  488. // Only handle output here if the command defined an output format
  489. // engine. If no engine was declared, then we presume that the command
  490. // handled its own output.
  491. if ((!empty($structured_output) || ($structured_output === 0))) {
  492. // If the command specifies a default pipe format and
  493. // returned a result, then output the formatted output when
  494. // in --pipe mode.
  495. $formatter = drush_get_outputformat();
  496. if (!$formatter && is_string($structured_output)) {
  497. $formatter = drush_load_engine('outputformat', 'string');
  498. }
  499. if ($formatter) {
  500. if ($formatter === TRUE) {
  501. return drush_set_error(dt('No outputformat class defined for !format', array('!format' => $format)));
  502. }
  503. if ((!empty($command['engines']['outputformat'])) && (!in_array($formatter->engine, $command['engines']['outputformat']['usable']))) {
  504. return $formatter->format_error(dt("The command '!command' does not produce output in a structure usable by this output format.", array('!command' => $command['command'])));
  505. }
  506. // Add any user-specified options to the metadata passed to the formatter.
  507. $metadata = array();
  508. $metadata['strict'] = drush_get_option('strict', FALSE);
  509. if (isset($formatter->engine_config['options'])) {
  510. $machine_parsable = $formatter->engine_config['engine-info']['machine-parsable'];
  511. if (drush_get_option('full', FALSE)) {
  512. if (isset($formatter->engine_config['fields-full'])) {
  513. $formatter->engine_config['fields-default'] = $formatter->engine_config['fields-full'];
  514. }
  515. else {
  516. $formatter->engine_config['fields-default'] = array_keys($formatter->engine_config['field-labels']);
  517. }
  518. }
  519. elseif ((drush_get_context('DRUSH_PIPE') || $machine_parsable) && isset($formatter->engine_config['fields-pipe'])) {
  520. $formatter->engine_config['fields-default'] = $formatter->engine_config['fields-pipe'];
  521. }
  522. // Determine the --format, and options relevant for that format.
  523. foreach ($formatter->engine_config['options'] as $option => $option_info) {
  524. $default_value = isset($formatter->engine_config[$option . '-default']) ? $formatter->engine_config[$option . '-default'] : FALSE;
  525. if (($default_value === FALSE) && array_key_exists('default', $option_info)) {
  526. $default_value = $option_info['default'];
  527. }
  528. if (isset($option_info['list'])) {
  529. $user_specified_value = drush_get_option_list($option, $default_value);
  530. }
  531. else {
  532. $user_specified_value = drush_get_option($option, $default_value);
  533. }
  534. if ($user_specified_value !== FALSE) {
  535. if (array_key_exists('key', $option_info)) {
  536. $option = $option_info['key'];
  537. }
  538. $metadata[$option] =$user_specified_value;
  539. }
  540. }
  541. }
  542. if (isset($metadata['fields']) && !empty($metadata['fields'])) {
  543. if (isset($formatter->engine_config['field-labels'])) {
  544. $formatter->engine_config['field-labels'] = drush_select_fields($formatter->engine_config['field-labels'], $metadata['fields'], $metadata['strict']);
  545. }
  546. }
  547. $output = $formatter->process($structured_output, $metadata);
  548. if (drush_get_context('DRUSH_PIPE')) {
  549. drush_print_pipe($output);
  550. }
  551. else {
  552. drush_print($output);
  553. }
  554. }
  555. }
  556. }
  557. /**
  558. * Fail with an error if the user specified options on the
  559. * command line that are not documented in the current command
  560. * record. Also verify that required options are present.
  561. */
  562. function _drush_verify_cli_options($command) {
  563. // Start out with just the options in the current command record.
  564. $options = _drush_get_command_options($command);
  565. // Skip all tests if the command is marked to allow anything.
  566. // Also skip backend commands, which may have options on the commandline
  567. // that were inherited from the calling command.
  568. if (($command['allow-additional-options'] === TRUE)) {
  569. return TRUE;
  570. }
  571. // If 'allow-additional-options' contains a list of command names,
  572. // then union together all of the options from all of the commands.
  573. if (is_array($command['allow-additional-options'])) {
  574. $implemented = drush_get_commands();
  575. foreach ($command['allow-additional-options'] as $subcommand_name) {
  576. if (array_key_exists($subcommand_name, $implemented)) {
  577. $options = array_merge($options, _drush_get_command_options($implemented[$subcommand_name]));
  578. }
  579. }
  580. }
  581. // Also add in global options
  582. $options = array_merge($options, drush_get_global_options());
  583. // Add a placeholder option so that backend requests originating from prior versions of Drush are valid.
  584. $options += array('invoke' => '');
  585. // Now we will figure out which options in the cli context
  586. // are not represented in our options list.
  587. $cli_options = array_keys(drush_get_context('cli'));
  588. $allowed_options = _drush_flatten_options($options);
  589. $allowed_options = drush_append_negation_options($allowed_options);
  590. $disallowed_options = array_diff($cli_options, $allowed_options);
  591. if (!empty($disallowed_options)) {
  592. $unknown = count($disallowed_options) > 1 ? dt('Unknown options') : dt('Unknown option');
  593. if (drush_get_option('strict', TRUE)) {
  594. $msg = dt("@unknown: --@options. See `drush help @command` for available options. To suppress this error, add the option --strict=0.", array('@unknown' => $unknown, '@options' => implode(', --', $disallowed_options), '@command' => $command['command']));
  595. return drush_set_error('DRUSH_UNKNOWN_OPTION', $msg);
  596. }
  597. }
  598. // Next check to see if all required options were specified,
  599. // and if all specified options with required values have values.
  600. $missing_required_options = array();
  601. $options_missing_required_values = array();
  602. foreach ($command['options'] as $option_name => $option) {
  603. if (is_array($option) && !empty($option['required']) && drush_get_option($option_name, NULL) === NULL) {
  604. $missing_required_options[] = $option_name;
  605. }
  606. // Note that drush_get_option() will return TRUE if an option
  607. // was specified without a value (--option), as opposed to
  608. // the string "1" is --option=1 was used.
  609. elseif (is_array($option) && !empty($option['value']) && ($option['value'] == 'required') && drush_get_option($option_name, NULL) === TRUE) {
  610. $options_missing_required_values[] = $option_name;
  611. }
  612. }
  613. if (!empty($missing_required_options) || !empty($options_missing_required_values)) {
  614. $missing_message = '';
  615. if (!empty($missing_required_options)) {
  616. $missing = count($missing_required_options) > 1 ? dt('Missing required options') : dt('Missing required option');
  617. $missing_message = dt("@missing: --@options.", array('@missing' => $missing, '@options' => implode(', --', $missing_required_options)));
  618. }
  619. if (!empty($options_missing_required_values)) {
  620. if (!empty($missing_message)) {
  621. $missing_message .= " ";
  622. }
  623. $missing = count($options_missing_required_values) > 1 ? dt('Options used without providing required values') : dt('Option used without a value where one was required');
  624. $missing_message .= dt("@missing: --@options.", array('@missing' => $missing, '@options' => implode(', --', $options_missing_required_values)));
  625. }
  626. return drush_set_error(dt("!message See `drush help @command` for information on usage.", array('!message' => $missing_message, '@command' => $command['command'])));
  627. }
  628. return TRUE;
  629. }
  630. function drush_append_negation_options($allowed_options) {
  631. $new_allowed = $allowed_options;
  632. foreach ($allowed_options as $option) {
  633. $new_allowed[] = 'no-' . $option;
  634. }
  635. return $new_allowed;
  636. }
  637. function _drush_verify_cli_arguments($command) {
  638. // Check to see if all of the required arguments
  639. // are specified.
  640. if ($command['required-arguments']) {
  641. $required_arg_count = $command['required-arguments'];
  642. if ($required_arg_count === TRUE) {
  643. $required_arg_count = count($command['argument-description']);
  644. }
  645. if (count($command['arguments']) < $required_arg_count) {
  646. $missing = $required_arg_count > 1 ? dt('Missing required arguments') : dt('Missing required argument');
  647. $required = array_slice(array_keys($command['argument-description']), 0, $required_arg_count);
  648. return drush_set_error(dt("@missing: @required. See `drush help @command` for information on usage.", array(
  649. '@missing' => $missing,
  650. '@required' => implode(", ", $required),
  651. '@command' => $command['command'],
  652. )));
  653. }
  654. }
  655. return TRUE;
  656. }
  657. /**
  658. * Return the list of all of the options for the given
  659. * command record by merging the 'options' and 'sub-options'
  660. * records.
  661. */
  662. function _drush_get_command_options($command) {
  663. drush_command_invoke_all_ref('drush_help_alter', $command);
  664. $options = $command['options'];
  665. foreach ($command['sub-options'] as $group => $suboptions) {
  666. $options = array_merge($options, $suboptions);
  667. }
  668. return $options;
  669. }
  670. /**
  671. * Return the list of all of the options for the given
  672. * command record including options provided by engines and additional-options.
  673. */
  674. function drush_get_command_options_extended($command) {
  675. drush_merge_engine_data($command);
  676. // Start out with just the options in the current command record.
  677. $options = _drush_get_command_options($command);
  678. // If 'allow-additional-options' contains a list of command names,
  679. // then union together all of the options from all of the commands.
  680. if (is_array($command['allow-additional-options'])) {
  681. $implemented = drush_get_commands();
  682. foreach ($command['allow-additional-options'] as $subcommand_name) {
  683. if (array_key_exists($subcommand_name, $implemented)) {
  684. $options = array_merge($options, _drush_get_command_options($implemented[$subcommand_name]));
  685. }
  686. }
  687. }
  688. return $options;
  689. }
  690. /**
  691. * Return the array keys of $options, plus any 'short-form'
  692. * representations that may appear in the option's value.
  693. */
  694. function _drush_flatten_options($options) {
  695. $flattened_options = array();
  696. foreach($options as $key => $value) {
  697. // engine sections start with 'package-handler=git_drupalorg',
  698. // or something similar. Get rid of everything from the = onward.
  699. if (($eq_pos = strpos($key, '=')) !== FALSE) {
  700. $key = substr($key, 0, $eq_pos);
  701. }
  702. $flattened_options[] = $key;
  703. if (is_array($value)) {
  704. if (array_key_exists('short-form', $value)) {
  705. $flattened_options[] = $value['short-form'];
  706. }
  707. }
  708. }
  709. return $flattened_options;
  710. }
  711. /**
  712. * Get the options that were passed to the current command.
  713. *
  714. * This function returns an array that contains all of the options
  715. * that are appropriate for forwarding along to drush_invoke_process.
  716. *
  717. * @return
  718. * An associative array of option key => value pairs.
  719. */
  720. function drush_redispatch_get_options() {
  721. $options = array();
  722. // Add in command-specific and alias options, but for global options only.
  723. $options_soup = drush_get_context('specific') + drush_get_context('alias');
  724. $global_option_list = drush_get_global_options(FALSE);
  725. foreach ($options_soup as $key => $value) {
  726. if (array_key_exists($key, $global_option_list)) {
  727. $options[$key] = $value;
  728. }
  729. }
  730. // Local php settings should not override sitealias settings.
  731. $cli_context = drush_get_context('cli');
  732. unset($cli_context['php'], $cli_context['php-options']);
  733. // Pass along CLI parameters, as higher priority.
  734. $options = $cli_context + $options;
  735. $options = array_diff_key($options, array_flip(drush_sitealias_site_selection_keys()));
  736. unset($options['command-specific']);
  737. unset($options['path-aliases']);
  738. // If we can parse the current command, then examine all contexts
  739. // in order for any option that is directly related to the current command
  740. $command = drush_parse_command();
  741. if (is_array($command)) {
  742. foreach (drush_get_command_options_extended($command) as $key => $value) {
  743. $value = drush_get_option($key);
  744. if (isset($value)) {
  745. $options[$key] = $value;
  746. }
  747. }
  748. }
  749. // If --bootstrap-to-first-arg is specified, do not
  750. // pass it along to remote commands.
  751. unset($options['bootstrap-to-first-arg']);
  752. return $options;
  753. }
  754. /**
  755. * @} End of "defgroup dispatching".
  756. */
  757. /**
  758. * @file
  759. * The drush command engine.
  760. *
  761. * Since drush can be invoked independently of a proper Drupal
  762. * installation and commands may operate across sites, a distinct
  763. * command engine is needed.
  764. *
  765. * It mimics the Drupal module engine in order to economize on
  766. * concepts and to make developing commands as familiar as possible
  767. * to traditional Drupal module developers.
  768. */
  769. /**
  770. * Parse console arguments.
  771. */
  772. function drush_parse_args() {
  773. $args = drush_get_context('argv');
  774. $command_args = NULL;
  775. $global_options = array();
  776. $target_alias_name = NULL;
  777. // It would be nice if commandfiles could somehow extend this list,
  778. // but it is not possible. We need to parse args before we find commandfiles,
  779. // because the specified options may affect how commandfiles are located.
  780. // Therefore, commandfiles are loaded too late to affect arg parsing.
  781. // There are only a limited number of short options anyway; drush reserves
  782. // all for use by drush core.
  783. static $arg_opts = array('c', 'u', 'r', 'l', 'i');
  784. // Check to see if we were executed via a "#!/usr/bin/env drush" script
  785. drush_adjust_args_if_shebang_script($args);
  786. // Now process the command line arguments. We will divide them
  787. // into options (starting with a '-') and arguments.
  788. $arguments = $options = array();
  789. for ($i = 1; $i < count($args); $i++) {
  790. $opt = $args[$i];
  791. // We set $command_args to NULL until the first argument that is not
  792. // an alias is found (the command); we put everything that follows
  793. // into $command_args.
  794. if (is_array($command_args)) {
  795. $command_args[] = $opt;
  796. }
  797. // Is the arg an option (starting with '-')?
  798. if (!empty($opt) && $opt{0} == "-" && strlen($opt) != 1) {
  799. // Do we have multiple options behind one '-'?
  800. if (strlen($opt) > 2 && $opt{1} != "-") {
  801. // Each char becomes a key of its own.
  802. for ($j = 1; $j < strlen($opt); $j++) {
  803. $options[substr($opt, $j, 1)] = TRUE;
  804. }
  805. }
  806. // Do we have a longopt (starting with '--')?
  807. elseif ($opt{1} == "-") {
  808. if ($pos = strpos($opt, '=')) {
  809. $options[substr($opt, 2, $pos - 2)] = substr($opt, $pos + 1);
  810. }
  811. else {
  812. $options[substr($opt, 2)] = TRUE;
  813. }
  814. }
  815. else {
  816. $opt = substr($opt, 1);
  817. // Check if the current opt is in $arg_opts (= has to be followed by an argument).
  818. if ((in_array($opt, $arg_opts))) {
  819. // Raising errors for missing option values should be handled by the
  820. // bootstrap or specific command, so we no longer do this here.
  821. $options[$opt] = $args[$i + 1];
  822. $i++;
  823. }
  824. else {
  825. $options[$opt] = TRUE;
  826. }
  827. }
  828. }
  829. // If it's not an option, it's a command.
  830. else {
  831. $arguments[] = $opt;
  832. // Once we find the first argument, record the command args and global options
  833. if (!is_array($command_args)) {
  834. // Remember whether we set $target_alias_name on a previous iteration,
  835. // then record the $target_alias_name iff this arguement references a valid site alias.
  836. $already_set_target = is_string($target_alias_name);
  837. if (!$already_set_target && drush_sitealias_valid_alias_format($opt)) {
  838. $target_alias_name = $opt;
  839. }
  840. // If an alias record was set on a previous iteration, then this
  841. // argument must be the command name. If we set the target alias
  842. // record on this iteration, then this is not the command name.
  843. // If we've found the command name, then save $options in $global_options
  844. // (all options that came before the command name), and initialize
  845. // $command_args to an array so that we will begin storing all args
  846. // and options that follow the command name in $command_args.
  847. if ($already_set_target || (!is_string($target_alias_name))) {
  848. $command_args = array();
  849. $global_options = $options;
  850. }
  851. }
  852. }
  853. }
  854. // If no arguments are specified, then the command will
  855. // be either 'help' or 'version' (the latter if --version is specified)
  856. // @todo: it would be handy if one could do `drush @remote st --help` and
  857. // have that show help for st. Today, that shows --help for help command!
  858. if (!count($arguments)) {
  859. if (array_key_exists('version', $options)) {
  860. $arguments = array('version');
  861. }
  862. else {
  863. $arguments = array('help');
  864. }
  865. }
  866. if (is_array($command_args)) {
  867. drush_set_context('DRUSH_COMMAND_ARGS', $command_args);
  868. }
  869. drush_set_context('DRUSH_GLOBAL_CLI_OPTIONS', $global_options);
  870. // Handle the "@shift" alias, if present
  871. drush_process_bootstrap_to_first_arg($arguments);
  872. drush_set_arguments($arguments);
  873. drush_set_config_special_contexts($options);
  874. drush_set_context('cli', $options);
  875. return $arguments;
  876. }
  877. /**
  878. * Pop an argument off of drush's argument list
  879. */
  880. function drush_shift() {
  881. $arguments = drush_get_arguments();
  882. $result = NULL;
  883. if (!empty($arguments)) {
  884. // The php-script command uses the DRUSH_SHIFT_SKIP
  885. // context to cause drush_shift to skip the 'php-script'
  886. // command and the script path argument when it is
  887. // called from the user script.
  888. $skip_count = drush_get_context('DRUSH_SHIFT_SKIP');
  889. if (is_numeric($skip_count)) {
  890. for ($i = 0; $i < $skip_count; $i++) {
  891. array_shift($arguments);
  892. }
  893. $skip_count = drush_set_context('DRUSH_SHIFT_SKIP', 0);
  894. }
  895. $result = array_shift($arguments);
  896. drush_set_arguments($arguments);
  897. }
  898. return $result;
  899. }
  900. /**
  901. * Special checking for "shebang" script handling.
  902. *
  903. * If there is a file 'script.php' that begins like so:
  904. * #!/path/to/drush
  905. * Then $args will be:
  906. * /path/to/drush /path/to/script userArg1 userArg2 ...
  907. * If it instead starts like this:
  908. * #!/path/to/drush --flag php-script
  909. * Then $args will be:
  910. * /path/to/drush "--flag php-script" /path/to/script userArg1 userArg2 ...
  911. * (Note that execve does not split the parameters from
  912. * the shebang line on whitespace; see http://en.wikipedia.org/wiki/Shebang_%28Unix%29)
  913. * When drush is called via one of the "shebang" lines above,
  914. * the first or second parameter will be the full path
  915. * to the "shebang" script file -- and if the path to the
  916. * script is in the second position, then we will expect that
  917. * the argument in the first position must begin with a
  918. * '@' (alias) or '-' (flag). Under ordinary circumstances,
  919. * we do not expect that the drush command must come before
  920. * any argument that is the full path to a file. We use
  921. * this assumption to detect "shebang" script execution.
  922. */
  923. function drush_adjust_args_if_shebang_script(&$args) {
  924. if (drush_has_bash()) {
  925. // The drush.launcher script may add --php or --php-options at the
  926. // head of the argument list; skip past those.
  927. $base_arg_number = 1;
  928. while (substr($args[$base_arg_number], 0, 5) == '--php') {
  929. ++$base_arg_number;
  930. }
  931. if (_drush_is_drush_shebang_script($args[$base_arg_number])) {
  932. // If $args[1] is a drush "shebang" script, we will insert
  933. // the option "--bootstrap-to-first-arg" and the command
  934. // "php-script" at the beginning of @args, so the command
  935. // line args become:
  936. // /path/to/drush --bootstrap-to-first-arg php-script /path/to/script userArg1 userArg2 ...
  937. drush_set_option('bootstrap-to-first-arg', TRUE);
  938. array_splice($args, $base_arg_number, 0, array('php-script'));
  939. drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE);
  940. }
  941. elseif (((strpos($args[$base_arg_number], ' ') !== FALSE) || (!ctype_alnum($args[$base_arg_number][0]))) && (_drush_is_drush_shebang_script($args[$base_arg_number + 1]))) {
  942. // If $args[2] is a drush "shebang" script, we will insert
  943. // the space-exploded $arg[1] in place of $arg[1], so the
  944. // command line args become:
  945. // /path/to/drush scriptArg1 scriptArg2 ... /path/to/script userArg1 userArg2 ...
  946. // If none of the script arguments look like a drush command,
  947. // then we will insert "php-script" as the default command to
  948. // execute.
  949. $script_args = explode(' ', $args[$base_arg_number]);
  950. $has_command = FALSE;
  951. foreach ($script_args as $script_arg) {
  952. if (preg_match("/^[a-z][a-z0-9-]*$/",$script_arg)) {
  953. $has_command = TRUE;
  954. }
  955. }
  956. if (!$has_command) {
  957. $script_args[] = 'php-script';
  958. }
  959. array_splice($args, 1, $base_arg_number, $script_args);
  960. drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE);
  961. }
  962. }
  963. }
  964. /**
  965. * Process the --bootstrap-to-first-arg option, if it is present.
  966. *
  967. * This option checks to see if the first user-provided argument is an alias
  968. * or site specification; if it is, it will be shifted into the first argument
  969. * position, where it will specify the site to bootstrap. The result of this
  970. * is that if your shebang line looks like this:
  971. *
  972. * #!/path/to/drush --bootstrap-to-first-arg php-script
  973. *
  974. * Then when you run that script, you can optionally provide an alias such
  975. * as @dev as the first argument (e.g. $ ./mydrushscript.php @dev scriptarg1
  976. * scriptarg2). Since this is the behavior that one would usually want,
  977. * it is default behavior for a canonical script. That is, a script
  978. * with a simple shebang line, like so:
  979. *
  980. * #!/path/to/drush
  981. *
  982. * will implicitly have "--bootstrap-to-first-arg" and "php-script" prepended, and will therefore
  983. * behave exactly like the first example. To write a script that does not
  984. * use --bootstrap-to-first-arg, then the drush command or at least one flag must be explicitly
  985. * included, like so:
  986. *
  987. * #!/path/to/drush php-script
  988. */
  989. function drush_process_bootstrap_to_first_arg(&$arguments) {
  990. if (drush_get_option('bootstrap-to-first-arg', FALSE)) {
  991. $shift_alias_pos = 1 + (drush_get_context('DRUSH_SHEBANG_SCRIPT') === TRUE);
  992. if (count($arguments) >= $shift_alias_pos) {
  993. $shifted_alias = $arguments[$shift_alias_pos];
  994. $alias_record = drush_sitealias_get_record($shifted_alias);
  995. if (!empty($alias_record)) {
  996. // Move the alias we shifted from its current position
  997. // in the argument list to the front of the list
  998. array_splice($arguments, $shift_alias_pos, 1);
  999. array_unshift($arguments, $shifted_alias);
  1000. }
  1001. }
  1002. }
  1003. }
  1004. /**
  1005. * Get a list of all implemented commands.
  1006. * This invokes hook_drush_command().
  1007. *
  1008. * @return
  1009. * Associative array of currently active command descriptors.
  1010. *
  1011. */
  1012. function drush_get_commands($reset = FALSE) {
  1013. static $commands = array();
  1014. if ($reset) {
  1015. $commands = array();
  1016. return;
  1017. }
  1018. elseif ($commands) {
  1019. return $commands;
  1020. }
  1021. $list = drush_commandfile_list();
  1022. foreach ($list as $commandfile => $path) {
  1023. if (drush_command_hook($commandfile, 'drush_command')) {
  1024. $function = $commandfile . '_drush_command';
  1025. $result = $function();
  1026. foreach ((array)$result as $key => $command) {
  1027. // Add some defaults and normalize the command descriptor.
  1028. $command += drush_command_defaults($key, $commandfile, $path);
  1029. // Add engine data.
  1030. drush_merge_engine_data($command);
  1031. // Translate command.
  1032. drush_command_translate($command);
  1033. // If the command callback is not 'drush_command', then
  1034. // copy the callback function to an alternate element
  1035. // of the command array that will be called when Drush
  1036. // calls the command function hooks. Then, set the
  1037. // callback to drush_command so that the function hooks
  1038. // will be called.
  1039. if (($command['callback'] != 'drush_command') && $command['invoke hooks']) {
  1040. $command['primary function'] = $command['callback'];
  1041. $command['callback'] = 'drush_command';
  1042. }
  1043. $commands[$key] = $command;
  1044. }
  1045. }
  1046. }
  1047. $commands = array_merge($commands, annotationcommand_adapter_commands());
  1048. foreach ($commands as $command) {
  1049. // For every alias, make a copy of the command and store it in the command list
  1050. // using the alias as a key
  1051. if (isset($command['aliases']) && count($command['aliases'])) {
  1052. foreach ($command['aliases'] as $alias) {
  1053. $commands[$alias] = $command;
  1054. $commands[$alias]['is_alias'] = TRUE;
  1055. }
  1056. }
  1057. }
  1058. return $commands;
  1059. }
  1060. /**
  1061. * Organize commands into categories. Used by help listing and core-cli.
  1062. *
  1063. * @param array $commands
  1064. * A commands array as per drush_get_commands().
  1065. *
  1066. * @return array $command_categories
  1067. * A categorized associative array of commands.
  1068. */
  1069. function drush_commands_categorize($commands) {
  1070. $command_categories = array();
  1071. $category_map = array();
  1072. foreach ($commands as $key => $candidate) {
  1073. if ((!array_key_exists('is_alias', $candidate) || !$candidate['is_alias']) && !$candidate['hidden']) {
  1074. $category = $candidate['category'];
  1075. // If we have decided to remap a category, remap every command
  1076. if (array_key_exists($category, $category_map)) {
  1077. $category = $category_map[$category];
  1078. }
  1079. if (!array_key_exists($category, $command_categories)) {
  1080. $title = drush_command_invoke_all('drush_help', "meta:$category:title");
  1081. $alternate_title = '';
  1082. if (!$title) {
  1083. // If there is no title, then check to see if the
  1084. // command file is stored in a folder with the same
  1085. // name as some other command file (e.g. 'core') that
  1086. // defines a title.
  1087. $alternate = basename($candidate['path']);
  1088. $alternate_title = drush_command_invoke_all('drush_help', "meta:$alternate:title");
  1089. }
  1090. if (!empty($alternate_title)) {
  1091. $category_map[$category] = $alternate;
  1092. $category = $alternate;
  1093. $title = $alternate_title;
  1094. }
  1095. $command_categories[$category]['title'] = empty($title) ? '' : $title[0];
  1096. $summary = drush_command_invoke_all('drush_help', "meta:$category:summary");
  1097. if ($summary) {
  1098. $command_categories[$category]['summary'] = $summary[0];
  1099. }
  1100. }
  1101. $candidate['category'] = $category;
  1102. $command_categories[$category]['commands'][$key] = $candidate;
  1103. }
  1104. }
  1105. // Make sure that 'core' is always first in the list
  1106. $core_category = array('core' => $command_categories['core']);
  1107. unset($command_categories['core']);
  1108. // Post-process the categories that have no title.
  1109. // Any that have fewer than 4 commands go into a section called "other".
  1110. $processed_categories = array();
  1111. $misc_categories = array();
  1112. $other_commands = array();
  1113. $other_categories = array();
  1114. foreach ($command_categories as $key => $info) {
  1115. if (empty($info['title'])) {
  1116. $one_category = $key;
  1117. if (count($info['commands']) < 4) {
  1118. $other_commands = array_merge($other_commands, $info['commands']);
  1119. $other_categories[] = $one_category;
  1120. }
  1121. else {
  1122. $info['title'] = dt("All commands in !category", array('!category' => $key));
  1123. $misc_categories[$one_category] = $info;
  1124. }
  1125. }
  1126. else {
  1127. $processed_categories[$key] = $info;
  1128. }
  1129. }
  1130. $other_category = array();
  1131. if (!empty($other_categories)) {
  1132. $other_category[implode(',', $other_categories)] = array('title' => dt("Other commands"), 'commands' => $other_commands);
  1133. }
  1134. asort($processed_categories);
  1135. asort($misc_categories);
  1136. $command_categories = array_merge($core_category, $processed_categories, $misc_categories, $other_category);
  1137. // If the user specified --sort, then merge all of the remaining
  1138. // categories together
  1139. if (drush_get_option('sort', FALSE)) {
  1140. $combined_commands = array();
  1141. foreach ($command_categories as $key => $info) {
  1142. $combined_commands = array_merge($combined_commands, $info['commands']);
  1143. }
  1144. $command_categories = array('all' => array('commands' => $combined_commands, 'title' => dt("Commands:")));
  1145. }
  1146. return $command_categories;
  1147. }
  1148. function drush_command_defaults($key, $commandfile, $path) {
  1149. $defaults = array(
  1150. 'command' => $key,
  1151. 'command-hook' => $key,
  1152. 'invoke hooks' => TRUE,
  1153. 'callback arguments' => array(),
  1154. 'commandfile' => $commandfile,
  1155. 'path' => dirname($path),
  1156. 'engines' => array(), // Helpful for drush_show_help().
  1157. 'callback' => 'drush_command',
  1158. 'primary function' => FALSE,
  1159. 'description' => NULL,
  1160. 'sections' => array(
  1161. 'examples' => 'Examples',
  1162. 'arguments' => 'Arguments',
  1163. 'options' => 'Options',
  1164. ),
  1165. 'arguments' => array(),
  1166. 'required-arguments' => FALSE,
  1167. 'options' => array(),
  1168. 'sub-options' => array(),
  1169. 'allow-additional-options' => FALSE,
  1170. 'global-options' => array(),
  1171. 'examples' => array(),
  1172. 'aliases' => array(),
  1173. 'core' => array(),
  1174. 'scope' => 'site',
  1175. 'drush dependencies' => array(),
  1176. 'handle-remote-commands' => FALSE,
  1177. 'remote-tty' => FALSE,
  1178. 'strict-option-handling' => FALSE,
  1179. 'tilde-expansion' => TRUE,
  1180. 'bootstrap_errors' => array(),
  1181. 'topics' => array(),
  1182. 'hidden' => FALSE,
  1183. 'category' => $commandfile,
  1184. 'add-options-to-arguments' => FALSE,
  1185. 'consolidation-output-formatters' => FALSE,
  1186. 'annotated-command-callback' => '',
  1187. 'annotations' => new AnnotationData(['command' => $key]),
  1188. );
  1189. // We end up here, setting the defaults for a command, when
  1190. // called from drush_get_global_options(). Early in the Drush
  1191. // bootstrap, there will be no bootstrap object, because we
  1192. // need to get the list of global options when loading config
  1193. // files, and config files are loaded before the bootstrap object
  1194. // is created. In this early stage, we just use the core global
  1195. // options list. Later, the bootstrap object can also provide
  1196. // additional defaults if needed. The bootstrap command defaults
  1197. // will be merged into the command object again just before
  1198. // running it in bootstrap_and_dispatch().
  1199. if ($bootstrap = \Drush::bootstrap()) {
  1200. $defaults = array_merge($defaults, $bootstrap->command_defaults());
  1201. }
  1202. return $defaults;
  1203. }
  1204. /**
  1205. * Translates description and other keys of a command definition.
  1206. *
  1207. * @param $command
  1208. * A command definition.
  1209. */
  1210. function drush_command_translate(&$command) {
  1211. $command['description'] = _drush_command_translate($command['description']);
  1212. $keys = array('arguments', 'options', 'examples', 'sections');
  1213. foreach ($keys as $key) {
  1214. foreach ($command[$key] as $k => $v) {
  1215. if (is_array($v)) {
  1216. $v['description'] = _drush_command_translate($v['description']);
  1217. }
  1218. else {
  1219. $v = _drush_command_translate($v);
  1220. }
  1221. $command[$key][$k] = $v;
  1222. }
  1223. }
  1224. }
  1225. /**
  1226. * Helper function for drush_command_translate().
  1227. *
  1228. * @param $source
  1229. * String or array.
  1230. */
  1231. function _drush_command_translate($source) {
  1232. return is_array($source) ? call_user_func_array('dt', $source) : dt($source);
  1233. }
  1234. /**
  1235. * Matches a commands array, as returned by drush_get_arguments, with the
  1236. * current command table.
  1237. *
  1238. * Note that not all commands may be discoverable at the point-of-call,
  1239. * since Drupal modules can ship commands as well, and they are
  1240. * not available until after bootstrapping.
  1241. *
  1242. * drush_parse_command returns a normalized command descriptor, which
  1243. * is an associative array. Some of its entries are:
  1244. * - callback arguments: an array of arguments to pass to the calback.
  1245. * - callback: the function to run. Usually, this is 'drush_command', which
  1246. * will determine the primary hook for the function automatically. Only
  1247. * specify a callback function if you need many commands to call the same
  1248. * function (e.g. drush_print_file).
  1249. * - invoke hooks: If TRUE (the default), Drush will invoke all of the pre and
  1250. * post hooks for this command. Set to FALSE to suppress hooks. This setting
  1251. * is ignored unless the command 'callback' is also set.
  1252. * - primary function: Drush will copy the 'callback' parameter here if
  1253. * necessary. This value should not be set explicitly; use 'callback' instead.
  1254. * - description: description of the command.
  1255. * - arguments: an array of arguments that are understood by the command. for help texts.
  1256. * - required-arguments: The minimum number of arguments that are required, or TRUE if all are required.
  1257. * - options: an array of options that are understood by the command. for help texts.
  1258. * - global-options: a list of options from the set of Drush global options (@see:
  1259. * drush_get_global_options()) that relate to this command. The help for these
  1260. * options will be included in the help output for this command.
  1261. * - examples: an array of examples that are understood by the command. for help texts.
  1262. * - scope: one of 'system', 'project', 'site'.
  1263. * - bootstrap: drupal bootstrap level (depends on Drupal major version). -1=no_bootstrap.
  1264. * - core: Drupal major version required.
  1265. * - drupal dependencies: drupal modules required for this command.
  1266. * - drush dependencies: other drush command files required for this command.
  1267. * - handle-remote-commands: set to TRUE if `drush @remote mycommand` should be executed
  1268. * locally rather than remotely dispatched. When this mode is set, the target site
  1269. * can be obtained via:
  1270. * drush_get_context('DRUSH_TARGET_SITE_ALIAS')
  1271. * - remote-tty: set to TRUE if Drush should force ssh to allocate a pseudo-tty
  1272. * when this command is being called remotely. Important for interactive commands.
  1273. * Remote commands that allocate a psedo-tty always print "Connection closed..." when done.
  1274. * - strict-option-handling: set to TRUE if drush should strictly separate local command
  1275. * cli options from the global options. Usually, drush allows global cli options and
  1276. * command cli options to be interspersed freely on the commandline. For commands where
  1277. * this flag is set, options are separated, with global options comming before the
  1278. * command names, and command options coming after, like so:
  1279. * drush --global-options command --command-options
  1280. * In this mode, the command options are no longer available via drush_get_option();
  1281. * instead, they can be retrieved via:
  1282. * $args = drush_get_original_cli_args_and_options();
  1283. * $args = drush_get_context('DRUSH_COMMAND_ARGS', array());
  1284. * In this case, $args will contain the command args and options literally, exactly as they
  1285. * were entered on the command line, and in the same order as they appeared.
  1286. * - 'outputformat': declares the data format to be used to render the
  1287. * command result. In addition to the output format engine options
  1288. * listed below, each output format type can take additional metadata
  1289. * items that control the way that the output is rendered. See the
  1290. * comment in each particular output format class for information. The
  1291. * Drush core output format engines can be found in commands/core/outputformat.
  1292. * - 'default': The default type to render output as. If declared, the
  1293. * command should not print any output on its own, but instead should
  1294. * return a data structure (usually an associative array) that can
  1295. * be rendered by the output type selected.
  1296. * - 'pipe-format': When the command is executed in --pipe mode, the
  1297. * command output will be rendered by the format specified by the
  1298. * pipe-format item instead of the default format. Note that in
  1299. * either event, the user may specify the format to use via the
  1300. * --format command-line option.
  1301. * - 'formatted-filter': specifies a function callback that will be
  1302. * used to filter the command result if the selected output formatter
  1303. * is NOT declared to be machine-parsable. "table" is an example of
  1304. * an output format that is not machine-parsable.
  1305. * - 'parsable-filter': function callback that will be used to filter the
  1306. * command result if the selected output formatter is declared to be
  1307. * machine-parsable. "var_export" is an example of an output format that
  1308. * is machine-parsable.
  1309. * - 'output-data-type': An identifier representing the data structure that
  1310. * the command returns. @see outputformat_drush_engine_outputformat() for
  1311. * a description of the supported values.
  1312. * - 'field-labels': A mapping from machine name to human-readable name
  1313. * for all of the fields in a table-format command result. All
  1314. * possible field names should appear in this list.
  1315. * - 'fields-default': A list of the machine names of the fields that
  1316. * should be displayed by default in tables.
  1317. * - 'private-fields': A list of any fields that contain sensitive
  1318. * information, such as passwords. By default, Drush will hide private
  1319. * fields before printing the results to the console, but will include
  1320. * them in backend invoke results. Use --show-passwords to display.
  1321. * - 'column-widths': A mapping from field machine name to the column width
  1322. * that should be used in table output. Drush will automatically
  1323. * calculate the width of any field not listed here based on the length
  1324. * of the data items in it.
  1325. * - engines: declares information on Drush engines the command will load.
  1326. * Available engines can vary by command type.
  1327. *
  1328. * @return bool|array
  1329. * A command definition.
  1330. */
  1331. function drush_parse_command() {
  1332. $args = drush_get_arguments();
  1333. $command = FALSE;
  1334. // Get a list of all implemented commands.
  1335. $implemented = drush_get_commands();
  1336. if (!empty($args) && isset($implemented[$args[0]])) {
  1337. $command = $implemented[$args[0]];
  1338. $arguments = array_slice($args, 1);
  1339. }
  1340. // We have found a command that matches. Set the appropriate values.
  1341. if ($command) {
  1342. // Special case. Force help command if --help option was specified.
  1343. if (drush_get_option('help')) {
  1344. $arguments = array($command['command']);
  1345. $command = $implemented['helpsingle'];
  1346. $command['arguments'] = $arguments;
  1347. $command['allow-additional-options'] = TRUE;
  1348. }
  1349. else {
  1350. _drush_prepare_command($command, $arguments);
  1351. }
  1352. drush_set_command($command);
  1353. }
  1354. return $command;
  1355. }
  1356. /**
  1357. * Called by drush_parse_command(). If a command is dispatched
  1358. * directly by drush_dispatch(), then drush_dispatch() will call
  1359. * this function.
  1360. */
  1361. function _drush_prepare_command(&$command, $arguments = array()) {
  1362. // Drush overloads $command['arguments']; save the argument description
  1363. if (!isset($command['argument-description'])) {
  1364. $command['argument-description'] = $command['arguments'];
  1365. }
  1366. // Merge specified callback arguments, which precede the arguments passed on the command line.
  1367. if (isset($command['callback arguments']) && is_array($command['callback arguments'])) {
  1368. $arguments = array_merge($command['callback arguments'], $arguments);
  1369. }
  1370. $command['arguments'] = $arguments;
  1371. }
  1372. /**
  1373. * Invoke a hook in all available command files that implement it.
  1374. *
  1375. * @see drush_command_invoke_all_ref()
  1376. *
  1377. * @param $hook
  1378. * The name of the hook to invoke.
  1379. * @param ...
  1380. * Arguments to pass to the hook.
  1381. * @return
  1382. * An array of return values of the hook implementations. If commands return
  1383. * arrays from their implementations, those are merged into one array.
  1384. */
  1385. function drush_command_invoke_all() {
  1386. $args = func_get_args();
  1387. if (count($args) == 1) {
  1388. $args[] = NULL;
  1389. }
  1390. $reference_value = $args[1];
  1391. $args[1] = &$reference_value;
  1392. return call_user_func_array('drush_command_invoke_all_ref', $args);
  1393. }
  1394. /**
  1395. * A drush_command_invoke_all() that wants the first parameter to be passed by reference.
  1396. *
  1397. * @see drush_command_invoke_all()
  1398. */
  1399. function drush_command_invoke_all_ref($hook, &$reference_parameter) {
  1400. $args = func_get_args();
  1401. array_shift($args);
  1402. // Insure that call_user_func_array can alter first parameter
  1403. $args[0] = &$reference_parameter;
  1404. $return = array();
  1405. $modules = drush_command_implements($hook);
  1406. if ($hook != 'drush_invoke_alter') {
  1407. // Allow modules to control the order of hook invocations
  1408. drush_command_invoke_all_ref('drush_invoke_alter', $modules, $hook);
  1409. }
  1410. foreach ($modules as $module) {
  1411. $function = $module .'_'. $hook;
  1412. $result = call_user_func_array($function, $args);
  1413. if (isset($result) && is_array($result)) {
  1414. $return = array_merge_recursive($return, $result);
  1415. }
  1416. else if (isset($result)) {
  1417. $return[] = $result;
  1418. }
  1419. }
  1420. return $return;
  1421. }
  1422. /**
  1423. * Determine which command files are implementing a hook.
  1424. *
  1425. * @param $hook
  1426. * The name of the hook (e.g. "drush_help" or "drush_command").
  1427. *
  1428. * @return
  1429. * An array with the names of the command files which are implementing this hook.
  1430. */
  1431. function drush_command_implements($hook) {
  1432. $implementations[$hook] = array();
  1433. $list = drush_commandfile_list();
  1434. foreach ($list as $commandfile => $file) {
  1435. if (drush_command_hook($commandfile, $hook)) {
  1436. $implementations[$hook][] = $commandfile;
  1437. }
  1438. }
  1439. return (array)$implementations[$hook];
  1440. }
  1441. /**
  1442. * @param string
  1443. * name of command to check.
  1444. *
  1445. * @return boolean
  1446. * TRUE if the given command has an implementation.
  1447. */
  1448. function drush_is_command($command) {
  1449. $commands = drush_get_commands();
  1450. return isset($commands[$command]);
  1451. }
  1452. /**
  1453. * @param string
  1454. * name of command or command alias.
  1455. *
  1456. * @return string
  1457. * Primary name of command.
  1458. */
  1459. function drush_command_normalize_name($command_name) {
  1460. $commands = drush_get_commands();
  1461. return isset($commands[$command_name]) ? $commands[$command_name]['command'] : $command_name;
  1462. }
  1463. /**
  1464. * Collect a list of all available drush command files.
  1465. *
  1466. * Scans the following paths for drush command files:
  1467. *
  1468. * - The "/path/to/drush/commands" folder.
  1469. * - Folders listed in the 'include' option (see example.drushrc.php).
  1470. * - The system-wide drush commands folder, e.g. /usr/share/drush/commands
  1471. * - The ".drush" folder in the user's HOME folder.
  1472. * - /drush and sites/all/drush in current Drupal site.
  1473. * - Folders belonging to enabled modules in the current Drupal site.
  1474. *
  1475. * A Drush command file is a file that matches "*.drush.inc".
  1476. *
  1477. * @see drush_scan_directory()
  1478. *
  1479. * @return
  1480. * An associative array whose keys and values are the names of all available
  1481. * command files.
  1482. */
  1483. function drush_commandfile_list() {
  1484. return commandfiles_cache()->get();
  1485. }
  1486. function _drush_find_commandfiles($phase, $phase_max = FALSE) {
  1487. drush_log(dt("Find command files for phase !phase (max=!max)", array('!phase' => $phase, '!max' => (string)$phase_max)), LogLevel::DEBUG);
  1488. if ($bootstrap = \Drush::bootstrap()) {
  1489. $searchpath = $bootstrap->commandfile_searchpaths($phase, $phase_max);
  1490. _drush_add_commandfiles($searchpath, $phase);
  1491. annotationcommand_adapter_discover($searchpath, $phase, $phase_max);
  1492. }
  1493. }
  1494. function _drush_add_commandfiles($searchpath, $phase = NULL, $reset = FALSE) {
  1495. static $evaluated = array();
  1496. $needs_sort = FALSE;
  1497. if (count($searchpath)) {
  1498. if (!$reset) {
  1499. // Assemble a cid specific to the bootstrap phase and searchpaths.
  1500. // Bump $cf_version when making a change to a dev version of Drush
  1501. // that invalidates the commandfile cache.
  1502. $cf_version = 8;
  1503. $cid = drush_get_cid('commandfiles-' . $phase, array(), array_merge($searchpath, array($cf_version)));
  1504. $command_cache = drush_cache_get($cid);
  1505. if (isset($command_cache->data)) {
  1506. $cached_list = $command_cache->data;
  1507. // If we want to temporarily ignore modules via 'ignored-modules',
  1508. // then we need to take these out of the cache as well.
  1509. foreach (drush_get_option_list('ignored-modules') as $ignored) {
  1510. unset($cached_list[$ignored]);
  1511. }
  1512. }
  1513. }
  1514. // Build a list of all of the modules to attempt to load.
  1515. // Start with any modules deferred from a previous phase.
  1516. $list = commandfiles_cache()->deferred();
  1517. if (isset($cached_list)) {
  1518. $list = array_merge($list, $cached_list);
  1519. }
  1520. else {
  1521. // Scan for drush command files; add to list for consideration if found.
  1522. foreach (array_unique($searchpath) as $path) {
  1523. if (is_dir($path)) {
  1524. $nomask = array_merge(drush_filename_blacklist(), drush_get_option_list('ignored-modules'));
  1525. $dmv = DRUSH_MAJOR_VERSION;
  1526. $files = drush_scan_directory($path, "/\.drush($dmv|)\.inc$/", $nomask);
  1527. foreach ($files as $filename => $info) {
  1528. $module = basename($filename);
  1529. $module = preg_replace('/\.*drush[0-9]*\.inc/', '', $module);
  1530. // Only try to bootstrap modules that we have never seen before.
  1531. if (!array_key_exists($module, $evaluated) && file_exists($filename)) {
  1532. $evaluated[$module] = TRUE;
  1533. $list[$module] = Path::canonicalize($filename);
  1534. }
  1535. }
  1536. }
  1537. }
  1538. if (isset($cid)) {
  1539. drush_cache_set($cid, $list);
  1540. }
  1541. }
  1542. // Check to see if the commandfile is valid for this version of Drupal
  1543. // and is still present on filesystem (in case of cached commandfile list).
  1544. foreach ($list as $module => $filename) {
  1545. // Only try to require if the file exists. If not, a file from the
  1546. // command file cache may not be available anymore, in which case
  1547. // we rebuild the cache for this phase.
  1548. if (file_exists($filename)) {
  1549. // Avoid realpath() here as Drush commandfiles can have phar:// locations.
  1550. $load_command = commandfiles_cache()->add($filename);
  1551. if ($load_command) {
  1552. $needs_sort = TRUE;
  1553. }
  1554. }
  1555. elseif (!$reset) {
  1556. _drush_add_commandfiles($searchpath, $phase, TRUE);
  1557. $needs_sort = FALSE;
  1558. }
  1559. }
  1560. if ($needs_sort) {
  1561. commandfiles_cache()->sort();
  1562. }
  1563. }
  1564. }
  1565. /**
  1566. * Substrings to ignore during commandfile and site alias searching.
  1567. */
  1568. function drush_filename_blacklist() {
  1569. $blacklist = array('.', '..', 'drush_make', 'examples', 'tests', 'disabled', 'gitcache', 'cache');
  1570. for ($v=4; $v<=(DRUSH_MAJOR_VERSION)+3; ++$v) {
  1571. if ($v != DRUSH_MAJOR_VERSION) {
  1572. $blacklist[] = 'drush' . $v;
  1573. }
  1574. }
  1575. $blacklist = array_merge($blacklist, drush_get_option_list('exclude'));
  1576. return $blacklist;
  1577. }
  1578. /**
  1579. * Conditionally include files based on the command used.
  1580. *
  1581. * Steps through each of the currently loaded commandfiles and
  1582. * loads an optional commandfile based on the key.
  1583. *
  1584. * When a command such as 'pm-enable' is called, this
  1585. * function will find all 'enable.pm.inc' files that
  1586. * are present in each of the commandfile directories.
  1587. */
  1588. function drush_command_include($command) {
  1589. $include_files = drush_command_get_includes($command);
  1590. foreach($include_files as $filename => $commandfile) {
  1591. drush_log(dt('Including !filename', array('!filename' => $filename)), LogLevel::BOOTSTRAP);
  1592. include_once($filename);
  1593. }
  1594. }
  1595. function drush_command_get_includes($command) {
  1596. $include_files = array();
  1597. $parts = explode('-', $command);
  1598. $command = implode(".", array_reverse($parts));
  1599. $commandfiles = drush_commandfile_list();
  1600. $options = array();
  1601. foreach ($commandfiles as $commandfile => $file) {
  1602. $filename = sprintf("%s/%s.inc", dirname($file), $command);
  1603. if (file_exists($filename)) {
  1604. $include_files[$filename] = $commandfile;
  1605. }
  1606. }
  1607. return $include_files;
  1608. }
  1609. /**
  1610. * Conditionally include default options based on the command used.
  1611. */
  1612. function drush_command_default_options($command = NULL) {
  1613. $command_default_options = drush_get_context('command-specific');
  1614. drush_command_set_command_specific($command_default_options, $command);
  1615. }
  1616. function drush_sitealias_command_default_options($site_record, $prefix, $command = NULL) {
  1617. if (isset($site_record) && array_key_exists($prefix . 'command-specific', $site_record)) {
  1618. drush_command_set_command_specific($site_record[$prefix . 'command-specific'], $command);
  1619. }
  1620. return FALSE;
  1621. }
  1622. function drush_command_set_command_specific_options($prefix, $command = NULL) {
  1623. $command_default_options = drush_get_option($prefix . 'command-specific', array());
  1624. drush_command_set_command_specific($command_default_options, $command);
  1625. }
  1626. function drush_command_set_command_specific($command_default_options, $command = NULL) {
  1627. if (!$command) {
  1628. $command = drush_get_command();
  1629. }
  1630. if ($command) {
  1631. // Look for command-specific options for this command
  1632. // keyed both on the command's primary name, and on each
  1633. // of its aliases.
  1634. $options_were_set = _drush_command_set_default_options($command_default_options, $command['command']);
  1635. if (isset($command['aliases']) && count($command['aliases'])) {
  1636. foreach ($command['aliases'] as $alias) {
  1637. $options_were_set += _drush_command_set_default_options($command_default_options, $alias);
  1638. }
  1639. }
  1640. // If we set or cleared any options, go back and re-bootstrap any global
  1641. // options such as -y and -v.
  1642. if (!empty($options_were_set)) {
  1643. _drush_preflight_global_options();
  1644. }
  1645. // If the command uses strict option handling, back out any global
  1646. // options that were set.
  1647. if ($command['strict-option-handling']) {
  1648. $global_options = drush_get_global_options();
  1649. foreach ($options_were_set as $key) {
  1650. if (array_key_exists($key, $global_options)) {
  1651. if (!array_key_exists('context', $global_options[$key])) {
  1652. $strict_options_warning =& drush_get_context('DRUSH_STRICT_OPTIONS_WARNING', array());
  1653. if (!array_key_exists($key, $strict_options_warning)) {
  1654. drush_log(dt("Global option --!option not supported in command-specific options for command !command due to a limitation in strict option handling.", array('!option' => $key, '!command' => $command['command'])), LogLevel::WARNING);
  1655. $strict_options_warning[$key] = TRUE;
  1656. }
  1657. }
  1658. drush_unset_option($key, 'specific');
  1659. }
  1660. }
  1661. }
  1662. }
  1663. }
  1664. function _drush_command_set_default_options($command_default_options, $command) {
  1665. $options_were_set = array();
  1666. if (array_key_exists($command, $command_default_options)) {
  1667. foreach ($command_default_options[$command] as $key => $value) {
  1668. // We set command-specific options in their own context
  1669. // that is higher precedence than the various config file
  1670. // context, but lower than command-line options.
  1671. if (!drush_get_option('no-' . $key, FALSE)) {
  1672. drush_set_option($key, $value, 'specific');
  1673. $options_were_set[] = $key;
  1674. }
  1675. }
  1676. }
  1677. return $options_were_set;
  1678. }
  1679. /**
  1680. * Return all of the command-specific options defined in the given
  1681. * options set for the specified command name. Note that it is valid
  1682. * to use the command name alias rather than the primary command name,
  1683. * both in the parameter to this function, and in the options set.
  1684. */
  1685. function drush_command_get_command_specific_options($options, $command_name, $prefix = '') {
  1686. $result = array();
  1687. $command_name = drush_command_normalize_name($command_name);
  1688. if (isset($options[$prefix . 'command-specific'])) {
  1689. foreach ($options[$prefix . 'command-specific'] as $options_for_command => $values) {
  1690. if ($command_name == drush_command_normalize_name($options_for_command)) {
  1691. $result = array_merge($result, $values);
  1692. }
  1693. }
  1694. }
  1695. return $result;
  1696. }
  1697. /**
  1698. * Return the original cli args and options, exactly as they
  1699. * appeared on the command line, and in the same order.
  1700. * Any command-specific options that were set will also
  1701. * appear in this list, appended at the very end.
  1702. *
  1703. * The args and options returned are raw, and must be
  1704. * escaped as necessary before use.
  1705. */
  1706. function drush_get_original_cli_args_and_options($command = NULL) {
  1707. $args = drush_get_context('DRUSH_COMMAND_ARGS', array());
  1708. $command_specific_options = drush_get_context('specific');
  1709. if ($command == NULL) {
  1710. $command = drush_get_command();
  1711. }
  1712. $command_options = ($command == NULL) ? array() : _drush_get_command_options($command);
  1713. foreach ($command_specific_options as $key => $value) {
  1714. if (!array_key_exists($key, $command_options)) {
  1715. if (($value === TRUE) || (!isset($value))) {
  1716. $args[] = "--$key";
  1717. }
  1718. else {
  1719. $args[] = "--$key=$value";
  1720. }
  1721. }
  1722. }
  1723. return $args;
  1724. }
  1725. /**
  1726. * Determine whether a command file implements a hook.
  1727. *
  1728. * @param $module
  1729. * The name of the module (without the .module extension).
  1730. * @param $hook
  1731. * The name of the hook (e.g. "help" or "menu").
  1732. * @return
  1733. * TRUE if the the hook is implemented.
  1734. */
  1735. function drush_command_hook($commandfile, $hook) {
  1736. return function_exists($commandfile . '_' . $hook);
  1737. }
  1738. /**
  1739. * Check that a command is valid for the current bootstrap phase.
  1740. *
  1741. * @param $command
  1742. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  1743. *
  1744. * @return
  1745. * TRUE if command is valid.
  1746. */
  1747. function drush_enforce_requirement_bootstrap_phase(&$command) {
  1748. $valid = array();
  1749. $current_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
  1750. if ($command['bootstrap'] <= $current_phase) {
  1751. return TRUE;
  1752. }
  1753. // TODO: provide description text for each bootstrap level so we can give
  1754. // the user something more helpful and specific here.
  1755. $command['bootstrap_errors']['DRUSH_COMMAND_INSUFFICIENT_BOOTSTRAP'] = dt('Command !command needs a higher bootstrap level to run - you will need to invoke drush from a more functional Drupal environment to run this command.', array('!command' => $command['command']));
  1756. }
  1757. /**
  1758. * Check that a command has its declared drush dependencies available or have no
  1759. * dependencies. Drush dependencies are helpful when a command is invoking
  1760. * another command, or implementing its API.
  1761. *
  1762. * @param $command
  1763. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  1764. * @return
  1765. * TRUE if dependencies are met.
  1766. */
  1767. function drush_enforce_requirement_drush_dependencies(&$command) {
  1768. // If there are no drush dependencies, then do nothing.
  1769. if (!empty($command['drush dependencies'])) {
  1770. $commandfiles = drush_commandfile_list();
  1771. foreach ($command['drush dependencies'] as $dependency) {
  1772. if (!isset($commandfiles[$dependency])) {
  1773. $dt_args = array(
  1774. '!command' => $command['command'],
  1775. '!dependency' => "$dependency.drush.inc",
  1776. );
  1777. $command['bootstrap_errors']['DRUSH_COMMANDFILE_DEPENDENCY_ERROR'] = dt('Command !command needs the following drush command file to run: !dependency.', $dt_args);
  1778. return FALSE;
  1779. }
  1780. }
  1781. }
  1782. return TRUE;
  1783. }
  1784. /**
  1785. * Check that a command is valid for the current major version of core. Handles
  1786. * explicit version numbers and 'plus' numbers like 7+ (compatible with 7,8 ...).
  1787. *
  1788. * @param $command
  1789. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  1790. *
  1791. * @return
  1792. * TRUE if command is valid.
  1793. */
  1794. function drush_enforce_requirement_core(&$command) {
  1795. $major = drush_drupal_major_version();
  1796. if (!$core = $command['core']) {
  1797. return TRUE;
  1798. }
  1799. foreach ($core as $compat) {
  1800. if ($compat == $major) {
  1801. return TRUE;
  1802. }
  1803. elseif (substr($compat, -1) == '+' && $major >= substr($compat, 0, strlen($compat)-1)) {
  1804. return TRUE;
  1805. }
  1806. }
  1807. $versions = array_pop($core);
  1808. if (!empty($core)) {
  1809. $versions = implode(', ', $core) . dt(' or ') . $versions;
  1810. }
  1811. $command['bootstrap_errors']['DRUSH_COMMAND_CORE_VERSION_ERROR'] = dt('Command !command requires Drupal core version !versions to run.', array('!command' => $command['command'], '!versions' => $versions));
  1812. }
  1813. /**
  1814. * Check if a shell alias exists for current request. If so, re-route to
  1815. * core-execute and pass alias value along with rest of CLI arguments.
  1816. */
  1817. function drush_shell_alias_replace($target_site_alias) {
  1818. $escape = TRUE;
  1819. $args = drush_get_arguments();
  1820. $argv = drush_get_context('argv');
  1821. $first = current($args);
  1822. // @todo drush_get_option is awkward here.
  1823. $shell_aliases = drush_get_context('shell-aliases', array());
  1824. if (isset($shell_aliases[$first])) {
  1825. // Shell alias found for first argument in the request.
  1826. $alias_value = $shell_aliases[$first];
  1827. if (!is_array($alias_value)) {
  1828. // Shell aliases can have embedded variables such as {{@target}} and {{%root}}
  1829. // that are replaced with the name of the target site alias, or the value of a
  1830. // path alias defined in the target site alias record. We only support replacements
  1831. // when the alias value is a string; if it is already broken out into an array,
  1832. // then the values therein are used literally.
  1833. $alias_variables = array( '{{@target}}' => '@none' );
  1834. if ($target_site_alias) {
  1835. $alias_variables = array( '{{@target}}' => $target_site_alias );
  1836. $target = drush_sitealias_get_record($target_site_alias);
  1837. foreach ($target as $key => $value) {
  1838. if (!is_array($value)) {
  1839. $alias_variables['{{' . $key . '}}'] = $value;
  1840. }
  1841. }
  1842. if (array_key_exists('path-aliases', $target)) {
  1843. foreach ($target['path-aliases'] as $key => $value) {
  1844. // n.b. $key will contain something like "%root" or "%files".
  1845. $alias_variables['{{' . $key . '}}'] = $value;
  1846. }
  1847. }
  1848. }
  1849. $alias_value = str_replace(array_keys($alias_variables), array_values($alias_variables), $alias_value);
  1850. // Check for unmatched replacements
  1851. $matches = array();
  1852. $match_result = preg_match('/{{[%@#]*[a-z0-9.]*}}/', $alias_value, $matches);
  1853. if ($match_result) {
  1854. $unmatched_replacements = implode(', ', $matches);
  1855. $unmatched_replacements = preg_replace('/[{}]/', '', $unmatched_replacements);
  1856. return drush_set_error('DRUSH_SHELL_ALIAS_UNMATCHED_REPLACEMENTS', dt('The shell alias @alias-name uses replacements "@unmatched". You must use this command with a site alias (e.g. `drush @myalias @alias-name ...`) that defines all of these variables.', array('@alias-name' => $first, '@unmatched' => $unmatched_replacements)));
  1857. }
  1858. if (substr($alias_value, 0, 1) == '!') {
  1859. $alias_value = ltrim($alias_value, '!');
  1860. $alias_value = array('core-execute', $alias_value);
  1861. $escape = FALSE;
  1862. }
  1863. else {
  1864. // Respect quoting. See http://stackoverflow.com/questions/2202435/php-ex
  1865. $alias_value = str_getcsv($alias_value, ' ');
  1866. }
  1867. }
  1868. drush_log(dt('Shell alias found: !key => !value', array('!key' => $first, '!value' => implode(' ', $alias_value))), LogLevel::DEBUG);
  1869. $pos = array_search($first, $argv);
  1870. $number = 1;
  1871. if ($target_site_alias && ($argv[$pos - 1] == $target_site_alias)) {
  1872. --$pos;
  1873. ++$number;
  1874. }
  1875. array_splice($argv, $pos, $number, $alias_value);
  1876. if (!$escape) {
  1877. drush_set_option('escape', FALSE);
  1878. }
  1879. drush_set_context('argv', $argv);
  1880. drush_parse_args();
  1881. _drush_preflight_global_options();
  1882. }
  1883. }
  1884. function commandfiles_cache() {
  1885. static $commandfiles_cache = NULL;
  1886. if (!isset($commandfiles_cache)) {
  1887. $commandfiles_cache = new Drush\Command\Commandfiles();
  1888. }
  1889. return $commandfiles_cache;
  1890. }