command.inc 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336
  1. <?php
  2. /**
  3. * @defgroup dispatching Command dispatching functions.
  4. * @{
  5. *
  6. * These functions handle command dispatching, and can
  7. * be used to programatically invoke drush commands in
  8. * different ways.
  9. */
  10. /**
  11. * Invoke drush api calls.
  12. *
  13. * Executes the specified command with the specified arguments on the
  14. * currently bootstrapped site using the current option contexts.
  15. * Note that drush_invoke will not bootstrap any further than the
  16. * current command has already bootstrapped; therefore, you should only invoke
  17. * commands that have the same (or lower) bootstrap requirements.
  18. *
  19. * Call the correct hook for all the modules that implement it.
  20. * Additionally, the ability to rollback when an error has been encountered is also provided.
  21. * If at any point during execution, the drush_get_error() function returns anything but 0,
  22. * drush_invoke() will trigger $hook_rollback for each of the hooks that implement it,
  23. * in reverse order from how they were executed. Rollbacks are also triggered any
  24. * time a hook function returns FALSE.
  25. *
  26. * This function will also trigger pre_$hook and post_$hook variants of the hook
  27. * and its rollbacks automatically.
  28. *
  29. * HOW DRUSH HOOK FUNCTIONS ARE NAMED:
  30. *
  31. * The name of the hook is composed from the name of the command and the name of
  32. * the command file that the command definition is declared in. The general
  33. * form for the hook filename is:
  34. *
  35. * drush_COMMANDFILE_COMMANDNAME
  36. *
  37. * In many cases, drush commands that are functionally part of a common collection
  38. * of similar commands will all be declared in the same file, and every command
  39. * defined in that file will start with the same command prefix. For example, the
  40. * command file "pm.drush.inc" defines commands such as "pm-enable" and "pm-disable".
  41. * In the case of "pm-enable", the command file is "pm", and and command name is
  42. * "pm-enable". When the command name starts with the same sequence of characters
  43. * as the command file, then the repeated sequence is dropped; thus, the command
  44. * hook for "pm-enable" is "drush_pm_enable", not "drush_pm_pm_enable".
  45. *
  46. * @param command
  47. * The drush command to execute.
  48. * @return
  49. * A boolean specifying whether or not the command was successfully completed.
  50. */
  51. function drush_invoke($command) {
  52. $args = func_get_args();
  53. array_shift($args);
  54. return drush_invoke_args($command, $args);
  55. }
  56. /**
  57. * As drush_invoke, but args are passed in as an array
  58. * rather than as individual function parameters.
  59. *
  60. * @see drush_invoke()
  61. */
  62. function drush_invoke_args($command, $args) {
  63. return _drush_invoke_args($command, $args, drush_get_commandfile_for_command($command));
  64. }
  65. /**
  66. * Variant of _drush_invoke_args that allows the
  67. * commandfile to be specified correctly; this allows
  68. * the hook functions to be correctly determined for
  69. * commands that provide a 'callback' function.
  70. *
  71. * This function should not be called directly; use
  72. * @see drush_dispatch() and @see drush_invoke_args()
  73. */
  74. function _drush_invoke_args($command, $args, $defined_in_commandfile) {
  75. drush_command_include($command);
  76. // Generate the base name for the hook by converting all
  77. // dashes in the command name to underscores.
  78. $hook = str_replace("-", "_", $command);
  79. // Call the hook init function, if it exists.
  80. // If a command needs to bootstrap, it is advisable
  81. // to do so in _init; otherwise, new commandfiles
  82. // will miss out on participating in any stage that
  83. // has passed or started at the time it was discovered.
  84. $func = 'drush_' . $hook . '_init';
  85. if (function_exists($func)) {
  86. drush_log(dt("Calling drush command init function: !func", array('!func' => $func)), 'bootstrap');
  87. call_user_func_array($func, $args);
  88. _drush_log_drupal_messages();
  89. if (drush_get_error()) {
  90. drush_log(dt('The command @command could not be initialized.', array('@command' => $command)), 'error');
  91. return FALSE;
  92. }
  93. }
  94. $rollback = FALSE;
  95. $completed = array();
  96. $available_rollbacks = array();
  97. $all_available_hooks = array();
  98. // Iterate through the different hook variations
  99. $variations = array($hook . "_validate", "pre_$hook", $hook, "post_$hook");
  100. foreach ($variations as $var_hook) {
  101. // Get the list of command files.
  102. // We re-fetch the list every time through
  103. // the loop in case one of the hook function
  104. // does something that will add additional
  105. // commandfiles to the list (i.e. bootstrapping
  106. // to a higher phase will do this).
  107. $list = drush_commandfile_list();
  108. $functions = array();
  109. // Run all of the functions available for this variation
  110. foreach ($list as $commandfile => $filename) {
  111. $func = sprintf("drush_%s_%s", $commandfile, $var_hook);
  112. if (($defined_in_commandfile == $commandfile) && ($commandfile . "_" == substr($var_hook . "_",0,strlen($commandfile)+ 1))) {
  113. $oldfunc = $func;
  114. $func = sprintf("drush_%s", $var_hook);
  115. if (($oldfunc != $func) && (function_exists($oldfunc))) {
  116. drush_log(dt("The drush command hook naming conventions changed in March 2010; the function !oldfunc must be renamed to !func in order to be called.", array('!oldfunc' => $oldfunc, '!func' => $func)), "error");
  117. }
  118. }
  119. if (function_exists($func)) {
  120. $functions[] = $func;
  121. $all_available_hooks[] = $func . ' [* Defined in ' . $filename . ']';
  122. $available_rollbacks[] = $func . '_rollback';
  123. $completed[] = $func;
  124. $result = call_user_func_array($func, $args);
  125. _drush_log_drupal_messages();
  126. if (drush_get_error() || ($result === FALSE)) {
  127. $rollback = TRUE;
  128. // break out of the foreach variations and foreach list
  129. break 2;
  130. }
  131. }
  132. else {
  133. $all_available_hooks[] = $func;
  134. }
  135. }
  136. }
  137. // If no hook functions were found, print a warning.
  138. if (empty($all_available_hooks)) {
  139. drush_log(dt("No hook functions were found for !command.", array('!command' => $command)), 'warning');
  140. drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command, '!available' => "\n" . implode("\n", $all_available_hooks))), 'warning');
  141. }
  142. elseif (drush_get_option('show-invoke')) {
  143. // We show all available hooks up to and including the one that failed (or all, if there were no failures)
  144. drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command, '!available' => "\n" . implode("\n", $all_available_hooks))), 'ok');
  145. }
  146. if (drush_get_option('show-invoke')) {
  147. drush_log(dt("Available rollback hooks for !command: !rollback", array('!command' => $command, '!rollback' => "\n" . implode("\n", $available_rollbacks))), 'ok');
  148. }
  149. // something went wrong, we need to undo
  150. if ($rollback) {
  151. foreach (array_reverse($completed) as $func) {
  152. $rb_func = $func . '_rollback';
  153. if (function_exists($rb_func)) {
  154. call_user_func_array($rb_func, $args);
  155. _drush_log_drupal_messages();
  156. drush_log(dt("Changes made in !func have been rolled back.", array('!func' => $func)), 'rollback');
  157. }
  158. }
  159. }
  160. return !$rollback;
  161. }
  162. /**
  163. * Given a command record, dispatch it as if it were
  164. * the original command. Executes in the currently
  165. * bootstrapped site using the current option contexts.
  166. * Note that drush_dispatch will not bootstrap any further than the
  167. * current command has already bootstrapped; therefore, you should only invoke
  168. * commands that have the same (or lower) bootstrap requirements.
  169. *
  170. * @param command
  171. * A full $command such as returned by drush_get_commands().
  172. * @param arguments
  173. * An array of argument values.
  174. *
  175. * @see drush_topic_docs_topic().
  176. */
  177. function drush_dispatch($command, $arguments = array()) {
  178. drush_set_command($command);
  179. $return = FALSE;
  180. // Set warning for Windows users. We have already loaded site-specific drushrc.
  181. drush_environment_check_os();
  182. if ($command) {
  183. // Add arguments, if this has not already been done.
  184. // (If the command was fetched from drush_parse_command,
  185. // then you cannot provide arguments to drush_dispatch.)
  186. if (empty($command['arguments'])) {
  187. _drush_prepare_command($command, $arguments);
  188. }
  189. // Add command-specific options, if applicable
  190. drush_command_default_options($command);
  191. // Print a warning if someone tries to use a deprecated alias.
  192. if (isset($command['deprecated'])) {
  193. drush_log(dt('Warning: The command name "!deprecated" is deprecated. Please use a recommended form instead (!recommended).', array('!deprecated' => $command['deprecated-name'], '!recommended' => implode(',', array_merge(array($command['command']), $command['aliases'])))), 'warning');
  194. }
  195. // Call the callback function of the active command.
  196. $return = call_user_func_array($command['callback'], $command['arguments']);
  197. }
  198. // Add a final log entry, just so a timestamp appears.
  199. drush_log(dt('Command dispatch complete'), 'notice');
  200. return $return;
  201. }
  202. /**
  203. * Invoke a command in a new process, targeting the site specified by
  204. * the provided site alias record.
  205. *
  206. * @param array $site_alias_record
  207. * The site record to execute the command on.
  208. * @param string $command_name
  209. * The command to invoke.
  210. * @param array $commandline_args
  211. * The arguments to pass to the command.
  212. * @param array $commandline_options
  213. * The options (e.g. --select) to provide to the command.
  214. * @return
  215. * If the command could not be completed successfully, FALSE.
  216. * If the command was completed, this will return an associative
  217. * array containing the results of the API call.
  218. * @see drush_backend_get_result()
  219. *
  220. * This function may also be called via its deprecated function signature
  221. * (drush-3.x compatible): drush_invoke_process($command_name, $arg1, $arg2, ...);
  222. * Instead of this old form, prefer: drush_invoke_process("@self", $command_name, array($arg1, $arg2, ...));
  223. */
  224. function drush_invoke_process($site_alias_record /*, $command_name, $commandline_args = array(), $commandline_options = array() */) {
  225. if (is_array($site_alias_record)) {
  226. $args = func_get_args();
  227. array_shift($args);
  228. $command_name = array_shift($args);
  229. $commandline_args = empty($args) ? array() : array_shift($args);
  230. $commandline_options = empty($args) ? array() : array_shift($args);
  231. return drush_invoke_sitealias_args($site_alias_record, $command_name, $commandline_args, $commandline_options);
  232. }
  233. else {
  234. $args = func_get_args();
  235. $command_name = array_shift($args);
  236. return drush_invoke_process_args($command_name, $args);
  237. }
  238. }
  239. /**
  240. * Invoke a command in a new process.
  241. *
  242. * @param command_name
  243. * The drush command to execute.
  244. * @return
  245. * If the command could not be completed successfully, FALSE.
  246. * If the command was completed, this will return an associative
  247. * array containing the results of the API call.
  248. * @see drush_backend_get_result()
  249. * @deprecated; @see drush_invoke_process("@self", $command_name, $commandline_args, $commandline_options) for a better option
  250. */
  251. function drush_invoke_process_args($command_name, $commandline_args, $commandline_options = array()) {
  252. return drush_backend_invoke_args($command_name, $commandline_args, $commandline_options);
  253. }
  254. /**
  255. * Invoke a command in a new process, targeting the site specified by
  256. * the provided site alias record.
  257. *
  258. * @param array $site_alias_record
  259. * The site record to execute the command on.
  260. * @param string $command_name
  261. * The command to invoke.
  262. * @return
  263. * If the command could not be completed successfully, FALSE.
  264. * If the command was completed, this will return an associative
  265. * array containing the results of the API call.
  266. * @see drush_backend_get_result()
  267. * @deprecated; @see drush_invoke_process($site_alias_record, $command_name, array($arg1, $arg2, ...)) for a better option
  268. */
  269. function drush_invoke_sitealias($site_alias_record, $command_name) {
  270. $args = func_get_args();
  271. array_shift($args);
  272. array_shift($args);
  273. return drush_invoke_sitealias_args($site_alias_record, $command_name, $args);
  274. }
  275. /**
  276. * Invoke a command in a new process, targeting the site specified by
  277. * the provided site alias record.
  278. *
  279. * @param array $site_alias_record
  280. * The site record to execute the command on.
  281. * @param string $command_name
  282. * The command to invoke.
  283. * @param array $commandline_args
  284. * The arguments to pass to the command.
  285. * @param array $commandline_options
  286. * The options (e.g. --select) to provide to the command.
  287. * @return
  288. * If the command could not be completed successfully, FALSE.
  289. * If the command was completed, this will return an associative
  290. * array containing the results of the API call.
  291. * @see drush_backend_get_result()
  292. * @deprecated; @see drush_invoke_process($site_alias_record, $command_name, $commandline_args, $commandline_options) for a better option
  293. */
  294. function drush_invoke_sitealias_args($site_alias_record, $command_name, $commandline_args, $commandline_options = array()) {
  295. // If the first parameter is not a site alias record,
  296. // then presume it is an alias name, and try to look up
  297. // the alias record.
  298. if (!is_array($site_alias_record)) {
  299. $site_alias_record = drush_sitealias_get_record($site_alias_record);
  300. }
  301. return drush_do_site_command($site_alias_record, $command_name, $commandline_args, $commandline_options);
  302. }
  303. /**
  304. * Get the options that were passed to the current command.
  305. *
  306. * This function returns an array that contains all of the options
  307. * that are appropriate for forwarding along to one of the drush_invoke_*_args
  308. * functions.
  309. *
  310. * @return
  311. * An associative array of option key => value pairs.
  312. */
  313. function drush_redispatch_get_options() {
  314. // Start off by taking everything from the site alias and command line
  315. // ('cli' context)
  316. $options = array_merge(drush_get_context('alias'), drush_get_context('cli'));
  317. $options = array_diff_key($options, array_flip(drush_sitealias_site_selection_keys()));
  318. unset($options['command-specific']);
  319. unset($options['path-aliases']);
  320. // If we can parse the current command, then examine all contexts
  321. // in order for any option that is directly related to the current command
  322. $command = drush_parse_command();
  323. if (is_array($command)) {
  324. foreach ($command['options'] as $key => $value) {
  325. // Strip leading --
  326. $key = ltrim($key, '-');
  327. $value = drush_get_option($key);
  328. if (isset($value)) {
  329. $options[$key] = $value;
  330. }
  331. }
  332. }
  333. // 'php', if needed, will be included in DRUSH_COMMAND. If DRUSH_COMMAND
  334. // is not used (e.g. when calling a remote instance of drush), then --php
  335. // should not be passed along.
  336. unset($options['php']);
  337. // If --bootstrap-to-first-arg is specified, do not
  338. // pass it along to remote commands.
  339. unset($options['bootstrap-to-first-arg']);
  340. return $options;
  341. }
  342. /**
  343. * @} End of "defgroup dispatching".
  344. */
  345. /**
  346. * @file
  347. * The drush command engine.
  348. *
  349. * Since drush can be invoked independently of a proper Drupal
  350. * installation and commands may operate across sites, a distinct
  351. * command engine is needed.
  352. *
  353. * It mimics the Drupal module engine in order to economize on
  354. * concepts and to make developing commands as familiar as possible
  355. * to traditional Drupal module developers.
  356. */
  357. /**
  358. * Parse console arguments.
  359. */
  360. function drush_parse_args() {
  361. $args = drush_get_context('argv');
  362. // TODO: commandfiles should be able to extend this list.
  363. static $arg_opts = array('c', 'u', 'r', 'l', 'i');
  364. // Check to see if we were executed via a "#!/usr/bin/env drush" script
  365. drush_adjust_args_if_shebang_script($args);
  366. // Now process the command line arguments. We will divide them
  367. // into options (starting with a '-') and arguments.
  368. $arguments = $options = array();
  369. for ($i = 1; $i < count($args); $i++) {
  370. $opt = $args[$i];
  371. // Is the arg an option (starting with '-')?
  372. if ($opt{0} == "-" && strlen($opt) != 1) {
  373. // Do we have multiple options behind one '-'?
  374. if (strlen($opt) > 2 && $opt{1} != "-") {
  375. // Each char becomes a key of its own.
  376. for ($j = 1; $j < strlen($opt); $j++) {
  377. $options[substr($opt, $j, 1)] = true;
  378. }
  379. }
  380. // Do we have a longopt (starting with '--')?
  381. elseif ($opt{1} == "-") {
  382. if ($pos = strpos($opt, '=')) {
  383. $options[substr($opt, 2, $pos - 2)] = substr($opt, $pos + 1);
  384. }
  385. else {
  386. $options[substr($opt, 2)] = true;
  387. }
  388. }
  389. else {
  390. $opt = substr($opt, 1);
  391. // Check if the current opt is in $arg_opts (= has to be followed by an argument).
  392. if ((in_array($opt, $arg_opts))) {
  393. if (($args[$i+1] == NULL) || ($args[$i+1] == "") || ($args[$i + 1]{0} == "-")) {
  394. drush_set_error('DRUSH_INVALID_INPUT', "Invalid input: -$opt needs to be followed by an argument.");
  395. }
  396. $options[$opt] = $args[$i + 1];
  397. $i++;
  398. }
  399. else {
  400. $options[$opt] = true;
  401. }
  402. }
  403. }
  404. // If it's not an option, it's a command.
  405. else {
  406. $arguments[] = $opt;
  407. }
  408. }
  409. // If no arguments are specified, then the command will
  410. // be either 'help' or 'version' (the later if --version is specified)
  411. if (!sizeof($arguments)) {
  412. if (array_key_exists('version', $options)) {
  413. $arguments = array('version');
  414. }
  415. else {
  416. $arguments = array('help');
  417. }
  418. }
  419. // Handle the "@shift" alias, if present
  420. drush_process_bootstrap_to_first_arg($arguments);
  421. drush_set_arguments($arguments);
  422. drush_set_context('cli', $options);
  423. }
  424. /**
  425. * Get the short commandfile name that matches the
  426. * command.
  427. *
  428. * @param $command
  429. * The name of the command (e.g. search-index)
  430. * @return String
  431. * The short commandfile name where that command was
  432. * defined (e.g. search, not search.drush.inc)
  433. */
  434. function drush_get_commandfile_for_command($command) {
  435. $commandfile = FALSE;
  436. $commands = drush_get_commands();
  437. if (array_key_exists($command, $commands)) {
  438. $commandfile = $commands[$command]['commandfile'];
  439. }
  440. return $commandfile;
  441. }
  442. /**
  443. * Pop an argument off of drush's argument list
  444. */
  445. function drush_shift() {
  446. $arguments = drush_get_arguments();
  447. $result = NULL;
  448. if (!empty($arguments)) {
  449. // The php-script command uses the DRUSH_SHIFT_SKIP
  450. // context to cause drush_shift to skip the 'php-script'
  451. // command and the script path argument when it is
  452. // called from the user script.
  453. $skip_count = drush_get_context('DRUSH_SHIFT_SKIP');
  454. if (is_numeric($skip_count)) {
  455. for ($i = 0; $i < $skip_count; $i++) {
  456. array_shift($arguments);
  457. }
  458. $skip_count = drush_set_context('DRUSH_SHIFT_SKIP', 0);
  459. }
  460. $result = array_shift($arguments);
  461. drush_set_arguments($arguments);
  462. }
  463. return $result;
  464. }
  465. /**
  466. * Special checking for "shebang" script handling.
  467. *
  468. * If there is a file 'script.php' that begins like so:
  469. * #!/path/to/drush
  470. * Then $args will be:
  471. * /path/to/drush /path/to/script userArg1 userArg2 ...
  472. * If it instead starts like this:
  473. * #!/path/to/drush --flag php-script
  474. * Then $args will be:
  475. * /path/to/drush "--flag php-script" /path/to/script userArg1 userArg2 ...
  476. * (Note that execve does not split the parameters from
  477. * the shebang line on whitespace; see http://en.wikipedia.org/wiki/Shebang_%28Unix%29)
  478. * When drush is called via one of the "shebang" lines above,
  479. * the first or second parameter will be the full path
  480. * to the "shebang" script file -- and if the path to the
  481. * script is in the second position, then we will expect that
  482. * the argument in the first position must begin with a
  483. * '@' (alias) or '-' (flag). Under ordinary circumstances,
  484. * we do not expect that the drush command must come before
  485. * any argument that is the full path to a file. We use
  486. * this assumption to detect "shebang" script execution.
  487. */
  488. function drush_adjust_args_if_shebang_script(&$args) {
  489. if (_drush_is_drush_shebang_script($args[1])) {
  490. // If $args[1] is a drush "shebang" script, we will insert
  491. // the option "--bootstrap-to-first-arg" and the command
  492. // "php-script" at the beginning of @args, so the command
  493. // line args become:
  494. // /path/to/drush --bootstrap-to-first-arg php-script /path/to/script userArg1 userArg2 ...
  495. drush_set_option('bootstrap-to-first-arg', TRUE);
  496. array_splice($args, 1, 0, array('php-script'));
  497. drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE);
  498. }
  499. elseif (((strpos($args[1], ' ') !== FALSE) || (!ctype_alnum($args[1][0]))) && (_drush_is_drush_shebang_script($args[2]))) {
  500. // If $args[2] is a drush "shebang" script, we will insert
  501. // the space-exploded $arg[1] in place of $arg[1], so the
  502. // command line args become:
  503. // /path/to/drush scriptArg1 scriptArg2 ... /path/to/script userArg1 userArg2 ...
  504. // If none of the script arguments look like a drush command,
  505. // then we will insert "php-script" as the default command to
  506. // execute.
  507. $script_args = explode(' ', $args[1]);
  508. $has_command = FALSE;
  509. foreach ($script_args as $script_arg) {
  510. if (preg_match("/^[a-z][a-z0-9-]*$/",$script_arg)) {
  511. $has_command = TRUE;
  512. }
  513. }
  514. if (!$has_command) {
  515. $script_args[] = 'php-script';
  516. }
  517. array_splice($args, 1, 1, $script_args);
  518. drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE);
  519. }
  520. }
  521. /**
  522. * Process the --bootstrap-to-first-arg option, if it is present.
  523. *
  524. * This option checks to see if the first user-provided argument is an alias
  525. * or site specification; if it is, it will be shifted into the first argument
  526. * position, where it will specify the site to bootstrap. The result of this
  527. * is that if your shebang line looks like this:
  528. *
  529. * #!/path/to/drush --bootstrap-to-first-arg php-script
  530. *
  531. * Then when you run that script, you can optionally provide an alias such
  532. * as @dev as the first argument (e.g. $ ./mydrushscript.php @dev scriptarg1
  533. * scriptarg2). Since this is the behavior that one would usually want,
  534. * it is default behavior for a canonical script. That is, a script
  535. * with a simple shebang line, like so:
  536. *
  537. * #!/path/to/drush
  538. *
  539. * will implicitly have "--bootstrap-to-first-arg" and "php-script" prepended, and will therefore
  540. * behave exactly like the first example. To write a script that does not
  541. * use --bootstrap-to-first-arg, then the drush command or at least one flag must be explicitly
  542. * included, like so:
  543. *
  544. * #!/path/to/drush php-script
  545. */
  546. function drush_process_bootstrap_to_first_arg(&$arguments) {
  547. if (drush_get_option('bootstrap-to-first-arg', FALSE)) {
  548. $shift_alias_pos = 1 + (drush_get_context('DRUSH_SHEBANG_SCRIPT') === TRUE);
  549. if (sizeof($arguments) >= $shift_alias_pos) {
  550. $shifted_alias = $arguments[$shift_alias_pos];
  551. $alias_record = drush_sitealias_get_record($shifted_alias);
  552. if (!empty($alias_record)) {
  553. // Move the alias we shifted from its current position
  554. // in the argument list to the front of the list
  555. array_splice($arguments, $shift_alias_pos, 1);
  556. array_unshift($arguments, $shifted_alias);
  557. }
  558. }
  559. }
  560. }
  561. /**
  562. * Get a list of all implemented commands.
  563. * This invokes hook_drush_command().
  564. *
  565. * @return
  566. * Associative array of currently active command descriptors.
  567. *
  568. */
  569. function drush_get_commands() {
  570. $commands = $available_commands = array();
  571. $list = drush_commandfile_list();
  572. foreach ($list as $commandfile => $path) {
  573. if (drush_command_hook($commandfile, 'drush_command')) {
  574. $function = $commandfile . '_drush_command';
  575. $result = $function();
  576. foreach ((array)$result as $key => $command) {
  577. // Add some defaults and normalize the command descriptor
  578. $command += drush_command_defaults($key, $commandfile, $path);
  579. // Translate command.
  580. drush_command_translate($command);
  581. // If command callback function name begins with "drush_$commandfile_",
  582. // then fix up the command entry so that drush_invoke will be
  583. // called by way of drush_command. This will cause all
  584. // of the applicable hook functions to be called for the
  585. // command when it is invoked. If the callback function does
  586. // -not- begin with its commandfile name, then it will be
  587. // called directly by drush_dispatch, and no hook functions
  588. // will be called (e.g. you cannot hook drush_print_file).
  589. if ($command['callback'] != 'drush_command') {
  590. $required_command_prefix = 'drush_' . $commandfile . '_';
  591. if ((substr($command['callback'], 0, strlen($required_command_prefix)) == $required_command_prefix)) {
  592. $command['command-hook'] = substr($command['callback'], strlen('drush_'));
  593. $command['callback'] = 'drush_command';
  594. }
  595. }
  596. $commands[$key] = $command;
  597. // For every alias, make a copy of the command and store it in the command list
  598. // using the alias as a key
  599. if (isset($command['aliases']) && count($command['aliases'])) {
  600. foreach ($command['aliases'] as $alias) {
  601. $commands[$alias] = $command;
  602. $commands[$alias]['is_alias'] = TRUE;
  603. }
  604. }
  605. // Do the same operation on the deprecated aliases.
  606. if (isset($command['deprecated-aliases']) && count($command['deprecated-aliases'])) {
  607. foreach ($command['deprecated-aliases'] as $alias) {
  608. $commands[$alias] = $command;
  609. $commands[$alias]['is_alias'] = TRUE;
  610. $commands[$alias]['deprecated'] = TRUE;
  611. $commands[$alias]['deprecated-name'] = $alias;
  612. }
  613. }
  614. }
  615. }
  616. }
  617. return drush_set_context('DRUSH_COMMANDS', $commands);
  618. }
  619. function drush_command_defaults($key, $commandfile, $path) {
  620. return array(
  621. 'command' => $key,
  622. 'command-hook' => $key,
  623. 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN,
  624. 'callback arguments' => array(),
  625. 'commandfile' => $commandfile,
  626. 'path' => dirname($path),
  627. 'engines' => array(), // Helpful for drush_show_help().
  628. 'callback' => 'drush_command',
  629. 'description' => NULL,
  630. 'sections' => array(
  631. 'examples' => 'Examples',
  632. 'arguments' => 'Arguments',
  633. 'options' => 'Options',
  634. ),
  635. 'arguments' => array(),
  636. 'options' => array(),
  637. 'examples' => array(),
  638. 'aliases' => array(),
  639. 'deprecated-aliases' => array(),
  640. 'core' => array(),
  641. 'scope' => 'site',
  642. 'drupal dependencies' => array(),
  643. 'drush dependencies' => array(),
  644. 'bootstrap_errors' => array(),
  645. 'topics' => array(),
  646. 'hidden' => FALSE,
  647. );
  648. }
  649. /**
  650. * Translates description and other keys of a command definition.
  651. *
  652. * @param $command
  653. * A command definition.
  654. */
  655. function drush_command_translate(&$command) {
  656. $command['description'] = _drush_command_translate($command['description']);
  657. $keys = array('arguments', 'options', 'examples', 'engines', 'sections');
  658. foreach ($keys as $key) {
  659. foreach ($command[$key] as $k => $v) {
  660. $command[$key][$k] = _drush_command_translate($v);
  661. }
  662. }
  663. }
  664. /**
  665. * Helper function for drush_command_translate().
  666. *
  667. * @param $source
  668. * String or array.
  669. */
  670. function _drush_command_translate($source) {
  671. return is_array($source)?call_user_func_array('dt', $source):dt($source);
  672. }
  673. /**
  674. * Matches a commands array, as returned by drush_get_arguments, with the
  675. * current command table.
  676. *
  677. * Note that not all commands may be discoverable at the point-of-call,
  678. * since Drupal modules can ship commands as well, and they are
  679. * not available until after bootstrapping.
  680. *
  681. * drush_parse_command returns a normalized command descriptor, which
  682. * is an associative array. Some of its entries are:
  683. * - callback arguments: an array of arguments to pass to the calback.
  684. * - description: description of the command.
  685. * - arguments: an array of arguments that are understood by the command. for help texts.
  686. * - options: an array of options that are understood by the command. for help texts.
  687. * - examples: an array of examples that are understood by the command. for help texts.
  688. * - scope: one of 'system', 'project', 'site'.
  689. * - bootstrap: drupal bootstrap level (depends on Drupal major version). -1=no_bootstrap.
  690. * - core: Drupal major version required.
  691. * - drupal dependencies: drupal modules required for this command.
  692. * - drush dependencies: other drush command files required for this command.
  693. *
  694. */
  695. function drush_parse_command() {
  696. $args = drush_get_arguments();
  697. $command = FALSE;
  698. // Get a list of all implemented commands.
  699. $implemented = drush_get_commands();
  700. if (isset($implemented[$args[0]])) {
  701. $command = $implemented[$args[0]];
  702. $arguments = array_slice($args, 1);
  703. }
  704. // We have found a command that matches. Set the appropriate values.
  705. if ($command) {
  706. // Special case. Force help command if --help option was specified.
  707. if (drush_get_option(array('h', 'help'))) {
  708. $arguments = array($command['command']);
  709. $command = $implemented['help'];
  710. $command['arguments'] = $arguments;
  711. }
  712. else {
  713. _drush_prepare_command($command, $arguments);
  714. }
  715. drush_set_command($command);
  716. }
  717. return $command;
  718. }
  719. /*
  720. * Called by drush_parse_command. If a command is dispatched
  721. * directly by drush_dispatch, then drush_dispatch will call
  722. * this function.
  723. */
  724. function _drush_prepare_command(&$command, $arguments = array()) {
  725. // Merge specified callback arguments, which precede the arguments passed on the command line.
  726. if (isset($command['callback arguments']) && is_array($command['callback arguments'])) {
  727. $arguments = array_merge($command['callback arguments'], $arguments);
  728. }
  729. $command['arguments'] = $arguments;
  730. }
  731. /**
  732. * Entry point for commands into the drush_invoke API
  733. *
  734. * If a command does not have a callback specified, this function will be called.
  735. *
  736. * This function will trigger $hook_drush_init, then if no errors occur,
  737. * it will call drush_invoke() with the command that was dispatch.
  738. *
  739. * If no errors have occured, it will run $hook_drush_exit.
  740. */
  741. function drush_command() {
  742. $args = func_get_args();
  743. $command = drush_get_command();
  744. foreach (drush_command_implements("drush_init") as $name) {
  745. $func = $name . '_drush_init';
  746. drush_log(dt("Initializing drush commandfile: !name", array('!name' => $name)), 'bootstrap');
  747. call_user_func_array($func, $args);
  748. _drush_log_drupal_messages();
  749. }
  750. if (!drush_get_error()) {
  751. _drush_invoke_args($command['command-hook'], $args, $command['commandfile']);
  752. }
  753. if (!drush_get_error()) {
  754. foreach (drush_command_implements('drush_exit') as $name) {
  755. $func = $name . '_drush_exit';
  756. call_user_func_array($func, $args);
  757. _drush_log_drupal_messages();
  758. }
  759. }
  760. }
  761. /**
  762. * Invoke a hook in all available command files that implement it.
  763. *
  764. * @see drush_command_invoke_all_ref()
  765. *
  766. * @param $hook
  767. * The name of the hook to invoke.
  768. * @param ...
  769. * Arguments to pass to the hook.
  770. * @return
  771. * An array of return values of the hook implementations. If commands return
  772. * arrays from their implementations, those are merged into one array.
  773. */
  774. function drush_command_invoke_all() {
  775. $args = func_get_args();
  776. if (count($args) == 1) {
  777. $args[] = NULL;
  778. }
  779. $reference_value = $args[1];
  780. $args[1] = &$reference_value;
  781. return call_user_func_array('drush_command_invoke_all_ref', $args);
  782. }
  783. /**
  784. * A drush_command_invoke_all() that wants the first parameter to be passed by reference.
  785. *
  786. * @see drush_command_invoke_all()
  787. */
  788. function drush_command_invoke_all_ref($hook, &$reference_parameter) {
  789. $args = func_get_args();
  790. array_shift($args);
  791. // Insure that call_user_func_array can alter first parameter
  792. $args[0] = &$reference_parameter;
  793. $return = array();
  794. foreach (drush_command_implements($hook) as $module) {
  795. $function = $module .'_'. $hook;
  796. $result = call_user_func_array($function, $args);
  797. if (isset($result) && is_array($result)) {
  798. $return = array_merge_recursive($return, $result);
  799. }
  800. else if (isset($result)) {
  801. $return[] = $result;
  802. }
  803. }
  804. return $return;
  805. }
  806. /**
  807. * Determine which command files are implementing a hook.
  808. *
  809. * @param $hook
  810. * The name of the hook (e.g. "drush_help" or "drush_command").
  811. *
  812. * @return
  813. * An array with the names of the command files which are implementing this hook.
  814. */
  815. function drush_command_implements($hook) {
  816. $implementations[$hook] = array();
  817. $list = drush_commandfile_list();
  818. foreach ($list as $commandfile => $file) {
  819. if (drush_command_hook($commandfile, $hook)) {
  820. $implementations[$hook][] = $commandfile;
  821. }
  822. }
  823. return (array)$implementations[$hook];
  824. }
  825. /**
  826. * @param string
  827. * name of command to check.
  828. *
  829. * @return boolean
  830. * TRUE if the given command has an implementation.
  831. */
  832. function drush_is_command($command) {
  833. $commands = drush_get_commands();
  834. return isset($commands[$command]);
  835. }
  836. /**
  837. * Collect a list of all available drush command files.
  838. *
  839. * Scans the following paths for drush command files:
  840. *
  841. * - The "/path/to/drush/commands" folder.
  842. * - Folders listed in the 'include' option (see example.drushrc.php).
  843. * - The system-wide drush commands folder, e.g. /usr/share/drush/commands
  844. * - The ".drush" folder in the user's HOME folder.
  845. * - All modules in the current Drupal installation whether they are enabled or
  846. * not. Commands implementing hook_drush_load() in MODULE.drush.load.inc with
  847. * a return value FALSE will not be loaded.
  848. *
  849. * A drush command file is a file that matches "*.drush.inc".
  850. *
  851. * @see drush_scan_directory()
  852. *
  853. * @return
  854. * An associative array whose keys and values are the names of all available
  855. * command files.
  856. */
  857. function drush_commandfile_list() {
  858. return drush_get_context('DRUSH_COMMAND_FILES', array());
  859. }
  860. function _drush_find_commandfiles($phase, $phase_max = FALSE) {
  861. if (!$phase_max) {
  862. $phase_max = $phase;
  863. }
  864. $searchpath = array();
  865. switch ($phase) {
  866. case DRUSH_BOOTSTRAP_DRUSH:
  867. // Core commands shipping with drush
  868. $searchpath[] = realpath(dirname(__FILE__) . '/../commands/');
  869. // User commands, specified by 'include' option
  870. if ($include = drush_get_option(array('i', 'include'), FALSE)) {
  871. foreach (explode(PATH_SEPARATOR, $include) as $path) {
  872. $searchpath[] = $path;
  873. }
  874. }
  875. // System commands, residing in $SHARE_PREFIX/share/drush/commands
  876. $share_path = drush_get_context('SHARE_PREFIX', '/usr') . '/share/drush/commands';
  877. if (is_dir($share_path)) {
  878. $searchpath[] = $share_path;
  879. }
  880. // User commands, residing in ~/.drush
  881. if (!is_null(drush_server_home())) {
  882. $searchpath[] = drush_server_home() . '/.drush';
  883. }
  884. break;
  885. case DRUSH_BOOTSTRAP_DRUPAL_SITE:
  886. // If we are going to stop bootstrapping at the site, then
  887. // we will quickly add all commandfiles that we can find for
  888. // any module associated with the site, whether it is enabled
  889. // or not. If we are, however, going to continue on to bootstrap
  890. // all the way to DRUSH_BOOTSTRAP_DRUPAL_FULL, then we will
  891. // instead wait for that phase, which will more carefully add
  892. // only those drush command files that are associated with
  893. // enabled modules.
  894. if ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL) {
  895. $searchpath[] = conf_path() . '/modules';
  896. // Too early for variable_get('install_profile', 'default'); Just use default.
  897. $searchpath[] = "profiles/default/modules";
  898. // Add all module paths, even disabled modules. Prefer speed over accuracy.
  899. $searchpath[] = 'sites/all/modules';
  900. }
  901. $searchpath[] = 'sites/all/themes';
  902. $searchpath[] = conf_path() . '/themes';
  903. break;
  904. case DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION:
  905. // See comment above regarding this if() condition.
  906. if ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL) {
  907. // You must define your install_profile in settings.php. The DB is not sufficient.
  908. // Drupal core does not yet do that. See http://drupal.org/node/545452.
  909. if ($profile = variable_get('install_profile', NULL)) {
  910. $searchpath[] = "profiles/$profile/modules";
  911. }
  912. }
  913. break;
  914. case DRUSH_BOOTSTRAP_DRUPAL_FULL:
  915. // Add enabled module paths. Since we are bootstrapped,
  916. // we can use the Drupal API.
  917. foreach (module_list() as $module) {
  918. $filename = drupal_get_filename('module', $module);
  919. $searchpath[] = dirname($filename);
  920. }
  921. break;
  922. }
  923. _drush_add_commandfiles($searchpath, $phase);
  924. }
  925. function _drush_add_commandfiles($searchpath, $phase = NULL) {
  926. $cache =& drush_get_context('DRUSH_COMMAND_FILES', array());
  927. static $evaluated = array();
  928. static $deferred = array();
  929. if (sizeof($searchpath)) {
  930. // Build a list of all of the modules to attempt to load.
  931. // Start with any modules deferred from a previous phase.
  932. $list = $deferred;
  933. // Scan for drush command files; add to list for consideration if found.
  934. foreach (array_unique($searchpath) as $path) {
  935. if (is_dir($path)) {
  936. $files = drush_scan_directory($path, '/\.drush\.inc$/', array('.', '..', 'examples'));
  937. foreach ($files as $filename => $info) {
  938. $module = basename($filename, '.drush.inc');
  939. // Only try to bootstrap modules that we have never seen before, or that we
  940. // have tried to load but did not due to an unmet _drush_load() requirement.
  941. if (!array_key_exists($module, $evaluated) && file_exists($filename)) {
  942. $evaluated[$module] = TRUE;
  943. $list[$module] = $filename;
  944. }
  945. }
  946. }
  947. }
  948. // Check each file in the consideration list; if there is
  949. // a modulename_drush_load() function in modulename.drush.load.inc,
  950. // then call it to determine if this file should be loaded.
  951. foreach ($list as $module => $filename) {
  952. $load_command = TRUE;
  953. $load_test_inc = dirname($filename) . "/" . $module . ".drush.load.inc";
  954. if (file_exists($load_test_inc)) {
  955. include_once($load_test_inc);
  956. $load_test_func = $module . "_drush_load";
  957. if (function_exists($load_test_func)) {
  958. $load_command = $load_test_func($phase);
  959. }
  960. }
  961. if ($load_command) {
  962. require_once realpath($filename);
  963. unset($deferred[$module]);
  964. }
  965. else {
  966. unset($list[$module]);
  967. // Signal that we should try again on
  968. // the next bootstrap phase. We set
  969. // the flag to the filename of the first
  970. // module we find so that only that one
  971. // will be retried.
  972. $deferred[$module] = $filename;
  973. }
  974. }
  975. if (sizeof($list)) {
  976. $cache = array_merge($cache, $list);
  977. ksort($cache);
  978. }
  979. }
  980. }
  981. /**
  982. * Conditionally include files based on the command used.
  983. *
  984. * Steps through each of the currently loaded commandfiles and
  985. * loads an optional commandfile based on the key.
  986. *
  987. * When a command such as 'pm-enable' is called, this
  988. * function will find all 'enable.pm.inc' files that
  989. * are present in each of the commandfile directories.
  990. */
  991. function drush_command_include($command) {
  992. $include_files = drush_command_get_includes($command);
  993. foreach($include_files as $filename => $commandfile) {
  994. drush_log(dt('Including !filename', array('!filename' => $filename)), 'bootstrap');
  995. include_once($filename);
  996. }
  997. }
  998. function drush_command_get_includes($command) {
  999. $include_files = array();
  1000. $parts = explode('-', $command);
  1001. $command = implode(".", array_reverse($parts));
  1002. $commandfiles = drush_commandfile_list();
  1003. $options = array();
  1004. foreach ($commandfiles as $commandfile => $file) {
  1005. $filename = sprintf("%s/%s.inc", dirname($file), $command);
  1006. if (file_exists($filename)) {
  1007. $include_files[$filename] = $commandfile;
  1008. }
  1009. }
  1010. return $include_files;
  1011. }
  1012. /**
  1013. * Conditionally include default options based on the command used.
  1014. */
  1015. function drush_command_default_options($command = NULL) {
  1016. if (!$command) {
  1017. $command = drush_get_command();
  1018. }
  1019. if ($command) {
  1020. // Look for command-specific options for this command
  1021. // keyed both on the command's primary name, and on each
  1022. // of its aliases.
  1023. $options_were_set = _drush_command_set_default_options($command['command']);
  1024. if (isset($command['aliases']) && count($command['aliases'])) {
  1025. foreach ($command['aliases'] as $alias) {
  1026. if (_drush_command_set_default_options($alias) === TRUE) {
  1027. $options_were_set = TRUE;
  1028. }
  1029. }
  1030. }
  1031. // If we set or cleared any options, go back and re-bootstrap any global
  1032. // options such as -y and -v.
  1033. if ($options_were_set) {
  1034. _drush_bootstrap_global_options();
  1035. }
  1036. }
  1037. }
  1038. function _drush_command_set_default_options($command) {
  1039. $options_were_set = FALSE;
  1040. $command_default_options = drush_get_context('command-specific');
  1041. if (array_key_exists($command, $command_default_options)) {
  1042. foreach ($command_default_options[$command] as $key => $value) {
  1043. // We set command-specific options in their own context
  1044. // that is higher precedence than the various config file
  1045. // context, but lower than command-line options.
  1046. if (!drush_get_option('no-' . $key, FALSE)) {
  1047. drush_set_option($key, $value, 'specific');
  1048. $options_were_set = TRUE;
  1049. }
  1050. }
  1051. }
  1052. return $options_were_set;
  1053. }
  1054. /**
  1055. * Determine whether a command file implements a hook.
  1056. *
  1057. * @param $module
  1058. * The name of the module (without the .module extension).
  1059. * @param $hook
  1060. * The name of the hook (e.g. "help" or "menu").
  1061. * @return
  1062. * TRUE if the the hook is implemented.
  1063. */
  1064. function drush_command_hook($commandfile, $hook) {
  1065. return function_exists($commandfile .'_'. $hook);
  1066. }
  1067. /**
  1068. * Finds all files that match a given mask in a given directory.
  1069. * Directories and files beginning with a period are excluded; this
  1070. * prevents hidden files and directories (such as SVN working directories
  1071. * and GIT repositories) from being scanned.
  1072. *
  1073. * @param $dir
  1074. * The base directory for the scan, without trailing slash.
  1075. * @param $mask
  1076. * The regular expression of the files to find.
  1077. * @param $nomask
  1078. * An array of files/directories to ignore.
  1079. * @param $callback
  1080. * The callback function to call for each match.
  1081. * @param $recurse_max_depth
  1082. * When TRUE, the directory scan will recurse the entire tree
  1083. * starting at the provided directory. When FALSE, only files
  1084. * in the provided directory are returned. Integer values
  1085. * limit the depth of the traversal, with zero being treated
  1086. * identically to FALSE, and 1 limiting the traversal to the
  1087. * provided directory and its immediate children only, and so on.
  1088. * @param $key
  1089. * The key to be used for the returned array of files. Possible
  1090. * values are "filename", for the path starting with $dir,
  1091. * "basename", for the basename of the file, and "name" for the name
  1092. * of the file without an extension.
  1093. * @param $min_depth
  1094. * Minimum depth of directories to return files from.
  1095. * @param $include_dot_files
  1096. * If TRUE, files that begin with a '.' will be returned if they
  1097. * match the provided mask. If FALSE, files that begin with a '.'
  1098. * will not be returned, even if they match the provided mask.
  1099. * @param $depth
  1100. * Current depth of recursion. This parameter is only used internally and should not be passed.
  1101. *
  1102. * @return
  1103. * An associative array (keyed on the provided key) of objects with
  1104. * "path", "basename", and "name" members corresponding to the
  1105. * matching files.
  1106. */
  1107. function drush_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse_max_depth = TRUE, $key = 'filename', $min_depth = 0, $include_dot_files = FALSE, $depth = 0) {
  1108. $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
  1109. $files = array();
  1110. if (is_string($dir) && is_dir($dir) && $handle = opendir($dir)) {
  1111. while (FALSE !== ($file = readdir($handle))) {
  1112. if (!in_array($file, $nomask) && (($include_dot_files && (!preg_match("/\.\+/",$file))) || ($file[0] != '.'))) {
  1113. if (is_dir("$dir/$file") && (($recurse_max_depth === TRUE) || ($depth < $recurse_max_depth))) {
  1114. // Give priority to files in this folder by merging them in after any subdirectory files.
  1115. $files = array_merge(drush_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse_max_depth, $key, $min_depth, $include_dot_files, $depth + 1), $files);
  1116. }
  1117. elseif ($depth >= $min_depth && preg_match($mask, $file)) {
  1118. // Always use this match over anything already set in $files with the same $$key.
  1119. $filename = "$dir/$file";
  1120. $basename = basename($file);
  1121. $name = substr($basename, 0, strrpos($basename, '.'));
  1122. $files[$$key] = new stdClass();
  1123. $files[$$key]->filename = $filename;
  1124. $files[$$key]->basename = $basename;
  1125. $files[$$key]->name = $name;
  1126. if ($callback) {
  1127. drush_op($callback, $filename);
  1128. }
  1129. }
  1130. }
  1131. }
  1132. closedir($handle);
  1133. }
  1134. return $files;
  1135. }
  1136. /**
  1137. * Check that a command is valid for the current bootstrap phase.
  1138. *
  1139. * @param $command
  1140. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  1141. *
  1142. * @return
  1143. * TRUE if command is valid.
  1144. */
  1145. function drush_enforce_requirement_bootstrap_phase(&$command) {
  1146. $valid = array();
  1147. $current_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
  1148. if ($command['bootstrap'] <= $current_phase) {
  1149. return TRUE;
  1150. }
  1151. // TODO: provide description text for each bootstrap level so we can give
  1152. // the user something more helpful and specific here.
  1153. $command['bootstrap_errors']['DRUSH_COMMAND_INSUFFICIENT_BOOTSTRAP'] = dt('Command !command needs a higher bootstrap level to run - you will need invoke drush from a more functional Drupal environment to run this command.', array('!command' => $command['command']));
  1154. }
  1155. /**
  1156. * Check that a command has its declared dependencies available or have no
  1157. * dependencies.
  1158. *
  1159. * @param $command
  1160. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  1161. *
  1162. * @return
  1163. * TRUE if command is valid.
  1164. */
  1165. function drush_enforce_requirement_drupal_dependencies(&$command) {
  1166. // If the command bootstrap is DRUSH_BOOTSTRAP_MAX, then we will
  1167. // allow the requirements to pass if we have not successfully
  1168. // bootstrapped Drupal. The combination of DRUSH_BOOTSTRAP_MAX
  1169. // and 'drupal dependencies' indicates that the drush command
  1170. // will use the dependent modules only if they are available.
  1171. if ($command['bootstrap'] == DRUSH_BOOTSTRAP_MAX) {
  1172. // If we have not bootstrapped, then let the dependencies pass;
  1173. // if we have bootstrapped, then enforce them.
  1174. if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') < DRUSH_BOOTSTRAP_DRUPAL_FULL) {
  1175. return TRUE;
  1176. }
  1177. }
  1178. // If there are no drupal dependencies, then do nothing
  1179. if (!empty($command['drupal dependencies'])) {
  1180. foreach ($command['drupal dependencies'] as $dependency) {
  1181. if(!function_exists('module_exists') || !module_exists($dependency)) {
  1182. $command['bootstrap_errors']['DRUSH_COMMAND_DEPENDENCY_ERROR'] = dt('Command !command needs the following modules installed/enabled to run: !dependencies.', array('!command' => $command['command'], '!dependencies' => implode(', ', $command['drupal dependencies'])));
  1183. return FALSE;
  1184. }
  1185. }
  1186. }
  1187. return TRUE;
  1188. }
  1189. /**
  1190. * Check that a command has its declared drush dependencies available or have no
  1191. * dependencies. Drush dependencies are helpful when a command is invoking
  1192. * another command, or implementing its API.
  1193. *
  1194. * @param $command
  1195. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  1196. * @return
  1197. * TRUE if dependencies are met.
  1198. */
  1199. function drush_enforce_requirement_drush_dependencies(&$command) {
  1200. // If there are no drush dependencies, then do nothing.
  1201. if (!empty($command['drush dependencies'])) {
  1202. $commandfiles = drush_commandfile_list();
  1203. foreach ($command['drush dependencies'] as $dependency) {
  1204. if (!isset($commandfiles[$dependency])) {
  1205. $dt_args = array(
  1206. '!command' => $command['command'],
  1207. '!dependency' => "$dependency.drush.inc",
  1208. );
  1209. $command['bootstrap_errors']['DRUSH_COMMAND_DEPENDENCY_ERROR'] = dt('Command !command needs the following drush command file to run: !dependency.', $dt_args);
  1210. return FALSE;
  1211. }
  1212. }
  1213. }
  1214. return TRUE;
  1215. }
  1216. /**
  1217. * Check that a command is valid for the current major version of core. Handles
  1218. * explicit version numbers and 'plus' numbers like 6+ (compatible with 6, 7 ...).
  1219. *
  1220. * @param $command
  1221. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  1222. *
  1223. * @return
  1224. * TRUE if command is valid.
  1225. */
  1226. function drush_enforce_requirement_core(&$command) {
  1227. $major = drush_drupal_major_version();
  1228. if (!$core = $command['core']) {
  1229. return TRUE;
  1230. }
  1231. foreach ($core as $compat) {
  1232. if ($compat == $major) {
  1233. return TRUE;
  1234. }
  1235. elseif (substr($compat, -1) == '+' && $major >= substr($compat, 0, strlen($compat)-1)) {
  1236. return TRUE;
  1237. }
  1238. }
  1239. $versions = array_pop($core);
  1240. if (!empty($core)) {
  1241. $versions = implode(', ', $core) . dt(' or ') . $versions;
  1242. }
  1243. $command['bootstrap_errors']['DRUSH_COMMAND_CORE_VERSION_ERROR'] = dt('Command !command requires Drupal core version !versions to run.', array('!command' => $command['command'], '!versions' => $versions));
  1244. }