diff --git a/includes/unl_bootstrap.inc b/includes/unl_bootstrap.inc index 41949dd9f6821709afe4914c5f1baa9ab1d8fe8f..513b05f8d3b056b33cc3af903e1cb8a3761efc59 100644 --- a/includes/unl_bootstrap.inc +++ b/includes/unl_bootstrap.inc @@ -1,5 +1,9 @@ <?php +/** + * Enable the set up of multiple sites without making symbolics links. + * Instead, a few entries in .htaccess will be all that is needed. + */ function unl_bootstrap() { $original_script_name = $_SERVER['SCRIPT_NAME']; $php_file = basename($original_script_name); @@ -37,3 +41,28 @@ function unl_bootstrap() { conf_path(TRUE, TRUE); } + +/** + * This will be called during update.php's bootstrap to remove any + * shared table prefixes from the database config. + * This allows the same updates to be run on all sites, even if + * they would normally be applied to the same table. + */ +function unl_bootstrap_update() { + foreach ($GLOBALS['databases'] as $key1 => $databases) { + foreach ($databases as $key2 => $database) { + if ($key2 == 'slave') { + foreach ($database as $key3 => $slave) { + if (is_array($slave['prefix'])) { + $GLOBALS['databases'][$key1][$key2][$key3]['prefix'] = $slave['prefix']['default']; + } + } + } + else { + if (is_array($database['prefix'])) { + $GLOBALS['databases'][$key1][$key2]['prefix'] = $database['prefix']['default']; + } + } + } + } +} diff --git a/includes/update.inc b/includes/update.inc index 3722c54fc6b7b28b8df4a1c2a8bf30b676f1dafa..2e0bbce5bfc9026f92cb1ba3d08caf7581b0238b 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -85,6 +85,8 @@ function update_prepare_d7_bootstrap() { // still being used. include_once DRUPAL_ROOT . '/includes/install.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); + require_once DRUPAL_ROOT . '/includes/unl_bootstrap.inc'; + unl_bootstrap_update(); global $databases, $db_url, $db_prefix, $update_rewrite_settings; if (empty($databases) && !empty($db_url)) { $databases = update_parse_db_url($db_url, $db_prefix); diff --git a/sites/all/modules/drush/README.txt b/sites/all/modules/drush/README.txt index da17c57a6793b70eead710e2c6f453b1dd24f4be..6895bdb092a1c47e0b4b26ed1903873272f721c6 100644 --- a/sites/all/modules/drush/README.txt +++ b/sites/all/modules/drush/README.txt @@ -1,4 +1,4 @@ -// $Id: README.txt,v 1.46 2010/06/16 15:08:02 weitzman Exp $ +// $Id: README.txt,v 1.52 2010/11/08 13:22:42 weitzman Exp $ DESCRIPTION ----------- @@ -11,6 +11,21 @@ Drush core ships with lots of useful commands for interacting with code like modules/themes/profiles/translations. Similarly, it runs update.php, executes sql queries and DB migrations, and misc utilities like run cron or clear cache. +REQUIREMENTS +------------ +* To use drush from the command line, you'll need a CLI-mode capable PHP + binary. The minimum PHP version is 5.2. +* drush also runs on Windows; however, drush commands make use of + unix command line tools, so to use it effectively, you have to install + some of them, e.g. from GnuWin32 (http://gnuwin32.sourceforge.net/). More info + about Drush on Windows available at http://drupal.org/node/594744. +* Drush works with Drupal 5, Drupal 6 and Drupal 7. However, occasionally + recent changes to the most recent version of Drupal can introduce issues + with drush. On Drupal 5, drush requires update_status v5.x-2.5 or later + in order to use pm-updatecode. If you have an earlier version of update_status, + upgrade it via "drush dl update_status" before using pm-updatecode. + + INSTALLATION ------------ For Linux/Unix/Mac: @@ -19,13 +34,12 @@ For Linux/Unix/Mac: 2. Make the 'drush' command executable: $ chmod u+x /path/to/drush/drush 3. (Optional, but recommended:) To ease the use of drush, - - create a link to drush in a directory that is in your $PATH, e.g.: + - create a link to drush in a directory that is in your PATH, e.g.: $ ln -s /path/to/drush/drush /usr/local/bin/drush OR - - create an alias to drush: - $ alias drush='/path/to/drush/drush' - For example, if drush is in your home directory: - $ alias drush='~/drush/drush' + - add the folder that contains drush to your PATH + PATH=$PATH:/path/to/drush + This goes into .profile, .bash_aliases or .bashrc in your home folder. NOTE: You must log out and then log back in again or re-load your bash configuration file to apply your changes to your current session: @@ -33,13 +47,39 @@ For Linux/Unix/Mac: NOTE FOR ADVANCED USERS - If you want to run drush with a specific version of php, rather than the - one found by the drush command, you can instead create an alias that - executes the drush.php file directly: - $ alias drush='/path/to/php/php5 /path/to/drush/drush.php' - If you do this, to allow Drush to detect the number of available columns, + one found by the drush command, you can define an environment variable + DRUSH_PHP that points to the php to execute: + export DRUSH_PHP=/usr/bin/php5 + OR + - If you want to exactly control how drush is called, you may define an alias + that executes the drush.php file directly and passes that path to drush: + $ alias drush='/path/to/php/php5 -d memory_limit=128M /path/to/drush/drush.php --php="/path/to/php/php5 -d memory_limit=128M"' + Note that it is necessary to pass the '--php' option to drush to define + how drush should call php if it needs to do so. + If you define an alias, to allow Drush to detect the number of available columns, you need to add the line 'export COLUMNS' to the .profile file in your home folder. + NOTE ON PHP.INI FILES + - Usually, php is configured to use separate php.ini files for the web server + and the command line. To see which php.ini file drush is using, run: + $ drush status + - Compare the php.ini that drush is using with the php.ini that the webserver is + using. Make sure that drush's php.ini is given as much memory to work with as + the web server is; otherwise, Drupal might run out of memory when drush + bootstraps it. + - Drush requires a fairly unrestricted php environment to run in. In particular, + you should insure that safe_mode, open_basedir, disable_functions and + disable_classes are empty. + - If drush is using the same php.ini file as the web server, you can create + a php.ini file exclusively for drush by copying your web server's php.ini + file to the folder $HOME/.drush or the folder /etc/drush. Then you may edit + this file and change the settings described above without affecting the + php enviornment of your web server. Alternately, if you only want to + override a few values, copy example.drush.ini from the "examples" folder + into $HOME/.drush or the folder /etc/drush and edit to suit. See comments + in example.drush.ini for more details. + 4. Start using drush by running "drush" from your Drupal root directory. (or, if you did not follow step 3, by running "/path/to/drush/drush" @@ -50,6 +90,10 @@ For Linux/Unix/Mac: For Windows: - Follow step 1. Use drush by navigating to /path/to/drush and running 'drush.bat'. + - You have to install gzip, libarchive, tar and wget executables. Go to + http://gnuwin32.sourceforge.net/packages.html and install the packages. + Add the folder %ProgramFiles%\GnuWin32\bin to your PATH. + Documentation can be found at http://drupal.org/node/594744 - Whenever the documentation or the help text refers to 'drush [option] <command>' or something similar, 'drush' has to be replaced by 'drush.bat'. @@ -90,12 +134,19 @@ functions like cdd which whisks you to any directory in your drupal site. Many commands support a --pipe option which returns machine readable output. See `drush pm-list --status=enabled --pipe` as an example +Very intensive scripts can exhaust your available PHP memory. One remedy is to +just restart automatically using bash. For example: + + while true; do drush search-index; sleep 5; done + EXAMPLES -------- Inside the "examples" folder you will find some example files to help you get started with your drush configuration file (example.drushrc.php), site alias definitions (example.aliases.drushrc.php) and drush commands -(example.drush.inc). +(sandwich.drush.inc). You will also see an example 'policy' file which +can be customized to block certain commands or arguments as your organization +needs. DRUSHRC.PHP -------- @@ -115,7 +166,7 @@ your own. In fact, writing a drush command is no harder than writing simple Drupal modules, since drush command files closely follow the structure of ordinary Drupal modules. -See example.drush.inc for light details on the internals of a drush command file. +See sandwich.drush.inc for light details on the internals of a drush command file. Otherwise, the core commands in drush are good models for your own commands. You can put your drush command file in a number of places: @@ -130,20 +181,6 @@ You can put your drush command file in a number of places: In any case, it is important that you end the filename with ".drush.inc", so that drush can find it. -REQUIREMENTS ------------- -* To use drush from the command line, you'll need a CLI-mode capable PHP - binary. The minimum PHP version is 5.2. -* drush also runs on Windows; however, drush commands make use of - unix command line tools, so to use it effectively, you have to install - some of them, e.g. from GnuWin32 (http://gnuwin32.sourceforge.net/). More info - about Drush on Windows available at http://drupal.org/node/594744. -* Drush works with Drupal 5, Drupal 6 and Drupal 7. However, occasionally - recent changes to the most recent version of Drupal can introduce issues - with drush. On Drupal 5, drush requires update_status v5.x-2.5 or later - in order to use pm-updatecode. If you have an earlier version of update_status, - upgrade it via "drush dl update_status" before using pm-updatecode. - FAQ --- Q: What does "drush" stand for? diff --git a/sites/all/modules/drush/commands/core/clear.cache.inc b/sites/all/modules/drush/commands/core/clear.cache.inc index 34699f1262f7d520ff1047d3b189cabe64927565..043b1a86181f039819c8c1f9cd2d67fbccdfc5cc 100644 --- a/sites/all/modules/drush/commands/core/clear.cache.inc +++ b/sites/all/modules/drush/commands/core/clear.cache.inc @@ -21,8 +21,13 @@ case 7: default: $types = drush_cache_clear_types(); + // Check if the provided type ($type) is a valid cache type. + if ($type && !key_exists($type, $types)) { + return drush_set_error(dt("'!type' cache is not a valid cache type", array('!type' => $type))); + } + if ($type) { - drush_op('call_user_func', $types[$type]); + drush_op($types[$type]); drush_log(dt("'!name' cache was cleared", array('!name' => $type)), 'success'); } else { @@ -42,10 +47,21 @@ function drush_cache_clear_types() { 'theme' => 'drush_cache_clear_theme_registry', 'menu' => 'menu_rebuild', 'css+js' => 'drush_cache_clear_css_js', + 'block' => 'drush_cache_clear_block', ); + if (drush_drupal_major_version() >= 7) { + $types['registry'] = 'registry_update'; + } + elseif (drush_drupal_major_version() == 6 && module_exists('autoload')) { + // TODO: move this to autoload module. + $types['registry'] = 'autoload_registry_update'; + } if (count(module_implements('node_grants'))) { $types['nodeaccess'] = 'node_access_rebuild'; } + + // Command files may customize $types as desired. + drush_command_invoke_all_ref('drush_cache_clear', $types); return $types; } @@ -58,3 +74,11 @@ function drush_cache_clear_css_js() { drupal_clear_css_cache(); drupal_clear_js_cache(); } + +/** + * Clear the cache of the block output. + */ +function drush_cache_clear_block() { + cache_clear_all(NULL, 'cache_block'); +} + diff --git a/sites/all/modules/drush/commands/core/core.drush.inc b/sites/all/modules/drush/commands/core/core.drush.inc index 906d12d9a16c5ddb6c557a69f7b3bbc22abe812b..381d3d788f0b65cae56aa653070f6e06e187e048 100644 --- a/sites/all/modules/drush/commands/core/core.drush.inc +++ b/sites/all/modules/drush/commands/core/core.drush.inc @@ -1,5 +1,5 @@ <?php -// $Id: core.drush.inc,v 1.103.2.2 2010/07/01 15:25:06 weitzman Exp $ +// $Id: core.drush.inc,v 1.135 2010/12/01 20:59:56 jonhattan Exp $ /** * @file @@ -23,15 +23,21 @@ function core_drush_command() { $items = array(); $items['help'] = array( - 'description' => 'Print this help message. Use --filter to limit command list to one command file (e.g. --filter=pm)', + 'description' => 'Print this help message. See `drush help help` for more options.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap. - 'options' => drush_get_option_help(), + 'options' => array( + '--sort' => 'Sort commands in alphabetical order. drush waits for full bootstrap before printing any commands when this option is used.', + '--filter' => 'Restrict command list to those commands defined in the specified file.', + '--html' => 'Print help for all commands in HTML format.', + '--pipe' => 'A list of available commands, one per line.', + ), 'examples' => array( - 'drush dl cck zen' => 'Download CCK module and Zen theme.', - 'drush --uri=http://example.com status' => 'Show status command for the example.com multi-site.', - 'drush help --pipe' => 'A list of available commands, one per line.', - 'drush help --html' => 'Print help for all commands in HTML format.', + 'drush' => 'List all commands.', + 'drush --filter=devel_generate' => 'Show only commands defined in devel_generate.drush.inc', + 'drush help pm-download' => 'Show help for one command.', + 'drush help dl' => 'Show help for one command using an alias.', ), + 'topics' => array('core-readme'), ); $items['core-cron'] = array( 'description' => 'Run all cron hooks.', @@ -56,7 +62,8 @@ function core_drush_command() { ), 'options' => array( 'show-passwords' => 'Show database password.', - ) + ), + 'topics' => array('core-readme'), ); $items['php-script'] = array( 'description' => "Run php script(s).", @@ -64,13 +71,16 @@ function core_drush_command() { 'drush php-script scratch' => 'Run scratch.php script. See commands/core directory.', 'drush php-script example --script-path=/path/to/scripts:/another/path' => 'Run script from specified paths', 'drush php-script' => 'List all available scripts.', + '' => '', + "#!/usr/bin/env drush\n<?php\nvariable_set('key', drush_shift());" => "Execute php code with a full Drupal bootstrap directly from a shell script.", ), 'arguments' => array( 'filename' => 'Optional. The file you wish to execute (without extension). If omitted, list files ending in .php in the current working directory and specified script-path. Some might not be real drush scripts. Beware.', ), 'options' => array( - '--script-path' => "Additional paths to search for scripts. Use POSIX path separator (':') for multiple paths.", + '--script-path' => "Additional paths to search for scripts, separated by : (Unix-based systems) or ; (Windows).", ), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap. 'aliases' => array('scr'), 'deprecated-aliases' => array('script'), ); @@ -114,8 +124,8 @@ function core_drush_command() { '--exclude-files' => 'Exclude the files directory.', '--exclude-sites' => 'Exclude all directories in "sites/" except for "sites/all".', '--exclude-other-sites' => 'Exclude all directories in "sites/" except for "sites/all" and the site directory for the site being synced. Note: if the site directory is different between the source and destination, use --exclude-sites followed by "drush rsync @from:%site @to:%site"', - '--exclude-paths' => 'Coma-separated list of paths to exclude.', - '--include-paths' => 'Coma-separated list of paths to include.', + '--exclude-paths' => 'List of paths to exclude, seperated by : (Unix-based systems) or ; (Windows).', + '--include-paths' => 'List of paths to include, seperated by : (Unix-based systems) or ; (Windows).', ), 'examples' => array( 'drush rsync @dev @stage' => 'Rsync Drupal root from dev to stage (one of which must be local).', @@ -154,9 +164,9 @@ function core_drush_command() { 'examples' => array( 'drush site-install expert --locale=uk' => '(Re)install using the expert install profile. Set default language to Ukranian.', 'drush site-install --db-url=mysql://root:pass@localhost:port/dbname ' => 'Install using the specified DB params.', - 'drush site-install --account-user=joe --account-pass=mom' => 'Re-install with specified uid1 credentials.', + 'drush site-install --account-name=joe --account-pass=mom' => 'Re-install with specified uid1 credentials.', ), - 'core' => array(7), + 'core' => array(6,7), 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT, 'aliases' => array('si'), 'deprecated-aliases' => array('installsite', 'is'), @@ -209,6 +219,16 @@ function core_drush_command() { 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE, ); + // Topic commands + $items['core-readme'] = array( + 'description' => dt('README.txt'), + 'hidden' => TRUE, + 'topic' => TRUE, + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, + 'callback' => 'drush_print_file', + 'callback arguments' => array(DRUSH_BASE_PATH . '/README.txt'), + ); + return $items; } @@ -217,6 +237,7 @@ function core_drush_engine_drupal() { $engines['batch'] = array(); $engines['update'] = array(); $engines['environment'] = array(); + $engines['site_install'] = array(); return $engines; } @@ -238,22 +259,36 @@ function drush_core_updatedb() { } /** - * This is called if no command or an unknown command is entered. + * This is called for help command, or no command. */ function drush_core_help() { $commands = func_get_args(); - if (drush_get_option('html')) { - return drush_print(drush_help_html()); - } - elseif (empty($commands)) { - drush_show_help(array('help')); + + if (empty($commands)) { + // Build a fake command for the purposes of showing examples and options. + $fake = array( + 'description' => 'Execute a drush command. Run `drush help [command]` to view command-specific help.', + 'options' => drush_get_global_options(), + 'examples' => array( + 'drush dl cck zen' => 'Download CCK module and Zen theme.', + 'drush --uri=http://example.com status' => 'Show status command for the example.com multi-site.', + ), + ); + $fake += drush_command_defaults('fake', 'fake', __FILE__); + if (!drush_get_option('html')) { + drush_print_help($fake); + } + + if (!drush_get_option('html')) { + drush_print(dt('Commands: ')); + } + $phases = _drush_bootstrap_phases(); // For speed, only bootstrap up to DRUSH_BOOTSTRAP_DRUPAL_SITE+1. $phases = array_slice($phases, 0, DRUSH_BOOTSTRAP_DRUPAL_SITE+1); - drush_print(dt('Commands: ')); - $printed_rows = array(); + $printed_rows = $all_rows = array(); $phase_index = DRUSH_BOOTSTRAP_DRUSH; foreach ($phases as $phase_index) { @@ -280,12 +315,17 @@ function drush_core_help() { if (!array_key_exists($key, $printed_rows)) { $name = $command['aliases'] ? $key . ' (' . implode(', ', $command['aliases']) . ')': $key; $rows[$key] = array($name, $command['description']); + $all_rows[$key] = array($name, $command['description']); $pipe[] = "\"$key\""; } } } } - drush_print_table($rows, FALSE, array(0 => 20)); + + // These two options require delayed printing. + if (!drush_get_option('sort') && !drush_get_option('html')) { + drush_print_table($rows, FALSE, array(0 => 20)); + } $printed_rows = array_merge($printed_rows, $rows); } else { @@ -293,12 +333,27 @@ function drush_core_help() { } } + // We delayed printing until now. + ksort($all_rows); + if (drush_get_option('sort')) { + drush_print_table($all_rows, FALSE, array(0 => 20)); + } + elseif (drush_get_option('html')) { + return drush_print(drush_help_html($commands)); + } + // Newline-delimited list for use by other scripts. Set the --pipe option. - drush_print_pipe($pipe); + if (drush_get_option('pipe') && isset($pipe)) { + drush_print_pipe($pipe); + } return; } else { - return drush_show_help($commands); + $result = TRUE; + while ((count($commands) > 0) && !drush_get_error()) { + $result = drush_show_help(array_shift($commands)); + } + return $result; } drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => implode(" ", $commands)))); @@ -308,26 +363,18 @@ function drush_core_help() { /** * Return an HTML page documenting all available commands and global options. */ -function drush_help_html() { - foreach (drush_get_commands() as $key => $command) { - // Get rid of aliases in command list. - if (empty($command['is_alias']) && !$command['hidden']) { - $commands[$key] = $command; - } - } - unset($commands['help']); - +function drush_help_html($commands) { $output = "<html><head><title>drush help</title><style>dt {font-size: 110%; font-weight: bold}</style></head><body>\n"; // Command table - $output .= '<h3>Command list</h3><table>'; + $output .= "<h3>Command list</h3>\n<table>\n"; foreach ($commands as $key => $command) { - $output .= "<tr><td><a href=\"#$key\">$key</a></td><td>" . $command['description'] . "</td></tr>\n"; + $output .= " <tr><td><a href=\"#$key\">$key</a></td><td>" . $command['description'] . "</td></tr>\n"; } $output .= "</table>\n"; // Global options - $options = drush_get_option_help(); + $options = drush_get_global_options(); $output .= '<h3>Global Options</h3><table>'; foreach ($options as $key => $value) { $output .= "<tr><td>$key</td><td>" . $value . "</td></tr>\n"; @@ -339,7 +386,7 @@ function drush_help_html() { foreach ($commands as $key => $command) { $output .= "\n<a name=\"$key\"></a><dt>$key</dt><dd><pre>\n"; ob_start(); - drush_show_help(array($key)); + drush_show_help($key); $output .= ob_get_clean(); $output .= "</pre></dd>\n"; } @@ -363,14 +410,12 @@ function drush_help_html() { */ function core_drush_help($section) { switch ($section) { - case 'drush:help': - return dt('Execute a drush command. Run `drush help [command]` to view command-specific help.'); case 'drush:cron': return dt("Runs all cron hooks in all active modules for specified site."); case 'drush:status': return dt("View the Drupal version and DB credentials for the current site."); case 'drush:php-script': - return dt("Runs the given php script(s) after a full Drupal bootstrap. A useful alternative to eval command when your php is lengthy or you can't be bothered to figure out bash quoting. if you plan to share a script with others, consider making a full drush command instead since thats more self-documenting."); + return dt("Runs the given php script(s) after a full Drupal bootstrap. A useful alternative to eval command when your php is lengthy or you can't be bothered to figure out bash quoting. If you plan to share a script with others, consider making a full drush command instead, since that's more self-documenting. Drush provides commandline options to the script via drush_get_option('option-name'), and commandline arguments can be accessed either via drush_get_arguments(), which returns all arguments in an array, or drush_shift(), which removes the next argument from the list and returns it."); case 'drush:cache-clear': return dt("Delete a specific drupal cache, or all caches."); case 'drush:search-status': @@ -390,7 +435,7 @@ function core_drush_help($section) { case 'drush:drupal-directory': return dt("Return the filesystem path for projects/themes and other key folders. Used by `cli` command for handy commands `cdd` and `lsd`. If you want to use this command directly, you usually want to prefix the command with cd and enclose the command invocation in backticks. See Examples below."); case 'drush:core-cli': - return dt("Enter a new shell optimized for drush use. See help for more details."); + return dt("Enter a new shell optimized for drush use. All .bashrc customizations are still available. See help for more details."); case 'error:DRUSH_DRUPAL_DB_ERROR' : $message = dt("Drush was not able to start (bootstrap) the Drupal database.\n"); $message .= dt("Hint: This error often occurs when Drush is trying to bootstrap a site that has not been installed or does not have a configured database.\n"); @@ -438,11 +483,23 @@ function _core_path_aliases($project = '') { $paths['%root'] = $drupal_root; if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) { $paths['%site'] = $site_root; - $paths['%modules'] = 'sites'.DIRECTORY_SEPARATOR.'all'.DIRECTORY_SEPARATOR.'modules'; - $paths['%themes'] = 'sites'.DIRECTORY_SEPARATOR.'all'.DIRECTORY_SEPARATOR.'themes'; + if (is_dir($modules_path = conf_path() . '/modules')) { + $paths['%modules'] = $modules_path; + } + else { + $paths['%modules'] = 'sites/all/modules'; + } + if (is_dir($themes_path = conf_path() . '/themes')) { + $paths['%themes'] = $themes_path; + } + else { + $paths['%themes'] = 'sites/all/themes'; + } if (drush_drupal_major_version() >= 7) { - $paths['%files'] = DrupalPublicStreamWrapper::getDirectoryPath(); - $paths['%private'] = DrupalPrivateStreamWrapper::getDirectoryPath(); + if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) { + $paths['%files'] = DrupalPublicStreamWrapper::getDirectoryPath(); + $paths['%private'] = DrupalPrivateStreamWrapper::getDirectoryPath(); + } } elseif (function_exists('file_directory_path')) { $paths['%files'] = file_directory_path(); @@ -501,8 +558,10 @@ function _core_site_status_table($project = '') { // users figure out their php.ini issues. $status_table['PHP configuration'] = php_ini_loaded_file(); } + drush_sitealias_load_all(); $status_table['Drush version'] = DRUSH_VERSION; $status_table['Drush configuration'] = implode(' ', drush_get_context_options('context-path', TRUE)); + $status_table['Drush alias files'] = implode(' ', drush_get_context('drush-alias-files')); // None of the Status keys are in dt(); this helps with machine-parsing of status? $path_names['root'] = 'Drupal root'; @@ -554,12 +613,20 @@ function drush_core_cron() { } } +/** + * Bootstrap 'status' command during the init phase. + * Always use bootstrap_max from _init() so that command + * hooks have a chance to be initialized. + */ +function drush_core_status_init() { + drush_bootstrap_max(); +} + /** * Command callback. Provides a birds-eye view of the current Drupal * installation. */ function drush_core_status() { - drush_bootstrap_max(); $status_table = _core_site_status_table(drush_get_option('project','')); // If args are specified, filter out any entry that is not named // (in other words, only show lines named by one of the arg values) @@ -606,50 +673,82 @@ function _drush_core_is_named_in_array($key, $the_array) { } /** - * Command callback. Runs "naked" php scripts. + * Bootstrap 'php-script' command during the init phase. + * Always use bootstrap_max from _init() so that command + * hooks have a chance to be initialized. */ -function drush_core_php_script() { - $args = func_get_args(); - - // Array of paths to search for scripts - $searchpath['DIR'] = dirname(__FILE__); - $searchpath['cwd'] = drush_cwd(); +function drush_php_script_init() { + drush_bootstrap_max(); +} - // Additional script paths, specified by 'script-path' option - if ($script_path = drush_get_option('script-path', FALSE)) { - foreach (explode(":", $script_path) as $path) { - $searchpath[] = $path; - } +/** + * Command callback. Runs "naked" php scripts + * and drush "shebang" scripts ("#!/usr/bin/env drush"). + */ +function drush_core_php_script() { + $found = FALSE; + $script = NULL; + if ($args = func_get_args()) { + $script = $args[0]; } - - if (empty($args)) { - // List all available scripts. - $all = array(); - foreach($searchpath as $key => $path) { - $recurse = !$key == 'cwd'; - $all = array_merge( $all , array_keys(drush_scan_directory($path, '/\.php$/', array('.', '..', 'CVS'), NULL, $recurse)) ); - } - drush_print(implode("\n", $all)); + + if ($script == '-') { + eval(stream_get_contents(STDIN)); + } + elseif (file_exists($script)) { + $found = $script; } else { - // Execute the specified script. - $script = $args[0]; - if (!preg_match('/\.php$/i', $script)) { - $script .= '.php'; + // Array of paths to search for scripts + $searchpath['DIR'] = dirname(__FILE__); + $searchpath['cwd'] = drush_cwd(); + + // Additional script paths, specified by 'script-path' option + if ($script_path = drush_get_option('script-path', FALSE)) { + foreach (explode(PATH_SEPARATOR, $script_path) as $path) { + $searchpath[] = $path; + } } - $found = FALSE; - foreach($searchpath as $path) { - $script_filename = $path . '/' . $script; - if (file_exists($script_filename)) { - include($script_filename); - $found = TRUE; - break; + drush_log(dt('Searching for scripts in ') . implode(',', $searchpath), 'debug'); + + if (!isset($script)) { + // List all available scripts. + $all = array(); + foreach($searchpath as $key => $path) { + $recurse = !(($key == 'cwd') || ($path == '/')); + $all = array_merge( $all , array_keys(drush_scan_directory($path, '/\.php$/', array('.', '..', 'CVS'), NULL, $recurse)) ); } - $all[] = $script_filename; + drush_print(implode("\n", $all)); } - - if (!$found) { - drush_set_error('Script not found.', dt('Unable to find any of the following: @files', array('@files' => implode(', ', $all)))); + else { + // Execute the specified script. + foreach($searchpath as $path) { + $script_filename = $path . '/' . $script; + if (file_exists($script_filename . '.php')) { + $script_filename .= '.php'; + } + if (file_exists($script_filename)) { + $found = $script_filename; + break; + } + $all[] = $script_filename; + } + if (!$found) { + return drush_set_error('Script not found.', dt('Unable to find any of the following: @files', array('@files' => implode(', ', $all)))); + } + } + } + + if ($found) { + // Set the DRUSH_SHIFT_SKIP to two; this will cause + // drush_shift to skip the next two arguments the next + // time it is called. This allows scripts to get all + // arguments, including the 'php-script' and script + // pathname, via drush_get_arguments(), or it can process + // just the arguments that are relevant using drush_shift(). + drush_set_context('DRUSH_SHIFT_SKIP', 2); + if (_drush_core_eval_shebang_script($found) === FALSE) { + include($found); } } } @@ -658,6 +757,41 @@ function drush_core_php_eval($command) { eval($command . ';'); } +/* + * Evaluate a script that begins with #!drush php-script + */ +function _drush_core_eval_shebang_script($script_filename) { + $found = FALSE; + $fp = fopen($script_filename, "r"); + if ($fp !== FALSE) { + $line = fgets($fp); + if (_drush_is_drush_shebang_line($line)) { + $first_script_line = ''; + while ($line = fgets($fp)) { + $line = trim($line); + if ($line == '<?php') { + $found = TRUE; + break; + } + elseif (!empty($line)) { + $first_script_line = $line . "\n"; + break; + } + } + $script = stream_get_contents($fp); + // Pop off the first two arguments, the + // command (php-script) and the path to + // the script to execute, as a service + // to the script. + eval($first_script_line . $script); + $found = TRUE; + } + fclose($fp); + } + return $found; +} + + /** * Process sets from the specified batch. * @@ -729,9 +863,9 @@ function drush_core_drupal_directory($target = 'root') { function drush_core_find_project_path($target) { $theme_suffix = drush_drupal_major_version() >= 6 ? '.info' : '/style.css'; $masks = array( - conf_path() . '/modules' => "/^$target.module/", - 'profiles/default/modules' => "/^$target.module/", // Too early for variable_get('install_profile', 'default'); Just use default. - 'sites/all/modules' => "/^$target.module/", // Add all module paths, even disabled modules. + conf_path() . '/modules' => "/^$target\.module$/", + 'profiles/default/modules' => "/^$target\.module$/", // Too early for variable_get('install_profile', 'default'); Just use default. + 'sites/all/modules' => "/^$target\.module$/", // Add all module paths, even disabled modules. conf_path() . '/themes' => "/^$target" . "$theme_suffix/", 'sites/all/themes' => "/^$target" . "$theme_suffix/", ); @@ -748,11 +882,18 @@ function drush_core_find_project_path($target) { return NULL; } -function drush_core_cli() { +/** + * Bootstrap 'cli' command during the init phase. + * Always use bootstrap_max from _init() so that command + * hooks have a chance to be initialized. + */ +function drush_core_cli_init() { drush_bootstrap_max(); +} +function drush_core_cli() { // Do not allow cli to start recursively, or from backend invoke. - if (drush_get_option('in-cli',FALSE)) { + if (drush_get_option('in-cli', FALSE)) { return drush_set_error(dt('Already in drush core-cli; press control-d to exit.')); } if (drush_get_context('DRUSH_BACKEND')) { @@ -791,6 +932,13 @@ function core_cli_bashrc($drush_command) { // Set up our default bashrc file for the drush cli $bashrc_data = <<<EOD +# source the user's .bashrc file +[ -r ~/.bashrc ] && { + pushd ~ > /dev/null + . .bashrc + popd > /dev/null +} + PS1="drush> " alias on="$drush_command" diff --git a/sites/all/modules/drush/commands/core/drupal/batch_6.inc b/sites/all/modules/drush/commands/core/drupal/batch_6.inc index df64a72597d4fc1425c081d8af3359c66e6cf6d2..f3cccf1eb88e2bfeb0e9d09526663c8fdda2fa16 100644 --- a/sites/all/modules/drush/commands/core/drupal/batch_6.inc +++ b/sites/all/modules/drush/commands/core/drupal/batch_6.inc @@ -15,6 +15,7 @@ * */ function _drush_backend_batch_process($command = 'batch-process') { + global $user; $batch =& batch_get(); if (isset($batch)) { @@ -34,9 +35,14 @@ function _drush_backend_batch_process($command = 'batch-process') { $finished = FALSE; while (!$finished) { - $data = drush_backend_invoke($command, array($batch['id'])); + if ($user->uid) { + $data = drush_backend_invoke($command, array($batch['id'], '-u', $user->uid)); + } + else { + $data = drush_backend_invoke($command, array($batch['id'])); + } - $finished = drush_get_error() || !$data || ($data['context']['drush_batch_process_finished'] == TRUE); + $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE); } } } diff --git a/sites/all/modules/drush/commands/core/drupal/batch_7.inc b/sites/all/modules/drush/commands/core/drupal/batch_7.inc index 081bc3c3a79bd9727f6b5218b37feab0795235c7..1c0259d33f0d09dae2e9c280c669a20b0f1cfc8d 100644 --- a/sites/all/modules/drush/commands/core/drupal/batch_7.inc +++ b/sites/all/modules/drush/commands/core/drupal/batch_7.inc @@ -15,6 +15,7 @@ * */ function _drush_backend_batch_process($command = 'batch-process') { + global $user; $batch =& batch_get(); if (isset($batch)) { @@ -51,7 +52,12 @@ function _drush_backend_batch_process($command = 'batch-process') { $finished = FALSE; while (!$finished) { - $data = drush_backend_invoke($command, array($batch['id'])); + if ($user->uid) { + $data = drush_backend_invoke($command, array($batch['id'], '-u', $user->uid)); + } + else { + $data = drush_backend_invoke($command, array($batch['id'])); + } $finished = drush_get_error() || !$data || ($data['context']['drush_batch_process_finished'] == TRUE); } } diff --git a/sites/all/modules/drush/commands/core/drupal/environment_5.inc b/sites/all/modules/drush/commands/core/drupal/environment_5.inc index 145dcd5f99155d052ed523a7c828f98dbf7dfc98..0d5e6bf75efb0b9c8bfca59981234fa5d40fd6c8 100644 --- a/sites/all/modules/drush/commands/core/drupal/environment_5.inc +++ b/sites/all/modules/drush/commands/core/drupal/environment_5.inc @@ -1,5 +1,5 @@ <?php -// $Id: environment_5.inc,v 1.10 2010/04/06 13:07:19 weitzman Exp $ +// $Id: environment_5.inc,v 1.13 2010/11/10 02:55:41 weitzman Exp $ /** * @file * Specific functions for a drupal 5 environment. @@ -8,6 +8,15 @@ * is called from. */ +/** + * Return the list of modules required by drupal. + * + * We use that function name for onward compatibility with drupal 6 and 7. + */ +function drupal_required_modules() { + return array('block', 'filter', 'node', 'system', 'user', 'watchdog'); +} + /** * Get complete information for all available modules. * @@ -48,7 +57,7 @@ function drush_get_modules() { function drush_check_module_dependencies($modules, $module_info) { $status = array(); foreach ($modules as $key => $module) { - $dependencies = $module_info[$module]->info['dependencies']; + $dependencies = array_reverse($module_info[$module]->info['dependencies']); $unmet_dependencies = array_diff($dependencies, array_keys($module_info)); if (!empty($unmet_dependencies)) { $status[$key]['error'] = array( @@ -138,7 +147,7 @@ function drush_module_uninstall($modules) { * the form submit (such as admin_role) are completed. */ function drush_system_modules_form_submit($active_modules) { - require_once('./'. drupal_get_path('module', 'system') .'/system.module'); + require_once './'. drupal_get_path('module', 'system') .'/system.module'; $form_state = array('values' => array('status' => $active_modules)); drupal_execute('system_modules', $form_state); } diff --git a/sites/all/modules/drush/commands/core/drupal/environment_6.inc b/sites/all/modules/drush/commands/core/drupal/environment_6.inc index 50495886b50e9537a3fcc2c840e5a95684517ba1..b2b21e647c31be2e06f061a1d4b6212e285ed471 100644 --- a/sites/all/modules/drush/commands/core/drupal/environment_6.inc +++ b/sites/all/modules/drush/commands/core/drupal/environment_6.inc @@ -1,8 +1,8 @@ <?php -// $Id: environment_6.inc,v 1.9 2010/04/06 13:07:19 weitzman Exp $ +// $Id: environment_6.inc,v 1.11 2010/09/10 19:58:06 weitzman Exp $ /** * @file - * Specific functions for a drupal 5 environment. + * Specific functions for a drupal 6 environment. * drush_include_engine() magically includes either this file * or environment_X.inc depending on which version of drupal drush * is called from. @@ -41,7 +41,7 @@ function drush_get_modules() { function drush_check_module_dependencies($modules, $module_info) { $status = array(); foreach ($modules as $key => $module) { - $dependencies = $module_info[$module]->info['dependencies']; + $dependencies = array_reverse($module_info[$module]->info['dependencies']); $unmet_dependencies = array_diff($dependencies, array_keys($module_info)); if (!empty($unmet_dependencies)) { $status[$key]['error'] = array( diff --git a/sites/all/modules/drush/commands/core/drupal/environment_7.inc b/sites/all/modules/drush/commands/core/drupal/environment_7.inc index 8f16f95f1dec8fe8681becc5657666fc270b74e8..c0e22634ee54d6d5c5caa4f11fa0849ee5a3c821 100644 --- a/sites/all/modules/drush/commands/core/drupal/environment_7.inc +++ b/sites/all/modules/drush/commands/core/drupal/environment_7.inc @@ -1,8 +1,8 @@ <?php -// $Id: environment_7.inc,v 1.12 2010/03/17 20:33:25 weitzman Exp $ +// $Id: environment_7.inc,v 1.16 2010/10/15 09:04:37 jonhattan Exp $ /** * @file - * Specific functions for a drupal 5 environment. + * Specific functions for a drupal 7 environment. * drush_include_engine() magically includes either this file * or environment_X.inc depending on which version of drupal drush * is called from. @@ -12,7 +12,7 @@ * Get complete information for all available modules. * * @return - * An array containing module info for all installed modules. + * An array containing module info for all available modules. */ function drush_get_modules() { return system_rebuild_module_data(); @@ -31,7 +31,7 @@ function drush_get_modules() { function drush_check_module_dependencies($modules, $module_info) { $status = array(); foreach ($modules as $key => $module) { - $dependencies = $module_info[$module]->requires; + $dependencies = array_reverse($module_info[$module]->requires); $unmet_dependencies = array_diff(array_keys($dependencies), array_keys($module_info)); if (!empty($unmet_dependencies)) { $status[$key]['error'] = array( @@ -71,7 +71,7 @@ function drush_check_module_dependencies($modules, $module_info) { function drush_module_dependents($modules, $module_info) { $dependents = array(); foreach ($modules as $module) { - $dependents = array_merge($dependents, array_keys($module_info[$module]->required_by)); + $dependents = array_merge($dependents, drupal_map_assoc(array_keys($module_info[$module]->required_by))); } return array_unique($dependents); @@ -119,6 +119,10 @@ function drush_system_modules_form_submit($active_modules) { module_load_include('inc', 'system', 'system.admin'); $form_state = array('values' => array('status' => $active_modules)); drupal_form_submit('system_modules', $form_state); + // Because normally system_modules_submit would call this function if modules + // had been changed, in this case we are submitting the module form without + // any changes, so we need to clear caches manually. + drupal_flush_all_caches(); } /** diff --git a/sites/all/modules/drush/commands/core/drupal/site_install_6.inc b/sites/all/modules/drush/commands/core/drupal/site_install_6.inc new file mode 100644 index 0000000000000000000000000000000000000000..2bbd593f0706a2a922a6fc8bdb52bb563c1c4fe8 --- /dev/null +++ b/sites/all/modules/drush/commands/core/drupal/site_install_6.inc @@ -0,0 +1,67 @@ +<?php +// $Id: site_install_6.inc,v 1.1 2010/11/28 14:45:04 weitzman Exp $ + +/** + * Install Drupal 6.x + */ +function drush_core_site_install_version($profile) { + if (is_null($profile)) { + $profile = 'default'; + } + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + + $phpcode = _drush_site_install6_cookies($profile). ' include("'. $drupal_root .'/install.php");'; + drush_shell_exec('php -r %s', $phpcode); + $cli_output = drush_shell_exec_output(); + $cli_cookie = end($cli_output); + + $phpcode = _drush_site_install6_cookies($profile, $cli_cookie). ' $_GET["op"]="start"; include("'. $drupal_root .'/install.php");'; + drush_shell_exec('php -r %s', $phpcode); + + $phpcode = _drush_site_install6_cookies($profile, $cli_cookie). ' $_GET["op"]="do_nojs"; include("'. $drupal_root .'/install.php");'; + drush_shell_exec('php -r %s', $phpcode); + + $phpcode = _drush_site_install6_cookies($profile, $cli_cookie). ' $_GET["op"]="finished"; include("'. $drupal_root .'/install.php");'; + drush_shell_exec('php -r %s', $phpcode); + + $account_pass = drush_get_option('account-pass', 'admin'); + $phpcode = _drush_site_install6_cookies($profile, $cli_cookie); + $phpcode .= ' + $_POST = array ( + "site_name" => "'. drush_get_option('site-name', 'Site-Install') .'", + "site_mail" => "'. drush_get_option('site-mail', 'admin@example.com') .'", + "account" => array ( + "name" => "'. drush_get_option('account-name', 'admin') .'", + "mail" => "'. drush_get_option('account-mail', 'admin@example.com') .'", + "pass" => array ( + "pass1" => "'. $account_pass .'", + "pass2" => "'. $account_pass .'" + ) + ), + "date_default_timezone"=>"0", + "clean_url"=>'. drush_get_option('clean-url', TRUE) .', + "form_id"=>"install_configure_form", + "update_status_module" => array("1"=>"1") + ); + include("'. $drupal_root .'/install.php");'; + drush_shell_exec('php -r %s', $phpcode); +} + +/** + * Utility function to grab/set current "cli cookie". + * + */ +function _drush_site_install6_cookies($profile, $cookie = NULL) { + $output = '$_GET=array("profile"=>"' . $profile . '", "locale"=>"' . drush_get_option('locale', 'en') . '", "id"=>"1"); $_REQUEST=&$_GET;'; + $output .= 'define("DRUSH_SITE_INSTALL6", TRUE);$_SERVER["SERVER_SOFTWARE"] = NULL;'; + + if ($cookie) { + $output .= sprintf('$_COOKIE=unserialize("%s");', str_replace('"', '\"', $cookie)); + } + else { + $output .= 'function _cli_cookie_print(){print(serialize(array(session_name()=>session_id())));} +register_shutdown_function("_cli_cookie_print");'; + } + + return $output; +} \ No newline at end of file diff --git a/sites/all/modules/drush/commands/core/drupal/site_install_7.inc b/sites/all/modules/drush/commands/core/drupal/site_install_7.inc new file mode 100644 index 0000000000000000000000000000000000000000..799142bc9c5d31c3a7a009fd64f66a553f1c661f --- /dev/null +++ b/sites/all/modules/drush/commands/core/drupal/site_install_7.inc @@ -0,0 +1,48 @@ +<?php +// $Id: site_install_7.inc,v 1.2 2010/11/29 13:59:58 weitzman Exp $ + +/** + * Install Drupal 7+ + */ +function drush_core_site_install_version($profile) { + if (is_null($profile)) { + $profile = 'standard'; + } + + define('MAINTENANCE_MODE', 'install'); + require_once DRUPAL_ROOT . '/includes/install.core.inc'; + + $db_spec = drush_core_site_install_db_spec(); + + $account_pass = drush_get_option('account-pass', 'admin'); + $settings = array( + 'parameters' => array( + 'profile' => $profile, + 'locale' => drush_get_option('locale', 'en'), + ), + 'forms' => array( + 'install_settings_form' => array( + $db_spec['driver'] => $db_spec, + ), + 'install_configure_form' => array( + 'site_name' => drush_get_option('site-name', 'Site-Install'), + 'site_mail' => drush_get_option('site-mail', 'admin@example.com'), + 'account' => array( + 'name' => drush_get_option('account-name', 'admin'), + 'mail' => drush_get_option('account-mail', 'admin@example.com'), + 'pass' => array( + 'pass1' => $account_pass, + 'pass2' => $account_pass, + ), + ), + 'update_status_module' => array( + 1 => TRUE, + 2 => TRUE, + ), + 'clean_url' => drush_get_option('clean-url', TRUE), + ), + ), + ); + drush_log(dt('Starting Drupal installation. This takes a few seconds ...'), 'ok'); + install_drupal($settings); +} \ No newline at end of file diff --git a/sites/all/modules/drush/commands/core/drupal/update_5.inc b/sites/all/modules/drush/commands/core/drupal/update_5.inc index e925044a11736903871582b7f1272403bfbdd457..c422279dd89a4f8ffa407da6663011f91f98ef96 100644 --- a/sites/all/modules/drush/commands/core/drupal/update_5.inc +++ b/sites/all/modules/drush/commands/core/drupal/update_5.inc @@ -1,5 +1,5 @@ <?php -// $Id: update_5.inc,v 1.8 2009/12/06 12:53:38 weitzman Exp $ +// $Id: update_5.inc,v 1.9 2010/11/10 02:55:41 weitzman Exp $ /** * @file @@ -80,7 +80,7 @@ function update_main() { } } if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { - drush_die('Aborting.'); + drush_user_abort(); } $update_results = array(); diff --git a/sites/all/modules/drush/commands/core/drupal/update_6.inc b/sites/all/modules/drush/commands/core/drupal/update_6.inc index 21278b3d3784e77f0fc29d4d16cfe386a755f075..7914e4e2dd2b9e10fc810be415aabff0e582537f 100644 --- a/sites/all/modules/drush/commands/core/drupal/update_6.inc +++ b/sites/all/modules/drush/commands/core/drupal/update_6.inc @@ -1,5 +1,5 @@ <?php -// $Id: update_6.inc,v 1.17 2010/04/12 14:37:10 weitzman Exp $ +// $Id: update_6.inc,v 1.19 2010/12/01 15:53:15 jonhattan Exp $ /** * @file * Update.php for provisioned sites. @@ -335,7 +335,7 @@ function update_main_prepare() { foreach ($themes as $theme) { unset($theme->status); } - drush_get_projects(); + drush_get_extensions(); include_once './includes/batch.inc'; drupal_load_updates(); @@ -403,7 +403,7 @@ function update_main() { } } if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { - drush_die('Aborting.'); + drush_user_abort(); } // Proceed with running all pending updates. $operations = array(); diff --git a/sites/all/modules/drush/commands/core/drupal/update_7.inc b/sites/all/modules/drush/commands/core/drupal/update_7.inc index e7fb9052a7430e93fea36f806ee151161b2aa833..8b01cff45df57bb1a8f890ded565cbb5d3e7bcd0 100644 --- a/sites/all/modules/drush/commands/core/drupal/update_7.inc +++ b/sites/all/modules/drush/commands/core/drupal/update_7.inc @@ -1,5 +1,5 @@ <?php -// $Id: update_7.inc,v 1.26 2010/06/21 19:18:01 weitzman Exp $ +// $Id: update_7.inc,v 1.29 2010/11/10 02:55:41 weitzman Exp $ /** * @file * Update.php for provisioned sites. @@ -187,10 +187,6 @@ function update_main_prepare() { drush_errors_on(); - // Rescan and repopulate the system table to ensure we have a full picture - // of the platform. - drush_get_projects(); - include_once DRUPAL_ROOT . '/includes/batch.inc'; drupal_load_updates(); @@ -238,7 +234,7 @@ function update_main() { } if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { - drush_die('Aborting.'); + drush_user_abort(); } drush_update_batch($start); @@ -316,7 +312,6 @@ function drush_update_batch($start) { function drush_update_finished($success, $results, $operations) { - // Clear the caches in case the data has been updated. - drupal_flush_all_caches(); + // Nothing to do here. All caches already cleared. Kept as documentation of 'finished' callback. } diff --git a/sites/all/modules/drush/commands/core/field.drush.inc b/sites/all/modules/drush/commands/core/field.drush.inc new file mode 100644 index 0000000000000000000000000000000000000000..ec9d66ebff9bce2c1a685263fadcadd017f12d5c --- /dev/null +++ b/sites/all/modules/drush/commands/core/field.drush.inc @@ -0,0 +1,290 @@ +<?php +// $Id: field.drush.inc,v 1.7 2010/08/12 14:28:46 weitzman Exp $ + +/** + * @file + * Field API's drush integration + */ + +/** + * Implementation of hook_drush_help(). + */ +function field_drush_help($section) { + switch ($section) { + case 'drush:field-create': + return dt('Quickly create fields and instances for a given bundle.'); + case 'drush:field-update': + return dt('Return URL for editing field.'); + case 'drush:field-clone': + return dt('Clone a field and all of its instances.'); + case 'drush:field-info': + return dt('View information about fields, field_types, widgets, etc.'); + } +} + +/** + * Implementation of hook_drush_command(). + */ +function field_drush_command() { + $items['field-create'] = array( + 'description' => 'Create fields and instances. Returns urls for field editing.', + 'core' => array(7), + 'drupal_dependencies' => array('field_ui'), + 'arguments' => array( + 'bundle' => 'Content type (for nodes). Name of bundle to attach fields to. Required.', + 'field_spec' => 'Comma delimited triple in the form: field_name,field_type,widget_name. If widget_name is omitted, the default widget will be used. Separate multiple fields by space. If omitted, a wizard will prompt you.' + ), + 'options' => array( + 'entity_type' => 'Type of entity (e.g. node, user, comment). Defaults to node.', + ), + 'examples' => array( + 'drush field-create article' => 'Define new article fields via interactive prompts.', + 'open `drush field-create article`' => 'Define new article fields and then open field edit form for refinement.', + 'drush field-create article city,text,text_textfield subtitle,text,text_textfield' => 'Create two new fields.' + ), + ); + $items['field-update'] = array( + 'description' => 'Return URL for field editing web page.', + 'core' => array(7), + 'drupal_dependencies' => array('field_ui'), + 'arguments' => array( + 'field_name' => 'Name of field that needs updating.', + ), + 'examples' => array( + 'field-update comment_body' => 'Quickly navigate to a field edit web page.', + ), + ); + $items['field-delete'] = array( + 'description' => 'Delete a field and its instances.', + 'core' => array(7), + 'arguments' => array( + 'field_name' => 'Name of field to delete.', + ), + 'options' => array( + 'bundle' => 'Only delete the instance attached to this bundle. If omitted, admin can choose to delete one instance or whole field.', + 'entity_type' => 'Disambiguate a particular bundle from identically named bundles. Usually not needed.' + ), + 'examples' => array( + 'field-delete city' => 'Delete the city field and any instances it might have.', + 'field-delete city --bundle=article' => 'Delete the city instance on the article bundle', + ), + ); + $items['field-clone'] = array( + 'description' => 'Clone a field and all its instances.', + 'core' => array(7), + 'arguments' => array( + 'source_field_name' => 'Name of field that will be cloned', + 'target_field_name' => 'Name of new, cloned field.', + ), + 'examples' => array( + 'field-clone tags labels' => 'Copy \'tags\' field into a new field \'labels\' field which has same instances.', + 'open `field-clone tags labels`' => 'Clone field and then open field edit forms for refinement.', + ), + ); + $items['field-info'] = array( + 'description' => 'View information about fields, field_types, and widgets.', + 'drupal_dependencies' => array('field_ui'), + 'core' => array(7), + 'arguments' => array( + 'type' => 'Recognized values: fields, types. If omitted, a choice list appears.', + ), + 'examples' => array( + 'field-info types' => 'Show a table which lists all field types and their available widgets', + ), + ); + return $items; +} + +function drush_field_create($bundle) { + $entity_type = drush_get_option('entity_type', 'node'); + + $args = func_get_args(); + array_shift($args); + if (empty($args)) { + // Just one item in this array for now. + $args[] = drush_field_create_wizard(); + } + + // Iterate over each field spec. + foreach ($args as $string) { + list($name, $type, $widget) = explode(',', $string); + $info = field_info_field($field_name); + if (empty($info)) { + // Field does not exist already. Create it. + $field = array( + 'field_name' => $name, + 'type' => $type, + ); + drush_op('field_create_field', $field); + } + + // Create the instance. + $instance = array( + 'field_name' => $name, + 'entity_type' => $entity_type, + 'bundle' => $bundle, + ); + if ($widget) { + $instance['widget'] = array('type' => $widget); + } + drush_op('field_create_instance', $instance); + + $urls[] = url(_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $name, array('absolute' => TRUE)); + } + drush_print(implode(' ', $urls)); +} + +function drush_field_update($field_name) { + $info = field_info_field($field_name); + foreach ($info['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $urls[] = url(_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $field_name, array('absolute' => TRUE)); + } + } + drush_print(implode(' ', $urls)); +} + +function drush_field_delete($field_name) { + $info = field_info_field($field_name); + $confirm = TRUE; + + if (!$bundle = drush_get_option('bundle')) { + foreach ($info['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $all_bundles[] = $bundle; + } + } + if (count($bundles) > 1) { + $options = array_merge(array('all' => dt('All bundles')), drupal_map_assoc($bundles)); + $bundle = drush_choice($options, dt("Choose a particular bundle or 'All bundles'")); + $confirm = FALSE; + } + else { + if (!drush_confirm(dt('Do you want to delete the %field_name field?', array('%field_name' => $field_name)))) { + return drush_log(dt('Aborting.')); + } + } + } + + if ($bundle == 'all') { + foreach ($info['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $instance = field_info_instance($entity_type, $field_name, $bundle); + drush_op('field_delete_instance', $instance); + } + } + } + else { + $entity_type = drush_field_get_entity_from_bundle($bundle); + $instance = field_info_instance($entity_type, $field_name, $bundle); + drush_op('field_delete_instance', $instance); + } + + // If there are no more bundles, delete the field. + $info = field_info_field($field_name); + if (empty($info['bundles'])) { + drush_op('field_delete_field', $field_name); + } +} + +function drush_field_clone($source_field_name, $target_field_name) { + if (!$info = field_info_field($source_field_name)) { + return drush_set_error(dt('%source not found in field list.', array('%source' => $source_field_name))); + } + + unset($info['id']); + $info['field_name'] = $target_field_name; + $target = drush_op('field_create_field', $info); + + foreach ($info['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $instance = field_info_instance($entity_type, $source_field_name, $bundle); + $instance['field_name'] = $target_field_name; + unset($instance['id']); + $instance['field_id'] = $target['id']; + drush_op('field_create_instance', $instance); + $urls[] = url(_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $target_field_name, array('absolute' => TRUE)); + } + } + + drush_print(implode(' ', $urls)); +} + +function drush_field_info($type = NULL) { + if (is_null($type)) { + $type = drush_choice(drupal_map_assoc(array('types', 'fields')), dt('Which information do you wish to see?')); + } + + switch ($type) { + case 'fields': + $rows[] = array( + dt('Field name'), + dt('Field type'), + dt('Bundles'), + ); + $info = field_info_fields(); + foreach ($info as $field_name => $field) { + $bundle_strs = array(); + foreach ($field['bundles'] as $entity_type => $bundles) { + $bundle_strs[] = implode(',', $bundles); + } + $rows[] = array( + $field_name, + $field['type'], + implode(' ', $bundle_strs), + ); + } + break; + case 'types': + $rows[] = array( + dt('Field type'), + dt('Default widget'), + dt('Widgets'), + ); + $info = field_info_field_types(); + module_load_include('inc', 'field_ui', 'field_ui.admin'); + $widgets = field_info_widget_types(); + foreach ($info as $type_name => $type) { + $widgets = field_ui_widget_type_options($type_name); + $rows[] = array( + $type_name, + $type['default_widget'], + implode(', ', array_keys($widgets)), + ); + } + break; + } + + drush_print_table($rows, TRUE); +} + +/** + * Prompt user enough to create basic field and instance. + * + * @return array $field_spec + * An array of brief field specifications. + */ +function drush_field_create_wizard() { + $specs[] = drush_prompt(dt('Field name')); + module_load_include('inc', 'field_ui', 'field_ui.admin'); + $types = field_ui_field_type_options(); + $field_type = drush_choice($types, dt('Choose a field type')); + $specs[] = $field_type; + $widgets = field_ui_widget_type_options($field_type); + $specs[] = drush_choice($widgets, dt('Choose a widget')); + return implode(',', $specs); +} + +function drush_field_get_entity_from_bundle($bundle) { + if (drush_get_option('entity_type')) { + return drush_get_option('entity_type'); + } + else { + $info = field_info_bundles(); + foreach ($info as $entity_type => $bundles) { + if (isset($bundles[$bundle])) { + return $entity_type; + } + } + } +} \ No newline at end of file diff --git a/sites/all/modules/drush/commands/core/rsync.core.inc b/sites/all/modules/drush/commands/core/rsync.core.inc index 6f07887351c30940827726258ce0c73f7eb43c01..2eba134fa305a4d942208e4af56d05007e714c8b 100644 --- a/sites/all/modules/drush/commands/core/rsync.core.inc +++ b/sites/all/modules/drush/commands/core/rsync.core.inc @@ -1,5 +1,5 @@ <?php -// $Id: rsync.core.inc,v 1.2 2010/06/11 19:17:42 greg1anderson Exp $ +// $Id: rsync.core.inc,v 1.7 2010/11/21 15:10:56 greg1anderson Exp $ /** * Entrypoint for drush rsync. @@ -47,7 +47,7 @@ function drush_core_rsync($source, $destination, $additional_options = array()) drush_print(dt("You will destroy data from !target and replace with data from !source", array('!source' => $source_path, '!target' => $destination_path))); if (!drush_confirm(dt('Do you really want to continue?'))) { // was: return drush_set_error('CORE_SYNC_ABORT', 'Aborting.'); - drush_die('Aborting.'); + drush_user_abort(); } } @@ -93,7 +93,7 @@ function drush_core_call_rsync($source, $destination, $additional_options = arra foreach (array('include', 'exclude') as $include_exclude) { // Get the option --include-path or --exclude path and explode to an array of paths // that we will translate into an --include or --exclude option to pass to rsync - $inc_ex_path = explode(',', drush_get_option($include_exclude . '-path', '')); + $inc_ex_path = explode(PATH_SEPARATOR, drush_get_option(array($include_exclude . '-path', $include_exclude . '-paths'), '')); foreach ($inc_ex_path as $one_path_to_inc_ex) { if (!empty($one_path_to_inc_ex)) { $options .= ' --' . $include_exclude . '="' . $one_path_to_inc_ex . '"'; @@ -191,7 +191,10 @@ function drush_core_call_rsync($source, $destination, $additional_options = arra 'skip-compress', 'filter', 'exclude', + 'exclude-from', 'include', + 'include-from', + 'files-from', 'address', 'port', 'sockopts', @@ -209,9 +212,22 @@ function drush_core_call_rsync($source, $destination, $additional_options = arra 'out-format', 'protocol', ); + // Check if the user has set $options['rsync-version'] to enable rsync legacy version support. + // Drush was written for rsync 2.6.9 or later, so assume that version if nothing was explicitly set. + $rsync_version = drush_get_option(array('rsync-version','source-rsync-version','target-rsync-version'), '2.6.9'); foreach ($rsync_available_options as $test_option) { $value = drush_get_option_override($additional_options, $test_option); - if (isset($value)) { + // Downgrade some options for older versions of rsync + if ($test_option == 'remove-source-files') { + if (version_compare($rsync_version, '2.6.4', '<')) { + $test_option = NULL; + drush_log('Rsync does not support --remove-sent-files prior to version 2.6.4; some temporary files may remain undeleted.', 'warning'); + } + elseif (version_compare($rsync_version, '2.6.9', '<')) { + $test_option = 'remove-sent-files'; + } + } + if ((isset($test_option)) && (isset($value))) { if ($value === TRUE) { $options .= " --$test_option"; } diff --git a/sites/all/modules/drush/commands/core/scratch.php b/sites/all/modules/drush/commands/core/scratch.php index 986f29cc3a3478f0085db5b29168c12e75a9f088..afd413a54bad467e04c1f36f3d76ae156e897ecb 100644 --- a/sites/all/modules/drush/commands/core/scratch.php +++ b/sites/all/modules/drush/commands/core/scratch.php @@ -10,7 +10,7 @@ * using `drush script scratch.php`. That command will bootstrap your drupal * site and then run the php below. * - * The script command enables to store your script files wherever you wish and + * The script command enables you to store your script files wherever you wish and * will help you list all of them should you collection grow. See its help. * */ diff --git a/sites/all/modules/drush/commands/core/site_install.drush.inc b/sites/all/modules/drush/commands/core/site_install.drush.inc index 06a7561c3dbded807bce4fa6f6af8eee323c9f43..379915dda2d34efd1565d8e9d72111cc2961785a 100644 --- a/sites/all/modules/drush/commands/core/site_install.drush.inc +++ b/sites/all/modules/drush/commands/core/site_install.drush.inc @@ -1,5 +1,5 @@ <?php -// $Id: site_install.drush.inc,v 1.4 2010/06/21 18:55:08 weitzman Exp $ +// $Id: site_install.drush.inc,v 1.12 2010/11/28 14:45:04 weitzman Exp $ // Perform setup tasks for installation. function drush_core_pre_site_install() { @@ -9,8 +9,15 @@ function drush_core_pre_site_install() { return; } - // TODO: not needed? - $sites_subdir = drush_get_option('sites-subdir', 'default'); + + if ($sites_subdir = drush_get_option('sites-subdir')) { + // Needed so that we later bootstrap into the right site. + drush_set_option('uri', $sites_subdir); + } + else { + $sites_subdir = 'default'; + } + $conf_path = "sites/$sites_subdir"; $files = "$conf_path/files"; $settingsfile = "$conf_path/settings.php"; @@ -41,9 +48,12 @@ function drush_core_pre_site_install() { drush_set_error(dt('Failed to copy sites/default/default.settings.php to @settingsfile', array('@settingsfile' => $settingsfile))); return; } + elseif (drush_drupal_major_version() == 6) { + // On D6, we have to write $db_url ourselves. On D7+, the installer does it. + file_put_contents($settingsfile, "\n" . '$db_url = \'' . drush_get_option('db-url') . "';\n", FILE_APPEND); + } } - // Add a files dir if needed if (!file_exists($files)) { if (!drush_op('mkdir', $files) && !drush_get_context('DRUSH_SIMULATE')) { @@ -58,61 +68,61 @@ function drush_core_pre_site_install() { return; // Drop and create DB if needed. - // TODO: support db-su like sql sync. + // @TODO: support db-su like sql sync. + // Can't use drush_sql_query() since might not have a DB. - $exec = 'mysql ' . _drush_sql_get_credentials($db_spec); - // Strip DB name from credentials. Soon it won't exist anymore. We do - // need a DB name to connect to so use built-in mysql DB. - $replacement_db = 'information_schema'; - // Make sure we are only replacing the database name, - // and not a username or password that is the same as the database name. - $exec = str_replace(" {$db_spec['database']}", " {$replacement_db}", $exec) . ' -e '; - if (drush_op('system', $exec . ' "DROP DATABASE IF EXISTS ' . $db_spec['database'] . '"') && !drush_get_context('DRUSH_SIMULATE')) { - drush_set_error(dt('Could not drop database: @name', array('@name' => $db_spec['database']))); + // Get credentials to connect to the server, but not the database which we + // are about to DROP. + + // Save the database name before we unset() it. + $db_name = $db_spec['database']; + $scheme = _drush_sql_get_scheme($db_spec); + $simulate = drush_get_context('DRUSH_SIMULATE'); + + if ($scheme === 'sqlite') { + // With SQLite, we don't DROP DATABASEs. Each database is in a single file, + // so we just remove the file. We also don't CREATE DATABASEs; it is created + // when SQLite attempts to open a database file which doesn't exist. + if (file_exists($db_spec['database']) && !$simulate) { + if (!unlink($db_spec['database'])) { + drush_set_error(dt('Could not drop database: @name', array('@name' => $db_name))); + } + } + // Nothing else to do for SQLite installations, so return. + return; + } + // _drush_sql_get_credentials() will set a default database according to + // the scheme if one is not set. + unset($db_spec['database']); + $credentials = _drush_sql_get_credentials($db_spec); + + $scheme_command = $scheme; + switch ($scheme) { + case 'mysql': + $command_parameter_name = 'execute'; + break; + case 'pgsql': + $scheme_command = 'psql'; + $command_parameter_name = 'command'; + break; + } + $execute = "$scheme_command $credentials --$command_parameter_name"; + + if (drush_op('system', "$execute='DROP DATABASE IF EXISTS `$db_name`'") && !$simulate) { + drush_set_error(dt('Could not drop database: @name', array('@name' => $db_name))); return; } - if (drush_op('system', $exec . '"CREATE DATABASE ' . $db_spec['database'] . '"') && !drush_get_context('DRUSH_SIMULATE')) { - drush_set_error(dt('Could not create new database: @name', array('@name' => $db_spec['database']))); + if (drush_op('system', "$execute='CREATE DATABASE `$db_name`'") && !$simulate) { + drush_set_error(dt('Could not create new database: @name', array('@name' => $db_name))); return; } } -function drush_core_site_install($profile = 'standard') { - define('MAINTENANCE_MODE', 'install'); - require_once DRUPAL_ROOT . '/includes/install.core.inc'; - - $db_spec = drush_core_site_install_db_spec(); - - $account_pass = drush_get_option('account-pass', 'admin'); - $settings = array( - 'parameters' => array( - 'profile' => $profile, - 'locale' => drush_get_option('locale', 'en'), - ), - 'forms' => array( - 'install_settings_form' => $db_spec, - 'install_configure_form' => array( - 'site_name' => drush_get_option('site-name', 'Site-Install'), - 'site_mail' => drush_get_option('site-mail', 'admin@example.com'), - 'account' => array( - 'name' => drush_get_option('account-name', 'admin'), - 'mail' => drush_get_option('account-mail', 'admin@example.com'), - 'pass' => array( - 'pass1' => $account_pass, - 'pass2' => $account_pass, - ), - ), - 'update_status_module' => array( - 1 => TRUE, - 2 => TRUE, - ), - 'clean_url' => drush_get_option('clean-url', TRUE), - ), - ), - ); - drush_log(dt('Starting Drupal installation. This takes 30 seconds or so ...'), 'ok'); - install_drupal($settings); +// Command callback. +function drush_core_site_install($profile = NULL) { + drush_include_engine('drupal', 'site_install', drush_drupal_major_version()); + drush_core_site_install_version($profile); } // Return a db_spec based on supplied db_url/db_prefix options or diff --git a/sites/all/modules/drush/commands/core/sitealias.drush.inc b/sites/all/modules/drush/commands/core/sitealias.drush.inc index a7d2e9d21de4fadd8d7d21ae70237681c98b064b..a83fd0bafd9c233391e9b80925d94e4d12426575 100644 --- a/sites/all/modules/drush/commands/core/sitealias.drush.inc +++ b/sites/all/modules/drush/commands/core/sitealias.drush.inc @@ -1,5 +1,5 @@ <?php -// $Id: sitealias.drush.inc,v 1.22 2010/04/22 17:57:40 weitzman Exp $ +// $Id: sitealias.drush.inc,v 1.23 2010/11/28 04:50:54 greg1anderson Exp $ /** * @file @@ -103,6 +103,13 @@ function _drush_sitealias_user_specified_list() { // If the user provided no args, then we will return everything. else { $site_list = _drush_sitealias_all_list(); + + // Filter out the hidden items + foreach ($site_list as $site_name => $one_site) { + if (array_key_exists('#hidden', $one_site)) { + unset($site_list[$site_name]); + } + } } return $site_list; diff --git a/sites/all/modules/drush/commands/core/test.drush.inc b/sites/all/modules/drush/commands/core/test.drush.inc new file mode 100644 index 0000000000000000000000000000000000000000..9aa5c72f1d09e7e6f9e6c5739e41640e7a2e5f26 --- /dev/null +++ b/sites/all/modules/drush/commands/core/test.drush.inc @@ -0,0 +1,193 @@ +<?php +// $Id: test.drush.inc,v 1.12 2010/11/27 21:20:02 weitzman Exp $ + +/** + * @file + * Simpletest module drush integration. + */ + +/** + * Implementation of hook_drush_command(). + */ +function test_drush_command() { + $items = array(); + + $items['test-run'] = array( + 'description' => "Run tests. Note that you must use the --uri option.", + 'arguments' => array( + 'targets' => 'A test class, a test group. If omitted, a list of test classes and test groups is presented. Delimit multiple targets using commas.', + ), + 'examples' => array( + 'test-run' => 'List all available classes and groups.', + 'sudo -u apache test-run --all' => 'Run all available tests. Avoid permission related failures by running as web server user.', + 'test-run XMLRPCBasicTestCase' => 'Run one test class.', + 'test-run XML-RPC' => 'Run all classes in a XML-RPC group.', + 'test-run XML-RPC,Filter' => 'Run all tests from multiple groups/classes.', + 'test-run XMLRPCBasicTestCase --methods="testListMethods, testInvalidMessageParsing"' => 'Run particular methods in the specified class or group.', + ), + 'options' => array( + 'all' => 'Run all available tests', + 'methods' => 'A comma delimited list of methods that should be run within the test class. Defaults to all methods.', + 'dirty' => 'Skip cleanup of temporary tables and files. Helpful for reading debug() messages and other post-mortem forensics.' + ), + 'drupal dependencies' => array('simpletest'), + // If you login, you fall victim to http://drupal.org/node/974768. We use a low + // bootstrap level here to avoid confusing the testing versus tested side. Causes some + // poorly written core drupal simpletests to fail. + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE, + ); + $items['test-clean'] = array( + 'description' => "Clean temporary tables and files.", + 'drupal dependencies' => array('simpletest'), + ); + + return $items; +} + +// Command callback +function drush_test_clean() { + return simpletest_clean_environment(); +} + +// Validate hook +function drush_test_run_validate($specs = NULL) { + if (!drush_get_option('uri')) { + return drush_set_error(dt("You must specify this site's URL using the --uri parameter.")); + } +} + +/** + * Test-run command callback. + * + * @specs + * A comman delimited string of test classes or group names. + */ +function drush_test_run($specs = NULL) { + cache_clear_all('simpletest', 'cache'); + + // Retrieve all tests and groups. + list($groups, $all_tests) = drush_test_get_all_tests(); + + if (drush_get_option('all')) { + // Run all tests. + foreach (array_keys($groups) as $group) { + foreach (array_keys($groups[$group]) as $class) { + drush_backend_invoke_args('test-run', array($class), array('--no-all')); + } + } + return; + } + elseif (empty($specs)) { + return drush_test_list($groups); + } + + foreach (explode(',', $specs) as $spec) { + $spec = trim($spec); + // Specific test class specified. + if (in_array($spec, $all_tests)) { + simpletest_drush_run_test($spec); + if (!drush_get_option('dirty')) { + simpletest_clean_environment(); + } + return; + } + // Specific group specified. + else if (isset($groups[$spec])) { + foreach (array_keys($groups[$spec]) as $class) { + drush_do_command_redispatch('test-run', array($class)); + } + return; + } + } +} + +/** + * Run a single test and display any failure messages. + */ +function simpletest_drush_run_test($class) { + if (drush_drupal_major_version() >= 7) { + $test_id = db_insert('simpletest_test_id') + ->useDefaults(array('test_id')) + ->execute(); + } + else { + db_query('INSERT INTO {simpletest_test_id} (test_id) VALUES (default)'); + $test_id = db_last_insert_id('simpletest_test_id', 'test_id'); + } + $test = new $class($test_id); + + if ($methods_string = drush_get_option('methods')) { + foreach (explode(',', $methods_string) as $method) { + $methods[] = trim($method); + } + $test->run($methods); + } + else { + $test->run(); + } + + $info = $test->getInfo(); + $status = ((isset($test->results['#fail']) && $test->results['#fail'] > 0) + || (isset($test->results['#exception']) && $test->results['#exception'] > 0) ? 'error' : 'ok'); + drush_log($info['name'] . ' ' . _simpletest_format_summary_line($test->results), $status); + + // If there were some failed tests show them. + if ($status === 'error') { + if (drush_drupal_major_version() >= 7) { + $args = array(':test_id' => $test_id); + $result = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id AND status IN ('exception', 'fail') ORDER BY test_class, message_id", $args); + foreach($result as $record) { + drush_set_error('DRUSH_TEST_FAIL', dt("Test !function failed: !message", array('!function' => $record->function, '!message' => $record->message))); + } + } + else { + $result = db_query("SELECT * FROM {simpletest} WHERE test_id = %d AND status IN ('exception', 'fail') ORDER BY test_class, message_id", $test_id); + while ($row = db_fetch_object($result)) { + drush_set_error('DRUSH_TEST_FAIL', dt("Test !function failed: !message", array('!function' => $row->function, '!message' => $row->message))); + } + } + } +} + +/** + * Retrieve all test groups and sanitize their names to make them command-line + * friendly. + */ +function simpletest_drush_test_groups($tests) { + $groups = array(); + foreach (simpletest_categorize_tests($tests) as $name => $group) { + $sanitized = strtr($name, array(' ' => '')); + $groups[$sanitized] = $group; + } + return $groups; +} + +// Print a listing of all available tests +function drush_test_list($groups) { + $rows[] = array(dt('Command'), dt('Description')); + $rows[] = array('-------', '-----------'); + foreach ($groups as $group_name => $group_tests) { + foreach ($group_tests as $test_class => $test_info) { + if (!isset($rows[$test_info['group']])) { + $rows[$test_info['group']] = array($group_name, $test_info['group']); + } + $rows[] = array(" {$test_class}", " {$test_info['name']}"); + } + } + return drush_print_table($rows, TRUE); +} + +function drush_test_get_all_tests() { + if (function_exists('simpletest_get_all_tests')) { + $all_tests = simpletest_get_all_tests(); + $groups = simpletest_drush_test_groups($all_tests); + } + else { + $groups = simpletest_test_get_all(); + $all_tests = array(); + foreach ($groups as $group) { + $all_tests = array_merge($all_tests, array_keys($group)); + } + } + return array($groups, $all_tests); +} diff --git a/sites/all/modules/drush/commands/core/topic.drush.inc b/sites/all/modules/drush/commands/core/topic.drush.inc new file mode 100644 index 0000000000000000000000000000000000000000..51686bc2cd355c0aeada08eeb2302a703631b11f --- /dev/null +++ b/sites/all/modules/drush/commands/core/topic.drush.inc @@ -0,0 +1,75 @@ +<?php +// $Id: topic.drush.inc,v 1.4 2010/10/06 21:00:26 weitzman Exp $ + +/** + * @file + * Topic command and associated hooks. + */ + +/** + * Implementation of hook_drush_command(). + * + * @return + * An associative array describing your command(s). + */ +function topic_drush_command() { + $items['core-topic'] = array( + 'description' => 'Read detailed documentation on a given topic.', + 'arguments' => array( + 'topic name' => 'The name of the topic you wish to view. If omitted, list all topic descriptions (and names in parenthesis).', + ), + 'examples' => array( + 'drush topic' => 'Show all available topics.', + 'drush topic core-context' => 'Show documentation for the drush context API', + 'drush core-context' => 'Show documentation for the drush context API', + ), + 'aliases' => array('topic'), + ); + return $items; +} + +/** + * Implement hook_drush_help_alter(). Show 'Topics' section on help detail. + */ +function topic_drush_help_alter($command) { + $implemented = drush_get_commands(); + foreach ($command['topics'] as $topic_name) { + // We have a related topic. Inject into the $command so the topic displays. + $command['sections']['topic_section'] = dt('Topics'); + $command['topic_section'][$topic_name] = dt($implemented[$topic_name]['description']); + } +} +/** + * A command callback. + * + * Show a choice list of available topics and then dispatch to the respective command. + * + * @param string $topic_name + * A command name. + */ +function drush_core_topic($topic_name = NULL) { + $commands = drush_get_commands(); + if (is_null($topic_name)) { + // Show choice list. + foreach (drush_get_topics() as $key => $topic) { + $choices[$key] = $topic['description']; + } + if (!$topic_name = drush_choice($choices, dt('Choose a topic'), '!value (!key)')) { + return; + } + } + return drush_do_command_redispatch($topic_name); +} + +/** + * Retrieve all defined topics + */ +function drush_get_topics() { + $commands = drush_get_commands(); + foreach ($commands as $key => $command) { + if (!empty($command['topic']) && empty($command['is_alias'])) { + $topics[$key] = $command; + } + } + return $topics; +} \ No newline at end of file diff --git a/sites/all/modules/drush/commands/core/upgrade.drush.inc b/sites/all/modules/drush/commands/core/upgrade.drush.inc index 720f3f5165ceda18c500f30feb80facf7bb923c0..69b16522330191b5754c93bc588ed230fa294142 100644 --- a/sites/all/modules/drush/commands/core/upgrade.drush.inc +++ b/sites/all/modules/drush/commands/core/upgrade.drush.inc @@ -1,13 +1,11 @@ <?php -// $Id: upgrade.drush.inc,v 1.17 2010/04/29 19:05:37 weitzman Exp $ - -/* TODO - * - upgrade to specific releases. -/* +// $Id: upgrade.drush.inc,v 1.21 2010/11/04 17:27:32 weitzman Exp $ /** * @file * Refine your Drupal major version upgrade. + * + * @todo Upgrade to specific releases. */ /** @@ -21,7 +19,7 @@ function upgrade_drush_command() { 'drupal dependencies' => array('update'), 'core' => array(6), // Remove once 3.0 is released. 'arguments' => array( - 'target' => 'The name of a sitealias, which points to the destination site. root, uri, and db-url keys are required. See examples/aliases.drushrc.php for more information.'), + 'target' => 'The name of a sitealias, which points to the destination site. root, uri, and db-url keys are required. See examples/aliases.drushrc.php for more information about creating a site alias.'), 'examples' => array( 'drush site-upgrade @onward' => 'Upgrade from the current site to the site specified by @onward alias.' ), @@ -42,7 +40,7 @@ function upgrade_drush_command() { function upgrade_drush_help($section) { switch ($section) { case 'drush:site-upgrade': - return dt("Execute a major version upgrade for Drupal core and enabled contrib modules. Command will download next version of Drupal and all available contrib modules that have releases (if not already downloaded). It prepares a settings.php for the target site, and copies the prior version's database to the target site. Finally, updatedb is run. The intent is for developers to keep re-running this command until they are satisfied with the resulting site."); + return dt("Execute a major version upgrade for Drupal core and enabled contrib modules. Command will download next version of Drupal and all available contrib modules that have releases (if not already downloaded). It prepares a settings.php for the target site, and copies the prior version's database to the target site. Finally, updatedb is run. The intent is for developers to keep re-running this command until they are satisfied with the resulting site. Run this command from within your source site (D6)."); } } @@ -110,9 +108,10 @@ function drush_upgrade_site_upgrade($target_key) { // Create sites subdirectory in target if needed. $settings_source = conf_path() . '/settings.php'; $settings_destination = $destination_core . '/' . $settings_source; - if (!file_exists(dirname($settings_destination))) { - if (!drush_op('mkdir', $settings_destination) && !drush_get_context('DRUSH_SIMULATE')) { - drush_set_error(dt('Failed to create directory @settings_destination', array('@settings_destination' => $settings_destination))); + $settings_destination_folder = dirname($settings_destination); + if (!file_exists($settings_destination_folder)) { + if (!drush_op('mkdir', $settings_destination_folder) && !drush_get_context('DRUSH_SIMULATE')) { + drush_set_error(dt('Failed to create directory @settings_destination', array('@settings_destination' => $settings_destination_folder))); return; } } @@ -136,7 +135,7 @@ function drush_upgrade_site_upgrade($target_key) { // Always blow away the target database so we start fresh. drush_set_option('create-db', TRUE); drush_include(DRUSH_BASE_PATH . '/commands/sql', 'sync.sql'); - drush_sql_sync('@self', $target_key); + drush_invoke('sql_sync', '@self', $target_key); if (drush_get_error()) return -1; // Early exit if we see an error. // Run update.php in a subshell. It is run on @target site whereas this request was on @self. diff --git a/sites/all/modules/drush/commands/core/variable.drush.inc b/sites/all/modules/drush/commands/core/variable.drush.inc index 5c2647e1d3a95ebbe76d679478a07f577b7cce1e..efe8a065d61ba77f5e27e54c4872ee96521111eb 100644 --- a/sites/all/modules/drush/commands/core/variable.drush.inc +++ b/sites/all/modules/drush/commands/core/variable.drush.inc @@ -1,5 +1,5 @@ <?php -// $Id: variable.drush.inc,v 1.19 2010/03/25 02:48:17 weitzman Exp $ +// $Id: variable.drush.inc,v 1.20 2010/11/27 02:22:21 weitzman Exp $ /** * Implementation of hook_drush_help(). @@ -94,6 +94,8 @@ function variable_drush_command() { */ function drush_variable_get() { global $conf; + $found = FALSE; + $keys = array_keys($conf); if ($args = func_get_args()) { $keys = preg_grep("/{$args[0]}/", $keys); @@ -115,6 +117,11 @@ function drush_variable_get() { $value = print_r($value, TRUE); } drush_print($name . ': ' . $value); + $found = TRUE; + } + + if (!$found) { + return drush_set_error('No matching variable found.'); } } diff --git a/sites/all/modules/drush/commands/core/watchdog.drush.inc b/sites/all/modules/drush/commands/core/watchdog.drush.inc index 4d5ddb210e998b1e23cc39fd0c0edf0a95365fec..bdc32c0c0b202c1ee127a233278d1b18dd826eb5 100644 --- a/sites/all/modules/drush/commands/core/watchdog.drush.inc +++ b/sites/all/modules/drush/commands/core/watchdog.drush.inc @@ -265,8 +265,8 @@ function drush_core_watchdog_delete($arg = NULL) { if (!drush_confirm(dt('Do you really want to continue?'))) { return drush_log(dt('Aborting.')); } - $affected_rows = drush_db_delete('watchdog'); - drush_log(dt('!affected watchdog messages have been deleted.', array('!affected' => $affected_rows)), 'ok'); + drush_db_delete('watchdog'); + drush_log(dt('All watchdog messages have been deleted.'), 'ok'); } else if (is_numeric($arg)) { drush_print(dt('Watchdog message #!wid will be deleted.', array('!wid' => $arg))); @@ -287,11 +287,11 @@ function drush_core_watchdog_delete($arg = NULL) { if ((is_null($arg))&&(is_null($type))&&(is_null($severity))) { return drush_set_error(dt('No options provided.')); } - $where = core_watchdog_query($type, $severity, $filter, 'OR'); + $where = core_watchdog_query($type, $severity, $arg, 'OR'); if ($where === FALSE) { return drush_log(dt('Aborting.'), 'error'); } - drush_print(dt('All messages matching "!where" will be deleted.', array('!where' => strtr($where['where'], $where['args'])))); + drush_print(dt('All messages with !where will be deleted.', array('!where' => preg_replace("/message LIKE %$arg%/", "message body containing '$arg'" , strtr($where['where'], $where['args']))))); if(!drush_confirm(dt('Do you really want to continue?'))) { return drush_log(dt('Aborting.')); } @@ -320,7 +320,7 @@ function core_watchdog_query($type = NULL, $severity = NULL, $filter = NULL, $cr if ($type) { $types = core_watchdog_message_types(); if (array_search($type, $types) === FALSE) { - $msg = "Unknown message type: !type.\nValid types are: !types."; + $msg = "Unrecognized message type: !type.\nRecognized types are: !types."; return drush_set_error(dt($msg, array('!type' => $type, '!types' => implode(', ', $types)))); } $conditions[] = "type = :type"; diff --git a/sites/all/modules/drush/commands/pm/package_handler/cvs.inc b/sites/all/modules/drush/commands/pm/package_handler/cvs.inc index 8102c0eb31e52e8f0414d7686a599bc60fc7be4b..10b136da1de7a4eae31326ec4960e156e97cb76d 100644 --- a/sites/all/modules/drush/commands/pm/package_handler/cvs.inc +++ b/sites/all/modules/drush/commands/pm/package_handler/cvs.inc @@ -1,5 +1,5 @@ <?php -// $Id: cvs.inc,v 1.15 2010/02/25 01:54:15 weitzman Exp $ +// $Id: cvs.inc,v 1.21 2010/10/13 19:48:58 jonhattan Exp $ /** * @file Drush PM CVS extension @@ -12,8 +12,6 @@ * @param $release The release details array from drupal.org */ function package_handler_install_project($project, $release) { - drush_log('Downloading project ' . $project['name'] . ' ...'); - // Check it out. drush_pm_cvs($project, $release); @@ -38,7 +36,6 @@ function package_handler_install_project($project, $release) { function package_handler_update_project($project, $release) { drush_log('Updating project ' . $project['name'] . ' ...'); - // Check out a fresh copy, or update an existing one. drush_pm_cvs($project, $release); @@ -52,74 +49,71 @@ function package_handler_update_project($project, $release) { } /** - * General CVS helper function + * General CVS helper function. + * * @param $project The project array with name, base and full (final) paths. * @param $release The release details array from drupal.org */ function drush_pm_cvs($project, $release) { - // By default we assume a module - $repos = 'drupal-contrib'; - $cvsdir = 'contributions/modules/'; + // Build the cvs command to execute. + $command = array('cvs'); - switch ($project['project_type']) { - case 'theme': - $cvsdir = 'contributions/themes/'; - break; - case 'theme engine': - $cvsdir = 'contributions/theme-engines/'; - break; - case 'translation': - drush_set_error('DRUSH_PM_CVS_NO_TRANSLATIONS', 'You cannot install translations with CVS, because they require processing (normally done on drupal.org to produce the tar.gz files).'); - exit(); - case 'profile': - $cvsdir = 'contributions/profiles/'; - break; - case 'core': - $cvsdir = ''; - $repos = 'drupal'; - } + // Global options. + $command[] = '-z6'; + // cvs root. + $cvsroot = '-d:pserver:' . drush_get_option('cvscredentials', 'anonymous:anonymous') . '@cvs.drupal.org:/cvs/'; + $cvsroot .= ($project['project_type'] == 'core')?'drupal':'drupal-contrib'; + $command[] = $cvsroot; + // CVS command ("cvs method"). $cvsmethod = drush_get_option('cvsmethod', FALSE); - $cvsparams = drush_get_option('cvsparams', FALSE); - // Determine reasonable defaults, based on context. if (empty($cvsmethod)) { $cvsmethod = 'checkout'; - // If we have an existing working copy we update. + // If we have an existing working copy we update. if (is_dir($project['full_project_path'] . '/CVS')) { $cvsmethod = 'update'; + } + } + $command[] = $cvsmethod; + + // CVS command options. + $cvsparams = drush_get_option('cvsparams', FALSE); + // common options for any command. + $command[] = '-r '. $release['tag']; + // command specific options. + if ($cvsmethod == 'checkout') { + // checkout dir. + $command[] = '-d ' . $project['project_dir']; + // path to cvs 'module' to check out. + if ($project['project_type'] == 'core') { + $command[] = $project['name']; // drupal + } + else { + // strtr for 'theme engine' type. + $command[] = 'contributions/' . strtr($project['project_type'], ' ' ,'-') . 's/' . $project['name']; + } + } + else { + if ($cvsparams === FALSE) { // By default we update overwriting changes, however if we have // an existing CVS checkout that is version controlled then the // default is to update in place, which will attempt to merge changes // but we assume anyone using SVN is competent enough to deal with this! // TODO: Make this work with BZR etc. - if (!file_exists($path . '/.svn') && $cvsparams === FALSE) { - $cvsparams = '-dPC'; + if (!file_exists($project['full_project_path'] . '/.svn')) { + $command[] = '-dPC'; + } + else { + $command[] = '-dP'; } } - } - if ($cvsparams === FALSE) { - $cvsparams = '-dP'; + // Directory to work on. + $command[] = $project['project_dir']; } // CVS only accepts relative paths. We will cd in the checkout path right // before running the cvs command. - $checkout_path = $project['base_project_path']; - - $cvsparts = array(); - $cvsparts[] = '-z6'; - $cvsparts[] = $cvsparams; - $cvsparts[] = '-d:pserver:' . drush_get_option('cvscredentials', 'anonymous:anonymous') . '@cvs.drupal.org:/cvs/' . $repos; - $cvsparts[] = $cvsmethod; - $cvsparts[] = '-r '. $release['tag']; - if ($cvsmethod == 'checkout') { - $cvsparts[] = '-d ' . $project['name']; - $cvsparts[] = $cvsdir . $project['name']; - } - else { - $cvsparts[] = $project['name']; - } - - if (!drush_shell_exec('cd ' . $checkout_path . ' ; cvs ' . implode(' ', $cvsparts))) { + if (!drush_shell_cd_and_exec($project['base_project_path'], implode(' ', $command))) { drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to checkout ' . $project['name'] . ' from cvs.drupal.org.'); } } diff --git a/sites/all/modules/drush/commands/pm/package_handler/git_drupalorg.inc b/sites/all/modules/drush/commands/pm/package_handler/git_drupalorg.inc new file mode 100644 index 0000000000000000000000000000000000000000..fd294187b4f9fcb97dfeff35f1e7a16a5decfe62 --- /dev/null +++ b/sites/all/modules/drush/commands/pm/package_handler/git_drupalorg.inc @@ -0,0 +1,117 @@ +<?php +// $Id: git_drupalorg.inc,v 1.2 2010/11/29 13:52:59 weitzman Exp $ + +/** + * @file Drush PM drupal.org Git extension. + */ + +/** + * Install a project. + * + * @param $project The project array with name, base and full (final) paths. + * @param $release The release details array from drupal.org + */ +function package_handler_install_project($project, $release) { + drush_log('Downloading project ' . $project['name'] . ' ...'); + + $project_name = strtok($project['name'], ' '); + + if (isset($release['version_extra']) && $release['version_extra'] == 'dev') { + // Use the development repository, not supported yet. + $repository = 'git://git.drupal.org/project/' . $project_name . '.git'; + $tag = $release['tag']; + } + else { + // Use a stable repository. + $repository = 'git://git.drupal.org/contributions-stable/' . $project_name . '.git'; + $tag = $release['version']; + } + + // Determine if we're going to init using submodules or not, as our logic + // branches as a result + $submodule = drush_get_option('gitsubmodule', FALSE); + $function = 'package_handler_install_project_gitdo_' . ($submodule ? 'submodule' : 'normal'); + return $function($project, $repository, $tag); + + +} + +function package_handler_install_project_gitdo_normal($project, $repository, $tag) { + // Clone the repo into its appropriate target location. + $commands = array(); + $commands[] = 'git clone ' . escapeshellarg($repository) . ' ' . escapeshellarg($project['full_project_path']); + // Check out the appropriate branch. + $commands[] = 'cd ' . escapeshellarg($project['full_project_path']); + $commands[] = 'git checkout ' . escapeshellarg($tag); + + if (!drush_shell_exec(implode(' && ', $commands))) { + return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $project['name'] . ' from git.drupal.org.'); + } + else { + return TRUE; + } +} + +function package_handler_install_project_gitdo_submodule($project, $repository, $tag) { + // Verify that we are in a Git repository. + if (drush_shell_exec("cd " . escapeshellarg($project['base_project_path']) . " ; git rev-parse --git-dir")) { + $output = drush_shell_exec_output(); + if (isset($output[0])) { + $git_dir = $output[0]; + } + } + if (!isset($git_dir)) { + return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to create !project as a git submodule: !dir is not in a Git repository.', array('!project' => $project['name'], '!dir' => $project['base_project_path']))); + } + + // Create the project path and find its true location, git submodule doesn't + // like symbolic links. + mkdir($project['full_project_path']); + $full_project_path = realpath($project['full_project_path']); + rmdir($full_project_path); + + // Add the submodule; this clones it into place and registers it in the + // superproject. + $commands = array(); + $commands[] = 'cd ' . escapeshellarg($git_dir . '/..'); + $commands[] = 'git submodule add ' . escapeshellarg($repository) . ' ' . escapeshellarg($full_project_path); + $commands[] = 'cd ' . escapeshellarg($project['full_project_path']); + $commands[] = 'git checkout ' . escapeshellarg($tag); + + if (!drush_shell_exec(implode(' && ', $commands))) { + return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $project['name'] . ' from git.drupal.org.'); + } + else { + return TRUE; + } +} + +/** + * Update a project (so far, only modules are supported). + * + * @param $project The project array with name, base and full (final) paths. + * @param $release The release details array from drupal.org + */ +function package_handler_update_project($project, $release) { + drush_log('Updating project ' . $project['name'] . ' ...'); + + $commands = array(); + $commands[] = 'cd ' . escapeshellarg($project['full_project_path']); + + if ($release['version_extra'] == 'dev') { + // Update the branch of the development repository. + $commands[] = 'git pull'; + } + else { + // Use a stable repository. + $commands[] = 'git fetch'; + $commands[] = 'git checkout ' . escapeshellarg($release['version']); + } + + if (!drush_shell_exec(implode(' ; ', $commands))) { + return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to update ' . $project['name'] . ' from git.drupal.org.'); + } + else { + return TRUE; + } +} diff --git a/sites/all/modules/drush/commands/pm/package_handler/wget.inc b/sites/all/modules/drush/commands/pm/package_handler/wget.inc index 89f7a73a27c25305f714f5f165cfb0b41f75fdeb..6b8d41d76e5201cbbe31a4903984b6114e664c0b 100644 --- a/sites/all/modules/drush/commands/pm/package_handler/wget.inc +++ b/sites/all/modules/drush/commands/pm/package_handler/wget.inc @@ -1,103 +1,79 @@ <?php -// $Id: wget.inc,v 1.14 2010/04/27 00:32:13 greg1anderson Exp $ +// $Id: wget.inc,v 1.17 2010/11/24 12:39:59 jonhattan Exp $ /** * @file Drush PM Wget extension */ /** - * Install a project. + * Download a project. * - * @param $project The project array with name, base and full (final) paths. - * @param $release The release details array from drupal.org + * @param $request Array with information on the request to download. + * @param $release The release details array from drupal.org. */ -function package_handler_install_project(&$project, $release) { - drush_log('Downloading project ' . $project['name'] . ' ...'); - - // Get the filename... +function package_handler_install_project(&$request, $release) { + // Get the filename. $filename = explode('/', $release['download_link']); $filename = array_pop($filename); - // Set our directory to the download location. + // Chdir to the download location. $olddir = getcwd(); - chdir($project['base_project_path']); + chdir($request['base_project_path']); - // Download it. + // Download the project. if (!drush_shell_exec("wget -P . " . $release['download_link'])) { drush_shell_exec("curl -O " . $release['download_link']); } - if (file_exists($filename) || drush_get_context('DRUSH_SIMULATE')) { drush_log("Downloading " . $filename . " was successful."); } else { chdir($olddir); - return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', 'Unable to download ' . $filename . ' to ' . $project['base_project_path'] . ' from '. $release['download_link']); + return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', 'Unable to download ' . $filename . ' to ' . $request['base_project_path'] . ' from '. $release['download_link']); } - // Check Md5 hash + // Check Md5 hash. if (md5_file($filename) != $release['mdhash'] && !drush_get_context('DRUSH_SIMULATE')) { + drush_set_error('DRUSH_PM_FILE_CORRUPT', "File $filename is corrupt (wrong md5 checksum)."); drush_op('unlink', $filename); chdir($olddir); - return drush_set_error('DRUSH_PM_FILE_CORRUPT', "File $filename is corrupt (wrong md5 checksum)."); + return FALSE; } else { drush_log("Md5 checksum of $filename verified."); } - // Decompress + // Decompress and untar in two steps as tar -xzf does not work on windows. drush_shell_exec("gzip -d " . $filename); $tarpath = basename($filename, '.tar.gz'); $tarpath = basename($tarpath, '.tgz'); $tarpath .= '.tar'; - - // Untar drush_shell_exec("tar -xf $tarpath"); - // We're not using tar -xzf because that's not working on windows... - // Handle the dodgy directory used by the drupal project' packaging script. - if ($project['name'] == 'drupal') { - // The dodgy directory name is always the first line of output. + // Move untarred directory to project_dir, if distinct. + if (($request['project_type'] == 'core') || (($request['project_type'] == 'profile') && (drush_get_option('variant', 'core') == 'core'))) { + // Obtain the dodgy project_dir for drupal core. + // We use a separate tar -tf instead of -xvf above because + // the output is not the same in Mac. drush_shell_exec("tar -tf $tarpath"); $output = drush_shell_exec_output(); - $project_destination_name = $output[0]; - - if ($rename = drush_get_option($project['name'] . '-project-rename')) { - // If someone specifies --drupal-project-rename, but does not - // provide a name, then default to 'drupal'. - if ($rename === TRUE) { - $rename = $project['name']; - } - $rename = trim($rename, '/'); - $renamed_destination = $project['base_project_path'] . (substr($project['base_project_path'], -1) != '/' ? '/' : '') . $rename; - - $rename_result = drush_op('rename', $project['base_project_path'] . $project_destination_name, $renamed_destination); - if ($rename_result) { - $project_destination_name = $rename; - } + $project_dir = rtrim($output[0], DIRECTORY_SEPARATOR); + if ($request['project_dir'] != $project_dir) { + $path = $request['base_project_path']; + drush_op('rename', $path . '/'. $project_dir, $path . '/' . $request['project_dir']); } - $project['full_project_path'] = $project['base_project_path'] . (substr($project['base_project_path'], -1) != '/' ? '/' : '') . $project_destination_name; } - // Remove the tarball + // Cleanup. Remove the tar file and set previous working directory. drush_op('unlink', $tarpath); - - // Set working directory back to the previous working directory. chdir($olddir); - // 'drupal' project and profiles get untarred as drupal-6-10/foo instead of drupal/foo so must be excluded from test below. - // Translations get untarred into the Drupal root, so there is no easy way to check. - if (!is_dir($project['full_project_path']) && !drush_get_context('DRUSH_SIMULATE') && $project['name'] != 'drupal' && !in_array($release['type'], array('translation', 'profile'))) { - return drush_set_error('DRUSH_PM_FILE_UNTAR_ERROR', 'Downloaded file ' . $filename . ' couldn\'t be untarred to ' . $project['full_project_path'] . ' correctly'); - } - else { - return TRUE; - } + return TRUE; } /** * This is an alias of the install function, since they are identical */ -function package_handler_update_project(&$project, $release) { - return package_handler_install_project($project, $release); +function package_handler_update_project(&$request, $release) { + return package_handler_install_project($request, $release); } diff --git a/sites/all/modules/drush/commands/pm/pm.drush.inc b/sites/all/modules/drush/commands/pm/pm.drush.inc index c762786202383ca09dec99e8f8e311730cd1c63a..f12d36f7ef51ce16b4830f7f1cefb424e9027cc4 100644 --- a/sites/all/modules/drush/commands/pm/pm.drush.inc +++ b/sites/all/modules/drush/commands/pm/pm.drush.inc @@ -1,16 +1,16 @@ <?php -// $Id: pm.drush.inc,v 1.116 2010/06/16 15:08:02 weitzman Exp $ +// $Id: pm.drush.inc,v 1.146 2010/12/01 21:02:21 jonhattan Exp $ /** * @file - * The drush Package Manager + * The drush Project Manager * * Terminology: - * - Request: a requested package (string or keyed array), with a project name and (optionally) version. - * - Project: a drupal.org project, such as cck or zen. + * - Request: a requested project (string or keyed array), with a name and (optionally) version. + * - Project: a drupal.org project (i.e drupal.org/project/*), such as cck or zen. + * - Extension: a drupal.org module or theme. * - Version: a requested version, such as 1.0 or 1.x-dev. * - Release: a specific release of a project, with associated metadata (from the drupal.org update service). - * - Package: the collection of files that make up a release. */ /** @@ -34,12 +34,11 @@ define('DRUSH_PM_NO_VERSION', 103); define('DRUSH_PM_REQUESTED_NOT_FOUND', 104); /** - * Sort callback function for sorting projects - * First by type, second by package and third by name + * Sort callback function for sorting extensions. * - * Special handling for 'Package' on modules and themes. + * It will sort first by type, second by package and third by name. */ -function _drush_pm_sort_projects($a, $b) { +function _drush_pm_sort_extensions($a, $b) { if ($a->type == 'module' && $b->type == 'theme') { return -1; } @@ -59,25 +58,34 @@ function _drush_pm_sort_projects($a, $b) { function pm_drush_help($section) { switch ($section) { case 'drush:pm-enable': - return dt('Enable one or more modules or themes. Enable dependant modules as well.'); + return dt('Enable one or more extensions (modules or themes). Enable dependant extensions as well.'); case 'drush:pm-disable': - return dt('Disable one or more modules or themes. Disable dependant modules as well.'); + return dt('Disable one or more extensions. Disable dependant extensions as well.'); case 'drush:pm-info': - return dt('Show detailed info for one or more projects.'); + return dt('Show detailed info for one or more extensions.'); case 'drush:pm-uninstall': return dt('Uninstall one or more modules. Modules must be disabled first.'); case 'drush:pm-list': - return dt('Show a list of available modules and themes.'); + return dt('Show a list of available extensions.'); case 'drush:pm-refresh': - return dt('Refresh update status information. Run this before running update or updatecode commands.'); + return dt('Refresh update status information. Run this before running pm-update or pm-updatecode commands.'); case 'drush:pm-updatecode': return dt("Display available update information and allow updating of all installed project code to the specified version (or latest by default). Note: The user is asked to confirm before the actual update. Use the --yes option to answer yes automatically."); + case 'drush:pm-updatecode-notify-pending-db-updates': + return dt("This is a helper command needed by updatecode. It is used to check for db updates in a backend process after code updated have been performed. We need to run this task in a separate process to not conflict with old code already in memory."); case 'drush:pm-update': return dt("Display available update information and allow updating of all installed projects to the specified version (or latest by default), followed by applying any database updates required (as with running update.php). Note: The user is asked to confirm before the actual update. Use the --yes option to answer yes automatically."); case 'drush:pm-releases': - return dt("View all releases for a given project (modules, themes, profiles, translations). Useful for deciding which version to install/update."); + return dt("View all releases for a given drupal.org project. Useful for deciding which version to install/update."); case 'drush:pm-download': - return dt("Quickly download projects (modules, themes, profiles, translations) from drupal.org. Automatically figures out which module version you want based on its latest release, or you may specify a particular version. Downloads drupal core as well. If no destination is provided, defaults to a site specific modules directory if available, then to sites/all/modules if available, then to the current working directory."); + return dt("Download Drupal core or projects from drupal.org (Drupal core, modules, themes, profiles or translations) and other sources. It will automatically figure out which project version you want based on its latest release, or you may specify a particular version. + +If no destination is provided, then it depends on the project type: + - Profiles will be downloaded to profiles/ in your Drupal root. + - Translations will be decompressed from your Drupal root. + - Modules and themes will be downloaded to the site specific directory (sites/example.com/modules|themes if available) or to sites/all/modules|themes. + - If you're downloading drupal core or you are not running the command within a bootstrapped drupal site, the default location is the current directory. + - Drush commands will be relocated to /usr/share/drush/commands (if available) or ~/.drush. Relocation is determined once the project is downloaded by examining its content. Note you can provide your own function in a commandfile to determine the relocation of any project."); } } @@ -95,27 +103,37 @@ function pm_drush_command() { 'package_handler' => 'Determine how to download/checkout new projects and acquire updates to projects.', ), ); - + $update_options = array( + '--security-only' => 'Only update modules that have security updates available. However, if there were other releases of a module between the installed version the security update, other changes to features or functionality may occur.', + '--lock' => 'Add a persistent lock to remove the specified projects from consideration during updates. Locks may be removed with the --unlock parameter, or overridden by specifically naming the project as a parameter to pm-update or pm-updatecode. The lock does not affect pm-download. See also the update-advanced project for similar and improved functionality.', + ); + $update_suboptions = array( + '--lock' => array( + '--lock-message' => 'A brief message explaining why a project is being locked; displayed during pm-updatecode. Optional.', + '--unlock' => 'Remove the persistent lock from the specified projects so that they may be updated again.', + ), + ); + $items['pm-enable'] = array( - 'description' => 'Enable one or more modules or themes.', + 'description' => 'Enable one or more extensions (modules or themes).', 'arguments' => array( - 'modules' => 'A space delimited list of modules or themes. You can use the * wildcard at the end of module and theme names to to enable all matches.', + 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to enable all matches.', ), 'aliases' => array('en'), 'deprecated-aliases' => array('enable'), ); $items['pm-disable'] = array( - 'description' => 'Disable one or more modules or themes.', + 'description' => 'Disable one or more extensions (modules or themes).', 'arguments' => array( - 'modules' => 'A space delimited list of modules or themes. You can use the * wildcard at the end of module and theme names to disable multiple matches.', + 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to disable multiple matches.', ), 'aliases' => array('dis'), 'deprecated-aliases' => array('disable'), ); $items['pm-info'] = array( - 'description' => 'Show info for one or more projects.', + 'description' => 'Show info for one or more modules or themes.', 'arguments' => array( - 'projects' => 'A space delimited list of projects. You can use the * wildcard at the end of module names to get info for the project and all its sub projects.', + 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to show info for multiple matches.', ), ); // Install command is reserved for the download and enable of projects including dependencies. @@ -126,24 +144,26 @@ function pm_drush_command() { $items['pm-uninstall'] = array( 'description' => 'Uninstall one or more modules.', 'arguments' => array( - 'modules' => 'A space delimited list of modules.', + 'modules' => 'A list of modules.', ), 'deprecated-aliases' => array('uninstall'), ); $items['pm-list'] = array( - 'description' => 'Show a list of available modules and themes', + 'description' => 'Show a list of available extensions (modules and themes).', 'callback arguments' => array(array(), FALSE), 'options' => array( - '--type' => 'Filter by project type. Choices: module, theme.', - '--status' => 'Filter by project status. Choices: enabled,disable and/or \'not installed\'. You can use multiple comma separated values. (i.e. --status="disabled,not installed").', + '--type' => 'Filter by extension type. Choices: module, theme.', + '--status' => 'Filter by extension status. Choices: enabled, disable and/or \'not installed\'. You can use multiple comma separated values. (i.e. --status="disabled,not installed").', '--package' => 'Filter by project packages. You can use multiple comma separated values. (i.e. --package="Core - required,Other").', - '--pipe' => 'Returns a space delimited list of the names of the resulting projects.', + '--core' => 'Filter out extensions that are not in core.', + '--no-core' => 'Filter out extensions that are provided by core projects.', + '--pipe' => 'Returns a space delimited list of the names of the resulting extensions.', ), 'aliases' => array('sm'), 'deprecated-aliases' => array('statusmodules'), ); $items['pm-refresh'] = array( - 'description' => 'Refresh update status information', + 'description' => 'Refresh update status information.', 'drupal dependencies' => array($update), 'aliases' => array('rf'), 'deprecated-aliases' => array('refresh'), @@ -152,33 +172,43 @@ function pm_drush_command() { 'description' => 'Update your project code', 'drupal dependencies' => array($update), 'arguments' => array( - 'projects' => 'Optional. A space delimited list of installed projects (modules or themes) to update.', + 'projects' => 'Optional. A list of installed projects to update.', ), 'options' => array( - '--backup-dir' => 'Specify a directory to backup packages into, defaults to a backup directory within your Drupal root.', - '--pipe' => 'Returns a space delimited list of enabled modules and their respective version and update information, one module per line. Order: module name, current version, recommended version, update status.', - ), + '--pipe' => 'Returns a space delimited list of projects with any of its extensions enabled and their respective version and update information, one project per line. Order: project name, current version, recommended version, update status.', + ) + $update_options, + 'sub-options' => $update_suboptions, 'aliases' => array('upc'), 'deprecated-aliases' => array('updatecode'), ) + $engines; - $items['pm-update'] = array( - 'description' => 'Update your project code and apply any database updates required (update.php)', + // Merge all items from above. + $items['pm-update'] = array_merge($items['pm-updatecode'], array( + 'description' => 'Update your project code and apply any database updates required (update.php).', + 'aliases' => array('up'), + 'deprecated-aliases' => array('update'), + )); + $items['pm-updatecode-notify-pending-db-updates'] = array( + 'description' => 'Notify of pending db updates.', + 'hidden' => TRUE + ); + $items['pm-releasenotes'] = array( + 'description' => 'Print release notes for given projects.', 'drupal dependencies' => array($update), 'arguments' => array( - 'projects' => 'Optional. A space delimited list of installed projects (modules or themes) to update.', + 'projects' => 'A list of drupal.org project names, with optional version. Defaults to \'drupal\'', ), - 'options' => array( - '--backup-dir' => 'Specify a directory to backup packages into, defaults to a backup directory within your Drupal root.', - '--php' => 'Specify absolute path to you php binary. Use this if you see errors.' + 'examples' => array( + 'drush rln cck' => 'Prints the release notes for the recommended version of CCK package.', + 'drush rln token-1.13' => 'View release notes of a specfic version of the Token project for my version of Drupal.', + 'drush rln pathauto zen' => 'View release notes for the recommended version of Pathauto and Zen projects.', ), - 'aliases' => array('up'), - 'deprecated-aliases' => array('update'), + 'aliases' => array('rln'), ); $items['pm-releases'] = array( - 'description' => 'Release information for a project', + 'description' => 'Print release information for given projects.', 'drupal dependencies' => array($update), 'arguments' => array( - 'projects' => 'A space separated list of drupal.org project names.', + 'projects' => 'A list of drupal.org project names. Defaults to \'drupal\'', ), 'examples' => array( 'drush pm-releases cck zen' => 'View releases for cck and Zen projects.', @@ -186,25 +216,28 @@ function pm_drush_command() { 'deprecated-aliases' => array('info'), ); $items['pm-download'] = array( - 'description' => 'Download core Drupal and projects like CCK, Zen, etc.', + 'description' => 'Download projects from drupal.org or other sources.', 'examples' => array( - 'drush dl' => 'Download latest version of Drupal core.', - 'drush dl drupal' => 'Download latest stable version of Drupal core', - 'drush dl drupal-7.x' => 'Download latest 7.x development version of Drupal core', - 'drush dl cck zen es' => 'Download latest versions of CCK, Zen and Spanish translations for my version of Drupal.', + 'drush dl' => 'Download latest recommended release of Drupal core.', + 'drush dl drupal' => 'Same as `drush dl`.', + 'drush dl drupal-7.x' => 'Download latest 7.x development version of Drupal core.', + 'drush dl cck zen es' => 'Download latest versions of CCK, Zen and Spanish translations for your Drupal version.', 'drush dl og-1.3' => 'Download a specfic version of Organic groups module for my version of Drupal.', 'drush dl diff-6.x-2.x' => 'Download a specific development branch of diff module for a specific Drupal version.', ), 'arguments' => array( - 'projects' => 'A space separated list of project names, with optional version. Defaults to \'drupal\'', + 'projects' => 'A list of drpal.org project names, with optional version. Defaults to \'drupal\'', ), 'options' => array( - '--destination' => 'Path to which the project will be copied.', + '--destination' => 'Path to which the project will be copied. If you\'re providing a relative path, note it is relative to the drupal root (if bootstrapped).', + '--use-site-dir' => 'Force to use the site specific directory. It will create the directory if it doesn\'t exist. If --destination is also present this option will be ignored.', '--source' => 'The base URL which provides project release history in XML. Defaults to http://updates.drupal.org/release-history.', + '--notes' => 'Show release notes after each project is downloaded.', '--variant' => "Only useful for install profiles. Possible values: 'core', 'no-core', 'make'.", - '--drupal-project-rename' => 'Alternate name for "drupal" directory when downloading drupal project.', + '--drupal-project-rename' => 'Alternate name for "drupal-x.y" directory when downloading Drupal project. If empty, will defaults to "drupal".', + '--pipe' => 'Returns a list of the names of the extensions (modules and themes) contained in the downloaded projects.', ), - 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all. + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap required. 'aliases' => array('dl'), 'deprecated-aliases' => array('download'), ) + $engines; @@ -212,7 +245,7 @@ function pm_drush_command() { } /** - * Command callback. Show a list of modules and status. + * Command callback. Show a list of extensions with type and status. * */ function drush_pm_list() { @@ -244,7 +277,7 @@ function drush_pm_list() { foreach ($type_filter as $type) { if (!in_array($type, $all_types)) { //TODO: this kind of chck can be implemented drush-wide return drush_set_error('DRUSH_PM_INVALID_PROJECT_TYPE', dt('!type is not a valid project type.', array('!type' => $type))); - } + } } //--status @@ -269,51 +302,66 @@ function drush_pm_list() { $header[] = dt('Version'); $rows[] = $header; - $projects = drush_pm_get_projects(); - uasort($projects, '_drush_pm_sort_projects'); + $extension_info = drush_pm_get_extensions(); + uasort($extension_info, '_drush_pm_sort_extensions'); $major_version = drush_drupal_major_version(); - foreach ($projects as $project) { - if (!in_array($project->type, $type_filter)) { + foreach ($extension_info as $extension) { + if (!in_array($extension->type, $type_filter)) { continue; } - $status = drush_get_project_status($project); + $status = drush_get_extension_status($extension); if (!in_array($status, $status_filter)) { continue; } - if (($major_version >= 7) and (isset($project->info['hidden']))) { + if (($major_version >= 6) and (isset($extension->info['hidden']))) { continue; } + + // filter out core if --no-core specified + if (drush_get_option('no-core', FALSE)) { + if (strcasecmp("Core ", substr($extension->info['package'], 0, 5)) == 0) { + continue; + } + } + + // filter out non-core if --core specified + if (drush_get_option('core', FALSE)) { + if (strcasecmp("Core ", substr($extension->info['package'], 0, 5)) != 0) { + continue; + } + } // filter by package if (!empty($package_filter)) { - if (!in_array(strtolower($project->info['package']), $package_filter)) { + if (!in_array(strtolower($extension->info['package']), $package_filter)) { continue; } } if (empty($package_filter) || count($package_filter) > 1) { - $row[] = $project->info['package']; + $row[] = $extension->info['package']; } - if (($major_version >= 6)||($project->type == 'module')) { - $row[] = $project->info['name'].' ('.$project->name.')'; + if (($major_version >= 6)||($extension->type == 'module')) { + $row[] = $extension->info['name'].' ('.$extension->name.')'; } else { - $row[] = $project->name; + $row[] = $extension->name; } if (count($type_filter) > 1) { - $row[] = ucfirst($project->type); + $row[] = ucfirst($extension->type); } if (count($status_filter) > 1) { $row[] = ucfirst($status); } - if (($major_version >= 6)||($project->type == 'module')) { - $row[] = $project->info['version']; + if (($major_version >= 6)||($extension->type == 'module')) { + // Suppress notice when version is not present. + $row[] = @$extension->info['version']; } $rows[] = $row; - $pipe[] = $project->name; + $pipe[] = $extension->name; unset($row); } drush_print_table($rows, TRUE); @@ -325,41 +373,41 @@ function drush_pm_list() { } /** - * Command callback. Enable one or more projects. + * Command callback. Enable one or more extensions from downloaded projects. */ function drush_pm_enable() { - $args = func_get_args(); + $args = _convert_csv_to_array(func_get_args()); - $project_info = drush_get_projects(); + $extension_info = drush_get_extensions(); // Classify $args in themes, modules or unknown. $modules = array(); $themes = array(); - drush_pm_classify_projects($args, $modules, $themes, $project_info); - $projects = array_merge($modules, $themes); - $unknown = array_diff($args, $projects); + drush_pm_classify_extensions($args, $modules, $themes, $extension_info); + $extensions = array_merge($modules, $themes); + $unknown = array_diff($args, $extensions); - // Discard and set an error for each unknown project. - foreach ($unknown as $project) { - drush_set_error('DRUSH_PM_ENABLE_PROJECT_NOT_FOUND', dt('!project was not found and will not be enabled.', array('!project' => $project))); + // Discard and set an error for each unknown extension. + foreach ($unknown as $name) { + drush_set_error('DRUSH_PM_ENABLE_EXTENSION_NOT_FOUND', dt('!extension was not found and will not be enabled.', array('!extension' => $name))); } - // Discard already enabled projects. - foreach ($projects as $project) { - if ($project_info[$project]->status) { - if ($project_info[$project]->type == 'module') { - unset($modules[$project]); + // Discard already enabled extensions. + foreach ($extensions as $name) { + if ($extension_info[$name]->status) { + if ($extension_info[$name]->type == 'module') { + unset($modules[$name]); } else { - unset($themes[$project]); + unset($themes[$name]); } - drush_log(dt('!project is already enabled.', array('!project' => $project)), 'ok'); + drush_log(dt('!extension is already enabled.', array('!extension' => $name)), 'ok'); } } if (!empty($modules)) { // Check module dependencies. - $dependencies = drush_check_module_dependencies($modules, $project_info); + $dependencies = drush_check_module_dependencies($modules, $extension_info); $all_dependencies = array(); foreach ($dependencies as $key => $info) { if (isset($info['error'])) { @@ -372,30 +420,30 @@ function drush_pm_enable() { $all_dependencies = array_merge($all_dependencies, $assoc); } } - $all_dependencies = array_unique($all_dependencies); $enabled = array_keys(array_filter(drush_get_modules(), 'pm_is_enabled')); $needed = array_diff($all_dependencies, $enabled); $modules = $needed + $modules; - // Discard modules which don't meet requirements. + // Discard modules which doesn't meet requirements. require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc'; foreach ($modules as $key => $module) { // Check to see if the module can be installed/enabled (hook_requirements). // See @system_modules_submit if (!drupal_check_module($module)) { unset($modules[$key]); - drush_set_error('DRUSH_PM_ENABLE_MODULE_UNMEET_REQUIREMENTS', dt('Module !module don\'t meet the requirements to be enabled.', array('!module' => $module))); + drush_set_error('DRUSH_PM_ENABLE_MODULE_UNMEET_REQUIREMENTS', dt('Module !module doesn\'t meet the requirements to be enabled.', array('!module' => $module))); + _drush_log_drupal_messages(); } } } - // Inform the user which projects will finally be enabled. - $projects = array_merge($modules, $themes); - if (empty($projects)) { - return drush_log(dt('There were no projects that could be enabled.'), 'ok'); + // Inform the user which extensions will finally be enabled. + $extensions = array_merge($modules, $themes); + if (empty($extensions)) { + return drush_log(dt('There were no extensions that could be enabled.'), 'ok'); } else { - drush_print(dt('The following projects will be enabled: !projects', array('!projects' => implode(', ', $projects)))); + drush_print(dt('The following extensions will be enabled: !extensions', array('!extensions' => implode(', ', $extensions)))); if(!drush_confirm(dt('Do you really want to continue?'))) { return drush_log(dt('Aborting.')); } @@ -416,47 +464,47 @@ function drush_pm_enable() { } // Inform the user of final status. - $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:projects)', array(':projects' => $projects)); - while ($project = drush_db_fetch_object($rsc)) { - if ($project->status) { - drush_log(dt('!project was enabled successfully.', array('!project' => $project->name)), 'ok'); + $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions)); + while ($extension = drush_db_fetch_object($rsc)) { + if ($extension->status) { + drush_log(dt('!extension was enabled successfully.', array('!extension' => $extension->name)), 'ok'); } else { - drush_set_error('DRUSH_PM_ENABLE_PROJECT_ISSUE', dt('There was a problem enabling !project.', array('!project' => $project->name))); + drush_set_error('DRUSH_PM_ENABLE_EXTENSION_ISSUE', dt('There was a problem enabling !extension.', array('!extension' => $extension->name))); } } } /** - * Command callback. Disable one or more projects. + * Command callback. Disable one or more extensions. */ function drush_pm_disable() { - $args = func_get_args(); + $args = _convert_csv_to_array(func_get_args()); - $project_info = drush_get_projects(); + $extension_info = drush_get_extensions(); - // classify $args in themes, modules or unknown + // classify $args in themes, modules or unknown. $modules = array(); $themes = array(); - drush_pm_classify_projects($args, $modules, $themes, $project_info); - $projects = array_merge($modules, $themes); - $unknown = array_diff($args, $projects); + drush_pm_classify_extensions($args, $modules, $themes, $extension_info); + $extensions = array_merge($modules, $themes); + $unknown = array_diff($args, $extensions); - // Discard and set an error for each unknown project. - foreach ($unknown as $project) { - drush_set_error('DRUSH_PM_ENABLE_PROJECT_NOT_FOUND', dt('!project was not found and will not be disabled.', array('!project' => $project))); + // Discard and set an error for each unknown extension. + foreach ($unknown as $namew) { + drush_set_error('DRUSH_PM_ENABLE_EXTENSION_NOT_FOUND', dt('!extension was not found and will not be disabled.', array('!extension' => $name))); } - // Discard already disabled projects. - foreach ($projects as $project) { - if (!$project_info[$project]->status) { - if ($project_info[$project]->type == 'module') { - unset($modules[$project]); + // Discard already disabled extensions. + foreach ($extensions as $name) { + if (!$extension_info[$name]->status) { + if ($extension_info[$name]->type == 'module') { + unset($modules[$name]); } else { - unset($themes[$project]); + unset($themes[$name]); } - drush_log(dt('!project is already disabled.', array('!project' => $project)), 'ok'); + drush_log(dt('!extension is already disabled.', array('!extension' => $name)), 'ok'); } } @@ -469,22 +517,34 @@ function drush_pm_disable() { } } - // Add enabled dependents to list of modules to disable. if (!empty($modules)) { + // Add enabled dependents to the list of modules to disable. $enabled = array_keys(array_filter(drush_get_modules(), 'pm_is_enabled')); - $dependents = drush_module_dependents($modules, $project_info); + $dependents = drush_module_dependents($modules, $extension_info); $dependents = array_unique($dependents); $dependents = array_intersect($dependents, $enabled); $modules = array_merge($modules, $dependents); + + // Discard required modules. + $required = drupal_required_modules(); + foreach ($required as $module) { + if (isset($modules[$module])) { + unset($modules[$module]); + // No message for hidden modules. + if (!isset($extension_info[$module]->info['hidden'])) { + drush_log(dt('!module is a required module and can\'t be disabled.', array('!module' => $module)), 'ok'); + } + } + } } - // Inform the user which projects will finally be disabled. - $projects = array_merge($modules, $themes); - if (empty($projects)) { - return drush_log(dt('There were no projects that could be disabled.'), 'ok'); + // Inform the user which extensions will finally be disabled. + $extensions = array_merge($modules, $themes); + if (empty($extensions)) { + return drush_log(dt('There were no extensions that could be disabled.'), 'ok'); } else { - drush_print(dt('The following projects will be disabled: !projects', array('!projects' => implode(', ', $projects)))); + drush_print(dt('The following extensions will be disabled: !extensions', array('!extensions' => implode(', ', $extensions)))); if(!drush_confirm(dt('Do you really want to continue?'))) { return drush_log(dt('Aborting.')); } @@ -504,72 +564,71 @@ function drush_pm_disable() { } // Inform the user of final status. - $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:projects)', array(':projects' => $projects)); - while ($project = drush_db_fetch_object($rsc)) { - if (!$project->status) { - drush_log(dt('!project was disabled successfully.', array('!project' => $project->name)), 'ok'); + $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions)); + while ($extension = drush_db_fetch_object($rsc)) { + if (!$extension->status) { + drush_log(dt('!extension was disabled successfully.', array('!extension' => $extension->name)), 'ok'); } else { - drush_set_error('DRUSH_PM_DISABLE_PROJECT_ISSUE', dt('There was a problem disabling !project.', array('!project' => $project->name))); + drush_set_error('DRUSH_PM_DISABLE_EXTENSION_ISSUE', dt('There was a problem disabling !extension.', array('!extension' => $extension->name))); } } } /** - * Wrapper of drupal_get_projects() with additional information used by + * Wrapper of drupal_get_extensions() with additional information used by * pm- commands. * * @return - * An array containing info for all available modules and themes w/additional - * info. + * An array containing info for all available extensions w/additional info. */ -function drush_pm_get_projects() { - $projects = drush_get_projects(); - foreach ($projects as $key => $project) { - if (empty($project->info['package'])) { - $projects[$key]->info['package'] = dt('Other'); +function drush_pm_get_extensions() { + $extensions = drush_get_extensions(); + foreach ($extensions as $key => $extension) { + if (empty($extension->info['package'])) { + $extensions[$key]->info['package'] = dt('Other'); } } - return $projects; + return $extensions; } /** - * Classify projects in modules, themes or unknown ones. + * Classify extensions as modules, themes or unknown. * - * @param $projects - * Array of project names, by reference. + * @param $extensions + * Array of extension names, by reference. * @param $modules - * Empty array to be filled with modules in $projects. + * Empty array to be filled with modules in the provided extension list. * @param $themes - * Empty array to be filled with themes in $projects. + * Empty array to be filled with themes in the provided extension list. */ -function drush_pm_classify_projects(&$projects, &$modules, &$themes, $project_info) { - _drush_pm_expand_projects($projects, $project_info); - foreach ($projects as $project) { - if (!isset($project_info[$project])) { +function drush_pm_classify_extensions(&$extensions, &$modules, &$themes, $extension_info) { + _drush_pm_expand_extensions($extensions, $extension_info); + foreach ($extensions as $extension) { + if (!isset($extension_info[$extension])) { continue; } - if ($project_info[$project]->type == 'module') { - $modules[$project] = $project; + if ($extension_info[$extension]->type == 'module') { + $modules[$extension] = $extension; } - else if ($project_info[$project]->type == 'theme') { - $themes[$project] = $project; + else if ($extension_info[$extension]->type == 'theme') { + $themes[$extension] = $extension; } } } /** - * Command callback. Show detailed info for one or more projects. + * Command callback. Show detailed info for one or more extension. */ function drush_pm_info() { - $args = func_get_args(); + $args = _convert_csv_to_array(func_get_args()); - $project_info = drush_pm_get_projects(); - _drush_pm_expand_projects($args, $project_info); + $extension_info = drush_pm_get_extensions(); + _drush_pm_expand_extensions($args, $extension_info); foreach ($args as $project) { - if (isset($project_info[$project])) { - $info = $project_info[$project]; + if (isset($extension_info[$project])) { + $info = $extension_info[$project]; } else { drush_set_error('DRUSH_PM_INFO_PROJECT_NOT_FOUND', dt('!project was not found.', array('!project' => $project))); @@ -587,9 +646,9 @@ function drush_pm_info() { } /** - * Return a string with general info of a project (module or theme). + * Return a string with general info of a extension. */ -function _drush_pm_info_project($info) { +function _drush_pm_info_extension($info) { $major_version = drush_drupal_major_version(); $data['Project'] = $info->name; @@ -606,7 +665,7 @@ function _drush_pm_info_project($info) { if ($major_version == 6) { $data['PHP'] = $info->info['php']; } - $data['Status'] = drush_get_project_status($info); + $data['Status'] = drush_get_extension_status($info); $path = (($info->type == 'module')&&($major_version == 7))?$info->uri:$info->filename; $path = substr($path, 0, strrpos($path, '/')); $data['Path'] = $path; @@ -620,7 +679,7 @@ function _drush_pm_info_project($info) { function _drush_pm_info_module($info) { $major_version = drush_drupal_major_version(); - $data = _drush_pm_info_project($info); + $data = _drush_pm_info_extension($info); if ($info->schema_version > 0) { $schema_version = $info->schema_version; } @@ -628,7 +687,7 @@ function _drush_pm_info_module($info) { $schema_version = "no schema installed"; } else { - $schema_version = "module has no schema"; + $schema_version = "module has no schema"; } $data['Schema version'] = $schema_version; if ($major_version == 7) { @@ -661,7 +720,7 @@ function _drush_pm_info_module($info) { function _drush_pm_info_theme($info) { $major_version = drush_drupal_major_version(); - $data = _drush_pm_info_project($info); + $data = _drush_pm_info_extension($info); if ($major_version == 5) { $data['Engine'] = $info->description; } @@ -691,29 +750,29 @@ function _drush_pm_info_theme($info) { } /** - * Add sub projects that match project_name*. + * Add extensions that match extension_name*. * - * A helper function for commands that take a space separated list of project - * names. It will identify project names that have been passed in with a - * trailing * and add all matching projects to the array that is returned. + * A helper function for commands that take a space separated list of extension + * names. It will identify extensions that have been passed in with a + * trailing * and add all matching extensions to the array that is returned. * - * @param $projects - * An array of projects, by reference. - * @param $project_info - * Optional. An array of project info as returned by drush_get_projects(). + * @param $extensions + * An array of extensions, by reference. + * @param $extension_info + * Optional. An array of extension info as returned by drush_get_extensions(). */ -function _drush_pm_expand_projects(&$projects, $project_info = array()) { - if (empty($project_info)) { - $project_info = drush_get_projects(); - } - foreach ($projects as $key => $project) { - if (($wildcard = rtrim($project, '*')) !== $project) { - foreach (array_keys($project_info) as $project_name) { - if (strpos($project_name, $wildcard) !== FALSE) { - $projects[] = $project_name; +function _drush_pm_expand_extensions(&$extensions, $extension_info = array()) { + if (empty($extension_info)) { + $extension_info = drush_get_extensions(); + } + foreach ($extensions as $key => $extension) { + if (($wildcard = rtrim($extension, '*')) !== $extension) { + foreach (array_keys($extension_info) as $extension_name) { + if (strpos($extension_name, $wildcard) !== FALSE) { + $extensions[] = $extension_name; } } - unset($projects[$key]); + unset($extensions[$key]); continue; } } @@ -724,7 +783,7 @@ function _drush_pm_expand_projects(&$projects, $project_info = array()) { * // TODO: Use drupal_execute on system_modules_uninstall_confirm_form so that input is validated. */ function drush_pm_uninstall() { - $modules = func_get_args(); + $modules = _convert_csv_to_array(func_get_args()); drush_include_engine('drupal', 'environment'); $module_info = drush_get_modules(); @@ -786,62 +845,131 @@ function pm_true() { } /** - * We need to set the project path by looking at the module location. Ideally, update.module would do this for us. + * Completes projects' update data with the path to install location on disk. * - * TODO: Improve logic so this works even if your project directory is not - * named the same as the project name. + * Given an array of release info for available projects, find the path to the install location. */ -function pm_get_project_path($projects, $lookup) { - foreach ($projects as $name => $project) { - if (!isset($project['path']) && $name != 'drupal') { - // looks for an enabled module. - foreach ($project[$lookup] as $filename => $title) { - if ($path = drupal_get_path($project['project_type'], $filename)) { +function _pm_get_project_path($data, $lookup) { + foreach ($data as $name => $release) { + if ($name == 'drupal') { + continue; + } + // Array of extensions (modules/themes) within the project. + $extensions = array_keys($release[$lookup]); + // Select the first path as the candidate to be the common prefix. + $path = drupal_get_path($release['project_type'], array_pop($extensions)); + // If there's only one extension we are done. Otherwise, we need to find + // the common prefix for all of them. + if (count($extensions) > 0) { + // Iterate over the other projects. + while($project = array_pop($extensions)) { + $path2 = drupal_get_path($release['project_type'], $project); + // Option 1: same path. + if ($path == $path2) { continue; } + // Option 2: $path is a prefix of $path2. + if (strpos($path2, $path) === 0) { + continue; + } + // Option 3: $path2 is a prefix of $path. + if (strpos($path, $path2) === 0) { + $path = $path2; + continue; + } + // Option 4: no one is a prefix of the other. Find the common + // prefix by iteratively strip the rigthtmost piece of $path. + // We will iterate until a prefix is found or path = '.', that on the + // other hand is a condition theorically impossible to reach. + do { + $path = dirname($path); + if (strpos($path2, $path) === 0) { + break; + } + } while ($path != '.'); } - // As some modules are not located in their project's root directory - // but in a subdirectory (e.g. all the ecommerce modules), we take the module's - // info file's path, and then move up until we are at a directory with the - // project's name. - $parts = explode('/', $path); - $i = count($parts) - 1; - $stop = array_search($name, $parts); - while ($i > $stop) { - unset($parts[$i]); - $i--; - } - $projects[$name]['path'] = implode('/', $parts); } + $data[$name]['path'] = $path; } - return $projects; + + return $data; } /** - * A drush command callback. Show release info for given project(s). - * - **/ + * Command callback. Show available releases for given project(s). + */ function drush_pm_releases() { + if (!$requests = _convert_csv_to_array(func_get_args())) { + $requests = array('drupal'); + } + + $info = _drush_pm_get_releases($requests); + if (!$info) { + return drush_log(dt('No valid projects given.'), 'ok'); + } + + foreach ($info as $name => $project) { + $header = dt('------- RELEASES FOR \'!name\' PROJECT -------', array('!name' => strtoupper($name))); + $rows = array(); + $rows[] = array(dt('Release'), dt('Date'), dt('Status')); + foreach ($project['releases'] as $release) { + $rows[] = array( + $release['version'], + format_date($release['date'], 'custom', 'Y-M-d'), + implode(', ', $release['release_status']) + ); + } + drush_print($header); + drush_print_table($rows, TRUE, array(0 => 14)); + } +} + +/** + * Get releases for given projects and fill in status information. + * + * @param $projects + * An array of drupal.org projects. + * + * @see drush_pm_releases() + * @see _drush_pm_releasenotes() + */ +function _drush_pm_get_releases($projects) { // We don't provide for other options here, so we supply an explicit path. drush_include_engine('update_info', 'drupal', NULL, DRUSH_BASE_PATH . '/commands/pm/update_info'); - $projects = func_get_args(); $projects = drupal_map_assoc($projects); $info = pm_get_project_info($projects); - $project_info = drush_get_projects(); + foreach ($projects as $project) { + if (!isset($info[$project])) { + drush_set_error('DRUSH_PM_PROJECT_NOT_FOUND', dt("No release history was found for the requested project (!project).", array('!project' => $project))); + continue; + } + if ($info[$project]['project_status'] == 'unpublished') { + drush_set_error('DRUSH_PM_PROJECT_UNPUBLISHED', dt("Project !project is unpublished ans has no releases available.", array('!project' => $key)), 'warning'); + unset($info[$key]); + continue; + } + } + + // Info on extensions present in the bootstrapped site will be used to + // identify if any project release is installed. + $extension_info = drush_get_extensions(); - $rows[] = array(dt('Project'), dt('Release'), dt('Date'), dt('Status')); + // Iterate the projects to collect relevant information. foreach ($info as $key => $project) { + // Find latest and recommended versions based on the recommended + // major version. $recommended = isset($project['recommended_major'])?$project['recommended_major']:NULL; - $supported = isset($project['supported_majors'])?explode(',', $project['supported_majors']):array(); - $default = $project['default_major']; $recommended_version = NULL; $latest_version = NULL; foreach ($project['releases'] as $release) { + //Only consider releases of recommended major version. if ($release['version_major'] == $recommended) { + // First release is the latest version. if (!isset($latest_version)) { $latest_version = $release['version']; } + // First stable release is the recommended version. if (empty($release['version_extra'])) { if (!isset($recommended_version)) { $recommended_version = $release['version']; @@ -849,25 +977,36 @@ function drush_pm_releases() { } } } + // If no stable release of recomended major version found, set + // latest version as the recommended one. if (!isset($recommended_version)) { $recommended_version = $latest_version; } - foreach ($project['releases'] as $release) { + + // Iterate the releases to fill in status data. + $supported = isset($project['supported_majors'])?explode(',', $project['supported_majors']):array(); + foreach ($project['releases'] as $version => $release) { $status = array(); - $type = array(); if (($k = array_search($release['version_major'], $supported)) !== FALSE) { $status[] = dt('Supported'); unset($supported[$k]); } - if ((isset($recommended_version)) && ($release['version'] == $recommended_version)) { + if ((isset($recommended_version)) && ($version == $recommended_version)) { $status[] = dt('Recommended'); + $info[$key]['recommended'] = $version; } if ($release['version_extra'] == 'dev') { $status[] = dt('Development'); } - if (isset($project_info[$key])) { - if ($project_info[$key]->info['version'] == $release['version']) { + // #TODO# Note here we suffer the project<->package-name problem: + // Following only works for modules/themes with name with + // drupal.org's project name (zen->zen but not for cck->content). + // See _drush_pm_releasenotes() for an option to get that info + // from update's project data. + if (isset($extension_info[$key])) { + if ($extension_info[$key]->info['version'] == $version) { $status[] = dt('Installed'); + $info[$key]['installed'] = $version; } } if (isset($release['terms']) && array_key_exists('Release type', $release['terms'])) { @@ -877,20 +1016,162 @@ function drush_pm_releases() { } } } - $rows[] = array( - $key, - $release['version'], - format_date($release['date'], 'custom', 'Y-M-d'), - implode(', ', $status) - ); + $info[$key]['releases'][$version]['release_status'] = $status; } } - if (count($rows) == 1) { - return drush_set_error('DRUSH_PM_PROJECT_NOT_FOUND', dt('No information available.')); + return $info; +} + +/** + * Command callback. Show release notes for given project(s). + */ +function drush_pm_releasenotes() { + if (!$requests = _convert_csv_to_array(func_get_args())) { + $requests = array('drupal'); } - else { - return drush_print_table($rows, TRUE); + + return _drush_pm_releasenotes($requests); +} + +/** + * Internal function: prints release notes for given drupal projects. + * + * @param $requests + * An array of drupal.org project names optionally with a version. + * @param $print_status + * Boolean. Used by pm-download to not print a informative note. + * + * @see drush_pm_releasenotes() + */ +function _drush_pm_releasenotes($requests, $print_status = TRUE) { + // Parse requests to strip versions. + $requests = pm_parse_project_version($requests); + // Get the releases. + $info = _drush_pm_get_releases(array_keys($requests)); + if (!$info) { + return drush_log(dt('No valid projects given.'), 'ok'); + } + + foreach ($info as $key => $project) { + $selected_versions = array(); + // #TODO# project<->package-name problem. See _drush_pm_get_releases(). + if (!isset($project['installed'])) { + // Installed package's version (e.g. cck) is detected by + // _drush_pm_get_releases(). Attempted to obtain information + // about it from update_cache. + $cache_project_info = _update_cache_get('update_project_projects'); + if (isset($cache_project_info->data[$key])) { + $project['installed'] = $cache_project_info->data[$key]['info']['version']; + } + } + // If the request included version, only show its release notes. + if (isset($requests[$key]['version'])) { + $selected_versions[] = $requests[$key]['version']; + } + else { + // Special handling if the project is installed. + if (isset($project['recommended'], $project['installed'])) { + $releases = array_reverse($project['releases']); + foreach($releases as $version => $release) { + if ($release['date'] >= $project['releases'][$project['installed']]['date']) { + if ($release['version_extra'] == 'dev' && $project['releases'][$project['installed']]['version_extra'] != 'dev') { + continue; + } + $selected_versions[] = $version; + } + } + } + else { + // Project is not installed so we will show the release notes + // for the recommended version, as the user did not specify one. + $selected_versions[] = $project['recommended']; + } + } + + foreach ($selected_versions as $version) { + // Stage of parsing. + if (!isset($project['releases'][$version]['release_link'])) { + // We avoid the cases where the URL of the release notes does not exist. + drush_log(dt("Project !project does not have release notes for version !version.", array('!project' => $key, '!version' => $version)), 'warning'); + continue; + } + else { + $release_page_url = $project['releases'][$version]['release_link']; + } + $release_page_url_parsed = parse_url($release_page_url); + $release_url_path = $release_page_url_parsed['path']; + if (!empty($release_url_path)) { + if ($release_page_url_parsed['host'] == 'drupal.org') { + $release_page_id = substr($release_url_path, strlen('/node/')); + drush_log(dt("Release link for !project (!version) project was found.", array('!project' => $key, '!version' => $version)), 'notice'); + } + else { + drush_log(dt("Release notes' page for !project project is not hosted on drupal.org. See !url.", array('!project' => $key, '!url' => $release_page_url)), 'warning'); + continue; + } + } + $data = drupal_http_request($release_page_url); + if (isset($data->error)) { + drush_log(dt("Error (!error) while requesting the release notes' page for !project project.", array('!error' => $data->error, '!project' => $key)), 'error'); + continue; + } + @$dom = DOMDocument::loadHTML($data->data); + if ($dom) { + drush_log(dt("Successfully parsed and loaded the HTML contained in the release notes' page for !project (!version) project.", array('!project' => $key, '!version' => $version)), 'notice'); + } + $xml = simplexml_import_dom($dom); + $xpath_expression = '//*[@id="node-' . $release_page_id . '"]/div[@class="node-content"]'; + $node_content = $xml->xpath($xpath_expression); + + // We create the print format. + $notes_last_update = $node_content[0]->div[1]->div[0]->asXML(); + unset($node_content[0]->div); + $project_notes = $node_content[0]->asXML(); + if (isset($project['installed'])) { + if (isset($request['version'])) { + $status_msg = 'Note: type in only the project name to see the release notes for all newer versions.'; + } + else { + if ($version == $project['installed']) { + $status_msg = 'Note: this is the installed version.'; + if ($version == $project['recommended']) { + $status_msg = 'Note: this is the installed & recommended version.'; + } + } + elseif ($version == $project['recommended']) { + $status_msg = 'Note: this is the recommended version.'; + } + else { + $status_msg = dt('Note: the installed version is !version.', array('!version' => $project['installed'])); + } + } + } + else { + $status_msg = 'Note: specific non-installed version.'; + if (!isset($request['version'])) { + $status_msg = 'Note: recommended non-installed version.'; + } + } + // #TODO# d5 compatibility && try a cleaner way to do this. + if (drush_drupal_major_version() < 7) { + $break = '<br>'; + $status_msg = '> ' . $status_msg; + } + else { + $break = ' -<hr>'; + $status_msg = '> ' . $status_msg . ' -<hr>'; + } + $notes_header = dt("<hr> + > RELEASE NOTES FOR '!name' PROJECT, VERSION !version:!break + > !time.!break + !status + <hr>", array('!status' => $print_status ? $status_msg : '', '!name' => strtoupper($key), '!break' => $break, '!version' => $version, '!time' => $notes_last_update)); + // Finally we print the release notes for the requested project. + $print = drupal_html_to_text($notes_header . $project_notes, array('br', 'p', 'ul', 'ol', 'li', 'hr')); + drush_print($print); + if (drush_drupal_major_version() < 7) {drush_print();} + } } } @@ -905,7 +1186,7 @@ function drush_pm_refresh() { } /** - * Command callback. Execute updatecode. + * Command callback. Execute pm-update. */ function drush_pm_update() { // Signal that we will update drush core after the drush modules @@ -913,7 +1194,7 @@ function drush_pm_update() { drush_set_context('DRUSH_PM_UPDATE_ALL', TRUE); // Call pm-updatecode. updatedb will be called in the post-update process. - $args = func_get_args(); + $args = _convert_csv_to_array(func_get_args()); array_unshift($args, 'pm-updatecode'); call_user_func_array('drush_invoke', $args); @@ -937,9 +1218,34 @@ function drush_pm_post_pm_update() { } /** - * Post-command callback for updatecode. Notify about any pending DB updates. + * Validate callback for updatecode command. Abort if 'backup' folder exists. + */ +function drush_pm_updatecode_validate() { + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + if (is_dir($drupal_root . '/' . 'backup')) { + return drush_set_error('DRUSH_PM_BACKUP_DIR_FORBIDDEN', dt('It\'s a security risk to have a backup folder in your Drupal root. You must move it elsewhere before proceeding.')); + } +} + +/** + * Post-command callback for updatecode. + * + * Execute pm-updatecode-notify-pending-db-updates in a backend process to not + * conflict with old code already in memory. */ function drush_pm_post_pm_updatecode() { + // Skip if updatecode was invoked by pm-update. + // This way we avoid being noisy, as updatedb is to be executed. + $command = drush_get_command(); + if ($command['command'] != 'pm-update') { + drush_backend_invoke('pm-updatecode-notify-pending-db-updates'); + } +} + +/** + * Command callback. Execute updatecode-notify-pending-db-updates. + */ +function drush_pm_updatecode_notify_pending_db_updates() { // Make sure the installation API is available require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc'; @@ -959,41 +1265,6 @@ function drush_pm_post_pm_updatecode() { } } - -/** - * Deletes a directory, all files in it and all subdirectories in it (recursively). - * Use with care! - * Written by Andreas Kalsch - */ -function delete_dir($dir) { - if (substr($dir, strlen($dir)-1, 1) != '/') - $dir .= '/'; - - if ($handle = opendir($dir)) { - while ($obj = readdir($handle)) { - if ($obj != '.' && $obj != '..') { - if (is_dir($dir.$obj)) { - if (!delete_dir($dir.$obj)) { - return false; - } - } - elseif (is_file($dir.$obj)) { - if (!unlink($dir.$obj)) { - return false; - } - } - } - } - - closedir($handle); - - if (!@rmdir($dir)) { - } - return true; - } - return false; -} - /** * Determine a candidate destination directory for a particular site path and * return it if it exists, optionally attempting to create the directory. @@ -1002,28 +1273,26 @@ function pm_dl_destination_lookup($type, $drupal_root, $sitepath, $create = FALS switch ($type) { case 'module': // Prefer sites/all/modules/contrib if it exists. - $destination = $sitepath . 'modules/'; - $contrib = $destination . 'contrib/'; + $destination = $sitepath . '/modules'; + $contrib = $destination . '/contrib'; if (is_dir($contrib)) { $destination = $contrib; } break; case 'theme': - $destination = $sitepath . 'themes/'; + $destination = $sitepath . '/themes'; break; case 'theme engine': - $destination = $sitepath . 'themes/engines/'; - break; - case 'translation': - $destination = $drupal_root . '/'; + $destination = $sitepath . '/themes/engines'; break; + //case 'translation': We never hit this case. case 'profile': - $destination = $drupal_root . '/profiles/'; + $destination = $drupal_root . '/profiles'; break; } if ($create) { drush_log(dt('Attempting to create destination directory at !dir', array('!dir' => $destination))); - @drush_op('mkdir', $destination, 0777, TRUE); + drush_mkdir($destination); } if (is_dir($destination)) { drush_log(dt('Using destination directory !dir', array('!dir' => $destination))); @@ -1034,64 +1303,56 @@ function pm_dl_destination_lookup($type, $drupal_root, $sitepath, $create = FALS } /** - * Return the best destination for a particular download type we can find, - * given the drupal and site contexts. + * Returns the best destination for a particular download type we can find. + * + * It is based on the project type and drupal and site contexts. */ function pm_dl_destination($type) { - // Attempt 0: Use the user specified destination directory, if it exists. - $destination = drush_get_option('destination'); - if (!empty($destination)) { - $destination = rtrim($destination, '/') . '/'; - if (!is_dir($destination)) { - drush_print(dt("The directory !destination does not exist.", array('!destination' => $destination))); - if (!drush_get_context('DRUSH_SIMULATE')) { - if (drush_confirm(dt('Would you like to create it?'))) { - @drush_op('mkdir', $destination, 0777, TRUE); - } - } - } - if (is_dir($destination)) { - return $destination; - } - else { - return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('The destination directory !destination does not appear to exist.', array('!destination' => $destination))); - } - } - $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); $site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT', FALSE); - $full_site_root = $drupal_root .'/'. $site_root . '/'; - $sites_all = $drupal_root . '/sites/all/'; + $full_site_root = $drupal_root .'/'. $site_root; + $sites_all = $drupal_root . '/sites/all'; $in_site_directory = FALSE; // Check if we are running within the site directory. - if ($full_site_root == substr(drush_cwd() . '/', 0, strlen($full_site_root))) { + if ($full_site_root == substr(drush_cwd(), 0, strlen($full_site_root)) || (drush_get_option('use-site-dir', FALSE))) { $in_site_directory = TRUE; } - // Attempt 1: If we are in a specific site directory, and the destination directory already exists, then we use that. - if (empty($destination) && $site_root && $in_site_directory) { - $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root); - } - // Attempt 2: If the destination directory already exists for sites/all, then we use that. - if (empty($destination) && $drupal_root) { - $destination = pm_dl_destination_lookup($type, $drupal_root, $sites_all); - } - // Attempt 3: If a specific (non default) site directory exists and sites/all does not exist, then we create destination in the site specific directory. - if (empty($destination) && $site_root && $site_root !== 'sites/default' && is_dir($full_site_root) && !is_dir($sites_all)) { - $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root, TRUE); - } - // Attempt 4: If sites/all exists, then we create destination in the sites/all directory. - if (empty($destination) && is_dir($sites_all)) { - $destination = pm_dl_destination_lookup($type, $drupal_root, $sites_all, TRUE); - } - // Attempt 5: If site directory exists (even default), then we create destination in the this directory. - if (empty($destination) && $site_root && is_dir($full_site_root)) { - $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root, TRUE); + $destination = ''; + if ($type != 'core') { + // Attempt 1: If we are in a specific site directory, and the destination + // directory already exists, then we use that. + if (empty($destination) && $site_root && $in_site_directory) { + $create_dir = drush_get_option('use-site-dir', FALSE); + $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root, $create_dir); + } + // Attempt 2: If the destination directory already exists for sites/all, + // then we use that. + if (empty($destination) && $drupal_root) { + $destination = pm_dl_destination_lookup($type, $drupal_root, $sites_all); + } + // Attempt 3: If a specific (non default) site directory exists and + // sites/all does not exist, then we create destination in the site + // specific directory. + if (empty($destination) && $site_root && $site_root !== 'sites/default' && is_dir($full_site_root) && !is_dir($sites_all)) { + $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root, TRUE); + } + // Attempt 4: If sites/all exists, then we create destination in the + // sites/all directory. + if (empty($destination) && is_dir($sites_all)) { + $destination = pm_dl_destination_lookup($type, $drupal_root, $sites_all, TRUE); + } + // Attempt 5: If site directory exists (even default), then we create + // destination in the this directory. + if (empty($destination) && $site_root && is_dir($full_site_root)) { + $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root, TRUE); + } } - // Attempt 6: If we didn't find a valid directory yet (or we somehow found one that doesn't exist) we always fall back to the current directory. + // Attempt 6: If we didn't find a valid directory yet (or we somehow found + // one that doesn't exist) we always fall back to the current directory. if (empty($destination) || !is_dir($destination)) { - $destination = drush_cwd() . '/'; + $destination = drush_cwd(); } return $destination; @@ -1192,10 +1453,13 @@ function pm_drush_engine_package_handler() { 'cvs' => array( 'options' => array( '--package-handler=cvs' => 'Use CVS to checkout and update projects.', - ' --cvsparams' => 'Add options to the `cvs` program', - ' --cvsmethod' => 'Force cvs updates or checkouts (checkout is default unless the directory is managed by a supported version control system).', - ' --cvscredentials' => 'A username and password that is sent for cvs checkout command. Defaults to anonymous:anonymous', - + ), + 'sub-options' => array( + '--package-handler=cvs' => array( + '--cvsparams' => 'Add options to the `cvs` program', + '--cvsmethod' => 'Force cvs updates or checkouts (checkout is default unless the directory is managed by a supported version control system).', + '--cvscredentials' => 'A username and password that is sent for cvs checkout command. Defaults to anonymous:anonymous', + ), ), 'examples' => array( 'drush [command] cck --cvscredentials=\"name:password\"' => 'Checkout should use these credentials.', @@ -1203,6 +1467,12 @@ function pm_drush_engine_package_handler() { 'drush [command] cck --cvsmethod=update' => 'Will update the project, and try to merge changes, rather than overwriting them. Any conflicts will need to be resolved manually.', ), ), + 'git_drupalorg' => array( + 'options' => array( + '--package-handler=git_drupalorg' => 'Use git.drupal.org to checkout and update projects.', + '--gitsubmodule' => 'Use git submodules for checking out new projects. Existing git checkouts are unaffected, and will continue to (not) use submodules regardless of this setting.', + ), + ), ); } @@ -1211,36 +1481,51 @@ function pm_drush_engine_package_handler() { */ function pm_drush_engine_version_control() { return array( + 'backup' => array( + 'options' => array( + '--version-control=backup' => 'Default engine. Backup all project files before updates.', + ), + 'sub-options' => array( + '--version-control=backup' => array( + '--backup-dir' => 'Specify a directory to backup packages into. Defaults to .drush-backups within the home directory of the user running the command. It is forbidden to specify a directory inside your drupal root.', + ), + ), + ), 'svn' => array( 'signature' => 'svn info %s', 'options' => array( '--version-control=svn' => 'Quickly add/remove/commit your project changes to Subversion.', - ' --svnsync' => 'Automatically add new files to the SVN repository and remove deleted files. Caution.', - ' --svncommit' => 'Automatically commit changes to SVN repository. You must also using the --svnsync option.', - ' --svnmessage' => 'Override default commit message which is: Drush automatic commit: <the drush command line used>', - ' --svnstatusparams' => "Add options to the 'svn status' command", - ' --svnaddparams' => 'Add options to the `svn add` command', - ' --svnremoveparams' => 'Add options to the `svn remove` command', - ' --svnrevertparams' => 'Add options to the `svn revert` command', - ' --svncommitparams' => 'Add options to the `svn commit` command', + ), + 'sub-options' => array( + '--version-control=svn' => array( + '--svnsync' => 'Automatically add new files to the SVN repository and remove deleted files. Caution.', + '--svncommit' => 'Automatically commit changes to SVN repository. You must also using the --svnsync option.', + '--svnmessage' => 'Override default commit message which is: Drush automatic commit: <the drush command line used>', + '--svnstatusparams' => "Add options to the 'svn status' command", + '--svnaddparams' => 'Add options to the `svn add` command', + '--svnremoveparams' => 'Add options to the `svn remove` command', + '--svnrevertparams' => 'Add options to the `svn revert` command', + '--svncommitparams' => 'Add options to the `svn commit` command', + ), ), 'examples' => array( 'drush [command] cck --svncommitparams=\"--username joe\"' => 'Commit changes as the user \'joe\' (Quotes are required).' ), ), - 'backup' => array( - 'options' => array( - '--version-control=backup' => 'Backup all project files before updates.', - ' --backup-dir' => 'Backup destination directory. Defaults to a "/backup" subdirectory inside your Drupal root.', - ), - ), 'bzr' => array( 'signature' => 'bzr root %s', 'options' => array( '--version-control=bzr' => 'Quickly add/remove/commit your project changes to Bazaar.', - ' --bzrsync' => 'Automatically add new files to the Bazaar repository and remove deleted files. Caution.', - ' --bzrcommit' => 'Automatically commit changes to Bazaar repository. You must also using the --bzrsync option.', - ' --bzrmessage' => 'Override default commit message which is: Drush automatic commit: <the drush command line used>', + ), + 'sub-options' => array( + '--version-control=bzr' => array( + '--bzrsync' => 'Automatically add new files to the Bazaar repository and remove deleted files. Caution.', + '--bzrcommit' => 'Automatically commit changes to Bazaar repository. You must also using the --bzrsync option.', + ' --bzrmessage' => 'Override default commit message which is: Drush automatic commit. Project <name> <type> Command: <the drush command line used>', + ), + ), + 'examples' => array( + 'drush dl cck --version-control=bzr --bzrsync --bzrcommit' => 'Download the cck module and then add it and commit it to Bazaar.' ), ), ); @@ -1264,7 +1549,7 @@ interface drush_pm_version_control { * specific directory. */ function drush_pm_include_version_control($directory = '.') { - $version_controls = explode(',', drush_get_option('version-control', 'svn,backup')); + $version_controls = explode(',', drush_get_option('version-control', 'svn,backup,bzr')); $version_control_engines = drush_get_engines('version_control'); // Find the first valid engine in the list, checking signatures if needed. @@ -1293,183 +1578,495 @@ function drush_pm_include_version_control($directory = '.') { } /** - * Command callback. Download Drupal core or any project. + * Implementation of drush_COMMAND_init(). + * + * pm-download doesn't require any bootstrap level to operate but it needs to + * know if running on a drupal directory in order to select the best location + * for projects to download. */ -function drush_pm_download() { - // Bootstrap to the highest level possible. +function drush_pm_download_init() { drush_bootstrap_max(); +} + +/** + * Implementation of drush_COMMAND_validate(). + */ +function drush_pm_download_validate() { + // Validate the user specified destination directory. + $destination = drush_get_option('destination'); + if (!empty($destination)) { + $destination = rtrim($destination, DIRECTORY_SEPARATOR); + if (!is_dir($destination)) { + drush_print(dt("The directory !destination does not exist.", array('!destination' => $destination))); + if (!drush_get_context('DRUSH_SIMULATE')) { + if (drush_confirm(dt('Would you like to create it?'))) { + drush_mkdir($destination); + } + if (!is_dir($destination)) { + return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Unable to create destination directory !destination.', array('!destination' => $destination))); + } + } + } + if (!is_writable($destination)) { + return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Destination directory !destination is not writable.', array('!destination' => $destination))); + } + // Ignore --use-site-dir, if given. + if (drush_get_option('use-site-dir', FALSE)) { + drush_set_option('use-site-dir', FALSE); + } + } + + // Validate --variant or enforce a sane default. + $variant = drush_get_option('variant', FALSE); + if ($variant) { + if (!in_array($variant, array('core', 'no-core', 'make'))) { + drush_log(dt('Unknown variant !variant. Valid values: !variations', array('!variant' => $variant, '!variations' => implode(', ', $variations))), 'error'); + } + } + // core and no-core variants are only valid for wget package handler. + $package_handler = drush_get_option('package-handler', 'wget'); + if (($package_handler != 'wget') && ($variant != 'make')) { + $new_variant = 'make'; + if ($variant) { + drush_log(dt('Variant !variant is incompatible with !ph package-handler.', array('!variant' => $variant, '!ph' => $package_handler)), 'warning'); + } + } + // If we are working on a drupal root, core variant is not an option. + else if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) { + if ((!$variant) || (($variant == 'core') && (!isset($new_variant)))) { + $new_variant = 'no-core'; + } + if ($variant == 'core') { + drush_log(dt('Variant core is not a valid option within a Drupal root.'), 'warning'); + } + } - drush_include_engine('package_handler', drush_get_option('package-handler', 'wget')); + if (isset($new_variant)) { + drush_set_option('variant', $new_variant); + if ($variant) { + drush_log(dt('Switching to --variant=!variant.', array('!variant' => $new_variant)), 'ok'); + } + } + + // At present we depend on update module to show release notes. + $notes = TRUE; + if (drush_get_option('notes', FALSE)) { + $update = 'update'; + if (drush_drupal_major_version() == 5) { + $update = 'update_status'; + } + if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') < DRUSH_BOOTSTRAP_DRUPAL_FULL) { + $msg = dt('A higher bootstrap level is needed to show release notes.'); + $notes = FALSE; + } + elseif (!module_exists($update)) { + $msg = dt('!module is required to show release notes.', array('!module' => $update)); + $notes = FALSE; + } + if (!$notes) { + drush_log($msg . ' ' . dt('Disabling --notes option.'), 'warning'); + drush_set_option('notes', FALSE); + } + } +} + +/** + * Command callback. Download Drupal core or any project. + */ +function drush_pm_download() { + $package_handler = drush_get_option('package-handler', 'wget'); + drush_include_engine('package_handler', $package_handler); - if (!$requests = func_get_args()) { + if (!$requests = _convert_csv_to_array(func_get_args())) { $requests = array('drupal'); } - // Parse out project name and version + // Parse out project name and version. $requests = pm_parse_project_version($requests); + // Get release history for each request and download the project. $project_types = pm_project_types(); $project_types_xpath = '(value="' . implode('" or value="', $project_types) . '")'; foreach ($requests as $name => $request) { - // Don't rely on UPDATE_DEFAULT_URL since we are not fully bootstrapped. + // Cleanup. We need to unset those variables to avoid using garbage from + // previous iteration. + unset($error, $release, $releases, $types); + + // Don't rely on UPDATE_DEFAULT_URL since perhaps we are not fully + // bootstrapped. $url = drush_get_option('source', 'http://updates.drupal.org/release-history') . '/' . $request['name'] . '/' . $request['drupal_version']; drush_log('Downloading release history from ' . $url); - // A simple download, which is never available via CVS. // Some hosts have allow_url_fopen disabled. if (!$xml = @simplexml_load_file($url)) { if (!drush_shell_exec("wget $url")) { drush_shell_exec("curl -O $url"); } - // Get the filename... $filename = explode('/', $url); $filename = array_pop($filename); $xml = simplexml_load_file($filename); drush_op('unlink', $filename); } + if (!$xml) { + // We are not getting here since drupal.org always serves an XML response. + drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Could not download project status information from !url', array('!url' => $url))); + continue; + } + if ($error = $xml->xpath('/error')) { + drush_set_error('DRUSH_PM_COULD_NOT_LOAD_UPDATE_FILE', $error[0]); + continue; + } - if ($xml) { - if ($error = $xml->xpath('/error')) { - drush_set_error('DRUSH_PM_COULD_NOT_LOAD_UPDATE_FILE', $error[0]); + // Try to get the specified release. + if (!empty($request['version'])) { + $releases = $xml->xpath("/project/releases/release[status='published'][version='" . $request['version'] . "']"); + if (empty($releases)) { + drush_log(dt("Could not locate specified project version, downloading latest stable version"), 'warning'); } - else { - // Try to get the specified release. - if (!empty($request['version'])) { - $releases = $xml->xpath("/project/releases/release[status='published'][version='" . $request['version'] . "']"); - if (empty($releases)) { - drush_log(dt("Could not locate specified project version, downloading latest stable version"), 'notice'); - } - } - // If that did not work, get the first published release for the recommended major version. - if (empty($releases)) { - if ($recommended_major = $xml->xpath("/project/recommended_major")) { - $xpath_releases = "/project/releases/release[status='published'][version_major=" . (string)$recommended_major[0] . "]"; - $releases = @$xml->xpath($xpath_releases); - } + } + // If that did not work, we will get the first published release for the + // recommended major version. + if (empty($releases)) { + if ($recommended_major = $xml->xpath("/project/recommended_major")) { + $xpath_releases = "/project/releases/release[status='published'][version_major=" . (string)$recommended_major[0] . "]"; + $releases = @$xml->xpath($xpath_releases); + } + } + // If there are recommended releases (no 'version_extra' elements), then use + // only recommended releases. Otherwise, use all; in this case, the + // recommended release defaults to the latest published release with the + // right recommended major version number. + $recommended_releases = array(); + if (!empty($releases)) { + foreach ($releases as $one_release) { + if (!array_key_exists('version_extra', $one_release)) { + $recommended_releases[] = $one_release; } + } + } + if (!empty($recommended_releases)) { + $releases = $recommended_releases; + } + if (empty($releases)) { + drush_log(dt('There is no *recommended* release for project !project on Drupal !drupal_version. Ask the maintainer to review http://drupal.org/node/197584 and create/recommend a release in order to be compatible with drush and the drupal.org security broadcast system. A recommended development snapshot release is sufficient. Alternatively, run pm-releases command and explicity pm-download any non-recommended release that might be available.', array('!drupal_version' => $request['drupal_version'], '!project' => $request['name'])), 'ok'); + continue; + } - // If there are recommended releases (no 'version_extra' elements), - // then use only recommended releases. Otherwise, use all; in - // this case, the recommended release defaults to the latest published - // release with the right recommended major version number. - $recommended_releases = array(); - if (!empty($releases)) { - foreach ($releases as $one_release) { - if (!array_key_exists('version_extra', $one_release)) { - $recommended_releases[] = $one_release; - } - } - } + // Determine what type of project we are to download. + $request['project_type'] = 'module'; + if ($types = $xml->xpath('/project/terms/term[name="Projects" and ' . $project_types_xpath . ']')) { + $request['project_type'] = array_search($types[0]->value, $project_types); + } - if (!empty($recommended_releases)) { - $releases = $recommended_releases; - } + // First published release for the recommended major version is just the + // first value in $releases. Special case for profiles, that have 3 + // variants(releases) for a given real release. + if ($request['project_type'] == 'profile') { + // Variants always come in a fixed order in releases list. + $variations = array('core', 'no-core', 'make'); + $variant = drush_get_option('variant', 'core'); + $release = (array)$releases[array_search($variant, $variations)]; + } + // Translations can't be obtained from cvs. + else if (($request['project_type'] == 'translation') && ($package_handler != 'wget')) { + drush_log(dt('Requested project !request is a translation package and can\'t be obtained from !ph.', array('!request' => $request['name'], '!ph' => $package_handler)), 'error'); + continue; + } + else { + $release = (array)$releases[0]; + } - if (empty($releases)) { - drush_log(dt('There is no *recommended* release for project !project on Drupal !drupal_version. Ask the maintainer to review http://drupal.org/node/197584 and create/recommend a release in order to be compatible with drush and the drupal.org security broadcast system. A recommended development snapshot release is sufficient. Alternatively, run pm-releases command and explicity pm-download any non-recommended release that might be available.', array('!drupal_version' => $request['drupal_version'], '!project' => $request['name'])), 'ok'); - continue; + // Determine the name of the directory that will contain the project. + // We face here all the asymetries to make it smooth for package handlers. + // For Drupal core: --drupal-project-rename or drupal-x.y + if ($request['project_type'] == 'core') { + if ($rename = drush_get_option('drupal-project-rename', FALSE)) { + if ($rename === TRUE) { + $request['project_dir'] = 'drupal'; } - - // Profiles have 3 variants for a given real relase. Admin can specify using option. - // Depends on a fixed order of variations in releases list. - // Usually, the first release is chosen. - $variations = array('core', 'no-core', 'make'); - $variant = drush_get_option('variant', 'core'); - $release = (array)$releases[array_search($variant, $variations)]; - - // Determine what type of project we have, so we know where to put it. - $release['type'] = 'module'; - - if ($types = $xml->xpath('/project/terms/term[name="Projects" and ' . $project_types_xpath . ']')) { - $release['type'] = array_search($types[0]->value, $project_types); + else { + $request['project_dir'] = $rename; } + } + else { + // Set to drupal-x.y, the expected name for .tar.gz contents. + // Explicitly needed for cvs package handler. + $request['project_dir'] = strtolower(strtr($release['name'], ' ', '-')); + } + } + // For the other project types we want the project name. Including core + // variant for profiles. Note those come with drupal-x.y in the .tar.gz. + else { + $request['project_dir'] = $request['name']; + } - // Create basic project array. - $project = $request; - if ($project['base_project_path'] = pm_dl_destination($release['type'])) { - $project['full_project_path'] = $project['base_project_path'] . $request['name']; - $project['project_type'] = $release['type']; - if (!$version_control = drush_pm_include_version_control($project['base_project_path'])) { - return FALSE; - } - if (package_handler_install_project($project, $release)) { - // If the --destination option was not specified, then - // allow commandfiles that implement the adjust-download-destination - // hook to pick a new default location for the project. - if (drush_get_option('destination', FALSE) === FALSE) { - drush_pm_relocate_project($project, $release); - } - drush_log(dt("Project !project (!version) downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $project['full_project_path'])), 'success'); - drush_command_invoke_all('drush_pm_post_download', $project, $release); - $version_control->post_download($project); - } - } + // Download the project to a temporary location. + $request['base_project_path'] = drush_tempdir(); + $request['full_project_path'] = $request['base_project_path'] .'/'. $request['project_dir']; + drush_log(dt('Downloading project !name to !dir ...', array('!name' => $request['name'], '!dir' => $request['base_project_path']))); + if (!package_handler_install_project($request, $release)) { + drush_log('Error downloading '.$request['name']); + continue; + } + + // Special case: translation projects. Content of .tar.gz is not within a + // common directory. + if ($request['project_type'] == 'translation') { + // If bootstrapped, copy the translation files over the drupal root and + // we are done. + if (defined('DRUPAL_ROOT')) { + drush_shell_exec('cp -r ' . $request['base_project_path'] . '/* ' . DRUPAL_ROOT); + drush_log(dt("Translation !project (!version) decompressed over !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => DRUPAL_ROOT)), 'success'); + continue; } + else { + // Move to its own directory, as any other project. + $olddir = drush_cwd(); + chdir($request['base_project_path']); + drush_shell_exec('mkdir ' . $request['project_dir']); + drush_shell_exec('mv * ' . $request['project_dir']); + chdir($olddir); + } + } + + // Determine the install location for the project. User provided + // --destination has preference. + $destination = drush_get_option('destination'); + if (!empty($destination)) { + $request['project_install_location'] = $destination; } else { - // We are not getting here since drupal.org always serves an XML response. - drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Could not download project status information from !url', array('!url' => $url))); + $request['project_install_location'] = pm_dl_destination($request['project_type']); } - unset($error, $release, $releases, $types); - } -} + // If user did not provide --destination, then call the + // download-destination-alter hook to give the chance to any commandfiles + // to adjust the install location. + if (empty($destination)) { + drush_command_invoke_all_ref('drush_pm_download_destination_alter', $request, $release); + } -/** - * drush_pm_relocate_project moves projects that should be relocated to a different - * installation directory to the location they belong in. For example, - * modules that are only collections of drush commands will be installed - * to $HOME/.drush. - * - * This function is called after the project is downloaded so that its - * contents can be examined to determine its optimal installation location. - * Every drush commandfile is given a chance to examine the project contents - * and decide where the project should be located. - */ -function drush_pm_relocate_project(&$project, $release) { - // Call the get-install-location hook to see if any drush commandfiles - // would like to adjust the install location for this project. - // drush_command_invoke_all() allows the first parameter to be passed by reference. - drush_command_invoke_all_ref('drush_pm_adjust_download_destination', $project, $release); + // Load version control engine and detect if (the parent directory of) the + // project install location is under a vcs. + if (!$version_control = drush_pm_include_version_control($request['project_install_location'])) { + continue; + } - if (isset($project['project_install_location']) && ($project['full_project_path'] != $project['project_install_location'])) { - if (drush_move_dir($project['full_project_path'], $project['project_install_location'], TRUE)) { - $project['full_project_path'] = $project['project_install_location']; + // Final project install location. + $request['project_install_location'] .= '/' . $request['project_dir']; + + // Check if install location already exists. + if (is_dir($request['project_install_location'])) { + if (!drush_confirm(dt('Install location !location already exists. Do you want to overwrite it?', array('!location' => $request['project_install_location'])))) { + drush_log(dt("Abort installation of !project to !dest.", array('!project' => $request['name'], '!dest' => $request['project_install_location'])), 'warning'); + continue; + } + } + + // Move the project to the install location. + if (drush_move_dir($request['full_project_path'], $request['project_install_location'], TRUE)) { + drush_log(dt("Project !project (!version) downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])), 'success'); + $request['base_project_path'] = basename($request['project_install_location']); + $request['full_project_path'] = $request['project_install_location']; } else { - drush_log(dt("Project !project (!version) could not be relocated to !dest.", array('!project' => $project['name'], '!version' => $release['version'], '!dest' => $project['project_install_location'])), 'warning'); + drush_log(dt("Project !project (!version) could not be downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])), 'error'); + continue; } + + // Post download actions. + drush_command_invoke_all('drush_pm_post_download', $request, $release); + $version_control->post_download($request); + + // Print release notes if --notes option is set. + if (drush_get_option('notes') && !drush_get_context('DRUSH_PIPE')) { + _drush_pm_releasenotes(array($name . '-' . $release['version']), FALSE); + } + + // Inform the user about available modules a/o themes in the downloaded package. + drush_pm_extensions_in_project($request); } - return TRUE; } /** - * Built-in adjust-download-destination hook. This particular version of + * Implementation of hook_drush_pm_download_destination_alter(). + * + * Built-in download-destination-alter hook. This particular version of * the hook will move modules that contain only drush commands to * /usr/share/drush/commands if it exists, or $HOME/.drush if the * site-wide location does not exist. */ -function pm_drush_pm_adjust_download_destination(&$project, $release) { - // If this project is a module, but it has no .module file, then - // check to see if it contains drush commands. If that is all - // that it contains, then install it to $HOME/.drush. - if ($release['type'] == 'module') { +function pm_drush_pm_download_destination_alter(&$project, $release) { + // A module is a pure drush command if it has no .module and contain + // .drush.inc files. + if ($project['project_type'] == 'module') { $module_files = drush_scan_directory($project['full_project_path'], '/.*\.module/'); if (empty($module_files)) { $drush_command_files = drush_scan_directory($project['full_project_path'], '/.*\.drush.inc/'); if (!empty($drush_command_files)) { - $install_dir = drush_get_context('SHARE_PREFIX', '/usr') . '/share/drush/commands/'; + $install_dir = drush_get_context('SHARE_PREFIX', '/usr') . '/share/drush/commands'; if (!is_dir($install_dir)) { - $install_dir = drush_server_home() . '/.drush/'; + $install_dir = drush_server_home() . '/.drush'; } - // Make the .drush dir if it does not already exist + // Make the .drush dir if it does not already exist. if (!is_dir($install_dir)) { mkdir($install_dir); } - // Change the location if the mkdir worked + // Change the location if the mkdir worked. if (is_dir($install_dir)) { - $project['project_install_location'] = $install_dir . basename($project['full_project_path']); + $project['project_install_location'] = $install_dir; + } + } + } + } +} + +/** + * Update the locked status of all of the candidate projects + * to be updated. + * + * @param array &$projects + * The projects array from pm_updatecode. $project['locked'] will + * be set for every file where a persistent lockfile can be found. + * The 'lock' and 'unlock' operations are processed first. + * @param array $projects_to_lock + * A list of projects to create peristent lock files for + * @param array $projects_to_unlock + * A list of projects to clear the persistent lock on + * @param string $lock_message + * The reason the project is being locked; stored in the lockfile. + * + * @return array + * A list of projects that are locked. + */ +function drush_pm_update_lock(&$projects, $projects_to_lock, $projects_to_unlock, $lock_message = NULL) { + $locked_result = array(); + + // Warn about ambiguous lock / unlock values + if ($projects_to_lock == array('1')) { + $projects_to_lock = array(); + drush_log(dt('Ignoring --lock with no value.'), 'warning'); + } + if ($projects_to_unlock == array('1')) { + $projects_to_unlock = array(); + drush_log(dt('Ignoring --unlock with no value.'), 'warning'); + } + + // Log if we are going to lock or unlock anything + if (!empty($projects_to_unlock)) { + drush_log(dt('Unlocking !projects', array('!projects' => implode(',', $projects_to_unlock))), 'ok'); + } + if (!empty($projects_to_lock)) { + drush_log(dt('Locking !projects', array('!projects' => implode(',', $projects_to_lock))), 'ok'); + } + + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + foreach ($projects as $name => $project) { + $message = NULL; + $lockfile = $drupal_root . '/' . $project['path'] . '/.drush-lock-update'; + + // Remove the lock file if the --unlock option was specified + if (((in_array($name, $projects_to_unlock)) || (in_array('all', $projects_to_unlock))) && (file_exists($lockfile))) { + drush_op('unlink', $lockfile); + } + + // Create the lock file if the --lock option was specified + if ((in_array($name, $projects_to_lock)) || (in_array('all', $projects_to_lock))) { + drush_op('file_put_contents', $lockfile, $lock_message != NULL ? $lock_message : "Locked via drush."); + // Note that the project is locked. This will work even if we are simulated, + // or if we get permission denied from the file_put_contents. + // If the lock is -not- simulated or transient, then the lock message will be + // read from the lock file below. + $message = drush_get_context('DRUSH_SIMULATE') ? 'Simulated lock.' : 'Transient lock.'; + } + + // If the persistent lock file exists, then mark the project as locked. + if (file_exists($lockfile)) { + $message = trim(file_get_contents($lockfile)); + } + + // If there is a message set, then mark the project as locked. + if (isset($message)) { + $projects[$name]['locked'] = !empty($message) ? $message : "Locked."; + $locked_result[$name] = $project; + } + } + + return $locked_result; +} + +/** + * Print out all extensions (modules/themes/profiles) found in specified project. + * + * Find .info files in the project path and identify modules, themes and + * profiles. It handles two kind of projects: drupal core/profiles and + * modules/themes. + * It does nothing with translation or theme engine projects. + */ +function drush_pm_extensions_in_project($project) { + // Mask for drush_scan_directory, to avoid tests directories. + $nomask = array('.', '..', 'CVS', 'tests'); + + // Drupal core and profiles can contain modules, themes and profiles. + if (in_array($project['project_type'], array('core', 'profile'))) { + $found = array('profile' => array(), 'theme' => array(), 'module' => array()); + // Find all of the .info files + foreach (drush_scan_directory($project['project_install_location'], "/.*\.info$/", $nomask) as $filename => $info) { + // Find the project type corresponding the .info file. + // (Only drupal >=7.x has .info for .profile) + $base = dirname($filename) . '/' . $info->name; + if (is_file($base . '.module')) { + $found['module'][] = $info->name; + } + else if (is_file($base . '.profile')) { + $found['profile'][] = $info->name; + } + else { + $found['theme'][] = $info->name; + } + } + // Special case: find profiles for drupal < 7.x (no .info) + if ($project['drupal_version'][0] < 7) { + foreach (drush_scan_directory($project['project_install_location'], "/.*\.profile$/", $nomask) as $filename => $info) { + $found['profile'][] = $info->name; + } + } + // Log results. + $msg = "Project !project contains:\n"; + $args = array('!project' => $project['name']); + foreach (array_keys($found) as $type) { + if ($count = count($found[$type])) { + $msg .= " - !count_$type !type_$type: !found_$type\n"; + $args += array("!count_$type" => $count, "!type_$type" => $type, "!found_$type" => implode(', ', $found[$type])); + if ($count > 1) { + $args["!type_$type"] = $type.'s'; } } } + drush_log(dt($msg, $args), 'success'); + drush_print_pipe(call_user_func_array('array_merge', array_values($found))); + } + // Modules and themes can only contain other extensions of the same type. + elseif (in_array($project['project_type'], array('module', 'theme'))) { + // Find all of the .info files + $found = array(); + foreach (drush_scan_directory($project['project_install_location'], "/.*\.info$/", $nomask) as $filename => $info) { + $found[] = $info->name; + } + // Log results. + // If there is only one module / theme in the project, only print out + // the message if is different than the project name. + if (count($found) == 1) { + if ($found[0] != $project['name']) { + $msg = "Project !project contains a !type named !found."; + } + } + // If there are multiple modules or themes in the project, list them all. + else { + $msg = "Project !project contains !count !types: !found."; + } + if (isset($msg)) { + drush_print(dt($msg, array('!project' => $project['name'], '!count' => count($found), '!type' => $project['project_type'], '!found' => implode(', ', $found)))); + } + drush_print_pipe($found); } } diff --git a/sites/all/modules/drush/commands/pm/update_info/drupal_5.inc b/sites/all/modules/drush/commands/pm/update_info/drupal_5.inc index 2032b888c42fd922c716dbe33370066a9a1c3e6c..54a9e06f42d0158f460c9750457a66af94205c09 100644 --- a/sites/all/modules/drush/commands/pm/update_info/drupal_5.inc +++ b/sites/all/modules/drush/commands/pm/update_info/drupal_5.inc @@ -1,5 +1,5 @@ <?php -// $Id: drupal_5.inc,v 1.8 2010/06/16 15:41:56 weitzman Exp $ +// $Id: drupal_5.inc,v 1.10 2010/11/24 10:00:02 jonhattan Exp $ function pm_update_filter(&$project) { $update = FALSE; @@ -56,16 +56,22 @@ function _pm_get_update_info($projects = NULL) { if (!cache_get('update_status_info', 'cache')) { _pm_refresh(); } - $info = update_status_get_available(); - $data = update_status_calculate_project_data($info); + $info = update_status_get_available(TRUE); + + // Force to invalidate some update_status caches that are only cleared + // when visiting update status report page. + _update_status_cache_clear('update_status_project_data'); + _update_status_cache_clear('update_status_project_projects'); + + $data = update_status_calculate_project_data($info); // update_status for drupal 5 can only process modules, // so we need to add this here for backwards compatibility // or pm_get_project_path() will fail foreach ($data as $project_name => $project_data) { $data[$project_name]['project_type'] = 'module'; } - $data = pm_get_project_path($data, 'modules'); + $data = _pm_get_project_path($data, 'modules'); return $data; } diff --git a/sites/all/modules/drush/commands/pm/update_info/drupal_6.inc b/sites/all/modules/drush/commands/pm/update_info/drupal_6.inc index 3da055540f4cf9e1d395393dba8c363281f64eae..c8085c4d8c47d9791d76a991b0562a84ed99df6f 100644 --- a/sites/all/modules/drush/commands/pm/update_info/drupal_6.inc +++ b/sites/all/modules/drush/commands/pm/update_info/drupal_6.inc @@ -1,5 +1,5 @@ <?php -// $Id: drupal_6.inc,v 1.7 2010/01/27 16:29:19 weitzman Exp $ +// $Id: drupal_6.inc,v 1.10 2010/11/24 10:00:02 jonhattan Exp $ function pm_update_filter(&$project) { $update = FALSE; @@ -55,12 +55,20 @@ function _pm_refresh() { */ function _pm_get_update_info($projects = NULL) { // We force a refresh if the cache is not available. - if (!cache_get('update_info', 'cache_update')) { + if (!cache_get('update_available_releases', 'cache_update')) { _pm_refresh(); } - $info = update_get_available(); + + $info = update_get_available(TRUE); + + // Force to invalidate some update_status caches that are only cleared + // when visiting update status report page. + _update_cache_clear('update_project_data'); + _update_cache_clear('update_project_projects'); + $data = update_calculate_project_data($info); - $data = pm_get_project_path($data, 'includes'); + $data = _pm_get_project_path($data, 'includes'); + return $data; } diff --git a/sites/all/modules/drush/commands/pm/update_info/drupal_7.inc b/sites/all/modules/drush/commands/pm/update_info/drupal_7.inc index 691f888899cf2535817f8a2e0bb855c0e9844667..ddbfe89c5332486590f7f5ed20c2040bf30b5e58 100644 --- a/sites/all/modules/drush/commands/pm/update_info/drupal_7.inc +++ b/sites/all/modules/drush/commands/pm/update_info/drupal_7.inc @@ -1,5 +1,5 @@ <?php -// $Id: drupal_7.inc,v 1.7 2010/04/13 05:13:49 weitzman Exp $ +// $Id: drupal_7.inc,v 1.10 2010/11/24 10:00:02 jonhattan Exp $ function pm_update_filter(&$project) { $update = FALSE; @@ -44,7 +44,7 @@ function pm_update_last_check() { */ function _pm_refresh() { drush_print(dt("Refreshing update status information ...")); - update_get_available(TRUE); + update_refresh(TRUE); drush_print(dt("Done.")); } @@ -55,12 +55,19 @@ function _pm_refresh() { */ function _pm_get_update_info($projects = NULL) { // We force a refresh if the cache is not available. - if (!cache_get('update_info', 'cache_update')) { + if (!cache_get('update_available_releases', 'cache_update')) { _pm_refresh(); } - $info = update_get_available(); + + $info = update_get_available(TRUE); + + // Force to invalidate some update_status caches that are only cleared + // when visiting update status report page. + _update_cache_clear('update_project_data'); + _update_cache_clear('update_project_projects'); $data = update_calculate_project_data($info); - $data = pm_get_project_path($data, 'includes'); + $data = _pm_get_project_path($data, 'includes'); + return $data; } diff --git a/sites/all/modules/drush/commands/pm/updatecode.pm.inc b/sites/all/modules/drush/commands/pm/updatecode.pm.inc index 22ac9779ce4fb24f5ebc0817ed62383f1d52ead4..ec408521f2b283d223bac9747e79297793d2594d 100644 --- a/sites/all/modules/drush/commands/pm/updatecode.pm.inc +++ b/sites/all/modules/drush/commands/pm/updatecode.pm.inc @@ -1,12 +1,14 @@ <?php -// $Id: updatecode.pm.inc,v 1.10 2010/04/09 20:08:46 weitzman Exp $ +// $Id: updatecode.pm.inc,v 1.24 2010/12/01 15:53:15 jonhattan Exp $ /** - * Command callback. Displays update status info and allows to update installed projects. - * Pass specific projects as arguments, otherwise we update all that have candidate releases. + * Command callback. Displays update status info and allows to update installed + * projects. + * Pass specific projects as arguments, otherwise we update all that have + * candidate releases. * - * This command prompts for confirmation before updating, so it is safe to run just to check on - * In this case, say at the confirmation prompt. + * This command prompts for confirmation before updating, so it is safe to run + * just to check on. In this case, say at the confirmation prompt. */ function drush_pm_updatecode() { // We don't provide for other options here, so we supply an explicit path. @@ -15,15 +17,22 @@ function drush_pm_updatecode() { // Get update status information. $projects = _pm_get_update_info(); - // Get specific requests - $requests = func_get_args(); + // Find only security updates? + $security_only = drush_get_option('security-only'); - // Parse out project name and version + // Process locks specified on the command line. + $locked_list = drush_pm_update_lock($projects, drush_get_option_list('lock'), drush_get_option_list('unlock'), drush_get_option('lock-message')); + + // Get specific requests. + $requests = _convert_csv_to_array(func_get_args()); + + // Parse out project name and version. $requests = pm_parse_project_version($requests); - // Preprocess releases + // Preprocess releases. if (!empty($requests)) { - // Force update projects where a specific version is reqested + // Check requests are installed and enabled projects and force + // update projects where a specific version is requested. foreach ($requests as $name => $request) { if (!isset($projects[$name])) { // Catch projects with no version data (common for CVS checkouts @@ -56,12 +65,12 @@ function drush_pm_updatecode() { $rows[] = array(dt('Name'), dt('Installed version'), dt('Proposed version'), dt('Status')); // Process releases, notifying user of status and building a list of proposed updates - $updateable = pm_project_filter($projects, $rows); + $updateable = pm_project_filter($projects, $rows, $security_only); - // Pipe preparation + // Pipe preparation. if (drush_get_context('DRUSH_PIPE')) { $pipe = ""; - foreach($projects as $project){ + foreach($updateable as $project) { $pipe .= $project['name']. " "; $pipe .= $project['existing_version']. " "; $pipe .= $project['candidate_version']. " "; @@ -87,6 +96,21 @@ function drush_pm_updatecode() { } } } + + // If there are any locked projects that were not requested, then remove them + if (!empty($locked_list)) { + foreach ($updateable as $name => $project) { + if ((isset($locked_list[$name])) && (!isset($requests[$name]))) { + unset($updateable[$name]); + } + } + } + + // Do no updates in simulated mode + if (drush_get_context('DRUSH_SIMULATE')) { + return drush_log(dt('No action taken in simulated mode.'), 'ok'); + return TRUE; + } if (isset($updateable['drupal'])) { $drupal_project = $updateable['drupal']; @@ -108,7 +132,12 @@ function drush_pm_updatecode() { // also continue, allowing the user to upgrade any upgradable // modules if desired. else { - drush_print(dt("NOTE: A code update for the Drupal core is available.")); + if ($drupal_project['status'] == UPDATE_NOT_SECURE) { + drush_print(dt("NOTE: A security update for the Drupal core is available.")); + } + else { + drush_print(dt("NOTE: A code update for the Drupal core is available.")); + } if (drush_get_context('DRUSH_PM_UPDATE_ALL', FALSE)) { drush_print(dt("Drupal core will be updated after all of the non-core modules are updated.\n")); } @@ -120,7 +149,12 @@ function drush_pm_updatecode() { } if (empty($updateable)) { - return drush_log(dt('No code updates available.'), 'ok'); + if ($security_only) { + return drush_log(dt('No security updates available.'), 'ok'); + } + else { + return drush_log(dt('No code updates available.'), 'ok'); + } } // Offer to update to the identified releases @@ -144,20 +178,36 @@ function _pm_update_core(&$project, $module_list = array()) { drush_print(dt('Code updates will be made to drupal core.')); drush_print(dt("WARNING: Updating core will discard any modifications made to Drupal core files, most noteworthy among these are .htaccess and robots.txt. If you have made any modifications to these files, please back them up before updating so that you can re-create your modifications in the updated version of the file.")); drush_print(dt("Note: Updating core can potentially break your site. It is NOT recommended to update production sites without prior testing.")); + drush_print(); + drush_pm_releasenotes(); if(!drush_confirm(dt('Do you really want to continue?'))) { - drush_die('Aborting.'); + drush_user_abort(); + } + + // We need write permission on $drupal_root. + if (!is_writable($drupal_root)) { + return drush_set_error('DRUSH_PATH_NO_WRITABLE', dt('Drupal root path is not writable.')); } - // Create a directory 'core' if it does not already exist + // Create a directory 'core' if it does not already exist. $project['path'] = 'drupal-' . $project['candidate_version']; $project['full_project_path'] = $drupal_root . '/' . $project['path']; if (!is_dir($project['full_project_path'])) { - mkdir($project['full_project_path']); + drush_op('mkdir', $project['full_project_path']); } - // Create a list of files and folders that are user-customized or otherwise - // not part of the update process - $project['skip_list'] = array('backup', 'sites', $project['path']); + // Create a list of directories to exclude from the update process. + $skip_list = array('sites', $project['path'], '.svn'); + // Add non-writable directories: we can't move them around. + // We will also use $items_to_test later for $version_control check. + $items_to_test = drush_scan_directory($drupal_root, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'basename', 0, TRUE); + foreach (array_keys($items_to_test) as $item) { + if (is_dir($item) && !is_writable($item)) { + $skip_list[] = $item; + unset($items_to_test[$item]); + } + } + $project['skip_list'] = $skip_list; // Move all files and folders in $drupal_root to the new 'core' directory // except for the items in the skip list @@ -171,15 +221,15 @@ function _pm_update_core(&$project, $module_list = array()) { return FALSE; } - // Make a list of every item at the root of core except 'sites' - $items_to_test = drush_scan_directory($project['full_project_path'], '/.*/', array('.', '..', 'sites', '.svn'), 0, FALSE, 'basename', 0, TRUE); - $project['base_project_path'] = dirname($project['full_project_path']); // Check we have a version control system, and it clears its pre-flight. if (!$version_control->pre_update($project, $items_to_test)) { return FALSE; } + // Package handlers want the project directory in project_dir. + $project['project_dir'] = $project['path']; + // Update core. if (pm_update_project($project, $version_control) === FALSE) { return FALSE; @@ -196,6 +246,7 @@ function _pm_update_core(&$project, $module_list = array()) { // drupal root. These are to be moved back. if (array_key_exists('backup_target', $project)) { _pm_update_move_files($project['backup_target'], $drupal_root, $project['skip_list'], FALSE); + _pm_update_move_files($project['backup_target'] . '/profiles', $drupal_root . '/profiles', array('default'), FALSE); } pm_update_complete($project, $version_control); @@ -220,8 +271,8 @@ function _pm_update_move_files($src_dir, $dest_dir, $skip_list, $remove_conflict } /** - * Update packages according to an array of releases, following interactive - * confirmation from the user. + * Update packages according to an array of releases and print the release notes + * for each project, following interactive confirmation from the user. * * @param $projects * An array of projects from the drupal.org update service, with an additional @@ -231,17 +282,45 @@ function pm_update_packages($projects) { drush_include_engine('package_handler', drush_get_option('package-handler', 'wget')); $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); - drush_print(dt('Code updates will be made to the following projects:')); + $print = ''; + $status = array(); foreach($projects as $project) { $print .= $project['title'] . " [" . $project['name'] . '-' . $project['candidate_version'] . "], "; + $status[$project['status']] = $project['status']; + } + // We print the list of the projects that need to be updated. + if (isset($status[UPDATE_NOT_SECURE])) { + if (isset($status[UPDATE_NOT_CURRENT])) { + drush_print(dt('Security and code updates will be made to the following projects:')); + } + else { + drush_print(dt('Security updates will be made to the following projects:')); + } + } + else { + drush_print(dt('Code updates will be made to the following projects:')); } drush_print(substr($print, 0, strlen($print)-2)); + + // Print the release notes for projects to be updated. Ask before if + // there is more than one project. + $keys = array_keys($projects); + if (count($keys) == 1) { + if (!drush_get_context('DRUSH_NEGATIVE')) { + _drush_pm_releasenotes($keys); + } + } + elseif (drush_confirm(dt('Do you want to see the release notes of the above exposed versions?'))) { + _drush_pm_releasenotes($keys); + } + + // We print some warnings before the user confirms the update. drush_print(); drush_print(dt("Note: Updated projects can potentially break your site. It is NOT recommended to update production sites without prior testing.")); drush_print(dt("Note: A backup of your package will be stored to backups directory if it is not managed by a supported version control system.")); drush_print(dt('Note: If you have made any modifications to any file that belongs to one of these projects, you will have to migrate those modifications after updating.')); - if(!drush_confirm(dt('Do you really want to continue?'))) { - drush_die('Aborting.'); + if(!drush_confirm(dt('Do you really want to continue with the update process?'))) { + drush_user_abort(); } // Now we start the actual updating. @@ -264,7 +343,13 @@ function pm_update_packages($projects) { if (!$version_control->pre_update($project)) { return FALSE; } - // Run update on one project + + // Package handlers want the name of the directory in project_dir. + // It may be different to the project name for pm-download. + // Perhaps we want here filename($project['full_project_path']). + $project['project_dir'] = $project['name']; + + // Run update on one project. if (pm_update_project($project, $version_control) === FALSE) { return FALSE; } @@ -275,7 +360,7 @@ function pm_update_packages($projects) { } /** - * Update one project -- a module, theme or Drupal core + * Update one project -- a module, theme or Drupal core. * * @param $project * The project to upgrade. $project['full_project_path'] must be set @@ -324,7 +409,7 @@ function drush_pm_updatecode_rollback() { } } -function pm_project_filter(&$projects, &$rows) { +function pm_project_filter(&$projects, &$rows, $security_only) { $updateable = array(); foreach ($projects as $key => $project) { if (empty($project['title'])) { @@ -348,6 +433,17 @@ function pm_project_filter(&$projects, &$rows) { $status = pm_update_filter($project); break; } + + // Special checking: if drush decides that the candidate version is older + // than the installed version, then we will set the candidate version to + // the installed version. + if ($project['releases'][$project['candidate_version']]['date'] < $project['releases'][$project['existing_version']]['date']) { + $project['candidate_version'] = $project['existing_version']; + } + + if (isset($project['locked'])) { + $status = $project['locked'] . " ($status)"; + } // Persist candidate_version in $projects (plural). if (empty($project['candidate_version'])) { @@ -358,6 +454,10 @@ function pm_project_filter(&$projects, &$rows) { } if (!empty($project['updateable'])) { $updateable[$key] = $project; + // Find only security updates + if ($security_only && ($project['status'] != UPDATE_NOT_SECURE)) { + unset($updateable[$key]); + } } $rows[] = array($project['title'], $project['existing_version'], $projects[$key]['candidate_version'], $status); } @@ -378,7 +478,7 @@ function pm_release_recommended(&$project) { * Get the a best release match for a requested update. * * @param $request A information array for the requested project - * @param $project A project information array for this project, as returned by an update service from pm_get_project_info() + * @param $project A project information array for this project, as returned by an update service from pm_get_extensions() */ function pm_get_release($request, $project) { $minor = ''; diff --git a/sites/all/modules/drush/commands/pm/version_control/backup.inc b/sites/all/modules/drush/commands/pm/version_control/backup.inc index 965ed1b567092997a908856fafafeb73c6852253..8b0e7c507b8d84da2dc3c9a2a9ddfada5a9f4fb9 100644 --- a/sites/all/modules/drush/commands/pm/version_control/backup.inc +++ b/sites/all/modules/drush/commands/pm/version_control/backup.inc @@ -1,5 +1,5 @@ <?php -// $Id: backup.inc,v 1.4 2010/04/02 04:06:38 weitzman Exp $ +// $Id: backup.inc,v 1.8 2010/11/30 16:45:36 weitzman Exp $ /** * @file Drush pm directory copy backup extension @@ -11,25 +11,18 @@ class drush_pm_version_control_backup implements drush_pm_version_control { * Implementation of pre_update(). */ public function pre_update(&$project, $items_to_test = array()) { - $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); - - // Save the date to be used in the backup directory's path name. - $date = date('YmdHis', $_SERVER['REQUEST_TIME']); - - $backup_dir = drush_get_option('backup-dir', $drupal_root . '/backup'); - $backup_dir = rtrim($backup_dir, '/'); - @drush_op('mkdir', $backup_dir, 0777); - $backup_dir .= '/modules'; - @drush_op('mkdir', $backup_dir, 0777); - $backup_dir .= "/$date"; - @drush_op('mkdir', $backup_dir, 0777); - $backup_target = $backup_dir . '/'. $project['name']; - // Save for rollback or notifications. - $project['backup_target'] = $backup_target; - if (!drush_op('rename', $project['full_project_path'], $backup_target)) { - return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Failed to backup project directory !project to !backup_target', array('!project' => $project['full_project_path'], '!backup_target' => $backup_target))); + if ($backup_dir = $this->prepare_back_dir()) { + $backup_target = $backup_dir . '/' . $project['name']; + // Save for rollback or notifications. + $project['backup_target'] = $backup_target; + if (!drush_move_dir($project['full_project_path'], $backup_target)) { + return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Failed to backup project directory !project to !backup_target', array('!project' => $project['full_project_path'], '!backup_target' => $backup_target))); + } + return TRUE; + } + else { + return FALSE; } - return TRUE; } /** @@ -57,4 +50,22 @@ class drush_pm_version_control_backup implements drush_pm_version_control { public function post_download($project) { // NOOP } + + // Helper for pre_update. + public function prepare_backup_dir() { + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + + // Save the date to be used in the backup directory's path name. + $date = date('YmdHis', $_SERVER['REQUEST_TIME']); + + $backup_dir = drush_get_option('backup-dir', $_SERVER['HOME'] . '/' . '.drush-backups'); + if (strpos($backup_dir, $drupal_root) === 0) { + return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('It\'s not allowed to store backups inside the Drupal root directory.')); + } + $backup_dir = rtrim($backup_dir, DIRECTORY_SEPARATOR); + + if (drush_mkdir($backup_dir)) { + return $backup_dir .= '/' . $date . '/' . 'modules'; + } + } } diff --git a/sites/all/modules/drush/commands/pm/version_control/bzr.inc b/sites/all/modules/drush/commands/pm/version_control/bzr.inc index 3fa1417a7cce4b1ed932a2175931fb00fced0bae..7d885e36586d421928e0964304ae0687c84dc4ca 100644 --- a/sites/all/modules/drush/commands/pm/version_control/bzr.inc +++ b/sites/all/modules/drush/commands/pm/version_control/bzr.inc @@ -1,41 +1,55 @@ <?php -// $Id: bzr.inc,v 1.6 2010/04/02 04:06:38 weitzman Exp $ +// $Id: bzr.inc,v 1.7 2010/09/02 23:51:50 weitzman Exp $ /** - * @file Drush pm BZR extension + * @file Drush pm Bazaar extension */ class drush_pm_version_control_bzr implements drush_pm_version_control { /** - * Helper function: get the root of a repository path. - * - * @param $path - * The path to check. - * @return - * An absolute path to the repository root. + * Implementation of pre_update(). */ - public function bzr_get_repository_root($path) { - if (drush_shell_exec('bzr root %s', $path)) { - $output = drush_shell_exec_output(); - return reset($output); + public function pre_update(&$project, $items_to_test = array()) { + // If items to test is empty, test everything; otherwise, pass just + // the list of files to test to Bazaar status. + $status_files = implode(' ', array_keys($items_to_test)); + + // Check the project directory looks clean + if (drush_shell_exec('cd %s && bzr status --short %s', $project['full_project_path'], $status_files)) { + $output = preg_grep('/^[\sRCP][\sNDKM][\s\*]/', drush_shell_exec_output()); + if (!empty($output)) { + return drush_set_error('DRUSH_PM_BZR_LOCAL_CHANGES', dt("The Bazaar working copy at !path appears to have uncommmitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); + } + } + else { + return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status on !path. Check that you have Bazaar \ninstalled and that this directory is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); } + return TRUE; } /** - * Implementation of pre_update(). + * Implementation of rollback(). */ - public function pre_update(&$project, $items_to_test = array()) { - // TODO: Add similar checks to SVN? + public function rollback($project) { + if (drush_shell_exec('bzr revert %s', $project['full_project_path'])) { + $output = drush_shell_exec_output(); + if (!empty($output)) { + return drush_set_error('DRUSH_PM_BZR_LOCAL_CHANGES', dt("The Bazaar working copy at !path appears to have uncommmitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); + } + } + else { + return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the Bazaar status on !path. Check that you have Bazaar \ninstalled and that this directory is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); + } } /** * Implementation of post_update(). */ public function post_update($project) { - if (is_versioned($project['full_project_path']) && sync($project)) { + if ($this->sync($project)) { // Only attempt commit on a sucessful sync - commit($project); + $this->commit($project); } } @@ -43,55 +57,41 @@ class drush_pm_version_control_bzr implements drush_pm_version_control { * Implementation of post_download(). */ public function post_download($project) { - if (is_versioned($project['full_project_path']) && sync($project)) { + if ($this->sync($project)) { // Only attempt commit on a sucessful sync - commit($project); + $this->commit($project); } } /** - * Automatically add any unversioned files to Bzr and remove any files + * Automatically add any unversioned files to Bazaar and remove any files * that have been deleted on the file system */ private function sync($project) { if (drush_get_option('bzrsync')) { $errors = ''; - - // All paths returned by bzr status are relative to the repository root. - $root = bzr_get_repository_root($project['full_project_path']); - - if (drush_shell_exec('bzr status -S %s', $project['full_project_path'])) { + $root = array(); + if (drush_shell_exec('bzr status --short %s', $project['full_project_path'])) { $output = drush_shell_exec_output(); - - // Parse the output of bzr status to get the list of added and removed - // files. - $new_files = array(); - $removed_files = array(); - foreach ($output as $line) { - if (preg_match('/^\? *(.*)/', $line, $matches)) { - $new_files[] = $root . '/' . $matches[1]; - } - if (preg_match('/^\ D *(.*)/', $line, $matches)) { - $removed_files[] = $root . '/' . $matches[1]; - } + // All paths returned by bzr status are relative to the repository root. + if (drush_shell_exec('bzr root %s', $project['full_project_path'])) { + $root = drush_shell_exec_output(); } - - // Proceed adding new files. - foreach (array_chunk($new_files, 100) as $chunk) { - $chunk = array_map('escapeshellarg', $chunk); - if (!drush_shell_exec('bzr add ' . implode(' ', $chunk))) { - $errors .= implode("\n", drush_shell_exec_output()); + foreach ($output as $line) { + if (preg_match('/^\?\s+(.*)/', $line, $matches)) { + $path = $root[0] .'/'. $matches[1]; + // Bazaar add is by default recursive so only run on "full_project_path". + if (($path == $project['full_project_path'] .'/') && !drush_shell_exec('bzr add %s', $path)) { + $errors .= implode("\n", drush_shell_exec_output()); + } } - } - - // Proceed removing old files. - foreach (array_chunk($removed_files, 100) as $chunk) { - $chunk = array_map('escapeshellarg', $chunk); - if (!drush_shell_exec('bzr remove ' . implode(' ', $chunk))) { - $errors .= implode("\n", drush_shell_exec_output()); + else if (preg_match('/^\s+D\s+(.*)/', $line, $matches)) { + $path = $root[0] .'/'. $matches[1]; + if (!drush_shell_exec('bzr remove %s', $path)) { + $errors .= implode("\n", drush_shell_exec_output()); + } } } - if (!empty($errors)) { return drush_set_error('DRUSH_PM_BZR_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => $errors))); } @@ -110,9 +110,9 @@ class drush_pm_version_control_bzr implements drush_pm_version_control { if (drush_get_option('bzrcommit')) { $message = drush_get_option('bzrmessage'); if (empty($message)) { - $message = dt("Drush automatic commit: \n") . implode(' ', $_SERVER['argv']); + $message = dt("Drush automatic commit.\nProject: @name @type\nCommand: @arguments", array('@name' => $project['name'], '@type' => $project['project_type'], '@arguments' => implode(' ', $_SERVER['argv']))); } - if (drush_shell_exec('bzr commit -m %s %s', $message, $project['full_project_path'])) { + if (drush_shell_exec('bzr commit --message=%s %s', $message, $project['full_project_path'])) { drush_log(dt('Project committed to Bazaar successfully'), 'ok'); } else { diff --git a/sites/all/modules/drush/commands/pm/version_control/svn.inc b/sites/all/modules/drush/commands/pm/version_control/svn.inc index a4ba6ea0054c6d6fdb51d2c4eed31328df57790f..ec5a9a32a27c1590a7e57ce9f94c5083454a868f 100644 --- a/sites/all/modules/drush/commands/pm/version_control/svn.inc +++ b/sites/all/modules/drush/commands/pm/version_control/svn.inc @@ -1,5 +1,5 @@ <?php -// $Id: svn.inc,v 1.13 2010/06/03 13:03:01 greg1anderson Exp $ +// $Id: svn.inc,v 1.14 2010/08/17 05:52:12 greg1anderson Exp $ /** * @file Drush pm SVN extension @@ -16,7 +16,7 @@ class drush_pm_version_control_svn implements drush_pm_version_control { $status_files = implode(' ', array_keys($items_to_test)); // Check the project directory looks clean - if (drush_shell_exec('cd ' . $project['full_project_path'] . ' && svn status '. drush_get_option('svnstatusparams') .' '. $status_files)) { + if (drush_shell_cd_and_exec($project['full_project_path'], 'svn status '. drush_get_option('svnstatusparams') .' '. $status_files)) { $output = preg_grep('/^[ ACDMRX?!~][ CM][ L][ +][ SX][ K]/', drush_shell_exec_output()); if (!empty($output)) { return drush_set_error('DRUSH_PM_SVN_LOCAL_CHANGES', dt("The SVN working copy at !path appears to have uncommmitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); @@ -27,7 +27,7 @@ class drush_pm_version_control_svn implements drush_pm_version_control { } // Check for incoming updates - if (drush_shell_exec('cd ' . $project['full_project_path'] . ' && svn status -u '. drush_get_option('svnstatusparams') .' '. $status_files)) { + if (drush_shell_cd_and_exec($project['full_project_path'], 'svn status -u '. drush_get_option('svnstatusparams') .' '. $status_files)) { $output = preg_grep('/\*/', drush_shell_exec_output()); if (!empty($output)) { return drush_set_error('DRUSH_PM_SVN_REMOTE_CHANGES', dt("The SVN working copy at !path appears to be out of date with the repository (see below). Please run 'svn update' to pull down changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); diff --git a/sites/all/modules/drush/commands/sql/sql.drush.inc b/sites/all/modules/drush/commands/sql/sql.drush.inc index 4273d1e933edcf4aba59b936c2607f57504fecaa..2bfb4e24e271a623d5d0fad7044f2344b67d1ad1 100644 --- a/sites/all/modules/drush/commands/sql/sql.drush.inc +++ b/sites/all/modules/drush/commands/sql/sql.drush.inc @@ -1,5 +1,5 @@ <?php -// $Id: sql.drush.inc,v 1.49 2010/06/08 20:24:42 greg1anderson Exp $ +// $Id: sql.drush.inc,v 1.67 2010/12/01 04:46:08 greg1anderson Exp $ /** * @file Drush sql commands @@ -34,10 +34,21 @@ function sql_drush_command() { $options['--target'] = 'The name of a target within the specified database.'; } + $items['sql-drop'] = array( + 'description' => 'Drop all tables in a given database.', + 'arguments' => array( + ), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE, + 'options' => array( + '--yes' => 'Skip confirmation and proceed.', + '--result-file' => 'Save to a file. The file should be relative to Drupal root. Recommended.', + ) + $options, + ); $items['sql-conf'] = array( 'description' => 'Print database connection details using print_r().', 'arguments' => array( 'all' => 'Show all database connections, instead of just one.', + 'show-passwords' => 'Show database password.', ), 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, 'options' => $options, @@ -47,12 +58,12 @@ function sql_drush_command() { 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, 'options' => $options, 'examples' => array( - '`drush sql connect` < example.sql' => 'Import sql statements from a file into the current database.', + '`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.', ), ); $items['sql-dump'] = array( 'callback' => 'drush_sql_dump_execute', - 'description' => 'Exports the Drupal DB as SQL using mysqldump.', + 'description' => 'Exports the Drupal DB as SQL using mysqldump or equivalent.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, 'examples' => array( 'drush sql-dump --result-file=../18.sql' => 'Save SQL dump to the directory above Drupal root.', @@ -106,15 +117,27 @@ function sql_drush_command() { '--source-remote-port' => 'Override sql database port number in source-db-url. Optional.', '--source-remote-host' => 'Remote machine to run sql-dump file on. Optional; default is local machine.', '--source-dump' => 'Path to dump file. Optional; default is to create a temporary file.', + '--target-database' => 'A key in the $db_url (D6) or $databases (D7+) array which provides the data.', + '--source-target' => 'Oy. A key within the --target_database identifying a particular server in the database group.', '--target-db-url' => '', '--target-remote-port' => '', '--target-remote-host' => '', '--target-dump' => '', + '--target-database' => 'A key in the $db_url (D6) or $databases (D7+) array which shall receive the data.', + '--target-target' => 'Oy. A key within the --target_database identifying a particular server in the database group.', '--temp' => 'Use a temporary file to hold dump files. Implies --no-cache.', '--dump-dir' => 'Directory to store sql dump files in when --source-dump or --target-dump are not used. Takes precedence over --temp.', '--create-db' => 'Create a new database before importing the database dump on the target machine.', '--db-su' => 'Account to use when creating a new database. Optional.', '--db-su-pw' => 'Password for the "db-su" account. Optional.', + '--sanitize' => 'Obscure email addresses and reset passwords in the user table post-sync. Optional.', + ), + 'sub-options' => array( + '--sanitize' => array( + '--sanitize-password' => 'The password to assign to all accounts in the sanitization operation, or "no" to keep passwords unchanged. Default is "password".', + '--sanitize-email' => 'The username for test email addresses in the sanitization operation, or "no" to keep email addresses unchanged. May contain replacement patterns %uid, %mail or %login. Default is "user+%uid@localhost".', + '--confirm-sanitizations' => 'Prompt yes/no after importing the database, but before running the sanitizations', + ), ), ); if (drush_drupal_major_version() >= 7) { @@ -162,13 +185,17 @@ function drush_sql_conf() { function _drush_sql_connect($db_spec = NULL) { switch (_drush_sql_get_scheme($db_spec)) { case 'mysql': - $command = 'mysql ' . (drush_get_context('DRUSH_DEBUG') ? ' -v' : ''); + $command = 'mysql '; $command .= _drush_sql_get_credentials($db_spec); break; case 'pgsql': $command = 'psql'; $command .= _drush_sql_get_credentials($db_spec); break; + case 'sqlite': + $command = 'sqlite3 '; + $command .= _drush_sql_get_credentials($db_spec); + break; } return $command; } @@ -214,9 +241,9 @@ function drush_sql_build_dump_command($tabel_selection, $db_spec = NULL) { $skip_tables = $tabel_selection['skip']; $structure_tables = $tabel_selection['structure']; $tables = $tabel_selection['tables']; - + $ignores = array(); - $skip_tables += $structure_tables; + $skip_tables = array_merge($structure_tables, $skip_tables); $data_only = drush_get_option('data-only'); // The ordered-dump option is only supported by MySQL for now. // @todo add documention once a hook for drush_get_option_help() is available. @@ -227,14 +254,33 @@ function drush_sql_build_dump_command($tabel_selection, $db_spec = NULL) { $db_spec = _drush_sql_get_db_spec(); } $database = $db_spec['database']; + + // Get the setting of --result-file. If the user + // has set $options['result-file'] = TRUE, then we + // will generate an SQL dump file in the same backup + // directory that pm-updatecode uses. + if ($file = drush_get_option('result-file', FALSE)) { + if ($file === TRUE) { + // User did not pass a specific value for --result-file. Make one. + drush_include_engine('version_control', 'backup'); + $backup = new drush_pm_version_control_backup(); + $backup_dir = $backup->prepare_backup_dir(); + if (empty($backup_dir)) { + $backup_dir = "/tmp"; + } + $file = $backup_dir . '/@DATABASE_@DATE.sql'; + } + $file = str_replace(array('@DATABASE', '@DATE'), array($database, date('Ymd_his')), $file); + } switch (_drush_sql_get_scheme($db_spec)) { case 'mysql': - $exec = 'mysqldump' . (drush_get_context('DRUSH_VERBOSE') ? ' -v' : ''); - if ($file = drush_get_option('result-file')) { + $exec = 'mysqldump'; + if ($file) { $exec .= ' --result-file '. $file; } - $extra = ' --single-transaction --opt -Q' . _drush_sql_get_credentials($db_spec); + // mysqldump wants 'databasename' instead of 'database=databasename' for no good reason. + $extra = ' --single-transaction --opt -Q' . str_replace('--database=', ' ', _drush_sql_get_credentials($db_spec)); if (isset($data_only)) { $extra .= ' --no-create-info'; } @@ -264,8 +310,8 @@ function drush_sql_build_dump_command($tabel_selection, $db_spec = NULL) { break; case 'pgsql': $create_db = drush_get_option('create-db'); - $exec = 'pg_dump ' . (drush_get_context('DRUSH_VERBOSE') ? ' -v' : ''); - if ($file = drush_get_option('result-file')) { + $exec = 'pg_dump '; + if ($file) { $exec .= ' --file '. $file; } // Unlike psql, pg_dump does not take a '-d' flag before the database name. @@ -304,6 +350,20 @@ function drush_sql_build_dump_command($tabel_selection, $db_spec = NULL) { } } break; + case 'sqlite': + // Dumping is usually not necessary in SQLite, since all database data + // is stored in a single file on the filesystem which can be copied just + // like any other file. But it still has a use in migration purposes and + // building human-readable diffs and such, so let's do it anyway. + $exec = _drush_sql_connect(); + // SQLite's dump command doesn't support many of the features of its + // Postgres or MySQL equivalents. We may be able to fake some in the + // future, but for now, let's just support simple dumps. + $exec .= ' ".dump"'; + if ($file = drush_get_option('result-file')) { + $exec .= ' > '. $file; + } + break; } return $exec; @@ -314,7 +374,7 @@ function drush_sql_build_dump_command($tabel_selection, $db_spec = NULL) { * specified. * * @param option_name - * The option name to check: skip-tables, structure-tables + * The option name to check: skip-tables, structure-tables * or tables. This funciton will check both *-key and *-list, * and, in the case of sql-sync, will also check target-* * and source-*, to see if an alias set one of these options. @@ -344,7 +404,7 @@ function _drush_sql_get_table_list($option_name) { } } } - + return array(); } @@ -368,33 +428,85 @@ function _drush_sql_query($query, $db_spec = NULL) { // Convert mysql 'show tables;' query into something pgsql understands if (($scheme == 'pgsql') && ($query == 'show tables;')) { - $query = "select tablename from pg_tables where schemaname='public';"; + $query = drush_sql_show_tables_pgsql(); } // Save $query to a tmp file. We will redirect it in. - if ($file = drush_save_data_to_temp_file($query)) { - switch ($scheme) { - case 'mysql': - $exec = 'mysql' . (drush_get_context('DRUSH_VERBOSE') ? ' -v' : ''); - $exec .= _drush_sql_get_credentials($db_spec); - $exec .= ' ' . drush_get_option('extra'); - $exec .= " < $file"; - - break; - case 'pgsql': - $exec = 'psql'; - $exec .= _drush_sql_get_credentials($db_spec); - $exec .= (drush_get_context('DRUSH_VERBOSE') ? '' : ' -q'); - $exec .= ' ' . (drush_get_option('extra') ? drush_get_option('extra') : "--no-align --field-separator=$'\t' --pset footer=off"); - $exec .= " --file $file"; - break; + if ($filename = drush_save_data_to_temp_file($query)) { + $exec = drush_sql_build_exec($db_spec, $filename); + // In --simulate mode, drush_op will show the call to mysql or psql, + // but the sql query itself is stored in a temp file and not displayed. + // We will therefore show the query explicitly in the interest of full disclosure. + if (drush_get_context('DRUSH_SIMULATE')) { + drush_print('sql-query: ' . $query); } - $return = drush_op('system', $exec) !== FALSE; return $return; } } +function drush_sql_drop() { + if (!drush_confirm(dt('Do you really want to drop all tables?'))) { + drush_user_abort(); + } + + // TODO: integrate with _drush_sql_get_table_list? + + $scheme = _drush_sql_get_scheme(); + switch ($scheme) { + case 'pgsql': + $query = drush_sql_show_tables_pgsql(); + break; + case 'sqlite': + $query = '.tables'; + break; + default: + $query = 'SHOW TABLES;'; + } + + $filename = drush_save_data_to_temp_file($query); + $exec = drush_sql_build_exec(NULL, $filename); + + // Actually run this prep query no matter if in SIMULATE. + $old = drush_get_context('DRUSH_SIMULATE'); + drush_set_context('DRUSH_SIMULATE', FALSE); + drush_shell_exec($exec); + drush_set_context('DRUSH_SIMULATE', $old); + $tables = drush_shell_exec_output(); + if ($scheme === 'sqlite') { + // SQLite's '.tables' command always outputs the table names in a column + // format, like this: + // table_alpha table_charlie table_echo + // table_bravo table_delta table_foxtrot + // …and there doesn't seem to be a way to fix that. So we need to do some + // clean-up. + // Since we're already doing iteration here, might as well build the SQL + // too, since SQLite only wants one table per DROP TABLE command (so we have + // to do "DROP TABLE foo; DROP TABLE bar;" instead of + // "DROP TABLE foo, bar;"). + $sql = ''; + foreach ($tables as $line) { + preg_match_all('/[^\s]+/', $line, $matches); + if (!empty($matches[0])) { + foreach ($matches[0] as $match) { + $sql .= "DROP TABLE {$match};"; + } + } + } + // We can't use drush_op('db_query', $sql) because it will only perform one + // SQL command and we're technically performing several. + $exec = _drush_sql_connect(); + $exec .= " '{$sql}'"; + return drush_op('system', $exec); + } + else { + // Shift off the header of the column of data returned. + array_shift($tables); + $sql = 'DROP TABLE '. implode(', ', $tables); + return drush_op('db_query', $sql); + } +} + function drush_sql_cli() { switch (_drush_sql_get_scheme()) { case 'mysql': @@ -402,7 +514,11 @@ function drush_sql_cli() { $command .= _drush_sql_get_credentials(); break; case 'pgsql': - $command = ' psql'; + $command = 'psql '; + $command .= _drush_sql_get_credentials(); + break; + case 'sqlite': + $command = 'sqlite3 '; $command .= _drush_sql_get_credentials(); break; } @@ -483,25 +599,32 @@ function _drush_sql_get_spec_from_options($prefix, $default_to_self = TRUE) { * @return string * The path to the dump file */ -function drush_sql_dump_file(&$db_spec) { +function drush_sql_dump_file(&$db_spec, $prefix) { + // Use an entry in the db spec to indicate whether the dump + // file we use is temporary or not. + $db_spec['dump-is-temp'] = FALSE; // Make a base filename pattern to use to name the dump file $filename_pattern = $db_spec['database']; if (isset($db_spec['remote-host'])) { $filename_pattern = $db_spec['remote-host'] . '_' . $filename_pattern; } - // If the user has set the --dump-dir option, then - // store persistant sql dump files there. - $dump_dir = drush_get_option(array('source-dump-dir', 'dump-dir')); - if (isset($dump_dir)) { - $dump_file = $dump_dir . '/' . $filename_pattern . '.sql'; - } - // If the --dump-dir option is not set, then store - // the sql dump in a temporary file. - else { - $dump_file = drush_tempnam($filename_pattern . '.sql.'); - $db_spec['dump-is-temp'] = TRUE; + // If the user has set the --{prefix}-dir option, then + // use the exact name provided. + $dump_file = drush_get_option($prefix . 'dump'); + if (!isset($dump_file)) { + // If the user has set the --dump-dir option, then + // store persistant sql dump files there. + $dump_dir = drush_get_option(array($prefix . 'dump-dir', 'dump-dir')); + if (isset($dump_dir)) { + $dump_file = $dump_dir . '/' . $filename_pattern . '.sql'; + } + // If the --dump-dir option is not set, then store + // the sql dump in a temporary file. + else { + $dump_file = drush_tempnam($filename_pattern . '.sql.'); + $db_spec['dump-is-temp'] = TRUE; + } } - return $dump_file; } @@ -523,26 +646,67 @@ function _drush_sql_get_credentials($db_spec = NULL) { $db_spec = _drush_sql_get_db_spec(); } + // Build an array of key-value pairs for the parameters. + $parameters = array(); + switch (_drush_sql_get_scheme($db_spec)) { case 'mysql': - $cred = ' -h' . $db_spec['host'] . - (empty($db_spec['port']) ? '' : ' -P' . $db_spec['port']) . - ' -u' . $db_spec['username'] . - (empty($db_spec['password']) ? '' : ' -p' . $db_spec['password']) . ' ' . $db_spec['database']; + // Some drush commands (e.g. site-install) want to connect to the + // server, but not the database. Connect to the built-in database. + $parameters['database'] = empty($db_spec['database']) ? 'information_schema' : $db_spec['database']; + + // Host is required. + $parameters['host'] = $db_spec['host']; + + // An empty port is invalid. + if (!empty($db_spec['port'])) { + $parameters['port'] = $db_spec['port']; + } + + // User is required. Drupal calls it 'username'. MySQL calls it 'user'. + $parameters['user'] = $db_spec['username']; + + // EMPTY password is not the same as NO password, and is valid. + if (isset($db_spec['password'])) { + $parameters['password'] = $db_spec['password']; + } break; - case 'pgsql': - $cred = (isset($db_spec['database']) ? ' -d ' . (empty($db_spec['database']) ? 'template1' : $db_spec['database']) : '') . - (empty($db_spec['host']) ? ' -h localhost ' : ' -h ' . $db_spec['host']) . - (empty($db_spec['port']) ? ' -p 5432 ' : ' -p ' . $db_spec['port']); - // Adding '-U' will cause Postgres to prompt for a password, so disable this for now. - // Use "sudo -u postgres drush sql ..." to access the database without a password, - // presuming that "postgres" is the database superuser and you have - // "local all all ident sameuser" in your Postgres pg_hba.conf file. - // See: http://drupal.org/node/438828 - $cred .= ' -U ' . $db_spec['username'] . ' '; + + case 'pgsql': + // Database is optional in Postgres. + if (isset($db_spec['database'])) { + $parameters['dbname'] = empty($db_spec['database']) ? 'template1' : $db_spec['database']; + } + + // Host and port are optional but have defaults. + $parameters['host'] = empty($db_spec['host']) ? 'localhost' : $db_spec['host']; + $parameters['port'] = empty($db_spec['port']) ? '5432' : $db_spec['port']; + + // Username is required. + $parameters['username'] = $db_spec['username']; + + // Don't set the password. + // @see http://drupal.org/node/438828 break; + + case 'sqlite': + // SQLite doesn't do user management, instead relying on the filesystem + // for that. So the only info we really need is the path to the database + // file, and not as a "--key=value" parameter. + return ' ' . $db_spec['database']; + break; + } + + // Turn each parameter into a valid parameter string. + $parameter_strings = array(); + foreach ($parameters as $key => $value) { + // Only escape the values, not the keys or the rest of the string. + $value = escapeshellcmd($value); + $parameter_strings[] = "--$key=$value"; } - return escapeshellcmd($cred); + + // Join the parameters and return. + return ' ' . implode(' ', $parameter_strings); } function _drush_sql_get_invalid_url_msg($db_spec = NULL) { @@ -557,3 +721,89 @@ function _drush_sql_get_invalid_url_msg($db_spec = NULL) { return dt('Unable to parse DB connection array'); } } + +/** + * Call from a pre-sql-sync hook to register an sql + * query to be executed in the post-sql-sync hook. + * @see drush_sql_pre_sql_sync() and @see drush_sql_post_sql_sync(). + * + * @param $id + * String containing an identifier representing this + * operation. This id is not actually used at the + * moment, it is just used to fufill the contract + * of drush contexts. + * @param $message + * String with the confirmation message that describes + * to the user what the post-sync operation is going + * to do. This confirmation message is printed out + * just before the user is asked whether or not the + * sql-sync operation should be continued. + * @param $query + * String containing the sql query to execute. If no + * query is provided, then the confirmation message will + * be displayed to the user, but no action will be taken + * in the post-sync hook. This is useful for drush modules + * that wish to provide their own post-sync hooks to fix + * up the target database in other ways (e.g. through + * Drupal APIs). + */ +function drush_sql_register_post_sync_op($id, $message, $query = NULL) { + $options = drush_get_context('post-sync-ops'); + + $options[$id] = array('message' => $message, 'query' => $query); + + drush_set_context('post-sync-ops', $options); +} + +/** + * Builds a confirmation message for all post-sync operations. + * + * @return string + * All post-sync operation messages concatenated together. + */ +function _drush_sql_get_post_sync_messages() { + $messages = FALSE; + + $options = drush_get_context('post-sync-ops'); + if (!empty($options)) { + $messages = dt('The following post-sync operations will be done on the destination:') . "\n"; + + foreach($options as $id => $data) { + $messages .= " * " . $data['message'] . "\n"; + } + } + + return $messages; +} + +// Convert mysql 'show tables;' query into something pgsql understands. +function drush_sql_show_tables_pgsql() { + return "select tablename from pg_tables where schemaname='public';"; +} + +function drush_sql_build_exec($db_spec, $filepath) { + $scheme = _drush_sql_get_scheme($db_spec); + switch ($scheme) { + case 'mysql': + $exec = 'mysql' . (drush_get_context('DRUSH_VERBOSE') ? ' -v' : ''); + $exec .= _drush_sql_get_credentials($db_spec); + $exec .= ' ' . drush_get_option('extra'); + $exec .= " < $filepath"; + + break; + case 'pgsql': + $exec = 'psql'; + $exec .= _drush_sql_get_credentials($db_spec); + $exec .= (drush_get_context('DRUSH_VERBOSE') ? '' : ' -q'); + $exec .= ' ' . (drush_get_option('extra') ? drush_get_option('extra') : "--no-align --field-separator='\t' --pset footer=off"); + $exec .= " --file $filepath"; + break; + case 'sqlite': + $exec = 'sqlite3'; + $exec .= ' ' . drush_get_option('extra'); + $exec .= _drush_sql_get_credentials($db_spec); + $exec .= " < $filepath"; + break; + } + return $exec; +} diff --git a/sites/all/modules/drush/commands/sql/sync.sql.inc b/sites/all/modules/drush/commands/sql/sync.sql.inc index e796f65229c762e4c47df01c441bfcd4cdfd7c64..d57d6abffde5f65f9aff591396851a9f0c9c49d9 100644 --- a/sites/all/modules/drush/commands/sql/sync.sql.inc +++ b/sites/all/modules/drush/commands/sql/sync.sql.inc @@ -1,43 +1,19 @@ <?php -// $Id: sync.sql.inc,v 1.26 2010/04/22 10:14:42 weitzman Exp $ +// $Id: sync.sql.inc,v 1.37 2010/12/01 15:45:53 weitzman Exp $ require_once DRUSH_BASE_PATH . '/commands/core/rsync.core.inc'; -function drush_sql_sync($source = null, $destination = null) { - $source_database = drush_get_option('source-database', 'default'); - $source_target = drush_get_option('source-target'); - $target_database = drush_get_option('target-database', 'default'); - $target_target = drush_get_option('target-target'); - - // - // If the destination was not explicitly set, but a particular - // target database was specified on the command line, then we - // will implicitly assume that the destination alias is the - // same as the source alias. - // - if (!isset($destination) && (isset($target_database) || (isset($target_target)))) { - $destination = $source; - } - - // - // If there is no destination specification, then exit. - // - if (!isset($destination)) { - drush_print(dt("You must specify a destination target.")); - exit(1); - } - // - // Default branch: copy 'sync' with the specified source - // and destination. - // - else { - _drush_sql_sync($source, $destination, TRUE); - } -} - -function _drush_sql_sync($source, $destination, $show_warning = TRUE) { +/** + * Sql sync init function. Bootstrap either the source or the + * destination site. At least one of the sites + * must be local for this to work; if both sites are remote, + * then it clearly will not be possible to bootstrap to + * either of them. If both are local, the source site is preferred. + */ +function drush_sql_sync_init($source = NULL, $destination = NULL) { // Preflight destination in case it defines the alias used by the source _drush_sitealias_get_record($destination); + // After preflight, get source and destination settings $source_settings = drush_sitealias_get_record($source); $destination_settings = drush_sitealias_get_record($destination); @@ -49,6 +25,102 @@ function _drush_sql_sync($source, $destination, $show_warning = TRUE) { // actually use the databases record at this time. sitealias_get_databases_from_record($source_settings); sitealias_get_databases_from_record($destination_settings); + + // Bootstrap to the source sites being sync'ed if it is local. + // This allows modules enabled in the site to participate in the + // sql-sync hook functions (e.g. to add sanitization operations, etc.). + // If the source is remote and the destination is local, then we + // will determine the sanitization operations after the database + // has been copied. + if (!drush_get_option('deferred-sanitization', FALSE) && drush_get_option(array('sanitize', 'destination-sanitize'), FALSE)) { + $bootstrapped = drush_bootstrap_max_to_sitealias($source_settings); + if ($bootstrapped) { + drush_command_invoke_all('drush_sql_sync_sanitize', $source); + } + else { + drush_set_option('deferred-sanitization', TRUE); + } + } + + return TRUE; +} + +/** + * Sql sync sanitization function. This hook function will sanitize usernames and + * passwords in the user table when the --sanitize option is used. It is + * also an example of how to write a database sanitizer for sql sync. + * + * To write your own sync hook function, define mymodule_drush_sql_sync_sanitize() + * and follow the form of this function to add your own database + * sanitization operations via the register post-sync op function; + * @see drush_sql_register_post_sync_op(). This is the only thing that the + * sync hook function needs to do; sql-sync takes care of the rest. + * + * The function below has a lot of logic to process user preferences and + * generate the correct SQL regardless of whether Postgres, Mysql, + * Drupal 6 or Drupal 7 is in use. A simpler sanitize function that + * always used default values and only worked with Drupal 6 + mysql + * appears in the drush.api.php. @see hook_drush_sql_sync_sanitize(). + */ +function sql_drush_sql_sync_sanitize($site) { + $site_settings = drush_sitealias_get_record($site); + $user_table_updates = array(); + $message_list = array(); + + // Sanitize email addresses + $newpassword = drush_get_option(array('sanitize-password', 'destination-sanitize-password'), 'password'); + if ($newpassword != 'no') { + $major_version = drush_drupal_major_version(); + $pw_op = ""; + + // In Drupal 6, passwords are hashed via the MD5 algorithm. + if ($major_version == 6) { + $pw_op = "MD5('$newpassword')"; + } + // In Drupal 7, passwords are hashed via a more complex algorithm, + // available via the user_hash_password function. + elseif ($major_version >= 7) { + include_once DRUPAL_ROOT . '/includes/password.inc'; + include_once DRUPAL_ROOT . '/includes/bootstrap.inc'; + $hash = user_hash_password($newpassword); + $pw_op = "'$hash'"; + } + if (!empty($pw_op)) { + $user_table_updates[] = "pass = $pw_op"; + $message_list[] = "passwords"; + } + } + + // Sanitize passwords + $newemail = drush_get_option(array('sanitize-email', 'destination-sanitize-email'), 'user+%uid@localhost'); + if ($newemail != 'no') { + if (strpos($newemail, '%') !== FALSE) { + // We need a different sanitization query for Postgres and Mysql + $db_driver = $site_settings['databases']['default']['default']['driver']; + if ($db_driver == 'pgsql') { + $email_map = array('%uid' => "' || uid || '", '%mail' => "' || replace(mail, '@', '_') || '", '%login' => "' || replace(login, ' ', '_') || '"); + $newmail = "'" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "'"; + } + else { + $email_map = array('%uid' => "', uid, '", '%mail' => "', replace(mail, '@', '_'), '", '%login' => "', replace(login, ' ', '_'), '"); + $newmail = "concat('" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "')"; + } + } + $user_table_updates[] = "mail = $newmail"; + $message_list[] = 'email addresses'; + } + + if (!empty($user_table_updates)) { + $sanitize_query = "update users set " . implode(', ', $user_table_updates) . " where uid > 0;"; + drush_sql_register_post_sync_op('user-email', dt('Reset !message in user table', array('!message' => implode(' and ', $message_list))), $sanitize_query); + } + +} + + +function drush_sql_sync($source = NULL, $destination = NULL) { + $source_settings = drush_sitealias_get_record($source); + $destination_settings = drush_sitealias_get_record($destination); // Check to see if this is an sql-sync multiple command (multiple sources and multiple destinations) $is_multiple = drush_do_multiple_command('sql-sync', $source_settings, $destination_settings); @@ -86,41 +158,39 @@ function _drush_sql_sync($source, $destination, $show_warning = TRUE) { // If the result file is not set, then create a temporary file. // If the remote file is not set, use the same name for the remote // and local files and hope for the best. - $source_dump = drush_get_option('source-dump'); - $target_dump = drush_get_option('target-dump'); + $source_dump = drush_sql_dump_file($source_db_url, 'source-'); + $target_dump = drush_sql_dump_file($target_db_url, 'target-'); $use_temp_files = drush_get_option('temp'); - $source_is_tmp = FALSE; - $target_is_tmp = FALSE; + // Only use one dump file if both the source and the target are on the local machine if (!isset($source_db_url['remote-host']) && !isset($target_db_url['remote-host'])) { - if (isset($source_dump)) { - $target_dump = $source_dump; + if ((!$target_db_url['dump-is-temp']) && ($source_db_url['dump-is-temp'])) { + $source_dump = $target_dump; + $source_db_url['dump-is-temp'] = FALSE; } else { - if (!isset($target_dump)) { - $target_dump = drush_sql_dump_file($target_db_url); - $target_is_tmp = TRUE; - } - $source_dump = $target_dump; + $target_dump = $source_dump; + $target_db_url['dump-is-temp'] = $source_db_url['dump-is-temp']; } - } - if (!isset($target_dump)) { - $target_dump = drush_sql_dump_file($target_db_url); - $target_is_tmp = TRUE; - } - if (!isset($source_dump)) { - $source_dump = drush_sql_dump_file($source_db_url); - $source_is_tmp = TRUE; - $source_rsync_options['remove-source-files'] = TRUE; - } - - if (isset($source_db_url['remote-host']) && isset($target_db_url['remote-host'])) { - $local_file = drush_tempnam($source_db_url['database'] . ($source_db_url['database'] == $target_db_url['database'] ? '' : '-to-' . $target_db_url['database']) . '.sql.'); - } - elseif (!isset($source_db_url['remote-host'])) { $local_file = $source_dump; } - elseif (!isset($target_db_url['remote-host'])) { - $local_file = $target_dump; + else { + // If one of the systems is remote, then set the --remove-source-files + // rsync option if the source dump file is temporary. This will get + // rsync to clean up after us automatically; useful if the source is remote. + if ($source_db_url['dump-is-temp']) { + $source_rsync_options['remove-source-files'] = TRUE; + } + // Set $local_file to whichever side of the operation is local, or make + // a temporary file if both source and target are remote. + if (!isset($source_db_url['remote-host'])) { + $local_file = $source_dump; + } + elseif (!isset($target_db_url['remote-host'])) { + $local_file = $target_dump; + } + else { + $local_file = drush_tempnam($source_db_url['database'] . ($source_db_url['database'] == $target_db_url['database'] ? '' : '-to-' . $target_db_url['database']) . '.sql.'); + } } // If source is remote, then use ssh to dump the database and then rsync to local machine @@ -145,8 +215,8 @@ function _drush_sql_sync($source, $destination, $show_warning = TRUE) { // target file exists and its modification date is less than "cache" hours. if (isset($cache)) { if (file_exists($local_file) && (filesize($local_file) > 0)) { - if ((filemtime($local_file) - time()) < ($cache * 60 * 60)) { - drush_log(dt('Modification time of local dump file is less than !cache hours old. Use the --no-cache option to force a refresh.', array('!cache' => $cache))); + if ((time() - filemtime($local_file)) < ($cache * 60 * 60)) { + drush_log(dt('Modification time of local dump file !file is less than !cache hours old. Use the --no-cache option to force a refresh.', array('!file' => $local_file, '!cache' => $cache)), 'warning'); $no_dump = TRUE; $no_sync = TRUE; } @@ -165,52 +235,68 @@ function _drush_sql_sync($source, $destination, $show_warning = TRUE) { } // Prompt for confirmation. This is destructive. - if (!drush_get_context('DRUSH_SIMULATE') && $show_warning) { + if (!drush_get_context('DRUSH_SIMULATE')) { + // Check to see if we are using a temporary file in a situation + // where the user did not specify "--temp". + if (($source_db_url['dump-is-temp'] || $target_db_url['dump-is-temp']) && (!isset($use_temp_files)) && (isset($source_db_url['remote-host']) || isset($target_db_url['remote-host']))) { + drush_print(dt('WARNING: Using temporary files to store and transfer sql-dump. It is recommended that you specify --source-dump and --target-dump options on the command line, or set \'%dump\' or \'%dump-dir\' in the path-aliases section of your site alias records. This facilitates fast file transfer via rsync.')); + } + + if (array_key_exists('tables', $table_selection) && (count($table_selection['tables']) > 0)) { + drush_print(); + drush_print(dt(' Only the following tables will be transferred: !list', array('!list' => implode(',', $table_selection['tables'])))); + } + elseif (!empty($table_selection)) { + $skip_tables_list = implode(',', $table_selection['skip'] + $table_selection['structure']); + if(!empty($skip_tables_list)) { + drush_print(); + drush_print(dt(' The following tables will be skipped: !list', array('!list' => $skip_tables_list))); + } + } // If there are multiple destinations, then // prompt once here and suppress the warning message // and the normal confirmation below. if (array_key_exists('site-list', $destination_settings)) { + drush_print(); drush_print(dt('You are about to sync the database from !source, overwriting all of the following targets:', array('!source' => $source))); foreach ($destination_settings['site-list'] as $one_destination) { drush_print(dt(' !target', array('!target' => $one_destination))); } - drush_print(); } else { - // Check to see if we are using a temporary file in a situation - // where the user did not specify "--temp". - if (($source_is_tmp || $target_is_tmp) && (!isset($use_temp_files)) && (isset($source_db_url['remote-host']) || isset($target_db_url['remote-host']))) { - drush_print(dt('WARNING: Using temporary files to store and transfer sql-dump. It is recommended that you specify --source-dump and --target-dump options on the command line, or set \'%dump\' in the path-aliases section of your site alias records. This facilitates fast file transfer via rsync.')); - } - + drush_print(); $txt_source = (isset($source_db_url['remote-host']) ? $source_db_url['remote-host'] . '/' : '') . $source_db_url['database']; $txt_destination = (isset($target_db_url['remote-host']) ? $target_db_url['remote-host'] . '/' : '') . $target_db_url['database']; drush_print(dt("You will destroy data from !target and replace with data from !source.", array('!source' => $txt_source, '!target' => $txt_destination))); - drush_print(); } - if (array_key_exists('tables', $table_selection) && (count($table_selection['tables']) > 0)) { - drush_print(dt(' Only the following tables will be transferred: !list', array('!list' => implode(',', $table_selection['tables'])))); + // If any sanitization operations are to be done, then get the + // sanitization messages and print them as part of the confirmation. + // If --sanitize was specified but there were no sanitize messages, + // then warn that sanitization operations will be accumulated and + // processed after the sync completes. + $messages = _drush_sql_get_post_sync_messages(); + if ($messages) { drush_print(); + drush_print($messages); } - elseif (!empty($table_selection)) { - $skip_tables_list = implode(',', $table_selection['skip'] + $table_selection['structure']); - if(!empty($skip_tables_list)) { - drush_print(dt(' The following tables will be skipped: !list', array('!list' => $skip_tables_list))); - drush_print(); - } + else if (drush_get_option('deferred-sanitization', FALSE) && !drush_get_option('confirm-sanitizations', FALSE)) { + drush_print(); + drush_print("WARNING: --sanitize was specified, but deferred (e.g. the source site is remote). The sanitization operations will be determined after the database is copied to the local system and will be run without further confirmation. Run with --confirm-sanitizations to force confirmation after the sync."); } // TODO: actually make the backup if desired. - drush_print(dt("You might want to make a backup first, using sql_dump command.\n")); + drush_print(); + drush_print(dt("You might want to make a backup first, using the sql-dump command.\n")); if (!drush_confirm(dt('Do you really want to continue?'))) { - drush_die('Aborting.'); + drush_user_abort(); } } if (isset($source_db_url['remote-host'])) { $source_remote_user = drush_get_option('source-remote-user'); + $source_at = ''; if (isset($source_remote_user)) { $source_at ='@'; $source_remote_pass = drush_get_option('source-remote-pass') ? ':' . drush_get_option('source-remote-pass') : ''; @@ -223,16 +309,16 @@ function _drush_sql_sync($source, $destination, $show_warning = TRUE) { // then first dump to a temporary file and move it to the specified file after // the dump is complete. This will reduce contention during simultaneous dumps // from different users sharing the same dump file. - if (!isset($source_is_tmp)) { + if (!$source_db_url['dump-is-temp']) { $source_intermediate = $source_dump . '-' . date("U"); $mv_intermediate = '; mv -f ' . $source_intermediate . ' ' . $source_dump; } drush_set_option('result-file', $source_intermediate); $dump_exec = drush_sql_build_dump_command($table_selection, $source_db_url) . $mv_intermediate; - if (isset($cache) && !isset($source_is_tmp)) { + if (isset($cache) && !$source_db_url['dump-is-temp']) { // Inject some bash commands to remotely test the modification date of the target file // if the cache option is set. - $dump_exec = 'if [ ! -s ' . $source_dump . '] || [ $((`date "+%s"`-`stat --format="%Y" ' . $source_dump . '`)) -gt ' . ($cache * 60 * 60) . ' ] ; then ' . $dump_exec . '; fi'; + $dump_exec = 'if [ ! -s ' . $source_dump . ' ] || [ $((`date "+%s"`-`stat --format="%Y" ' . $source_dump . '`)) -gt ' . ($cache * 60 * 60) . ' ] ; then ' . $dump_exec . '; fi'; } $dump_exec = "ssh $source_remote_ssh_options $source_remote_user$source_at" . $source_db_url['remote-host'] . " " . escapeshellarg($dump_exec); } @@ -309,10 +395,10 @@ function _drush_sql_sync($source, $destination, $show_warning = TRUE) { $import_command = _drush_sql_connect($target_db_url); switch (_drush_sql_get_scheme($target_db_url)) { case 'mysql': - $import_command .= ' ' . (drush_get_context('DRUSH_DEBUG') ? ' ' : '--silent'); + $import_command .= ' --silent'; break; case 'pgsql': - $import_command .= ' ' . (drush_get_context('DRUSH_DEBUG') ? ' ' : '-q'); + $import_command .= ' -q'; break; } @@ -320,6 +406,7 @@ function _drush_sql_sync($source, $destination, $show_warning = TRUE) { // If destination is local, then just import the database locally if (isset($target_db_url['remote-host'])) { $target_remote_user = drush_get_option('target-remote-user'); + $target_at = ''; if (isset($target_remote_user)) { $target_at ='@'; $target_remote_pass = drush_get_option('target-remote-pass') ? ':' . drush_get_option('target-remote-pass') : ''; @@ -330,7 +417,7 @@ function _drush_sql_sync($source, $destination, $show_warning = TRUE) { $connect_exec = $pre_import_commands . $import_command . ' < ' . $target_dump; $import_exec = "ssh $target_remote_ssh_options $target_remote_user$target_at" . $target_db_url['remote-host'] . ' ' . escapeshellarg($connect_exec); // delete the remote target file if it is a temporary file - if ($target_is_tmp) { + if ($target_db_url['dump-is-temp']) { $import_exec .= '; rm -f ' . escapeshellarg($target_dump); } } @@ -339,6 +426,65 @@ function _drush_sql_sync($source, $destination, $show_warning = TRUE) { } drush_op('system', $import_exec); + + // After the database is imported into the destination, we + // will check and see if we did not collect sanitization + // operations in drush_sql_sync_init (i.e. because the source + // site was remote), and if the destination site is local, + // then we will call the sanitization hooks now. + // This presumes an important precondition, that the code + // files were sync'ed before the database was sync'ed. + if (drush_get_option('deferred-sanitization', FALSE) && (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_SITE) == FALSE)) { + $bootstrapped = drush_bootstrap_max_to_sitealias($destination_settings); + if ($bootstrapped) { + drush_command_invoke_all('drush_sql_sync_sanitize', $destination); + } + } + } + } +} + +/** + * Apply all post-sync operations that were registered in any pre-sync hook. + * Follow the pattern of this function to make your own post-sync hook. + * If changing the database, be sure to also include a pre-sync hook to + * notify the user of the change that will be made. @see drush_sql_pre_sql_sync(). + */ +function drush_sql_post_sql_sync($source = NULL, $destination = NULL) { + $options = drush_get_context('post-sync-ops'); + if (!empty($options)) { + // If 'deferred-sanitization' is set, then we collected the + // sanitization operations -after- the database sync, which + // means they were not confirmed up-front. We will show the + // operations here, but we will not offer an opportunity to + // confirm unless --confirm-sanitizations is specified. + if (drush_get_option('deferred-sanitization', FALSE) || drush_get_option('confirm-sanitizations', FALSE)) { + if (!drush_get_context('DRUSH_SIMULATE')) { + $messages = _drush_sql_get_post_sync_messages(); + if ($messages) { + drush_print(); + drush_print($messages); + if (drush_get_option('confirm-sanitizations', FALSE)) { + if (!drush_confirm(dt('Do you really want to sanitize?'))) { + drush_user_abort('Aborting; sql-sync completed, but sanitizations skipped.'); + } + } + } + } + } + + $destination_settings = drush_sitealias_get_record($destination); + $sanitize_query = ''; + foreach($options as $id => $data) { + $sanitize_query .= $data['query'] . " "; + } + if ($sanitize_query) { + if (!drush_get_context('DRUSH_SIMULATE')) { + $result = drush_do_site_command($destination_settings, "sql-query", array($sanitize_query)); + } + else { + drush_print("Executing on $destination: $sanitize_query"); + } } } } diff --git a/sites/all/modules/drush/commands/user/user.drush.inc b/sites/all/modules/drush/commands/user/user.drush.inc new file mode 100644 index 0000000000000000000000000000000000000000..40b384645a1317296eb5e23a6a751c086ca2669c --- /dev/null +++ b/sites/all/modules/drush/commands/user/user.drush.inc @@ -0,0 +1,586 @@ +<?php +// $Id: + +/** + * @file Drush User Management commands + */ + +/** + * Implementation of hook_drush_help(). + */ +function user_drush_help($section) { + switch ($section) { + case 'drush:user-information': + return dt("Display information about a user identified by username, uid or email address."); + case 'drush:user-block': + return dt("Block the specified user(s)."); + case 'drush:user-unblock': + return dt("Unblock the specified user(s)."); + case 'drush:user-add-role': + return dt("Add a role to the specified user accounts."); + case 'drush:user-remove-role': + return dt("Remove a role from the specified user accounts."); + case 'drush:user-create': + return dt("Create a user account."); + case 'drush:user-cancel': + return dt("Cancel a user account."); + case 'drush:user-password': + return dt("(Re)Set the password for the given user account."); + case 'drush:user-login': + return dt("Display a one time login link for the given user account (defaults to uid 1)."); + } +} + +/** + * Implementation of hook_drush_command(). + */ +function user_drush_command() { + $items['user-information'] = array( + 'callback' => 'drush_user_information', + 'description' => 'Print information about the specified user(s).', + 'aliases' => array('uinf'), + 'examples' => array( + 'drush user-information 2,3,someguy,somegal,billgates@microsoft.com' => + 'Display information about any users with uids, names, or mail addresses matching the strings between commas.', + ), + 'arguments' => array( + 'users' => 'A comma delimited list of uids, user names, or email addresses.', + ), + 'options' => array( + '--full' => 'show extended information about the user', + '--short' => 'show basic information about the user (this is the default)', + ), + ); + $items['user-block'] = array( + 'callback' => 'drush_user_block', + 'description' => 'Block the specified user(s).', + 'aliases' => array('ublk'), + 'arguments' => array( + 'users' => 'A comma delimited list of uids, user names, or email addresses.', + ), + 'examples' => array( + 'drush user-block 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => + 'Block the users with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', + ), + 'options' => array( + '--uid' => 'A comma delimited list of uids to block', + '--name' => 'A comma delimited list of user names to block', + '--mail' => 'A comma delimited list of user mail addresses to block', + ), + ); + $items['user-unblock'] = array( + 'callback' => 'drush_user_unblock', + 'description' => 'Unblock the specified user(s).', + 'aliases' => array('uublk'), + 'arguments' => array( + 'users' => 'A comma delimited list of uids, user names, or email addresses.', + ), + 'examples' => array( + 'drush user-unblock 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => + 'Unblock the users with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', + ), + 'options' => array( + '--uid' => 'A comma delimited list of uids to unblock', + '--name' => 'A comma delimited list of user names to unblock', + '--mail' => 'A comma delimited list of user mail addresses to unblock', + ), + ); + $items['user-add-role'] = array( + 'callback' => 'drush_user_add_role', + 'description' => 'Add a role to the specified user accounts.', + 'aliases' => array('urol'), + 'arguments' => array( + 'role' => 'The name of the role to add', + 'users' => '(optional) A comma delimited list of uids, user names, or email addresses.', + ), + 'examples' => array( + 'drush user-add-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => + 'Add the "power user" role to the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', + ), + 'options' => array( + '--uid' => 'A comma delimited list of uids', + '--name' => 'A comma delimited list of user names', + '--mail' => 'A comma delimited list of user mail addresses', + ), + ); + $items['user-remove-role'] = array( + 'callback' => 'drush_user_remove_role', + 'description' => 'Remove a role from the specified user accounts.', + 'aliases' => array('urrol'), + 'arguments' => array( + 'role' => 'The name of the role to remove', + 'users' => '(optional) A comma delimited list of uids, user names, or email addresses.', + ), + 'examples' => array( + 'drush user-remove-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => + 'Remove the "power user" role from the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', + ), + 'options' => array( + '--uid' => 'A comma delimited list of uids', + '--name' => 'A comma delimited list of user names', + '--mail' => 'A comma delimited list of user mail addresses', + ), + ); + $items['user-create'] = array( + 'callback' => 'drush_user_create', + 'description' => 'Create a user account with the specified name.', + 'aliases' => array('ucrt'), + 'arguments' => array( + 'name' => 'The name of the account to add' + ), + 'examples' => array( + 'drush user-create newuser --mail="person@example.com" --password="letmein"' => + 'Create a new user account with the name newuser, the email address person@example.com, and the password letmein', + ), + 'options' => array( + '--password' => 'The password for the new account', + '--mail' => 'The email address for the new account', + ), + ); + $items['user-cancel'] = array( + 'callback' => 'drush_user_cancel', + 'description' => 'Cancel a user account with the specified name.', + 'aliases' => array('ucan'), + 'arguments' => array( + 'name' => 'The name of the account to cancel', + ), + 'examples' => array( + 'drush user-cancel username' => + 'Cancel the user account with the name username and anonymize all content created by that user.', + ), + ); + $items['user-password'] = array( + 'callback' => 'drush_user_password', + 'description' => '(Re)Set the password for the user account with the specified name.', + 'aliases' => array('upwd'), + 'arguments' => array( + 'name' => 'The name of the account to modify' + ), + 'options' => array( + '--password' => '(required) The new password for the account', + ), + 'examples' => array( + 'drush user-password someuser --password="gr3@tP@$s"' => + 'Set the password for the username someuser to gr3@tP@$s.', + ), + ); + $items['user-login'] = array( + 'callback' => 'drush_user_login', + 'description' => 'Display a one time login link for the given user account (defaults to uid 1).', + 'aliases' => array('uli'), + 'arguments' => array( + 'name' => 'The name of the account to log in as. Leave it empty to log in as uid 1.' + ), + 'examples' => array( + 'drush user-login ryan' => 'Displays a one-time login link for the user ryan.', + 'open `drush user-login ryan`' => 'Open web browser and login as user ryan.', + ), + ); + + // Drupal 7 only options. + if (drush_drupal_major_version() >= 7) { + $items['user-cancel']['options'] = array( + 'delete-content' => 'Delete all content created by the user', + ); + $items['user-cancel']['examples']['drush user-cancel --delete-content username'] = + 'Cancel the user account with the name username and delete all content created by that user.'; + } + return $items; +} + +// Implementation of hook_drush_init(). +function user_drush_init() { + $command_info = drush_get_command(); + $command = $command_info['command']; + $needs_parse_args = array('user-block', 'user-unblock', 'user-add-role', 'user-remove-role'); + if (in_array($command, $needs_parse_args)) { + // parse args and call drush_set_option for --uids + $users = array(); + foreach (array('uid', 'name', 'mail' ) as $user_attr) { + if ($arg = drush_get_option($user_attr)) { + foreach(explode(',', $arg) as $search) { + $uid_query = FALSE; + switch ($user_attr) { + case 'uid': + if (drush_drupal_major_version() >= 7) { + $uid_query = db_query("SELECT uid FROM {users} WHERE uid = :uid", array(':uid' => $search)); + } + else { + $uid_query = db_query("SELECT uid FROM {users} WHERE uid = %d", $search); + } + break; + case 'name': + if (drush_drupal_major_version() >= 7) { + $uid_query = db_query("SELECT uid FROM {users} WHERE name = :name", array(':name' => $search)); + } + else { + $uid_query = db_query("SELECT uid FROM {users} WHERE name = '%s'", $search); + } + break; + case 'mail': + if (drush_drupal_major_version() >= 7) { + $uid_query = db_query("SELECT uid FROM {users} WHERE mail = :mail", array(':mail' => $search)); + } + else { + $uid_query = db_query("SELECT uid FROM {users} WHERE mail = '%s'", $search); + } + break; + } + if ($uid_query !== FALSE) { + if ($uid = drush_db_result($uid_query)) { + $users[] = $uid; + } + else { + drush_set_error("Could not find a uid for $user_attr = $search"); + } + } + } + } + } + if (!empty($users)) { + drush_set_option('uids', $users); + } + } +} + +/** + * Prints information about the specified user(s). + */ +function drush_user_information($users) { + $users = explode(',', $users); + foreach($users as $user) { + $uid = _drush_user_get_uid($user); + if ($uid !== FALSE) { + _drush_user_print_info($uid); + } + } +} + +/** + * Block the specified user(s). + */ +function drush_user_block($users = '') { + $uids = drush_get_option('uids'); + if ($users !== '') { + $users = explode(',', $users); + foreach($users as $user) { + $uid = _drush_user_get_uid($user); + if ($uid !== FALSE) { + $uids[] = $uid; + } + } + } + if (!empty($uids)) { + drush_op('user_user_operations_block', $uids); + } + else { + return drush_set_error("Could not find any valid uids!"); + } +} + +/** + * Unblock the specified user(s). + */ +function drush_user_unblock($users = '') { + $uids = drush_get_option('uids'); + if ($users !== '') { + $users = explode(',', $users); + foreach($users as $user) { + $uid = _drush_user_get_uid($user); + if ($uid !== FALSE) { + $uids[] = $uid; + } + } + } + if (!empty($uids)) { + drush_op('user_user_operations_unblock', $uids); + } + else { + return drush_set_error("Could not find any valid uids!"); + } +} + +/** + * Add a role to the specified user accounts. + */ +function drush_user_add_role($role, $users = '') { + $uids = drush_get_option('uids'); + if ($users !== '') { + $users = explode(',', $users); + foreach($users as $user) { + $uid = _drush_user_get_uid($user); + if ($uid !== FALSE) { + $uids[] = $uid; + } + } + } + if (drush_drupal_major_version() >= 7) { + $rid_query = db_query("SELECT rid FROM {role} WHERE name = :role", array(':role' => $role)); + } + else { + $rid_query = db_query("SELECT rid FROM {role} WHERE name = '%s'", $role); + } + if (!empty($uids)) { + if ($rid = drush_db_result($rid_query)) { + drush_op('user_multiple_role_edit', $uids, 'add_role', $rid); + foreach($uids as $uid) { + drush_log(dt("Added the %role role to uid %uid", array('%role' => $role, '%uid' => $uid)), 'success'); + } + } + else { + return drush_set_error("There is no role named: \"$role\"!"); + } + } + else { + return drush_set_error("Could not find any valid uids!"); + } +} + +/** + * Remove a role from the specified user accounts. + */ +function drush_user_remove_role($role, $users = '') { + $uids = drush_get_option('uids'); + if ($users !== '') { + $users = explode(',', $users); + foreach($users as $user) { + $uid = _drush_user_get_uid($user); + if ($uid !== FALSE) { + $uids[] = $uid; + } + } + } + if (drush_drupal_major_version() >= 7) { + $rid_query = db_query("SELECT rid FROM {role} WHERE name = :role", array(':role' => $role)); + } + else { + $rid_query = db_query("SELECT rid FROM {role} WHERE name = '%s'", $role); + } + if (!empty($uids)) { + if ($rid = drush_db_result($rid_query)) { + drush_op('user_multiple_role_edit', $uids, 'remove_role', $rid); + foreach($uids as $uid) { + drush_log(dt("Removed the %role role from uid %uid", array('%role' => $role, '%uid' => $uid)), 'success'); + } + } + else { + return drush_set_error("There is no role named: \"$role\"!"); + } + } + else { + return drush_set_error("Could not find any valid uids!"); + } +} + +/** + * Creates a new user account. + */ +function drush_user_create($name) { + $mail = drush_get_option('mail'); + $pass = drush_get_option('password'); + $new_user = array( + 'name' => $name, + 'pass' => $pass, + 'mail' => $mail, + 'access' => '0', + 'status' => 1, + ); + if (drush_drupal_major_version() >= 7) { + $result = db_query("SELECT uid FROM {users} WHERE name = :name OR mail = :mail", array(':name' => $name, ':mail' => $new_user['mail'])); + } + else { + $result = db_query("SELECT uid FROM {users} WHERE name = '%s' OR mail = '%s'", $name, $new_user['mail']); + } + if (drush_db_result($result) === FALSE) { + if (!drush_get_context('DRUSH_SIMULATE')) { + $new_user_object = user_save(NULL, $new_user, NULL); + if ($new_user_object !== FALSE) { + _drush_user_print_info($new_user_object->uid); + } + else { + drush_set_error("Could not create a new user account with the name " . $name . "!"); + } + } + } + else { + drush_set_error("There is already a user account with the name " . $name . " or email address " . $new_user['mail'] . "!"); + } +} + +/** + * Cancels a user account. + */ +function drush_user_cancel($name) { + if (drush_drupal_major_version() >= 7) { + $result = db_query("SELECT uid FROM {users} WHERE name = :name", array(':name' => $name)); + } + else { + $result = db_query("SELECT uid FROM {users} WHERE name = '%s'", $name); + } + $uid = drush_db_result($result); + if ($uid !== FALSE) { + drush_print("Cancelling the user account with the following information:"); + _drush_user_print_info($uid); + if (drush_get_option('delete-content') && drush_drupal_major_version() >= 7) { + drush_print("All content created by this user will be deleted!"); + } + if (drush_confirm('Cancel user account?: ')) { + if (drush_drupal_major_version() >= 7) { + if (drush_get_option('delete-content')) { + user_cancel(array(), $uid, 'user_cancel_delete'); + } + else { + user_cancel(array(), $uid, 'user_cancel_reassign'); + } + // I got the following technique here: http://drupal.org/node/638712 + $batch =& batch_get(); + $batch['progressive'] = FALSE; + batch_process(); + } + else { + user_delete(array(), $uid); + } + } + } + else { + drush_set_error("Could not find a user account with the name " . $name . "!"); + } +} + +/** + * Sets the password for the account with the given username + */ +function drush_user_password($name) { + $pass = drush_get_option('password'); + if (empty($pass)) { + return drush_set_error("You must specify a password!"); + } + if (drush_drupal_major_version() >= 7) { + $user = user_load_by_name($name); + } + else { + $user = user_load(array('name' => $name)); + } + if ($user !== FALSE) { + if (!drush_get_context('DRUSH_SIMULATE')) { + $user_object = user_save($user, array('pass' => $pass)); + if ($user_object === FALSE) { + drush_set_error("Could not change the password for the user account with the name " . $name . "!"); + } + } + } + else { + drush_set_error("The user account with the name " . $name . " could not be loaded!"); + } +} + +/** + * Displays a one time login link for the given user. + */ +function drush_user_login($name = NULL) { + if (empty($name)) { + $user = user_load(1); + $name = '[uid 1]'; + } + elseif (drush_drupal_major_version() >= 7) { + $user = user_load_by_name($name); + } + else { + $user = user_load(array('name' => $name)); + } + + if ($user !== FALSE) { + drush_print(user_pass_reset_url($user)); + } + else { + drush_set_error("The user account with the name " . $name . " could not be loaded!"); + } +} + +/** + * Print information about a given uid + */ +function _drush_user_print_info($uid) { + if (drush_drupal_major_version() >= 7) { + $userinfo = user_load($uid); + } + else { + $userinfo = user_load(array('uid' => $uid)); + } + if (drush_get_option('full')) { + $userinfo = (array)$userinfo; + $userinfo_pipe = array(); + unset($userinfo['data']); + unset($userinfo['block']); + unset($userinfo['form_build_id']); + foreach($userinfo as $key => $val) { + if (is_array($val)) { + drush_print($key . ': '); + drush_print_r($val); + $userinfo_pipe[] = '"' . implode(",", $val) . '"'; + } + else { + if ($key === 'created' OR $key === 'access' OR $key === 'login') { + drush_print($key . ': ' . format_date($val)); + $userinfo_pipe[] = $val; + } + else { + drush_print($key . ': ' . $val); + $userinfo_pipe[] = $val; + } + } + } + drush_print_pipe(implode(",", $userinfo_pipe)); + drush_print_pipe("\n"); + } + else { + $userinfo_short = array( + 'User ID' => $userinfo->uid, + 'User name' => $userinfo->name, + 'User mail' => $userinfo->mail, + ); + $userinfo_short['User roles'] = implode(', ', $userinfo->roles); + $userinfo->status ? $userinfo_short['User status'] = 'active' : $userinfo_short['User status'] = 'blocked'; + drush_print_table(drush_key_value_to_array_table($userinfo_short)); + drush_print_pipe("$userinfo->name, $userinfo->uid, $userinfo->mail, $userinfo->status, \"" . implode(', ', $userinfo->roles) . "\"\n"); + } +} + +/** + * Get uid(s) from a uid, user name, or email address. + * Returns a uid, or FALSE if none found. + */ +function _drush_user_get_uid($search) { + // We use a DB query while looking for the uid to keep things speedy. + $uids = array(); + if (is_numeric($search)) { + if (drush_drupal_major_version() >= 7) { + $uid_query = db_query("SELECT uid, name FROM {users} WHERE uid = :uid OR name = :name", array(':uid' => $search, ':name' => $search)); + } + else { + $uid_query = db_query("SELECT uid, name FROM {users} WHERE uid = %d OR name = '%d'", $search, $search); + } + } + else { + if (drush_drupal_major_version() >= 7) { + $uid_query = db_query("SELECT uid, name FROM {users} WHERE mail = :mail OR name = :name", array(':mail' => $search, ':name' => $search)); + } + else { + $uid_query = db_query("SELECT uid, name FROM {users} WHERE mail = '%s' OR name = '%s'", $search, $search); + } + } + while ($uid = drush_db_fetch_object($uid_query)) { + $uids[$uid->uid] = $uid->name; + } + switch (count($uids)) { + case 0: + return drush_set_error("Could not find a uid for the search term '" . $search . "'!"); + break; + case 1: + return array_pop(array_keys($uids)); + break; + default: + drush_print('More than one user account was found for the search string "' . $search . '".'); + return(drush_choice($uids, 'Please choose a name:', '!value (uid=!key)')); + } +} diff --git a/sites/all/modules/drush/drush b/sites/all/modules/drush/drush index ce7700991e9e92b21fb9c5c2eac13d59e923aa1e..73c20d9c7ecc548c92f7ae43baf6b738c8b59964 100755 --- a/sites/all/modules/drush/drush +++ b/sites/all/modules/drush/drush @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# $Id: drush,v 1.20 2010/06/17 04:39:41 greg1anderson Exp $ +# $Id: drush,v 1.25 2010/11/10 13:57:54 weitzman Exp $ # # This script is a simple wrapper that will run Drush with the most appropriate # php executable it can find. @@ -10,7 +10,7 @@ ORIGDIR=$(pwd) SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0") # Resolve symlinks - this is the equivalent of "readlink -f", but also works with non-standard OS X readlink. -while [ -h $SELF_PATH ]; do +while [ -h "$SELF_PATH" ]; do # 1) cd to directory of the symlink # 2) cd to the directory of where the symlink points # 3) Get the pwd @@ -22,32 +22,68 @@ done cd "$ORIGDIR" # Build the path to drush.php. -SCRIPT_PATH=$(dirname $SELF_PATH)/drush.php +SCRIPT_PATH=$(dirname "$SELF_PATH")/drush.php case $(uname -a) in CYGWIN*) SCRIPT_PATH=$(cygpath -w -a -- "$SCRIPT_PATH") ;; esac -# If not exported and term is set determine and export the number of columns. -if [ -z $COLUMNS ] && [ -n "$TERM" ]; then - # Note to cygwin users: if you are getting "tput: command not found", - # install the ncurses package to get it. - export COLUMNS=$(tput cols) +# If not exported, try to determine and export the number of columns. +# We do not want to run $(tput cols) if $TERM is empty or "dumb", because +# if we do, tput will output an undesirable error message to stderr. If +# we redirect stderr in any way, e.g. $(tput cols 2>/dev/null), then the +# error message is suppressed, but tput cols becomes confused about the +# terminal and prints out the default value (80). +if [ -z $COLUMNS ] && [ -n "$TERM" ] && [ "$TERM" != dumb ] ; then + # Note to cygwin users: install the ncurses package to get tput command. + if COLUMNS=$(tput cols); then + export COLUMNS + fi +fi + +if [ ! -z "$DRUSH_PHP" ] ; then + # Use the DRUSH_PHP environment variable if it is available. + php="$DRUSH_PHP" +else + # Default to using the php that we find on the PATH. + # Note that we need the full path to php here for Dreamhost, which behaves oddly. See http://drupal.org/node/662926 + php=`which php` + + # We check for a command line (cli) version of php, and if found use that. + which php-cli >/dev/null 2>&1 + if [ "$?" = 0 ] ; then + php=`which php-cli` + fi + + # Special case for *AMP installers, since they normally don't set themselves as the default cli php out of the box. + for amp_php in /Applications/MAMP/bin/php5/bin/php /Applications/MAMP/bin/php5.2/bin/php /Applications/MAMP/bin/php5.3/bin/php /opt/lampp/bin/php /Applications/xampp/xamppfiles/bin/php /Applications/acquia-drupal/php/bin/php; do + if [ -x $amp_php ]; then + php=$amp_php + fi + done fi -# Special case for *AMP installers, since they normally don't set themselves as the default cli php out of the box. -for php in /Applications/MAMP/bin/php5/bin/php /Applications/MAMP/bin/php5.2/bin/php /Applications/MAMP/bin/php5.3/bin/php /opt/lampp/bin/php /Applications/xampp/xamppfiles/bin/php /Applications/acquia-drupal/php/bin/php; do - if [ -x $php ]; then - exec $php $SCRIPT_PATH --php="$php" "$@" +# Check to see if the user has provided a php.ini file or drush.ini file in any conf dir +# Last found wins, so search in reverse priority order +for conf_dir in /etc/drush $HOME/.drush ; do + if [ -f $conf_dir/php.ini ] ; then + drush_php_ini=$conf_dir/php.ini + fi + if [ -f $conf_dir/drush.ini ] ; then + drush_php_override=$conf_dir/drush.ini fi done -# We check for a command line (cli) version of php, and if found use that. -which php-cli >/dev/null 2>&1 -if [ "$?" = 0 ] ; then - exec php-cli $SCRIPT_PATH --php="php-cli" "$@" -else - # Alternatively we run with straight php, which works on most other systems. - # The --php=`which php` is for Dreamhost, which behaves oddly. See http://drupal.org/node/662926 - exec php $SCRIPT_PATH --php=`which php` "$@" +# Add in the php file location and/or the php override variables as appropriate +if [ "x$drush_php_ini" != "x" ] ; then + php="$php --php-ini $drush_php_ini" fi +if [ "x$drush_php_override" != "x" ] ; then + drush_override_vars=`grep '^[a-z_A-Z0-9]\+ *=' $drush_php_override | sed -e 's|\([^ =]*\) *= *\(.*\)|\1="\2"|' -e 's| ||g' -e 's|^|-d |' | tr '\n\r' ' '` + php="$php $drush_override_vars" +fi + +# Pass in the path to php so that drush knows which one +# to use if it re-launches itself to run subcommands +exec $php "$SCRIPT_PATH" "$@" --php="$php" + diff --git a/sites/all/modules/drush/drush.api.php b/sites/all/modules/drush/drush.api.php index 87dae7f4e573560eaff701effab073c2301c9460..f4933e1bc1c69863f6d82ef5e2576c7a38ee9f67 100644 --- a/sites/all/modules/drush/drush.api.php +++ b/sites/all/modules/drush/drush.api.php @@ -1,5 +1,5 @@ <?php -// $Id: drush.api.php,v 1.8 2010/06/10 11:29:13 weitzman Exp $ +// $Id: drush.api.php,v 1.16 2010/11/30 15:12:49 weitzman Exp $ /** * @file @@ -9,14 +9,15 @@ * drush-made hooks, very similar to the Drupal hook system. See drush_invoke() * for the actual implementation. * - * For any command named "hook", the following hooks are called, in - * order: + * For any commandfile named "hook", the following hooks are called, in + * order, for the command "COMMAND": * + * 0. drush_COMMAND_init() * 1. drush_hook_COMMAND_validate() * 2. drush_hook_pre_COMMAND() * 3. drush_hook_COMMAND() * 4. drush_hook_post_COMMAND() - * + * * For example, here are the hook opportunities for a mysite.drush.inc file * that wants to hook into the `pm-download` command. * @@ -25,7 +26,10 @@ * 3. drush_mysite_pm_download() * 4. drush_mysite_post_pm_download() * - * If any of those fails, the rollback mechanism is called. It will + * Note that the drush_COMMAND_init() hook is only for use by the + * commandfile that defines the command. + * + * If any of hook function fails, the rollback mechanism is called. It will * call, in reverse, all _rollback hooks. The mysite command file can implement * the following rollback hooks: * @@ -40,6 +44,7 @@ * @see includes/command.inc * * @see hook_drush_init() + * @see drush_COMMAND_init() * @see drush_hook_COMMAND_validate() * @see drush_hook_pre_COMMAND() * @see drush_hook_COMMAND() @@ -63,6 +68,22 @@ function hook_drush_init() { } +/** + * Initialize a command prior to validation. If a command + * needs to bootstrap to a higher level, this is best done in + * the command init hook. It is permisible to bootstrap in + * any hook, but note that if bootstrapping adds more commandfiles + * (*.drush.inc) to the commandfile list, the newly-added + * commandfiles will not have any hooks called until the next + * phase. For example, a command that calls drush_bootstrap_max() + * in drush_hook_COMMAND() would only permit commandfiles from + * modules enabled in the site to participate in drush_hook_post_COMMAND() + * hooks. + */ +function drush_COMMAND_init() { + drush_bootstrap_max(); +} + /** * Run before a specific command executes. * @@ -132,7 +153,7 @@ function hook_drush_exit() { /** * Take action after a project has been downloaded. */ -function hook_drush_pm_post_download($project, $release, $destination) { +function hook_drush_pm_post_download($project, $release) { } @@ -144,14 +165,62 @@ function hook_pm_post_update($release_name, $release_candidate_version, $project } /** - * Adjust the location that a project should be downloaded to. + * Adjust the location that a project should be copied to after being downloaded. + * + * See @pm_drush_pm_download_destination_alter(). */ -function hook_drush_pm_adjust_download_destination(&$project, $release) { +function hook_drush_pm_download_destination_alter(&$project, $release) { if ($some_condition) { - $project['project_install_location'] = '/path/to/install/to/' . basename($project['full_project_path']); + $project['project_install_location'] = '/path/to/install/to/' . $project['project_dir']; } } +/** + * Sql-sync sanitization example. This is equivalent to + * the built-in --sanitize option of sql-sync, but simplified + * to only work with default values on Drupal 6 + mysql. + * + * @see sql_drush_sql_sync_sanitize(). + */ +function hook_drush_sql_sync_sanitize($source) { + drush_sql_register_post_sync_op('my-sanitize-id', + dt('Reset passwords and email addresses in user table'), + "update users set pass = MD5('password'), mail = concat('user+', uid, '@localhost') where uid > 0;"); +} + +/** + * Add help components to a command + */ +function hook_drush_help_alter(&$command) { + if ($command['command'] == 'sql-sync') { + $command['options']['--myoption'] = "Description of modification of sql-sync done by hook"; + $command['sub-options']['--sanitize']['--my-sanitize-option'] = "Description of sanitization option added by hook (grouped with --sanitize option)"; + } +} + +/** + * Add/edit options to cache-clear command + */ +function hook_drush_cache_clear(&$types) { + $types['views'] = 'views_invalidate_cache'; +} + +/* + * Make shell aliases and other .bashrc code available during core-cli command. + * + * @return + * Bash code typically found in a .bashrc file. + * + * @see core_cli_bashrc() for an example implementation. + */ +function hook_cli_bashrc() { + $string = " + alias siwef='drush site-install wef --account-name=super --account-mail=me@wef' + alias dump='drush sql-dump --structure-tables-key=wef --ordered-dump' + "; + return $string; +} + /** * @} End of "addtogroup hooks". */ diff --git a/sites/all/modules/drush/drush.info b/sites/all/modules/drush/drush.info index f7d8a992709a7d91ec1f4101581797957dac8991..267f509747ea7a66aaaa0c454f3857c3b248b074 100644 --- a/sites/all/modules/drush/drush.info +++ b/sites/all/modules/drush/drush.info @@ -1,7 +1,7 @@ -drush_version=3.2-dev +drush_version=4.0-dev -; Information added by drupal.org packaging script on 2010-07-11 -version = "All-versions-3.x-dev" +; Information added by drupal.org packaging script on 2010-12-02 +version = "All-Versions-HEAD" project = "drush" -datestamp = "1278834030" +datestamp = "1291248601" diff --git a/sites/all/modules/drush/drush.php b/sites/all/modules/drush/drush.php index bd76c59db1560777651f19ab3c10d871eeacfda0..f06f5895dd0c1811208933b4a7aa32feebf5f627 100755 --- a/sites/all/modules/drush/drush.php +++ b/sites/all/modules/drush/drush.php @@ -1,6 +1,6 @@ #!/usr/bin/env php <?php -// $Id: drush.php,v 1.86 2010/02/21 05:33:28 weitzman Exp $ +// $Id: drush.php,v 1.91 2010/11/10 02:55:41 weitzman Exp $ /** * @file @@ -35,7 +35,9 @@ require_once DRUSH_BASE_PATH . '/includes/sitealias.inc'; drush_set_context('argc', $GLOBALS['argc']); drush_set_context('argv', $GLOBALS['argv']); +// Set an error handler and a shutdown function set_error_handler('drush_error_handler'); +register_shutdown_function('drush_shutdown'); exit(drush_main()); @@ -65,15 +67,18 @@ function drush_verify_cli() { * Whatever the given command returns. */ function drush_main() { - $phases = _drush_bootstrap_phases(); - $completed_phases = array(); + $phases = _drush_bootstrap_phases(FALSE, TRUE); + drush_set_context('DRUSH_BOOTSTRAP_PHASE', DRUSH_BOOTSTRAP_NONE); + + // We need some global options processed at this early stage. Namely --debug. + drush_parse_args(); + _drush_bootstrap_global_options(); $return = ''; $command_found = FALSE; foreach ($phases as $phase) { - if (drush_bootstrap($phase)) { - $completed_phases[$phase] = TRUE; + if (drush_bootstrap_to_phase($phase)) { $command = drush_parse_command(); // Process a remote command if 'remote-host' option is set. @@ -83,8 +88,14 @@ function drush_main() { } if (is_array($command)) { - if (array_key_exists($command['bootstrap'], $completed_phases) && empty($command['bootstrap_errors'])) { + $bootstrap_result = drush_bootstrap_to_phase($command['bootstrap']); + drush_enforce_requirement_bootstrap_phase($command); + drush_enforce_requirement_core($command); + drush_enforce_requirement_drupal_dependencies($command); + + if ($bootstrap_result && empty($command['bootstrap_errors'])) { drush_log(dt("Found command: !command (commandfile=!commandfile)", array('!command' => $command['command'], '!commandfile' => $command['commandfile'])), 'bootstrap'); + $command_found = TRUE; // Dispatch the command(s). $return = drush_dispatch($command); @@ -114,9 +125,10 @@ function drush_main() { elseif (!empty($args)) { drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt("The drush command '!args' could not be found.", array('!args' => $args))); } - else { - // This can occur if we get an error during _drush_bootstrap_drush_validate(); - drush_set_error('DRUSH_COULD_NOT_EXECUTE', dt("Drush could not execute.")); + // Set errors that ocurred in the bootstrap phases. + $errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS', array()); + foreach ($errors as $code => $message) { + drush_set_error($code, $message); } } @@ -146,12 +158,18 @@ function drush_shutdown() { // Mysteriously make $user available during sess_write(). Avoids a NOTICE. global $user; - if (!drush_get_context('DRUSH_EXECUTION_COMPLETED', FALSE)) { + if (!drush_get_context('DRUSH_EXECUTION_COMPLETED', FALSE) && !drush_get_context('DRUSH_USER_ABORT', FALSE)) { + $php_error_message = ''; + if ($error = error_get_last()) { + $php_error_message = "\n" . dt('Error: !message in !file, line !line', array('!message' => $error['message'], '!file' => $error['file'], '!line' => $error['line'])); + } // We did not reach the end of the drush_main function, // this generally means somewhere in the code a call to exit(), // was made. We catch this, so that we can trigger an error in // those cases. - drush_set_error("DRUSH_NOT_COMPLETED", dt("Drush command could not be completed.")); + drush_set_error("DRUSH_NOT_COMPLETED", dt("Drush command terminated abnormally due to an unrecoverable error.!message", array('!message' => $php_error_message))); + // Attempt to give the user some advice about how to fix the problem + _drush_postmortem(); } $phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); @@ -177,6 +195,14 @@ function drush_shutdown() { drush_pipe_output(); } + /** + * For now, drush skips end of page processing on D7. Doing so could write + * cache entries to module_implements and lookup_cache that don't match web requests. + */ + // if (drush_drupal_major_version() >= 7 && function_exists('drupal_page_footer')) { + // drupal_page_footer(); + // } + // this way drush_return_status will always be the last shutdown function (unless other shutdown functions register shutdown functions...) // and won't prevent other registered shutdown functions (IE from numerous cron methods) from running by calling exit() before they get a chance. register_shutdown_function('drush_return_status'); @@ -213,6 +239,10 @@ function drush_drupal_login($drush_user) { } return drush_set_error('DRUPAL_USER_LOGIN_FAILED', $message); } + else { + $name = $user->name ? $user->name : variable_get('anonymous', t('Anonymous')); + drush_log(dt('Successfully logged into Drupal as !name', array('!name' => $name . " (uid=$user->uid)")), 'bootstrap'); + } return TRUE; } diff --git a/sites/all/modules/drush/examples/example.aliases.drushrc.php b/sites/all/modules/drush/examples/example.aliases.drushrc.php index 91f87b3be8eaf23a95c8595bca2fe9b17e4bb9f6..56ef90459b1f4c807582a476f12df4b9ed843d8e 100644 --- a/sites/all/modules/drush/examples/example.aliases.drushrc.php +++ b/sites/all/modules/drush/examples/example.aliases.drushrc.php @@ -64,6 +64,37 @@ * When alias files use this form, then the name of the alias * is taken from the first part of the alias filename. * + * Alias groups (aliases stored together in files called + * GROUPNAME.aliases.drushrc.php, as mentioned above) also + * create an implicit namespace that is named after the group + * name. + * + * For example: + * + * # File: mysite.aliases.drushrc.php + * $aliases['dev'] = array( + * 'root' => '/path/to/drupal', + * 'uri' => 'dev.mydrupalsite.com', + * ); + * $aliases['live'] = array( + * 'root' => '/other/path/to/drupal', + * 'uri' => 'mydrupalsite.com', + * ); + * + * Then the following special aliases are defined: + * + * @mysite An alias named after the groupname + * may be used to reference all of the + * aliases in the group (e.g. drush @mydrupalsite status) + * + * @mysite.dev A copy of @dev + * + * @mysite.live A copy of @live + * + * Thus, aliases defined in an alias group file may be referred to + * either by their simple (short) name, or by thier full namespace-qualified + * name. + * * To see an example alias definition for the current bootstrapped * site, use the "site-alias" command with the built-in alias "@self": * @@ -117,6 +148,8 @@ * default is 'drush' on remote machines, or the full path to drush.php on * the local machine. Note that you only need to define one of '%drush' * or '%drush-script', as drush can infer one from the other. + * '%dump-dir': Path to directory that "drush sql-sync" should use to store + * sql-dump files. Helpful filenames are auto-generated. * '%dump': Path to the file that "drush sql-sync" should use to store sql-dump file. * '%files': Path to 'files' directory. This will be looked up if not specified. * '%root': A reference to the Drupal root defined in the 'root' item @@ -133,7 +166,7 @@ # 'path-aliases' => array( # '%drush' => '/path/to/drush', # '%drush-script' => '/path/to/drush/drush', -# '%dump' => '/path/to/live/sql_dump.sql', +# '%dump-dir' => '/path/to/dumps/', # '%files' => 'sites/mydrupalsite.com/files', # '%custom' => '/my/custom/path', # ), diff --git a/sites/all/modules/drush/examples/example.drushrc.php b/sites/all/modules/drush/examples/example.drushrc.php index 936e9c392320d0fdbe4608de333b02f882a6fddb..8d5ef3bac79ac19dac83d099b8abda37591ebeb4 100644 --- a/sites/all/modules/drush/examples/example.drushrc.php +++ b/sites/all/modules/drush/examples/example.drushrc.php @@ -1,5 +1,5 @@ <?php -// $Id: example.drushrc.php,v 1.8 2010/06/05 15:18:16 weitzman Exp $ +// $Id: example.drushrc.php,v 1.12 2010/12/01 04:46:08 greg1anderson Exp $ /* * Examples of valid statements for a drushrc.php file. Use this file to cut down on @@ -46,14 +46,6 @@ * for remote sites. */ -// DEPRECATED: Allow command names to contain spaces. -// This feature will be removed shortly; drush-3 will -// require commands to be named with dashes instead of -// spaces (e.g. "cache-clear" instead of "cache clear"). -// During the transition period, uncomment the line below -// to allow commands with spaces to be used. -# $options['allow-spaces-in-commands'] = 1; - // Specify a particular multisite. # $options['l'] = 'http://example.com/subir'; @@ -70,7 +62,7 @@ # $options['cvscredentials'] = 'name:password'; // Specify additional directories to search for *.drush.inc files -// Use POSIX path separator (':') +// Separate by : (Unix-based systems) or ; (Windows). # $options['i'] = 'sites/default:profiles/myprofile'; // Specify additional directories to search for *.alias.drushrc.php @@ -83,6 +75,18 @@ // is not specified, then sql-sync will store dumps in temporary files. # $options['dump-dir'] = '/path/to/dumpdir'; +// Specify directory where sql-dump should store backups of database +// dumps. @DATABASE is replaced with the name of the database being +// dumped, and @DATE is replaced with the current time and date of the +// dump. TRUE will cause sql-dump to use the same backup directory that +// pm-updatecode does. +// +// If set, this can be explicitly overridden by specifying --result-file +// on the commandline. The default behavior of dumping to +// STDOUT can be achieved via --result-file=0 +# $options['result-file'] = '/path/to/backup/dir/@DATABASE_@DATE.sql'; +# $options['result-file'] = TRUE; + // Enable verbose mode. # $options['v'] = 1; @@ -97,6 +101,19 @@ // Specify options to pass to ssh in backend invoke. (Default is to prohibit password authentication; uncomment to change) # $options['ssh-options'] = '-o PasswordAuthentication=no'; +// rsync version 2.6.8 or earlier will give an error message: +// "--remove-source-files: unknown option". To fix this, set +// $options['rsync-version'] = '2.6.8'; (replace with the lowest +// version of rsync installed on any system you are using with +// drush). Note that drush requires at least rsync version 2.6.4 +// for some functions to work correctly. +// +// Note that this option can also be set in a site alias. This +// is preferable if newer versions of rsync are available on some +// of the systems you use. +// See: http://drupal.org/node/955092 +# $options['rsync-version'] = '2.6.9'; + /* * The output charset suitable to pass to iconv PHP function as out_charset * parameter. Drush will convert its output from UTF-8 to the charset specified @@ -147,7 +164,7 @@ $options['skip-tables'] = array( # $command_specific['dl'] = array('cvscredentials' => 'user:pass'); // Specify additional directories to search for scripts -// Use POSIX path separator (':') +// Separate by : (Unix-based systems) or ; (Windows). # $command_specific['script']['script-path'] = 'sites/all/scripts:profiles/myprofile/scripts'; /** diff --git a/sites/all/modules/drush/examples/helloworld.script b/sites/all/modules/drush/examples/helloworld.script new file mode 100644 index 0000000000000000000000000000000000000000..3db298815dd5c33aa8f448b38cb3392b798ce01c --- /dev/null +++ b/sites/all/modules/drush/examples/helloworld.script @@ -0,0 +1,77 @@ +#!/usr/bin/env drush + +// +// This example demonstrates how to write a drush +// "shebang" script. These scripts start with the +// line "#!/usr/bin/env drush", or perhaps just "!drush" +// (although the latter is not supported on all flavors +// of *nix). +// +// These scripts do not need to be named "*.script"; +// they can be named anything at all. To run them, +// make sure they are executable (chmod +x helloworld.script) +// and then run them from the shell like any other +// script. +// +// There are two big advantages to drush scripts over +// bash scripts. +// +// 1. They are written in php +// +// 2. drush can bootstrap your Drupal site before +// running your script. +// +// To bootstrap a Drupal site, provide an alias to +// the site to bootstrap as the first commandline +// argument. +// +// For example: +// +// $ helloworld.script @dev a b c +// +// If the first argument is a valid site alias, drush +// will remove it from the arument list and bootstrap +// that site. +// + +drush_print("Hello world!"); +drush_print(); +drush_print("The arguments to this command were:"); + +// +// If called with --everything, use drush_get_arguments +// to print the commandline arguments. Note that this +// call will include 'php-script' (the drush command) +// and the path to this script. +// +if (drush_get_option('everything')) { + drush_print(" " . implode("\n ", drush_get_arguments())); +} +// +// If --everything is not included, then use +// drush_shift to pull off the arguments one at +// a time. drush_shift only returns the user +// commandline arguments, and does not include +// the drush command or the path to this script. +// +else { + while ($arg = drush_shift()) { + drush_print(' ' . $arg); + } +} + +drush_print(); + +// +// We can check which site was bootstrapped via +// the '@self' alias, which is defined only if +// there is a bootstrapped site. +// +$self_record = drush_sitealias_get_record('@self'); +if (empty($self_record)) { + drush_print('No bootstrapped site.'); +} +else { + drush_print('The following site is bootstrapped:'); + _drush_sitealias_print_record($self_record); +} diff --git a/sites/all/modules/drush/examples/policy.drush.inc b/sites/all/modules/drush/examples/policy.drush.inc new file mode 100644 index 0000000000000000000000000000000000000000..688725520a418a9f7171dfa725d8bc0d5d6d359a --- /dev/null +++ b/sites/all/modules/drush/examples/policy.drush.inc @@ -0,0 +1,59 @@ +<?php +// $Id: policy.drush.inc,v 1.4 2010/11/30 17:14:30 weitzman Exp $ + +/** + * @file + * Example policy commandfile. Modify as desired. + * + * Validates commands as they are issued and returns an error + * or changes options when policy is violated. + * + * You can copy this file to any of the following + * 1. A .drush folder in your HOME folder. + * 2. Anywhere in a folder tree below an active module on your site. + * 3. /usr/share/drush/commands (configurable) + * 4. In an arbitrary folder specified with the --include option. + */ + +/** + * Implement of drush_hook_COMMAND_validate(). + * + * Prevent catastrophic braino. Note that this file has to be local to the machine + * that intitiates sql-sync command. + */ +function drush_policy_sql_sync_validate($source = NULL, $destination = NULL) { + if ($destination == '@prod') { + return drush_set_error(dt('Per examples/policy.drush.inc, you may never overwrite the production database.')); + } +} + +/** + * Implement of drush_hook_COMMAND_validate(). + * + * To test this example without copying, execute `drush --include=./examples updatedb` + * from within your drush directory. + * + * Unauthorized users may view pending updates but not execute them. + */ +function drush_policy_updatedb_validate() { + // Check for a token in the request. In this case, we require --token=secret. + if (!drush_get_option('token') == 'secret') { + drush_log('Per site policy, you must add a secret --token complete this command.', 'examples/policy.drush.inc'); + drush_set_context('DRUSH_AFFIRMATIVE', FALSE); + drush_set_context('DRUSH_NEGATIVE', TRUE); + } +} + +/** + * Implementation of drush_hook_COMMAND_validate(). + * + * Only sudo tells me to make a sandwich: http://xkcd.com/149/ + */ +function drush_policy_make_me_a_sandwich_validate() { + $name = posix_getpwuid(posix_geteuid()); + if ($name['name'] !== 'root') { + return drush_set_error('MAKE_IT_YOUSELF', dt('What? Make your own sandwich.')); + } +} + + diff --git a/sites/all/modules/drush/examples/sandwich.drush.inc b/sites/all/modules/drush/examples/sandwich.drush.inc index 03071431f183752832d7570c4747202f6ee8782e..a9e8e3e4773af352951b3b342b3499768c14a0b4 100644 --- a/sites/all/modules/drush/examples/sandwich.drush.inc +++ b/sites/all/modules/drush/examples/sandwich.drush.inc @@ -1,5 +1,5 @@ <?php -// $Id: sandwich.drush.inc,v 1.1 2010/04/22 19:37:56 weitzman Exp $ +// $Id: sandwich.drush.inc,v 1.4 2010/10/28 23:20:48 greg1anderson Exp $ /** * @file @@ -44,7 +44,7 @@ function sandwich_drush_command() { 'spreads' => 'Comma delimited list of spreads (e.g. mayonnaise, mustard)', ), 'examples' => array( - 'drush make-me-a-sandwich turkey --spreads=ketchup,mustard', + 'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.', ), 'aliases' => array('mmas'), 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all. @@ -72,7 +72,7 @@ function sandwich_drush_help($section) { } } -/* +/** * Implementation of drush_hook_COMMAND_validate(). */ function drush_sandwich_make_me_a_sandwich_validate() { @@ -99,13 +99,15 @@ function drush_sandwich_make_me_a_sandwich_validate() { * after your command, you may define a 'callback' item in your command * object that specifies the exact name of the function that should be * called. However, the specified callback function must still begin - * with "drush_commandfile_" (e.g. 'callback' => "drush_example_foo_execute"). - * All hook functions are still called (e.g. drush_example_pre_foo_execute, - * and so on.) + * with "drush_commandfile_" (e.g. 'callback' => "drush_example_foo_execute") + * if you want that all hook functions are still called (e.g. + * drush_example_pre_foo_execute, and so on). * * In this function, all of Drupal's API is (usually) available, including * any functions you have added in your own modules/themes. * + * @see drush_invoke() + * @see drush.api.php */ function drush_sandwich_make_me_a_sandwich($filling = 'ascii') { $str_spreads = ''; diff --git a/sites/all/modules/drush/includes/backend.inc b/sites/all/modules/drush/includes/backend.inc index 4ef04fa02a3e26e2732e0cfef263edcd7e41066e..b7bc048bd8eb41b2ca0bd36d953d230d703085db 100644 --- a/sites/all/modules/drush/includes/backend.inc +++ b/sites/all/modules/drush/includes/backend.inc @@ -1,5 +1,5 @@ <?php -// $Id: backend.inc,v 1.30 2010/04/22 17:05:52 weitzman Exp $ +// $Id: backend.inc,v 1.33 2010/11/27 03:12:19 weitzman Exp $ /** * @file Drush backend API @@ -149,7 +149,7 @@ function _drush_backend_integrate($data) { } } // Output will either be printed, or buffered to the drush_backend_output command. - if (drush_cmp_error('DRUSH_APPLICATION_ERROR')) { + if (drush_cmp_error('DRUSH_APPLICATION_ERROR') && !empty($data['output'])) { drush_set_error("DRUSH_APPLICATION_ERROR", dt("Output from failed command :\n !output", array('!output' => $data['output']))); } else { @@ -252,11 +252,11 @@ function drush_backend_invoke($command, $data = array(), $method = 'GET', $integ return drush_backend_invoke_args($command, $args, $data, $method, $integrate, $drush_path, $hostname, $username); } -/* +/** * A variant of drush_backend_invoke() which specifies command and arguments separately. */ -function drush_backend_invoke_args($command, $args, $data = array(), $method = 'GET', $integrate = TRUE, $drush_path = NULL, $hostname = NULL, $username = NULL) { - $cmd = _drush_backend_generate_command($command, $args, $data, $method, $drush_path, $hostname, $username); +function drush_backend_invoke_args($command, $args, $data = array(), $method = 'GET', $integrate = TRUE, $drush_path = NULL, $hostname = NULL, $username = NULL, $ssh_options = NULL) { + $cmd = _drush_backend_generate_command($command, $args, $data, $method, $drush_path, $hostname, $username, $ssh_options); return _drush_backend_invoke($cmd, $data, $integrate); } @@ -336,11 +336,11 @@ function _drush_backend_invoke($cmd, $data = null, $integrate = TRUE) { * @return * A text string representing a fully escaped command. */ -function _drush_backend_generate_command($command, $args, &$data, $method = 'GET', $drush_path = null, $hostname = null, $username = null) { +function _drush_backend_generate_command($command, $args, &$data, $method = 'GET', $drush_path = null, $hostname = null, $username = null, $ssh_options = NULL) { if (drush_is_local_host($hostname)) { $hostname = null; } - + $drush_path = !is_null($drush_path) ? $drush_path : (is_null($hostname) ? DRUSH_COMMAND : 'drush'); // Call own drush.php file on local machines, or 'drush' on remote machines. $data['root'] = array_key_exists('root', $data) ? $data['root'] : drush_get_context('DRUSH_DRUPAL_ROOT'); $data['uri'] = array_key_exists('uri', $data) ? $data['uri'] : drush_get_context('DRUSH_URI'); @@ -360,7 +360,7 @@ function _drush_backend_generate_command($command, $args, &$data, $method = 'GET if (!is_null($hostname)) { $username = (!is_null($username)) ? $username : get_current_user(); - $ssh_options = drush_get_option('ssh-options', "-o PasswordAuthentication=no"); + $ssh_options = (!is_null($ssh_options)) ? $ssh_options : drush_get_option('ssh-options', "-o PasswordAuthentication=no"); $cmd = "ssh " . $ssh_options . " " . escapeshellarg($username) . "@" . escapeshellarg($hostname) . " " . escapeshellarg($cmd); } diff --git a/sites/all/modules/drush/includes/command.inc b/sites/all/modules/drush/includes/command.inc index 8dab5e5ce157858dc6d5e6f4cdc3697e874f2c40..6ff04fcf81de7546d9d525f02890d475cac92acd 100644 --- a/sites/all/modules/drush/includes/command.inc +++ b/sites/all/modules/drush/includes/command.inc @@ -1,5 +1,5 @@ <?php -// $Id: command.inc,v 1.76 2010/04/22 06:33:48 weitzman Exp $ +// $Id: command.inc,v 1.88 2010/11/10 02:55:41 weitzman Exp $ /** * @file @@ -19,9 +19,14 @@ */ function drush_parse_args() { $args = drush_get_context('argv'); - - static $arg_opts = array('c', 'h', 'u', 'r', 'l', 'i'); - + // TODO: commandfiles should be able to extend this list. + static $arg_opts = array('c', 'u', 'r', 'l', 'i'); + + // Check to see if we were executed via a "#!/usr/bin/env drush" script + drush_adjust_args_if_shebang_script($args); + + // Now process the command line arguments. We will divide them + // into options (starting with a '-') and arguments. $arguments = $options = array(); for ($i = 1; $i < count($args); $i++) { @@ -64,13 +69,137 @@ function drush_parse_args() { $arguments[] = $opt; } } - // If arguments are specified, print the help screen. + // If no arguments are specified, print the help screen. $arguments = sizeof($arguments) ? $arguments : array('help'); - + + // Handle the "@shift" alias, if present + drush_process_bootstrap_to_first_arg($arguments); + drush_set_arguments($arguments); drush_set_context('options', $options); } +/** + * Pop an argument off of drush's argument list + */ +function drush_shift() { + $arguments = drush_get_arguments(); + $result = NULL; + if (!empty($arguments)) { + // The php-script command uses the DRUSH_SHIFT_SKIP + // context to cause drush_shift to skip the 'php-script' + // command and the script path argument when it is + // called from the user script. + $skip_count = drush_get_context('DRUSH_SHIFT_SKIP'); + if (is_numeric($skip_count)) { + for ($i = 0; $i < $skip_count; $i++) { + array_shift($arguments); + } + $skip_count = drush_set_context('DRUSH_SHIFT_SKIP', 0); + } + $result = array_shift($arguments); + drush_set_arguments($arguments); + } + return $result; +} + +/** + * Special checking for "shebang" script handling. + * + * If there is a file 'script.php' that begins like so: + * #!/path/to/drush + * Then $args will be: + * /path/to/drush /path/to/script userArg1 userArg2 ... + * If it instead starts like this: + * #!/path/to/drush --flag php-script + * Then $args will be: + * /path/to/drush "--flag php-script" /path/to/script userArg1 userArg2 ... + * (Note that execve does not split the parameters from + * the shebang line on whitespace; see http://en.wikipedia.org/wiki/Shebang_%28Unix%29) + * When drush is called via one of the "shebang" lines above, + * the first or second parameter will be the full path + * to the "shebang" script file -- and if the path to the + * script is in the second position, then we will expect that + * the argument in the first position must begin with a + * '@' (alias) or '-' (flag). Under ordinary circumstances, + * we do not expect that the drush command must come before + * any argument that is the full path to a file. We use + * this assumption to detect "shebang" script execution. + */ +function drush_adjust_args_if_shebang_script(&$args) { + if (_drush_is_drush_shebang_script($args[1])) { + // If $args[1] is a drush "shebang" script, we will insert + // the option "--bootstrap-to-first-arg" and the command + // "php-script" at the beginning of @args, so the command + // line args become: + // /path/to/drush --bootstrap-to-first-arg php-script /path/to/script userArg1 userArg2 ... + drush_set_option('bootstrap-to-first-arg', TRUE); + array_splice($args, 1, 0, array('php-script')); + drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE); + } + elseif (((strpos($args[1], ' ') !== FALSE) || (!ctype_alnum($args[1][0]))) && (_drush_is_drush_shebang_script($args[2]))) { + // If $args[2] is a drush "shebang" script, we will insert + // the space-exploded $arg[1] in place of $arg[1], so the + // command line args become: + // /path/to/drush scriptArg1 scriptArg2 ... /path/to/script userArg1 userArg2 ... + // If none of the script arguments look like a drush command, + // then we will insert "php-script" as the default command to + // execute. + $script_args = explode(' ', $args[1]); + $has_command = FALSE; + foreach ($script_args as $script_arg) { + if (preg_match("/^[a-z][a-z0-9-]*$/",$script_arg)) { + $has_command = TRUE; + } + } + if (!$has_command) { + $script_args[] = 'php-script'; + } + array_splice($args, 1, 1, $script_args); + drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE); + } +} + +/** + * Process the --bootstrap-to-first-arg option, if it is present. + * + * This option checks to see if the first user-provided argument is an alias + * or site specification; if it is, it will be shifted into the first arguement + * position, where it will specify the site to bootstrap. The result of this + * is that if your shebang line looks like this: + * + * #!/path/to/drush --bootstrap-to-first-arg php-script + * + * Then when you run that script, you can optionally provide an alias such + * as @dev as the first argument (e.g. $ ./mydrushscript.php @dev scriptarg1 + * scriptarg2). Since this is the behavior that one would usually want, + * it is default behavior for a canonical script. That is, a script + * with a simple shebang line, like so: + * + * #!/path/to/drush + * + * will implicitly have "--bootstrap-to-first-arg" and "php-script" prepended, and will therefore + * behave exactly like the first example. To write a script that does not + * use --bootstrap-to-first-arg, then the drush command or at least one flag must be explicitly + * included, like so: + * + * #!/path/to/drush php-script + */ +function drush_process_bootstrap_to_first_arg(&$arguments) { + if (drush_get_option('bootstrap-to-first-arg', FALSE)) { + $shift_alias_pos = 1 + (drush_get_context('DRUSH_SHEBANG_SCRIPT') === TRUE); + if (sizeof($arguments) >= $shift_alias_pos) { + $shifted_alias = $arguments[$shift_alias_pos]; + $alias_record = drush_sitealias_get_record($shifted_alias); + if (!empty($alias_record)) { + // Move the alias we shifted from its current position + // in the argument list to the front of the list + array_splice($arguments, $shift_alias_pos, 1); + array_unshift($arguments, $shifted_alias); + } + } + } +} /** * Get a list of all implemented commands. @@ -89,56 +218,21 @@ function drush_get_commands() { $result = $function(); foreach ((array)$result as $key => $command) { // Add some defaults and normalize the command descriptor - $command += array( - 'command' => $key, - 'command-hook' => $key, - 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, - 'commandfile' => $commandfile, - 'path' => dirname($path), - 'engines' => array(), // Helpful for drush_show_help(). - 'callback' => 'drush_command', - 'description' => NULL, - 'arguments' => array(), - 'options' => array(), - 'examples' => array(), - 'aliases' => array(), - 'deprecated-aliases' => array(), - 'extras' => array(), - 'core' => array(), - 'scope' => 'site', - 'drupal dependencies' => array(), - 'drush dependencies' => array(), - 'bootstrap_errors' => array(), - 'hidden' => FALSE, - ); + $command += drush_command_defaults($key, $commandfile, $path); // If command callback is correctly named, then fix // up the command entry so that drush_invoke will be // called. if ($command['callback'] != 'drush_command') { $required_command_prefix = 'drush_' . $commandfile . '_'; if ((substr($command['callback'], 0, strlen($required_command_prefix)) == $required_command_prefix)) { - $command['command-hook'] = substr($command['callback'], strlen($required_command_prefix)); + $command['command-hook'] = substr($command['callback'], strlen('drush_')); $command['callback'] = 'drush_command'; } else { $command['callback-required-prefix'] = $required_command_prefix; } } - // Enforce the no-spaces in command names rule - if ((!drush_get_option('allow-spaces-in-commands', FALSE)) && (strpos($key, ' ') !== FALSE)) { - $command['must-replace-spaces'] = TRUE; - } - // Temporary: if there is a dash in the command name, - // then make a deprecated alias that has a space in the name. - if (strpos($key, '-') !== FALSE) { - $command['deprecated-aliases'][] = str_replace('-', ' ', $key); - } - // Collect all the commands (without filtering) so we can match non-executable - // commands, and later explain why they are not executable. - drush_enforce_requirement_bootstrap_phase($command); - drush_enforce_requirement_core($command); - drush_enforce_requirement_drupal_dependencies($command); $commands[$key] = $command; // For every alias, make a copy of the command and store it in the command list // using the alias as a key @@ -155,9 +249,6 @@ function drush_get_commands() { $commands[$alias]['is_alias'] = TRUE; $commands[$alias]['deprecated'] = TRUE; $commands[$alias]['deprecated-name'] = $alias; - if ((!drush_get_option('allow-spaces-in-commands', FALSE)) && (strpos($alias, ' ') !== FALSE)) { - $commands[$alias]['must-not-use-spaces'] = TRUE; - } } } @@ -168,6 +259,36 @@ function drush_get_commands() { return drush_set_context('DRUSH_COMMANDS', $commands); } +function drush_command_defaults($key, $commandfile, $path) { + return array( + 'command' => $key, + 'command-hook' => $key, + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, + 'commandfile' => $commandfile, + 'path' => dirname($path), + 'engines' => array(), // Helpful for drush_show_help(). + 'callback' => 'drush_command', + 'description' => NULL, + 'sections' => array( + 'examples' => 'Examples', + 'arguments' => 'Arguments', + 'options' => 'Options', + ), + 'arguments' => array(), + 'options' => array(), + 'examples' => array(), + 'aliases' => array(), + 'deprecated-aliases' => array(), + 'core' => array(), + 'scope' => 'site', + 'drupal dependencies' => array(), + 'drush dependencies' => array(), + 'bootstrap_errors' => array(), + 'topics' => array(), + 'hidden' => FALSE, + ); +} + /** * Matches a commands array, as returned by drush_get_arguments, with the * current command table. @@ -201,21 +322,13 @@ function drush_get_commands() { */ function drush_parse_command() { $args = drush_get_arguments(); + $command = FALSE; // Get a list of all implemented commands. $implemented = drush_get_commands(); - - $command = FALSE; - $arguments = array(); - // Try to determine the handler for the current command. - while (!$command && count($args)) { - $part = implode(" ", $args); - if (isset($implemented[$part])) { - $command = $implemented[$part]; - } - else { - $arguments[] = array_pop($args); - } + if (isset($implemented[$args[0]])) { + $command = $implemented[$args[0]]; + $arguments = array_slice($args, 1); } // We have found a command that matches. Set the appropriate values. @@ -227,8 +340,6 @@ function drush_parse_command() { $command['arguments'] = $arguments; } else { - $arguments = array_reverse($arguments); - // Merge specified callback arguments, which precede the arguments passed on the command line. if (isset($command['callback arguments']) && is_array($command['callback arguments'])) { $arguments = array_merge($command['callback arguments'], $arguments); @@ -280,20 +391,45 @@ function drush_invoke($command) { $args = func_get_args(); array_shift($args); - // Generate the base name for the hook by using the - // php string translation function to convert all - // dashes and spaces in the command name to underscores. - // TODO: put this back to $hook = str_replace("-", "_", $command); - // for better readability after the allow-spaces-in-commands - // backwards-compatibility feature is removed. - $hook = strtr($command, "- ", "__"); // n.b. str tr, not str str. - $list = drush_commandfile_list(); + // Generate the base name for the hook by converting all + // dashes in the command name to underscores. + $hook = str_replace("-", "_", $command); + + // Call the hook init function, if it exists. + // If a command needs to bootstrap, it is advisable + // to do so in _init; otherwise, new commandfiles + // will miss out on participating in any stage that + // has passed or started at the time it was discovered. + $func = 'drush_' . $hook . '_init'; + if (function_exists($func)) { + drush_log(dt("Calling drush command init function: !func", array('!func' => $func)), 'bootstrap'); + call_user_func_array($func, $args); + _drush_log_drupal_messages(); + if (drush_get_error()) { + drush_log(dt('The command @command could not be initialized.', array('@command' => $command)), 'error'); + return FALSE; + } + } - $functions = array(); - // First we build a list of functions that are about to be executed - $variations = array($hook . "_validate", "pre_$hook", $hook, "post_$hook"); + $rollback = FALSE; + $completed = array(); + $available_rollbacks = array(); $all_available_hooks = array(); + + // Iterate through the different hook variations + $variations = array($hook . "_validate", "pre_$hook", $hook, "post_$hook"); foreach ($variations as $var_hook) { + // Get the list of command files. + // We re-fetch the list every time through + // the loop in case one of the hook function + // does something that will add additional + // commandfiles to the list (i.e. bootstrapping + // to a higher phase will do this). + $list = drush_commandfile_list(); + + $functions = array(); + + // Run all of the functions available for this variation foreach ($list as $commandfile => $filename) { $oldfunc = sprintf("drush_%s_%s", $commandfile, $var_hook); $func = str_replace('drush_' . $commandfile . '_' . $commandfile, 'drush_' . $commandfile, $oldfunc); @@ -305,37 +441,33 @@ function drush_invoke($command) { if (function_exists($func)) { $functions[] = $func; $all_available_hooks[] = $func . ' [*]'; + $available_rollbacks[] = $func . '_rollback'; + $completed[] = $func; + call_user_func_array($func, $args); + _drush_log_drupal_messages(); + if (drush_get_error()) { + drush_log(dt('An error occurred at function : @func', array('@func' => $func)), 'error'); + $rollback = TRUE; + + // break out of the foreach variations and foreach list + break 2; + } } else { $all_available_hooks[] = $func; } } } + // If no hook functions were found, print a warning. - if (empty($functions)) { + if (empty($all_available_hooks)) { drush_log(dt("No hook functions were found for !command.", array('!command' => $command)), 'warning'); drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command, '!available' => "\n" . implode("\n", $all_available_hooks))), 'warning'); } elseif (drush_get_option('show-invoke')) { + // We show all available hooks up to and including the one that failed (or all, if there were no failures) drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command, '!available' => "\n" . implode("\n", $all_available_hooks))), 'internals'); } - $rollback = FALSE; - $completed = array(); - $available_rollbacks = array(); - foreach ($functions as $func) { - $available_rollbacks[] = $func . '_rollback'; - if ($rollback === FALSE) { - $completed[] = $func; - if (function_exists($func)) { - call_user_func_array($func, $args); - _drush_log_drupal_messages(); - if (drush_get_error()) { - drush_log(dt('An error occurred at function : @func', array('@func' => $func)), 'error'); - $rollback = TRUE; - } - } - } - } if (drush_get_option('show-invoke')) { drush_log(dt("Available rollback hooks for !command: !rollback", array('!command' => $command, '!rollback' => "\n" . implode("\n", $available_rollbacks))), 'internals'); } @@ -395,6 +527,8 @@ function drush_command() { /** * Invoke a hook in all available command files that implement it. * + * @see drush_command_invoke_all_ref() + * * @param $hook * The name of the hook to invoke. * @param ... @@ -414,6 +548,11 @@ function drush_command_invoke_all() { return call_user_func_array('drush_command_invoke_all_ref', $args); } +/** + * A drush_command_invoke_all() that wants the first parameter to be passed by reference. + * + * @see drush_command_invoke_all() + */ function drush_command_invoke_all_ref($hook, &$reference_parameter) { $args = func_get_args(); array_shift($args); @@ -490,9 +629,13 @@ function drush_commandfile_list() { return drush_get_context('DRUSH_COMMAND_FILES', array()); } -function _drush_find_commandfiles($phase) { +function _drush_find_commandfiles($phase, $phase_max = FALSE) { $cache =& drush_get_context('DRUSH_COMMAND_FILES', array()); + if (!$phase_max) { + $phase_max = $phase; + } + static $evaluated = array(); static $deferred = array(); @@ -504,7 +647,7 @@ function _drush_find_commandfiles($phase) { // User commands, specified by 'include' option if ($include = drush_get_option(array('i', 'include'), FALSE)) { - foreach (explode(":", $include) as $path) { + foreach (explode(PATH_SEPARATOR, $include) as $path) { $searchpath[] = $path; } } @@ -522,11 +665,21 @@ function _drush_find_commandfiles($phase) { } break; case DRUSH_BOOTSTRAP_DRUPAL_SITE: - $searchpath[] = conf_path() . '/modules'; - // Too early for variable_get('install_profile', 'default'); Just use default. - $searchpath[] = "profiles/default/modules"; - // Add all module paths, even disabled modules. Prefer speed over accuracy. - $searchpath[] = 'sites/all/modules'; + // If we are going to stop bootstrapping at the site, then + // we will quickly add all commandfiles that we can find for + // any module associated with the site, whether it is enabled + // or not. If we are, however, going to continue on to bootstrap + // all the way to DRUSH_BOOTSTRAP_DRUPAL_FULL, then we will + // instead wait for that phase, which will more carefully add + // only those drush command files that are associated with + // enabled modules. + if ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL) { + $searchpath[] = conf_path() . '/modules'; + // Too early for variable_get('install_profile', 'default'); Just use default. + $searchpath[] = "profiles/default/modules"; + // Add all module paths, even disabled modules. Prefer speed over accuracy. + $searchpath[] = 'sites/all/modules'; + } break; case DRUSH_BOOTSTRAP_DRUPAL_FULL: // Add enabled module paths. Since we are bootstrapped, @@ -566,14 +719,14 @@ function _drush_find_commandfiles($phase) { $load_command = TRUE; $load_test_inc = dirname($filename) . "/" . $module . ".drush.load.inc"; if (file_exists($load_test_inc)) { - require_once($load_test_inc); + include_once($load_test_inc); $load_test_func = $module . "_drush_load"; if (function_exists($load_test_func)) { $load_command = $load_test_func($phase); } } if ($load_command) { - require_once(realpath($filename)); + require_once realpath($filename); unset($deferred[$module]); } else { diff --git a/sites/all/modules/drush/includes/context.inc b/sites/all/modules/drush/includes/context.inc index b1ab69b7b0481ac3c94ad36970b84832512b5765..2a2fc05f1398225bd199f3205db032be509039e2 100644 --- a/sites/all/modules/drush/includes/context.inc +++ b/sites/all/modules/drush/includes/context.inc @@ -1,5 +1,5 @@ <?php -// $Id: context.inc,v 1.31 2010/05/27 13:36:41 weitzman Exp $ +// $Id: context.inc,v 1.35 2010/11/08 05:31:22 greg1anderson Exp $ /** * @file * The Drush context API implementation. @@ -181,9 +181,16 @@ function drush_set_config_options($context, $options, $override = array()) { drush_set_context($context, $options); // Instruct core not to store queries since we are not outputting them. - // This can be overridden by a command or a drushrc file if needed. - if (!isset($drush_conf_override['dev_query'])) { - $drush_conf_override['dev_query'] = FALSE; + // Don't run poormanscron during drush request (D7+). + $defaults = array( + 'dev_query' => FALSE, + 'cron_safe_threshold' => 0, + ); + foreach ($defaults as $key => $value) { + // This can be overridden by a command or a drushrc file if needed. + if (!isset($drush_conf_override[$key])) { + $drush_conf_override[$key] = $value; + } } /** @@ -222,8 +229,9 @@ function drush_set_config_special_contexts(&$options) { // Copy site aliases and command-specific options into their // appropriate caches. - foreach (array('site-aliases', 'command-specific') as $option_name) { - if ((isset($options)) && (array_key_exists($option_name, $options))) { + $special_contexts = drush_get_special_keys(); + foreach ($special_contexts as $option_name) { + if (isset($options[$option_name])) { $cache =& drush_get_context($option_name); $cache = array_merge($cache, $options[$option_name]); unset($options[$option_name]); @@ -300,7 +308,7 @@ function &drush_get_context($context = null, $default = null) { * * This function will set the 'arguments' context of the current running script. * - * When initially called by drush_parse_options, the entire list of arguments will + * When initially called by drush_parse_args, the entire list of arguments will * be populated, once the command is dispatched, this will be set to only the remaining * arguments to the command. * @@ -314,7 +322,7 @@ function drush_set_arguments($arguments) { /** * Get the arguments passed to the drush.php script. * - * When drush_set_arguments is initially called by drush_parse_options, + * When drush_set_arguments is initially called by drush_parse_args, * the entire list of arguments will be populated. * Once the command has been dispatched, this will be return only the remaining * arguments to the command. @@ -386,6 +394,30 @@ function drush_get_option($option, $default = NULL, $context = NULL) { return $default; } +/** + * Get the value for an option and return it as a list. If the + * option in question is passed on the command line, its value should + * be a comma-separated list (e.g. --flag=1,2,3). If the option + * was set in a drushrc.php file, then its value may be either a + * comma-separated list or an array of values (e.g. $option['flag'] = array('1', '2', '3')). + * + * @param option + * The name of the option to get + * @param default + * Optional. The value to return if the option has not been set + * @param context + * Optional. The context to check for the option. If this is set, only this context will be searched. + */ +function drush_get_option_list($option, $default = array(), $context = NULL) { + $result = drush_get_option($option, $default, $context); + + if (!is_array($result)) { + $result = explode(',', $result); + } + + return $result; +} + /** * Get the value for an option, but first checks the provided option overrides. * diff --git a/sites/all/modules/drush/includes/drush.inc b/sites/all/modules/drush/includes/drush.inc index e8f1774d1cfaed14cba886b0abd885f25aeef5ba..a087265d93633dc1ef2e9ba2bac3939dbd1dd65a 100644 --- a/sites/all/modules/drush/includes/drush.inc +++ b/sites/all/modules/drush/includes/drush.inc @@ -1,5 +1,5 @@ <?php -// $Id: drush.inc,v 1.117 2010/06/16 12:51:23 weitzman Exp $ +// $Id: drush.inc,v 1.151 2010/12/01 21:02:21 jonhattan Exp $ /** * @file @@ -27,18 +27,9 @@ function drush_dispatch($command = NULL) { // Add command-specific options, if applicable drush_command_default_options($command); - if (isset($command['must-replace-spaces'])) { - $required_name = str_replace(' ', '-', $command['command']); - drush_set_error(dt('Notice: "!name" must be renamed to "!requiredname" in order to work with this version of drush. If you are the maintainer for the module that defines this command, please rename it immediately. If you are a user of this command, you may enable spaces in commands for a while by setting "allow-spaces-in-commands" in your drush configuration file. See example.drushrc.php.', array('!name' => $command['command'], '!requiredname' => $required_name))); - return FALSE; - } // Print a warning if someone tries to use a deprecated alias. if (isset($command['deprecated'])) { 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'); - if (isset($command['must-not-use-spaces'])) { - drush_set_error(dt('You may enable spaces in commands for a while by setting "allow-spaces-in-commands" in your drush configuration file. See example.drushrc.php.', array('!name' => $command['command'], '!requiredname' => $required_name))); - return FALSE; - } } // Print a warning if a command callback function is misnamed if (isset($command['callback-required-prefix'])) { @@ -194,33 +185,35 @@ function drush_include_engine($type, $engine, $version = NULL, $path = NULL) { /** * Detects the version number of the current Drupal installation, - * if any. Returns false if there is no current Drupal installation, + * if any. Returns FALSE if there is no current Drupal installation, * or it is somehow broken. * - * This function relies on the presence of DRUPAL_ROOT/modules/system/system.module - * * @return * A string containing the version number of the current - * Drupal installation, if any. Otherwise, return false. + * Drupal installation, if any. Otherwise, return FALSE. */ -function drush_drupal_version() { +function drush_drupal_version($drupal_root = NULL) { static $version = FALSE; if (!$version) { - if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) { - if (file_exists($drupal_root . '/modules/system/system.module')) { - // We can safely include system.module as it contains defines and functions only. - require_once($drupal_root . '/modules/system/system.module'); - // We just might be dealing with an early Drupal version (pre 4.7) - if (defined('VERSION')) { - $version = VERSION; + if (($drupal_root != NULL) || ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'))) { + // D7 stores VERSION in bootstrap.inc + $version_constant_paths = array('/modules/system/system.module', '/includes/bootstrap.inc'); + foreach ($version_constant_paths as $path) { + if (file_exists($drupal_root . $path)) { + require_once $drupal_root . $path; } } + // We just might be dealing with an early Drupal version (pre 4.7) + if (defined('VERSION')) { + $version = VERSION; + } } } return $version; } + function drush_drupal_cache_clear_all() { $prior = drush_get_context('DRUSH_AFFIRMATIVE'); drush_set_context('DRUSH_AFFIRMATIVE', TRUE); @@ -231,9 +224,9 @@ function drush_drupal_cache_clear_all() { /** * Returns the Drupal major version number (5, 6, 7 ...) */ -function drush_drupal_major_version() { +function drush_drupal_major_version($drupal_root = NULL) { $major_version = FALSE; - if ($version = drush_drupal_version()) { + if ($version = drush_drupal_version($drupal_root)) { $version_parts = explode('.', $version); if (is_numeric($version_parts[0])) { $major_version = (integer)$version_parts[0]; @@ -249,7 +242,7 @@ function drush_drupal_major_version() { * with named placeholders in code for Drupal 5 and 6. * * @param $where - * Stringwith a WHERE snippet using named placeholders. + * String with a WHERE snippet using named placeholders. * @param $args * Array of placeholder values. * @return @@ -364,7 +357,7 @@ function drush_db_select($table, $fields = '*', $where = NULL, $args = NULL, $st * @param $args * Array. Arguments for the WHERE snippet. * @return - * Affected rows or FALSE. + * Affected rows (except on D7+mysql without a WHERE clause - returns TRUE) or FALSE. */ function drush_db_delete($table, $where = NULL, $args = NULL) { if (drush_drupal_major_version() >= 7) { @@ -404,6 +397,7 @@ function drush_db_result($result) { if ($array = db_fetch_array($result)) { return array_shift($array); // return first element in array. } + return FALSE; case 6: return db_result($result); case 7: @@ -442,6 +436,54 @@ function drush_save_data_to_temp_file($data) { return $file; } +/** + * Returns the path to a temporary directory. + * + * This is a custom version of file_directory_path(). + * We can't directly rely on sys_get_temp_dir() as this + * path is not valid in some setups for Mac. + */ +function drush_find_tmp() { + static $temporary_directory = NULL; + + if (is_null($temporary_directory)) { + $directories = array(); + + // Operating system specific dirs. + if (substr(PHP_OS, 0, 3) == 'WIN') { + $directories[] = 'c:\\windows\\temp'; + $directories[] = 'c:\\winnt\\temp'; + } + else { + $directories[] = '/tmp'; + } + // This function exists in PHP 5 >= 5.2.1, but drush + // requires PHP 5 >= 5.2.0, so we check for it. + if (function_exists('sys_get_temp_dir')) { + $directories[] = sys_get_temp_dir(); + } + + foreach ($directories as $directory) { + if (is_dir($directory) && is_writable($directory)) { + $temporary_directory = $directory; + break; + } + } + + if (empty($temporary_directory)) { + // If no directory has been found, create one in cwd. + $temporary_directory = drush_cwd() . '/tmp'; + drush_mkdir($temporary_directory); + if (!is_dir($directory)) { + return drush_set_error('DRUSH_UNABLE_TO_CREATE_TMP_DIR', dt("Unable to create a temporary directory.")); + } + drush_register_file_for_deletion($temporary_directory); + } + } + + return $temporary_directory; +} + /** * Creates a temporary file, and registers it so that * it will be deleted when drush exits. Whenever possible, @@ -450,7 +492,7 @@ function drush_save_data_to_temp_file($data) { */ function drush_tempnam($pattern, $tmp_dir = NULL) { if ($tmp_dir == NULL) { - $tmp_dir = sys_get_temp_dir(); + $tmp_dir = drush_find_tmp(); } $tmp_file = tempnam($tmp_dir, $pattern); drush_register_file_for_deletion($tmp_file); @@ -458,6 +500,19 @@ function drush_tempnam($pattern, $tmp_dir = NULL) { return $tmp_file; } +/** + * Creates a temporary directory and return its path. + */ +function drush_tempdir() { + $tmp_dir = rtrim(drush_find_tmp(), DIRECTORY_SEPARATOR); + $tmp_dir .= '/' . 'drush_tmp_' . time(); + + drush_mkdir($tmp_dir); + drush_register_file_for_deletion($tmp_dir); + + return $tmp_dir; +} + /** * Any file passed in to this function will be deleted * when drush exits. @@ -486,14 +541,18 @@ function _drush_delete_registered_files() { // in case someone came along and deleted it, even // though they did not need to. if (file_exists($file)) { - unlink($file); + if (is_dir($file)) { + drush_delete_dir($file); + } + else { + unlink($file); + } } } } /** - * Deletes the provided file or folder and - * everything inside it. + * Deletes the provided file or folder and everything inside it. * * @param $dir * The directory to delete @@ -511,7 +570,7 @@ function drush_delete_dir($dir) { if ($item == '.' || $item == '..') { continue; } - if (!drush_delete_dir($dir.DIRECTORY_SEPARATOR.$item)) { + if (!drush_delete_dir($dir.'/'.$item)) { return FALSE; } } @@ -519,26 +578,26 @@ function drush_delete_dir($dir) { } /** - * Move $src to $dest. If the php 'rename' function - * doesn't work, then we'll try rsync. + * Move $src to $dest. + * + * If the php 'rename' function doesn't work, then we'll try mv and if + * it also fails, rsync. * * @param $src * The directory to move. * @param $dest - * The destination to move the source to, including - * the new name of the folder. To move folder "a" - * from "/b" to "/c", then $src = "/b/a" and $dest = "/c/a". - * To move "a" to "/c" and rename it to "d", then - * $dest = "/c/d" (just like php rename function). + * The destination to move the source to, including the new name of + * the directory. To move directory "a" from "/b" to "/c", then + * $src = "/b/a" and $dest = "/c/a". To move "a" to "/c" and rename + * it to "d", then $dest = "/c/d" (just like php rename function). * @param $overwrite - * If TRUE, the destination will be deleted if it - * exists. Defaults to FALSE. + * If TRUE, the destination will be deleted if it exists. * @return - * TRUE on success, FALSE on failure + * TRUE on success, FALSE on failure. */ function drush_move_dir($src, $dest, $overwrite = FALSE) { // Preflight based on $overwrite if $dest exists. - if (is_dir($dest)) { + if (file_exists($dest)) { if ($overwrite) { drush_op('drush_delete_dir', $dest); } @@ -546,26 +605,40 @@ function drush_move_dir($src, $dest, $overwrite = FALSE) { return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest))); } } - - // If rename works, then we're done. + // $src readable? + if (!is_readable($src)) { + return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !dest is not readable or does not exist.', array('!src' => $src))); + } + // $dest writable? + if (!is_writable(dirname($dest))) { + return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest)))); + } + // Try rename. It will fail if $src and $dest are not in the same partition. if (@drush_op('rename', $src, $dest)) { return TRUE; } - // Bail if we can't make a directory at the - // destination (e.g. permissions) - if (!is_dir($dest) && (drush_op('mkdir', $dest) === FALSE)) { - return FALSE; + // Eventually it will create an empty file in $dest. See + // http://www.php.net/manual/es/function.rename.php#90025 + elseif (is_file($dest)) { + drush_op('unlink', $dest); + } + + // Do it with mv. + if (drush_shell_exec('mv %s %s', $src, $dest)) { + return TRUE; } - // If rename doesn't work, then try rsync. - $exec = 'rsync -raz --remove-source-files ' . $src . DIRECTORY_SEPARATOR . ' ' . $dest; - $rsync_result = drush_op('system', $exec); - if($rsync_result !== FALSE) { + + // Lastly try rsync. + drush_op('mkdir', $dest); + $exec = 'rsync -raz --remove-source-files ' . $src . '/ ' . $dest; + if (drush_shell_exec($exec)) { // --remove-source-files deletes all of the files, but // we still need to get rid of the directories. drush_op('drush_delete_dir', $src); return TRUE; } - return FALSE; + + return drush_set_error('DRUSH_MOVE_DIR_FAILURE', dt('Unable to move !src to !dest.', array('src' => $src, 'dest' => $dest))); } /** @@ -576,7 +649,7 @@ function drush_move_dir($src, $dest, $overwrite = FALSE) { * if the simulation mode is enabled. * * @param $function - * The name of the function. + * The name of the function. Any additional arguments are passed along. * @return * The return value of the function, or TRUE if simulation mode is enabled. */ @@ -623,13 +696,34 @@ function dt($string, $args = array()) { } /** - * Get the available options for Drush for use by help page. + * Convert a csv string, or an array of items which + * may contain csv strings, into an array of items. + * + * @param $args + * A simple csv string; e.g. 'a,b,c' + * or a simple list of items; e.g. array('a','b','c') + * or some combination; e.g. array('a,b','c') or array('a,','b,','c,') + * + * @returns array + * A simple list of items (e.g. array('a','b','c') + */ +function _convert_csv_to_array($args) { + // + // Step 1: implode(',',$args) converts from, say, array('a,','b,','c,') to 'a,,b,,c,' + // Step 2: explode(',', ...) converts to array('a','','b','','c','') + // Step 3: array_filter(...) removes the empty items + // + return array_filter(explode(',', is_array($args) ? implode(',',$args) : $args)); +} + +/** + * Get the available global options. Used by help command. * * @return * An associative array containing the option definition as the key, and the description as the value, * for each of the available options. */ -function drush_get_option_help() { +function drush_get_global_options() { // TODO: Add a hook for this, to allow other modules to add their global options $options['-r <path>, --root=<path>'] = dt("Drupal root directory to use (default: current directory)"); $options['-l <uri>, --uri=http://example.com'] = dt('URI of the drupal site to use (only needed in multisite environments)'); @@ -654,58 +748,25 @@ function drush_get_option_help() { /** * Prints out help for a given command. */ -function drush_show_help($commands) { +function drush_show_help($commandstring) { $phases = _drush_bootstrap_phases(); - $commandstring = implode(" ", $commands); - foreach ($phases as $phase_index) { - if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) { - drush_bootstrap($phase_index); + if ($validated = drush_bootstrap_validate($phase_index)) { + if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) { + drush_bootstrap($phase_index); + } } if (!drush_get_error()) { $commands = drush_get_commands(); if (array_key_exists($commandstring, $commands)) { $command = $commands[$commandstring]; - // Merge in engine specific help. - foreach ($command['engines'] as $type => $description) { - $all_engines = drush_get_engines($type); - foreach ($all_engines as $name => $engine) { - $command = array_merge_recursive($command, $engine); - } - } - - if (!$help = drush_command_invoke_all('drush_help', 'drush:'. $command['command'])) { - $help = array($command['description']); - } - drush_print(wordwrap(implode("\n", $help), drush_get_context('DRUSH_COLUMNS', 80))); - drush_print(); - - // TODO: Let commands define additional sections. - $sections = array( - 'examples' => 'Examples', - 'arguments' => 'Arguments', - 'options' => 'Options', - ); - - foreach ($sections as $key => $value) { - if (!empty($command[$key])) { - drush_print(dt($value) . ':'); - foreach ($command[$key] as $name => $description) { - // '[command] is a token representing the current command. @see pm_drush_engine_version_control(). - $rows[] = array(str_replace('[command]', $commandstring, $name), dt($description)); - } - drush_print_table($rows, false, array(40)); - unset($rows); - drush_print(); - } + if ($validated && $command['bootstrap'] > $phase_index) { + continue; } - // Append aliases if any. - if ($command['aliases']) { - drush_print(dt("Aliases: ") . implode(', ', $command['aliases'])); - } + drush_print_help($command); return TRUE; @@ -718,6 +779,83 @@ function drush_show_help($commands) { return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => $commandstring))); } +/** + * Print the help for a single command to the screen. + * + * @param array $command + * A fully loaded $command array. + */ +function drush_print_help($command) { + // Merge in engine specific help. + foreach ($command['engines'] as $type => $description) { + $all_engines = drush_get_engines($type); + foreach ($all_engines as $name => $engine) { + $command = array_merge_recursive($command, $engine); + } + } + + if (!$help = drush_command_invoke_all('drush_help', 'drush:'. $command['command'])) { + $help = array($command['description']); + } + + // Give commandfiles an opportunity to add examples and options to the command. + drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE); + drush_command_invoke_all_ref('drush_help_alter', $command); + + drush_print(wordwrap(implode("\n", $help), drush_get_context('DRUSH_COLUMNS', 80))); + drush_print(); + + foreach ($command['sections'] as $key => $value) { + if (!empty($command[$key])) { + drush_print(dt($value) . ':'); + foreach ($command[$key] as $name => $description) { + // '[command] is a token representing the current command. @see pm_drush_engine_version_control(). + $rows[] = array(str_replace('[command]', $command['command'], $name), dt($description)); + + // Process the subsections too, if any + if (!empty($command['sub-' . $key]) && array_key_exists($name, $command['sub-' . $key])) { + foreach ($command['sub-' . $key][$name] as $name => $description) { + $rows[] = array(' ' . str_replace('[command]', $command['command'], $name), dt($description)); + } + } + } + drush_print_table($rows, FALSE, array(40)); + unset($rows); + drush_print(); + } + } + + // Append aliases if any. + if ($command['aliases']) { + drush_print(dt("Aliases: ") . implode(', ', $command['aliases'])); + } +} + + +/** + * Executes a shell command at a new working directory. + * The old cwd is restored on exit. + * + * @param $effective_wd + * The new working directory to execute the shell command at. + * @param $cmd + * The command to execute. May include placeholders used for sprintf. + * @param ... + * Values for the placeholders specified in $cmd. Each of these will be passed through escapeshellarg() to ensure they are safe to use on the command line. + * @return + * 0 if success. + */ +function drush_shell_cd_and_exec($effective_wd, $cmd) { + $args = func_get_args(); + + $effective_wd = array_shift($args); + $cwd = getcwd(); + drush_op('chdir', $effective_wd); + $result = call_user_func_array('drush_shell_exec', $args); + drush_op('chdir', $cwd); + return $result; +} + /** * Executes a shell command. * Output is only printed if in verbose mode. @@ -732,8 +870,29 @@ function drush_show_help($commands) { * 0 if success. */ function drush_shell_exec($cmd) { - $args = func_get_args(); + return _drush_shell_exec(func_get_args()); +} + +/** + * Executes a command in interactive mode. + * + * @see drush_shell_exec. + */ +function drush_shell_exec_interactive($cmd) { + return _drush_shell_exec(func_get_args(), TRUE); +} +/** + * Internal function: executes a shell command: + * + * @param $args + * The command and its arguments. + * @param $interactive + * Whether to run in + * + * @see drush_shell_exec. + */ +function _drush_shell_exec($args, $interactive = FALSE) { //do not change the command itself, just the parameters. for ($x = 1; $x < sizeof($args); $x++) { $args[$x] = escapeshellarg($args[$x]); @@ -745,7 +904,7 @@ function drush_shell_exec($cmd) { } if (!drush_get_context('DRUSH_SIMULATE')) { - exec($command . ' 2>&1', $output, $result); + exec($command . ($interactive ? '' : ' 2>&1'), $output, $result); _drush_shell_exec_output_set($output); if (drush_get_context('DRUSH_DEBUG')) { @@ -792,6 +951,41 @@ function drush_die($msg = NULL, $status = NULL) { die($msg ? "drush: $msg\n" : ''); } +/** + * Exit due to user declining a confirmation prompt. + */ +function drush_user_abort($msg = NULL, $status = NULL) { + drush_set_context('DRUSH_USER_ABORT', TRUE); + drush_die($msg ? $msg : 'Aborting.', $status); +} + +/* + * Check to see if the provided line is a "#!/usr/bin/env drush" + * "shebang" script line. + */ +function _drush_is_drush_shebang_line($line) { + return ((substr($line,0,2) == '#!') && (strstr($line, 'drush') !== FALSE)); +} + +/* + * Check to see if the provided script file is a "#!/usr/bin/env drush" + * "shebang" script line. + */ +function _drush_is_drush_shebang_script($script_filename) { + $result = FALSE; + + if (file_exists($script_filename)) { + $fp = fopen($script_filename, "r"); + if ($fp !== FALSE) { + $line = fgets($fp); + $result = _drush_is_drush_shebang_line($line); + fclose($fp); + } + } + + return $result; +} + /** * Prints a message with optional indentation. In general, * drush_log($message, 'ok') is often a better choice than this function. @@ -812,6 +1006,16 @@ function drush_print($message = '', $indent = 0) { print $msg; } +/** + * Print the contents of a file. + * + * @param string $file + * Full path to a file. + */ +function drush_print_file($file) { + drush_print(file_get_contents($file)); +} + /** * Stores a message which is printed during drush_shutdown() if in compact mode. * @param $message @@ -927,9 +1131,16 @@ function drush_choice($options, $prompt = 'Enter a number.', $label = '!value') * The input can be anything that fits on a single line (not only y/n), * so we can't use drush_confirm() * + * @param $prompt + * The text which is displayed to the user. + * @param $default + * The default value of the input. + * @param $optional + * If TRUE, user may continue even when no value is in the input. + * * @see drush_confirm() */ -function drush_prompt($prompt, $default = NULL) { +function drush_prompt($prompt, $default = NULL, $optional = FALSE) { if (!is_null($default)) { $prompt .= " [" . $default . "]"; } @@ -948,7 +1159,7 @@ function drush_prompt($prompt, $default = NULL) { if ($line === "") { $line = $default; } - if ($line) { + if ($line || $optional) { break; } print $prompt; @@ -963,8 +1174,7 @@ function drush_prompt($prompt, $default = NULL) { * @param $rows * The rows to print. * @param $header - * If TRUE, the first line will be treated as table header and therefore be - * underlined. + * If TRUE, the first line will be treated as table header. * @param $widths * The widths of each column (in characters) to use - if not specified this * will be determined automatically, based on a "best fit" algorithm. @@ -990,7 +1200,11 @@ function drush_print_table($rows, $header = FALSE, $widths = array()) { } $tbl->addData($newrows); - drush_print($tbl->getTable()); + $output = $tbl->getTable(); + if (!stristr(PHP_OS, 'WIN')) { + $output = str_replace("\r\n", PHP_EOL, $output); + } + drush_print($output); return $tbl; } @@ -1101,6 +1315,14 @@ function drush_table_column_autowidth($rows, $widths) { return $auto_widths; } +/** + * Cross-platform compatible helper function to recursively create a directory tree. + * @see http://theserverpages.com/php/manual/en/function.mkdir.php#50383 + */ +function drush_mkdir($path) { + return is_dir($path) || (drush_mkdir(dirname($path)) && drush_shell_exec('mkdir %s', $path)); +} + /** * @defgroup dispatching Command dispatching functions. * @{ @@ -1147,7 +1369,7 @@ function drush_remote_command() { } if (drush_confirm('Continue? ') === FALSE) { - drush_die('Aborting.'); + drush_user_abort(); } } $command = array_shift($args); @@ -1200,7 +1422,7 @@ function drush_do_multiple_command($command, $source_record, $destination_record } if (drush_confirm('Continue? ') === FALSE) { - drush_die('Aborting.'); + drush_user_abort(); } } @@ -1237,8 +1459,7 @@ function drush_do_site_command($site_record, $command, $args = array(), $data = $drush_path = $site_record['path-aliases']['%drush-script']; } } - - $values = drush_backend_invoke_args($command, $args, $data, 'GET', $integrate, $drush_path, array_key_exists('remote-host', $site_record) ? $site_record['remote-host'] : NULL, array_key_exists('remote-user', $site_record) ? $site_record['remote-user'] : NULL); + $values = drush_backend_invoke_args($command, $args, $data, 'GET', $integrate, $drush_path, array_key_exists('remote-host', $site_record) ? $site_record['remote-host'] : NULL, array_key_exists('remote-user', $site_record) ? $site_record['remote-user'] : NULL, array_key_exists('ssh-options', $site_record) ? $site_record['ssh-options'] : NULL); } return $values; } @@ -1279,8 +1500,8 @@ function drush_do_command_redispatch($command, $args = array(), $remote_host = N function drush_redispatch_get_options() { // Start off by taking everything from the site alias and command line // ('options' context) - $alias_context = array_diff_key(drush_get_context('alias'), array_flip(drush_sitealias_site_selection_keys())); - $options = array_merge($alias_context, drush_get_context('options')); + $options = array_merge(drush_get_context('alias'), drush_get_context('options')); + $options = array_diff_key($options, array_flip(drush_sitealias_site_selection_keys())); unset($options['command-specific']); unset($options['path-aliases']); // If we can parse the current command, then examine all contexts @@ -1300,7 +1521,10 @@ function drush_redispatch_get_options() { // is not used (e.g. when calling a remote instance of drush), then --php // should not be passed along. unset($options['php']); - + // If --bootstrap-to-first-arg is specified, do not + // pass it along to remote commands. + unset($options['bootstrap-to-first-arg']); + return $options; } @@ -1372,12 +1596,14 @@ function dlm($object) { drush_log($contents); } -/* +/** * Display the pipe output for the current request. */ function drush_pipe_output() { $pipe = drush_get_context('DRUSH_PIPE_BUFFER'); - drush_print_r($pipe); + if (!empty($pipe)) { + drush_print_r($pipe); + } } /** @@ -1424,6 +1650,7 @@ function _drush_print_log($entry) { case 'notice' : case 'message' : case 'info' : + case 'status': if (!$verbose) { // print nothing. exit cleanly. return TRUE; @@ -1507,7 +1734,7 @@ function drush_print_timers() { function _drush_log_drupal_messages() { if (function_exists('drupal_get_messages')) { - $messages = drupal_get_messages(); + $messages = drupal_get_messages(NULL, TRUE); if (array_key_exists('error', $messages)) { //Drupal message errors. @@ -1584,7 +1811,6 @@ function drush_format_size($size, $langcode = NULL) { * A sneaky implementation of hook_watchdog(). */ function system_watchdog($log_entry) { - // Transform non informative severity levels to 'error' for compatibility with _drush_print_log. // Other severity levels are coincident with the ones we use in drush. if (drush_drupal_major_version() >= 6 && $log_entry['severity'] <= 2) { @@ -1762,7 +1988,20 @@ function drush_errors_on() { * @} End of "defgroup errorhandling". */ - /** +/** + * Test to see if a file exists and is not empty + */ +function drush_file_not_empty($file_to_test) { + if (file_exists($file_to_test)) { + $stat = stat($file_to_test); + if ($stat['size'] > 0) { + return TRUE; + } + } + return FALSE; +} + +/** * Get the PHP memory_limit value in bytes. */ function drush_memory_limit() { @@ -1770,11 +2009,11 @@ function drush_memory_limit() { $last = strtolower($value[strlen($value)-1]); switch ($last) { case 'g': - $value *= 1024; + $value *= DRUSH_DRUPAL_KILOBYTE; case 'm': - $value *= 1024; + $value *= DRUSH_DRUPAL_KILOBYTE; case 'k': - $value *= 1024; + $value *= DRUSH_DRUPAL_KILOBYTE; } return $value; diff --git a/sites/all/modules/drush/includes/environment.inc b/sites/all/modules/drush/includes/environment.inc index 723e597c7c7a134f81d1cb70af369a363fd3f89c..b33465f82127ad66de7b6f5eb2ac000d2f80e316 100644 --- a/sites/all/modules/drush/includes/environment.inc +++ b/sites/all/modules/drush/includes/environment.inc @@ -1,5 +1,5 @@ <?php -// $Id: environment.inc,v 1.87 2010/06/16 23:47:23 weitzman Exp $ +// $Id: environment.inc,v 1.104 2010/12/01 15:53:15 jonhattan Exp $ /** * @file @@ -18,6 +18,14 @@ define('DRUSH_DRUPAL_BOOTSTRAP', 'includes/bootstrap.inc'); * Sequential Drush bootstrapping phases. */ +/** + * No bootstrap. + * + * This constant is only used to indicate that the bootstrap process has + * not started yet. It is not possible to have no bootstrap. + */ +define('DRUSH_BOOTSTRAP_NONE', -1); + /** * Only bootstrap Drush, without any Drupal specific code. * @@ -109,14 +117,14 @@ define('DRUSH_TABLE_VERSION', '1.1.3'); /** * URL for automatic file download for supported version of Console Table. */ -define('DRUSH_TABLE_URL', 'http://cvs.php.net/viewvc.cgi/pear/Console_Table/Table.php?revision=1.28&view=co'); +define('DRUSH_TABLE_URL', 'http://svn.php.net/viewvc/pear/packages/Console_Table/trunk/Table.php?revision=267580&view=co'); /** * Helper function listing phases. * * For commands that need to iterate through the phases, such as help */ -function _drush_bootstrap_phases($function_names = FALSE) { +function _drush_bootstrap_phases($function_names = FALSE, $init_phases_only = FALSE) { static $functions = array( DRUSH_BOOTSTRAP_DRUSH => '_drush_bootstrap_drush', DRUSH_BOOTSTRAP_DRUPAL_ROOT => '_drush_bootstrap_drupal_root', @@ -125,14 +133,20 @@ function _drush_bootstrap_phases($function_names = FALSE) { DRUSH_BOOTSTRAP_DRUPAL_DATABASE => '_drush_bootstrap_drupal_database', DRUSH_BOOTSTRAP_DRUPAL_FULL => '_drush_bootstrap_drupal_full', DRUSH_BOOTSTRAP_DRUPAL_LOGIN => '_drush_bootstrap_drupal_login'); - static $phases; - if ($function_names) { - return $functions; + + $result = array(); + if ($init_phases_only) { + foreach (array(DRUSH_BOOTSTRAP_DRUSH, DRUSH_BOOTSTRAP_DRUPAL_FULL) as $phase) { + $result[$phase] = $functions[$phase]; + } } - if (!$phases) { - $phases = array_keys($functions); + else { + $result = $functions; + } + if (!$function_names) { + $result = array_keys($result); } - return $phases; + return $result; } /** @@ -156,7 +170,7 @@ function _drush_bootstrap_phases($function_names = FALSE) { * DRUSH_BOOTSTRAP_DRUPAL_FULL = Initialize Drupal fully. * DRUSH_BOOTSTRAP_DRUPAL_LOGIN = Log into Drupal with a valid user. */ -function drush_bootstrap($phase) { +function drush_bootstrap($phase, $phase_max = FALSE) { static $phases; if (!$phases) { $phases = _drush_bootstrap_phases(TRUE); @@ -170,6 +184,9 @@ function drush_bootstrap($phase) { if (function_exists($current_phase) && !drush_get_error()) { drush_log(dt("Drush bootstrap phase : !function()", array('!function' => $current_phase)), 'bootstrap'); $current_phase(); + + // Find any command files that are available during this bootstrap phase. + _drush_find_commandfiles($phase_index, $phase_max); } drush_set_context('DRUSH_BOOTSTRAP_PHASE', $phase_index); } @@ -186,6 +203,21 @@ function drush_bootstrap($phase) { return !drush_get_error(); } +/** + * Determine whether a given bootstrap phase has been completed + * + * @param phase + * The bootstrap phase to test + * + * @returns + * TRUE if the specified bootstrap phase has completed. + */ +function drush_has_boostrapped($phase) { + $phase_index = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); + + return isset($phase_index) && ($phase_index >= $phase); +} + /** * Validate whether a bootstrap phases can be reached. * @@ -238,18 +270,60 @@ function drush_bootstrap_validate($phase) { return $result_cache[$phase]; } +/** + * Bootstrap to the specified phase. + * + * @param $max_phase_index + * Only attempt bootstrap to the specified level. + */ +function drush_bootstrap_to_phase($max_phase_index) { + drush_log(dt("Bootstrap to phase !phase.", array('!phase' => $max_phase_index)), 'bootstrap'); + $phases = _drush_bootstrap_phases(); + $result = TRUE; + + // Try to bootstrap to the maximum possible level, without generating errors + foreach ($phases as $phase_index) { + if ($phase_index > $max_phase_index) { + // Stop trying, since we achieved what was specified. + break; + } + + if (drush_bootstrap_validate($phase_index)) { + if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) { + $result = drush_bootstrap($phase_index, $max_phase_index); + } + } + else { + break; + } + } + + return $result; +} + /** * Bootstrap to the highest level possible, without triggering any errors. + * + * @param $max_phase_index + * Only attempt bootstrap to the specified level. */ -function drush_bootstrap_max() { +function drush_bootstrap_max($max_phase_index = FALSE) { $phases = _drush_bootstrap_phases(); $phase_index = DRUSH_BOOTSTRAP_DRUSH; + if (!$max_phase_index) { + $max_phase_index = count($phases); + } // Try to bootstrap to the maximum possible level, without generating errors foreach ($phases as $phase_index) { + if ($phase_index > $max_phase_index) { + // Stop trying, since we achieved what was specified. + break; + } + if (drush_bootstrap_validate($phase_index)) { if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) { - drush_bootstrap($phase_index); + drush_bootstrap($phase_index, $max_phase_index); } } else { @@ -257,9 +331,29 @@ function drush_bootstrap_max() { } } - return $phase_index; + return drush_get_context('DRUSH_BOOTSTRAP_PHASE'); } +/** + * Bootstrap the specified site alias. The site alias must + * be a valid alias to a local site. + * + * @param $site_record + * The alias record for the given site alias. + * @see drush_sitealias_get_record(). + * @param $max_phase_index + * Only attempt bootstrap to the specified level. + * @returns TRUE if attempted to bootstrap, or FALSE + * if no bootstrap attempt was made. + */ +function drush_bootstrap_max_to_sitealias($site_record, $max_phase_index = NULL) { + if ((array_key_exists('root', $site_record) && !array_key_exists('remote-host', $site_record))) { + drush_sitealias_set_alias_context($site_record); + drush_bootstrap_max($max_phase_index); + return TRUE; + } + return FALSE; +} /** * Helper function to collect any errors that occur during the bootstrap process. @@ -322,22 +416,68 @@ function drush_bootstrap_value($context, $value = null) { return null; } +/* + * Returns a localizable message about php.ini that + * varies depending on whether the php_ini_loaded_file() + * is available or not. + */ +function _drush_php_ini_loaded_file_message() { + if (function_exists('php_ini_loaded_file')) { + return dt('Please check your configuration settings in !phpini or in your drush.ini file; see examples/example.drush.ini for details.', array('!phpini' => php_ini_loaded_file())); + } + else { + return dt('Please check your configuration settings in your php.ini file or in your drush.ini file; see examples/example.drush.ini for details.'); + } +} + +/** + * Evalute the environment after an abnormal termination and + * see if we can determine any configuration settings that the user might + * want to adjust. + */ +function _drush_postmortem() { + // Make sure that the memory limit has been bumped up from the minimum default value of 32M. + $php_memory_limit = drush_memory_limit(); + if (($php_memory_limit > 0) && ($php_memory_limit <= 32*DRUSH_DRUPAL_KILOBYTE*DRUSH_DRUPAL_KILOBYTE)) { + drush_set_error('DRUSH_MEMORY_LIMIT', dt('Your memory limit is set to !memory_limit; drush needs as much memory to run as Drupal. !php_ini_msg', array('!memory_limit' => $php_memory_limit / (DRUSH_DRUPAL_KILOBYTE*DRUSH_DRUPAL_KILOBYTE) . 'M', '!php_ini_msg' => _drush_php_ini_loaded_file_message()))); + } +} + +/** + * Evaluate the environment before command bootstrapping + * begins. If the php environment is too restrictive, then + * notify the user that a setting change is needed and abort. + */ +function _drush_environment_preflight_checks() { + // Test to insure that certain php ini restrictions have not been enabled + $prohibited_list = array(); + foreach (array('safe_mode', 'open_basedir', 'disable_functions', 'disable_classes') as $prohibited_mode) { + if (ini_get($prohibited_mode)) { + $prohibited_list[] = $prohibited_mode; + } + } + if (!empty($prohibited_list)) { + return drush_bootstrap_error('DRUSH_PHP_INI_RESTRICTIONS', dt('The following restricted PHP modes have non-empty values: !prohibited_list. This configuration is incompatible with drush. !php_ini_msg', array('!prohibited_list' => implode(' and ', $prohibited_list), '!php_ini_msg' => _drush_php_ini_loaded_file_message()))); + } + + return TRUE; +} + /** * Validate initial Drush bootstrap phase. */ function _drush_bootstrap_drush_validate() { - // Test safe mode is off. - if (ini_get('safe_mode')) { - return drush_bootstrap_error('DRUSH_SAFE_MODE', dt('PHP safe mode is activated. Drush requires that safe mode is disabled.')); + if (($preflight_result = _drush_environment_preflight_checks()) !== TRUE) { + return $preflight_result; } - + // try using the PEAR installed version of Console_Table $tablefile = 'Console/Table.php'; if (@file_get_contents($tablefile, FILE_USE_INCLUDE_PATH) === FALSE) { $tablefile = DRUSH_BASE_PATH . '/includes/table.inc'; // Attempt to download Console Table, via various methods. - if (!file_exists($tablefile)) { + if (!drush_file_not_empty($tablefile)) { $targetpath = dirname($tablefile); // not point continuing if we can't write to the target path if (!is_writable($targetpath)) { @@ -359,7 +499,7 @@ function _drush_bootstrap_drush_validate() { } } require_once $tablefile; - + return TRUE; } @@ -384,9 +524,6 @@ function _drush_bootstrap_drush_validate() { * the execution flow to be able to take affect/ */ function _drush_bootstrap_drush() { - // The bootstrap can fail silently, so we catch that in a shutdown function. - register_shutdown_function('drush_shutdown'); - // Set the terminal width, used for wrapping table output. // Normally this is exported using tput in the drush script. // If this is not present we do an additional check using stty here. @@ -398,9 +535,6 @@ function _drush_bootstrap_drush() { } drush_set_context('DRUSH_COLUMNS', $columns); - // parse the command line arguments. - drush_parse_args(); - // statically define a way to call drush again define('DRUSH_COMMAND', drush_find_drush()); @@ -458,9 +592,6 @@ function _drush_bootstrap_drush() { } _drush_bootstrap_global_options(); - - // Find any command files that are available during this bootstrap phase. - _drush_find_commandfiles(DRUSH_BOOTSTRAP_DRUSH); } function _drush_bootstrap_global_options() { @@ -468,7 +599,6 @@ function _drush_bootstrap_global_options() { drush_set_context('DRUSH_VERBOSE', drush_get_option(array('v', 'verbose', 'd', 'debug'), FALSE)); drush_set_context('DRUSH_DEBUG', drush_get_option(array('d', 'debug'))); - // Backend implies affirmative drush_set_context('DRUSH_AFFIRMATIVE', drush_get_option(array('y', 'yes', 'b', 'backend'), FALSE)); drush_set_context('DRUSH_NEGATIVE', drush_get_option(array('n', 'no'), FALSE)); @@ -476,9 +606,11 @@ function _drush_bootstrap_global_options() { // Suppress colored logging if --nocolor option is explicitly given or if // terminal does not support it. - $nocolor = (drush_get_option(array('nocolor'), FALSE) || !getenv('TERM')); + $nocolor = (drush_get_option(array('nocolor'), FALSE)); if (!$nocolor) { - // Check for colorless terminal. + // Check for colorless terminal. If there is no terminal, then + // 'tput colors 2>&1' will return "tput: No value for $TERM and no -T specified", + // which is not numeric and therefore will put us in no-color mode. $colors = exec('tput colors 2>&1'); $nocolor = !($colors === FALSE || (is_numeric($colors) && $colors >= 3)); } @@ -529,10 +661,12 @@ function _drush_bootstrap_drupal_root() { chdir($drupal_root); drush_load_config('drupal'); - require_once(DRUPAL_ROOT . '/' . DRUSH_DRUPAL_BOOTSTRAP); + require_once DRUPAL_ROOT . '/' . DRUSH_DRUPAL_BOOTSTRAP; $version = drush_set_context('DRUSH_DRUPAL_VERSION', drush_drupal_version()); $major_version = drush_set_context('DRUSH_DRUPAL_MAJOR_VERSION', drush_drupal_major_version()); + _drush_bootstrap_global_options(); + drush_log(dt("Initialized Drupal !version root directory at !drupal_root", array("!version" => $version, '!drupal_root' => $drupal_root))); } @@ -555,7 +689,7 @@ function _drush_bootstrap_drupal_site_validate() { } $uri = 'http://'. $current; - $drush_uri = drush_bootstrap_value('drush_uri', drush_get_option(array('l', 'uri'), $uri)); + $drush_uri = drush_bootstrap_value('drush_uri', drush_get_option(array('l', 'uri'), $uri, 'options')); // Fake the necessary HTTP headers that Drupal needs: if ($drush_uri) { @@ -587,6 +721,7 @@ function _drush_bootstrap_drupal_site_validate() { $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF']; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; $_SERVER['REQUEST_METHOD'] = NULL; + $_SERVER['SERVER_SOFTWARE'] = NULL; $_SERVER['HTTP_USER_AGENT'] = NULL; @@ -614,11 +749,9 @@ function _drush_bootstrap_do_drupal_site() { // Create an alias '@self' _drush_sitealias_cache_alias('self', array('root' => drush_get_context('DRUSH_DRUPAL_ROOT'), 'uri' => $drush_uri)); - // Find any command files that are available during this bootstrap phase. - _drush_find_commandfiles(DRUSH_BOOTSTRAP_DRUPAL_SITE); - drush_log(dt("Initialized Drupal site !site at !site_root", array('!site' => $site, '!site_root' => $conf_path))); drush_load_config('site'); + _drush_bootstrap_global_options(); } /** @@ -689,7 +822,7 @@ function _drush_bootstrap_drupal_configuration() { switch (drush_drupal_major_version()) { case 5: case 6: - if (isset($GLOBALS['db_url'])) { + if (!empty($GLOBALS['db_url'])) { $url = $GLOBALS['db_url']; if (is_array($url)) { $url = $url['default']; @@ -705,7 +838,8 @@ function _drush_bootstrap_drupal_configuration() { } break; case 7: - if (isset($GLOBALS['databases']['default']['default'])) { + default: + if (!empty($GLOBALS['databases']['default']['default'])) { $conn = $GLOBALS['databases']['default']['default']; $creds['driver'] = $conn['driver']; $creds['user'] = $conn['username']; @@ -729,7 +863,7 @@ function _drush_bootstrap_drupal_configuration() { */ function _drush_bootstrap_drupal_database_validate() { if (!drush_valid_db_credentials()) { - return drush_bootstrap_error("DRUSH_DRUPAL_DB_ERROR"); + return drush_bootstrap_error('DRUSH_DRUPAL_DB_ERROR'); } return TRUE; } @@ -749,11 +883,15 @@ function _drush_bootstrap_drupal_full() { ob_start(); drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); ob_end_clean(); - // Poke Drupal with a cluestick so it recognizes our system_watchdog() implementation. - module_implements('watchdog', FALSE, TRUE); + + // If needed, prod module_implements() to recognize our system_watchdog() implementation. + $dogs = module_implements('watchdog'); + if (!in_array('system', $dogs)) { + // Note that this resets module_implements cache. + module_implements('watchdog', FALSE, TRUE); + } + _drush_log_drupal_messages(); - // Find any command files that are available during this bootstrap phase. - _drush_find_commandfiles(DRUSH_BOOTSTRAP_DRUPAL_FULL); } /** @@ -777,7 +915,7 @@ function drush_cwd() { // We use PWD if available because getcwd() resolves symlinks, which // could take us outside of the Drupal root, making it impossible to find. // $_SERVER['PWD'] isn't set on windows and generates a Notice. - $path = isset($_SERVER['PWD'])?$_SERVER['PWD']:''; + $path = isset($_SERVER['PWD']) ? $_SERVER['PWD'] : ''; if (empty($path)) { $path = getcwd(); } @@ -933,37 +1071,97 @@ function drush_valid_drupal_root($path) { * Tests the currently loaded database credentials to ensure a database connection can be made. */ function drush_valid_db_credentials() { - if (class_exists('PDO')) { - $creds = drush_get_context('DRUSH_DB_CREDENTIALS'); - - $type = ($creds['driver'] == 'mysqli') ? 'mysql' : $creds['driver']; - - if (!in_array($type, PDO::getAvailableDrivers())) { - drush_log(dt('PDO support available, but the !type driver has not been installed. Assuming success.', array('!type' => $type)), 'bootstrap'); - return TRUE; - } - - $constr = sprintf("%s:dbname=%s;host=%s", $type, $creds['name'], $creds['host']); - if (!empty($creds['port'])) { - $constr .= sprintf(";port=%d", $creds['port']); - } - - try { - $db = new PDO($constr, $creds['user'], $creds['pass']); - $db = null; - return TRUE; - } - catch (PDOException $e) { - // We do not use drush_set_error here , because it's up to the calling function - // to determine whether or not this is an error or a warning. - drush_log($e->getMessage(), 'warning'); - return FALSE; - } + $creds = drush_get_context('DRUSH_DB_CREDENTIALS'); + // Do minimal checking that we have the necessary information. + if (count($creds) == 0) { + return FALSE; } - else { - drush_log(dt('PDO support not available. Could not pre-validate database credentials. Assuming success'), 'bootstrap'); - return TRUE; + $type = $creds['driver']; + switch (drush_drupal_major_version()) { + case 5: + case 6: + // Check availability of db extension in PHP and also Drupal support. + if (file_exists('./includes/install.'. $type .'.inc')) { + require_once './includes/install.'. $type .'.inc'; + $function = $type .'_is_available'; + if (!$function()) { + drush_log(dt('!type extension for PHP is not installed. Check your php.ini to see how you can enable it.', array('!type' => $type)), 'bootstrap'); + return FALSE; + } + } + else { + drush_log('!type database type is unsupported.', array('!type' => $type), 'bootstrap'); + return FALSE; + } + // Verify connection settings. + switch ($type) { + case 'mysql': + case 'mysqli': + $connection = @mysql_connect($creds['host'], $creds['user'], $creds['pass']); + if (!$connection || !mysql_select_db($creds['name'])) { + drush_log(mysql_error(), 'bootstrap'); + return FALSE; + } + break; + case 'pgsql': + $conn_string = sprintf("host=%s user=%s password=%s dbname=%s", $creds['host'], $creds['user'], $creds['pass'], $creds['name']); + if (isset($creds['port'])) { + $conn_string .= ' port=' . $creds['port']; + } + // Copied from d6's database.pgsql.inc: + // pg_last_error() does not return a useful error message for database + // connection errors. We must turn on error tracking to get at a good error + // message, which will be stored in $php_errormsg. + $track_errors_previous = ini_get('track_errors'); + ini_set('track_errors', 1); + $connection = @pg_connect($conn_string); + if (!$connection) { + require_once './includes/unicode.inc'; + drush_log(decode_entities($php_errormsg), 'bootstrap'); + // Restore error tracking setting + ini_set('track_errors', $track_errors_previous); + return FALSE; + } + // Restore error tracking setting + ini_set('track_errors', $track_errors_previous); + break; + } + break; + case 7: + default: + // Drupal >=7 requires PDO and drush requires php 5.2, that ships with PDO + // but it may be compiled with --disable-pdo. + if (!class_exists('PDO')) { + drush_log(dt('PDO support is required.'), 'bootstrap'); + return FALSE; + } + // Check the database specific driver is available. + if (!in_array($type, PDO::getAvailableDrivers())) { + drush_log(dt('!type extension for PHP PDO is not installed. Check your php.ini to see how you can enable it.', array('!type' => $type)), 'bootstrap'); + return FALSE; + } + // Build the connection string. + if ($type === 'sqlite') { + $constr = 'sqlite:' . $creds['name']; + } + else { + $constr = sprintf("%s:dbname=%s;host=%s", $type, $creds['name'], $creds['host']); + if (!empty($creds['port'])) { + $constr .= sprintf(";port=%d", $creds['port']); + } + } + try { + $db = new PDO($constr, $creds['user'], $creds['pass']); + $db = null; + } + catch (PDOException $e) { + drush_log($e->getMessage(), 'bootstrap'); + return FALSE; + } + break; } + + return TRUE; } /** @@ -996,7 +1194,7 @@ function drush_valid_db_credentials() { function drush_find_drush() { $php = drush_get_option('php'); if (isset($php)) { - $drush = $php . " " . realpath($_SERVER['argv'][0]) . " --php=$php"; + $drush = $php . " " . realpath($_SERVER['argv'][0]) . " --php='$php'"; } else { $drush = realpath($_SERVER['argv']['0']); } @@ -1024,38 +1222,50 @@ function drush_read_drush_info() { function drush_is_local_host($host) { // In order for this to work right, you must use 'localhost' or '127.0.0.1' // or the machine returned by 'uname -n' for your 'remote-host' entry in - // your site alias. Note that sometimes 'uname -n' does not return the - // correct value. To fix it, put the correct hostname in /etc/hostname - // and then run 'hostname -F /etc/hostname'. - return ($host == 'localhost') || ($host == '127.0.0.1') || ($host == php_uname('n')); + // your site alias. + if (($host == 'localhost') || ($host == '127.0.0.1')) { + return TRUE; + } + + // If uname -n returns an fqdn (that is, uname -n == hostname -f), + // then we will require that it exactly match the host in order + // to be considered local. However, the usual convention is for + // uname -n to return only the node name (that is, uname -n == hostname -a). + // When this is the case, we will consider $host to be local if the + // machine portion of it (everything up to the first dot) matches the + // current value of uname -n. We prefer uname -n to hostname as + // the output of uname is more regular than hostname. + $uname = php_uname('n'); + return (strpos($uname,'.') !== FALSE) ? ($host == $uname) : substr($host . '.',0,strlen($uname)+1) == $uname . '.'; } /** - * Get complete information for all available modules and themes. + * Get complete information for all available extensions (modules and + * themes). * * @return - * An array containing info for all available modules and themes. + * An array containing info for all available extensions. */ -function drush_get_projects() { +function drush_get_extensions() { drush_include_engine('drupal', 'environment'); return array_merge(drush_get_modules(), drush_get_themes()); } /** - * Calculate a project status based on current status and schema version. + * Calculate a extension status based on current status and schema version. * - * @param $project - * Array of a single project info. + * @param $extension + * Object of a single extension info. * * @return - * String describing project status. Values: enabled|disabled|not installed + * String describing extension status. Values: enabled|disabled|not installed */ -function drush_get_project_status($project) { - if (($project->type == 'module')&&($project->schema_version == -1)) { +function drush_get_extension_status($extension) { + if (($extension->type == 'module')&&($extension->schema_version == -1)) { $status = "not installed"; } else { - $status = ($project->status == 1)?'enabled':'disabled'; + $status = ($extension->status == 1)?'enabled':'disabled'; } return $status; diff --git a/sites/all/modules/drush/includes/sitealias.inc b/sites/all/modules/drush/includes/sitealias.inc index 55ee2473cf723002a932d2323b56050817e1e9eb..6934517630d3ea5cb514580833fc73b53c0621fb 100644 --- a/sites/all/modules/drush/includes/sitealias.inc +++ b/sites/all/modules/drush/includes/sitealias.inc @@ -1,12 +1,12 @@ <?php -// $Id: sitealias.inc,v 1.47 2010/06/08 21:33:51 greg1anderson Exp $ +// $Id: sitealias.inc,v 1.60 2010/12/01 15:45:53 weitzman Exp $ /** * @file * The site alias API. * * Run commands on remote server(s). - * @see example.drushrc.php + * @see example.aliases.drushrc.php * @see http://drupal.org/node/670460 */ @@ -111,7 +111,7 @@ function drush_sitealias_get_record($alias) { return $alias_record; } -/* +/** * This is a continuation of drush_sitealias_get_record, above. It is * not intended to be called directly. */ @@ -392,6 +392,9 @@ function _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context = } else { $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + if ($drupal_root == NULL) { + $drupal_root = drush_get_option(array('r', 'root'), drush_locate_root()); + } } if (isset($drupal_root) && !is_array($drupal_root)) { drush_sitealias_create_sites_alias($drupal_root); @@ -442,16 +445,19 @@ function _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context = // a site-list of all of the aliases in the file if (substr($filename, -20) == ".aliases.drushrc.php") { $group_name = basename($filename,".aliases.drushrc.php"); - if (!array_key_exists($group_name, $aliases)) { + if (!empty($aliases) && !array_key_exists($group_name, $aliases)) { $alias_names = array(); foreach (array_keys($aliases) as $one_alias) { - $alias_names[] = "@$one_alias"; + $alias_names[] = "@$group_name.$one_alias"; + $aliases["$group_name.$one_alias"] = $aliases[$one_alias]; + $aliases[$one_alias]["#hidden"] = TRUE; } $aliases[$group_name] = array('site-list' => implode(',', $alias_names)); } } // If aliasname is NULL, then we will store // all $aliases into the alias cache + $alias_recorded = FALSE; if ($aliasname == NULL) { if (!empty($aliases)) { if (!empty($options)) { @@ -466,6 +472,7 @@ function _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context = } $result = array_merge($result, $aliases); + $alias_recorded = TRUE; } } // If aliasname is not NULL, then we will store @@ -474,6 +481,16 @@ function _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context = drush_set_config_special_contexts($options); // maybe unnecessary $result = array_merge($options, $aliases[$aliasname]); _drush_sitealias_initialize_alias_record($result); + $alias_recorded = TRUE; + } + // If we found at least one alias from this file + // then record it in the drush-alias-files context. + if ($alias_recorded) { + $drush_alias_files = drush_get_context('drush-alias-files'); + if (!in_array($filename, $drush_alias_files)) { + $drush_alias_files[] = $filename; + } + drush_set_context('drush-alias-files', $drush_alias_files); } } } @@ -492,16 +509,20 @@ function _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context = */ function _drush_sitealias_add_inherited_values(&$aliases) { foreach ($aliases as $alias_name => $alias_value) { - if (array_key_exists('parent', $alias_value)) { + if (isset($alias_value['parent'])) { // Prevent circular references from causing an infinite loop _drush_sitealias_cache_alias($alias_name, array()); // Fetch and merge in each parent foreach (explode(',', $alias_value['parent']) as $parent) { $parent_record = drush_sitealias_get_record($parent); - if ($parent_record != NULL) { - unset($parent_record['name']); - $aliases[$alias_name] = array_merge($parent_record, $aliases[$alias_name]); + unset($parent_record['name']); + $array_based_keys = array_merge(drush_get_special_keys(), array('path-aliases')); + foreach ($array_based_keys as $array_based_key) { + if (isset($aliases[$alias_name][$array_based_key]) && isset($parent_record[$array_based_key])) { + $aliases[$alias_name][$array_based_key] = array_merge($parent_record[$array_based_key], $aliases[$alias_name][$array_based_key]); + } } + $aliases[$alias_name] = array_merge($parent_record, $aliases[$alias_name]); } unset($aliases[$alias_name]['parent']); } @@ -936,9 +957,14 @@ function drush_sitealias_alias_record_to_spec($alias_record, $with_db = false) { } } - // add the database info to the specification if desired + // Add the database info to the specification if desired if ($with_db) { - $result = $result . '?db-url=' . urlencode($alias_record['db-url']); + // If db-url is not supplied, look it up from the remote + // or local site and add it to the site alias + if (!isset($alias_record['db-url'])) { + drush_sitealias_add_db_url($alias_record); + } + $result = $result . '?db-url=' . urlencode(is_array($alias_record['db-url']) ? $alias_record['db-url']['default'] : $alias_record['db-url']); } } @@ -1168,14 +1194,16 @@ function drush_convert_db_from_db_url($db_url) { 'port' => NULL, 'database' => NULL, ); - $url = (object)$url; + $url = (object)array_map('urldecode', $url); return array( 'driver' => $url->scheme == 'mysqli' ? 'mysql' : $url->scheme, - 'username' => urldecode($url->user), - 'password' => urldecode($url->pass), - 'port' => urldecode($url->port), - 'host' => urldecode($url->host), - 'database' => substr(urldecode($url->path), 1), // skip leading '/' character + 'username' => $url->user, + 'password' => $url->pass, + 'port' => $url->port, + 'host' => $url->host, + // Remove leading / character from database names, unless we're installing + // to SQLite (which won't have a slash there unless it's part of a path). + 'database' => $url->scheme === 'sqlite' ? $url->path : substr($url->path, 1) ); } @@ -1255,7 +1283,7 @@ function drush_sitealias_set_alias_context($site_alias_settings, $prefix = '') { $options = drush_get_context('alias'); // There are some items that we should just skip - $skip_list = array('site-aliases', 'command-specific'); + $skip_list = drush_get_special_keys(); // Also skip 'remote-host' and 'remote-user' if 'remote-host' is actually // the local machine if (array_key_exists('remote-host', $site_alias_settings) && drush_is_local_host($site_alias_settings['remote-host'])) { @@ -1267,7 +1295,7 @@ function drush_sitealias_set_alias_context($site_alias_settings, $prefix = '') { foreach ($site_alias_settings as $key => $value) { // Special handling for path aliases: if ($key == "path-aliases") { - foreach (array('%drush-script', '%dump', '%include') as $path_key) { + foreach (array('%drush-script', '%dump', '%dump-dir', '%include') as $path_key) { if (array_key_exists($path_key, $value)) { $options[$prefix . substr($path_key, 1)] = $value[$path_key]; } @@ -1280,7 +1308,7 @@ function drush_sitealias_set_alias_context($site_alias_settings, $prefix = '') { drush_set_config_options('alias', $options); } -/* +/** * Call prior to drush_sitealias_evaluate_path to insure * that any site-specific aliases associated with any * local site in $path are defined. @@ -1502,5 +1530,33 @@ function drush_sitealias_evaluate_path($path, &$additional_options) { * Option keys used for site selection. */ function drush_sitealias_site_selection_keys() { - return array('remote-host', 'remote-user', 'name'); + return array('remote-host', 'remote-user', 'ssh-options', 'name'); +} + + +function sitealias_find_local_drupal_root($site_list) { + $drupal_root = NULL; + + foreach ($site_list as $site) { + if (($drupal_root == NULL) && (array_key_exists('root', $site) && !array_key_exists('remote-host', $site))) { + $drupal_root = $site['root']; + } + } + + return $drupal_root; +} + + +/** + * Helper function to obtain the keys' names that need special handling in certain + * cases. + * @return + * A non-associative array containing the needed keys' names. + */ +function drush_get_special_keys() { + $special_keys = array( + 'command-specific', + 'site-aliases', + ); + return $special_keys; } diff --git a/sites/all/modules/drush/includes/table.inc b/sites/all/modules/drush/includes/table.inc deleted file mode 100644 index d3acfb2975df8945beb0299c6eee91f6920ff571..0000000000000000000000000000000000000000 --- a/sites/all/modules/drush/includes/table.inc +++ /dev/null @@ -1,893 +0,0 @@ -<?php -/** - * Utility for printing tables from commandline scripts. - * - * PHP versions 4 and 5 - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * o Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * o Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * o The names of the authors may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * @category Console - * @package Console_Table - * @author Richard Heyes <richard@phpguru.org> - * @author Jan Schneider <jan@horde.org> - * @copyright 2002-2005 Richard Heyes - * @copyright 2006-2008 Jan Schneider - * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) - * @version CVS: $Id: Table.php,v 1.27 2008/10/20 22:22:13 yunosh Exp $ - * @link http://pear.php.net/package/Console_Table - */ - -define('CONSOLE_TABLE_HORIZONTAL_RULE', 1); -define('CONSOLE_TABLE_ALIGN_LEFT', -1); -define('CONSOLE_TABLE_ALIGN_CENTER', 0); -define('CONSOLE_TABLE_ALIGN_RIGHT', 1); -define('CONSOLE_TABLE_BORDER_ASCII', -1); - -/** - * The main class. - * - * @category Console - * @package Console_Table - * @author Jan Schneider <jan@horde.org> - * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) - * @link http://pear.php.net/package/Console_Table - */ -class Console_Table -{ - /** - * The table headers. - * - * @var array - */ - var $_headers = array(); - - /** - * The data of the table. - * - * @var array - */ - var $_data = array(); - - /** - * The maximum number of columns in a row. - * - * @var integer - */ - var $_max_cols = 0; - - /** - * The maximum number of rows in the table. - * - * @var integer - */ - var $_max_rows = 0; - - /** - * Lengths of the columns, calculated when rows are added to the table. - * - * @var array - */ - var $_cell_lengths = array(); - - /** - * Heights of the rows. - * - * @var array - */ - var $_row_heights = array(); - - /** - * How many spaces to use to pad the table. - * - * @var integer - */ - var $_padding = 1; - - /** - * Column filters. - * - * @var array - */ - var $_filters = array(); - - /** - * Columns to calculate totals for. - * - * @var array - */ - var $_calculateTotals; - - /** - * Alignment of the columns. - * - * @var array - */ - var $_col_align = array(); - - /** - * Default alignment of columns. - * - * @var integer - */ - var $_defaultAlign; - - /** - * Character set of the data. - * - * @var string - */ - var $_charset = 'utf-8'; - - /** - * Border character. - * - * @var string - */ - var $_border = CONSOLE_TABLE_BORDER_ASCII; - - /** - * Whether the data has ANSI colors. - * - * @var boolean - */ - var $_ansiColor = false; - - /** - * Constructor. - * - * @param integer $align Default alignment. One of - * CONSOLE_TABLE_ALIGN_LEFT, - * CONSOLE_TABLE_ALIGN_CENTER or - * CONSOLE_TABLE_ALIGN_RIGHT. - * @param string $border The character used for table borders or - * CONSOLE_TABLE_BORDER_ASCII. - * @param integer $padding How many spaces to use to pad the table. - * @param string $charset A charset supported by the mbstring PHP - * extension. - * @param boolean $color Whether the data contains ansi color codes. - */ - function Console_Table($align = CONSOLE_TABLE_ALIGN_LEFT, - $border = CONSOLE_TABLE_BORDER_ASCII, $padding = 1, - $charset = null, $color = false) - { - $this->_defaultAlign = $align; - $this->_border = $border; - $this->_padding = $padding; - $this->_ansiColor = $color; - if ($this->_ansiColor) { - include_once 'Console/Color.php'; - } - if (!empty($charset)) { - $this->setCharset($charset); - } - } - - /** - * Converts an array to a table. - * - * @param array $headers Headers for the table. - * @param array $data A two dimensional array with the table - * data. - * @param boolean $returnObject Whether to return the Console_Table object - * instead of the rendered table. - * - * @static - * - * @return Console_Table|string A Console_Table object or the generated - * table. - */ - function fromArray($headers, $data, $returnObject = false) - { - if (!is_array($headers) || !is_array($data)) { - return false; - } - - $table = new Console_Table(); - $table->setHeaders($headers); - - foreach ($data as $row) { - $table->addRow($row); - } - - return $returnObject ? $table : $table->getTable(); - } - - /** - * Adds a filter to a column. - * - * Filters are standard PHP callbacks which are run on the data before - * table generation is performed. Filters are applied in the order they - * are added. The callback function must accept a single argument, which - * is a single table cell. - * - * @param integer $col Column to apply filter to. - * @param mixed &$callback PHP callback to apply. - * - * @return void - */ - function addFilter($col, &$callback) - { - $this->_filters[] = array($col, &$callback); - } - - /** - * Sets the charset of the provided table data. - * - * @param string $charset A charset supported by the mbstring PHP - * extension. - * - * @return void - */ - function setCharset($charset) - { - $locale = setlocale(LC_CTYPE, 0); - setlocale(LC_CTYPE, 'en_US'); - $this->_charset = strtolower($charset); - setlocale(LC_CTYPE, $locale); - } - - /** - * Sets the alignment for the columns. - * - * @param integer $col_id The column number. - * @param integer $align Alignment to set for this column. One of - * CONSOLE_TABLE_ALIGN_LEFT - * CONSOLE_TABLE_ALIGN_CENTER - * CONSOLE_TABLE_ALIGN_RIGHT. - * - * @return void - */ - function setAlign($col_id, $align = CONSOLE_TABLE_ALIGN_LEFT) - { - switch ($align) { - case CONSOLE_TABLE_ALIGN_CENTER: - $pad = STR_PAD_BOTH; - break; - case CONSOLE_TABLE_ALIGN_RIGHT: - $pad = STR_PAD_LEFT; - break; - default: - $pad = STR_PAD_RIGHT; - break; - } - $this->_col_align[$col_id] = $pad; - } - - /** - * Specifies which columns are to have totals calculated for them and - * added as a new row at the bottom. - * - * @param array $cols Array of column numbers (starting with 0). - * - * @return void - */ - function calculateTotalsFor($cols) - { - $this->_calculateTotals = $cols; - } - - /** - * Sets the headers for the columns. - * - * @param array $headers The column headers. - * - * @return void - */ - function setHeaders($headers) - { - $this->_headers = array(array_values($headers)); - $this->_updateRowsCols($headers); - } - - /** - * Adds a row to the table. - * - * @param array $row The row data to add. - * @param boolean $append Whether to append or prepend the row. - * - * @return void - */ - function addRow($row, $append = true) - { - if ($append) { - $this->_data[] = array_values($row); - } else { - array_unshift($this->_data, array_values($row)); - } - - $this->_updateRowsCols($row); - } - - /** - * Inserts a row after a given row number in the table. - * - * If $row_id is not given it will prepend the row. - * - * @param array $row The data to insert. - * @param integer $row_id Row number to insert before. - * - * @return void - */ - function insertRow($row, $row_id = 0) - { - array_splice($this->_data, $row_id, 0, array($row)); - - $this->_updateRowsCols($row); - } - - /** - * Adds a column to the table. - * - * @param array $col_data The data of the column. - * @param integer $col_id The column index to populate. - * @param integer $row_id If starting row is not zero, specify it here. - * - * @return void - */ - function addCol($col_data, $col_id = 0, $row_id = 0) - { - foreach ($col_data as $col_cell) { - $this->_data[$row_id++][$col_id] = $col_cell; - } - - $this->_updateRowsCols(); - $this->_max_cols = max($this->_max_cols, $col_id + 1); - } - - /** - * Adds data to the table. - * - * @param array $data A two dimensional array with the table data. - * @param integer $col_id Starting column number. - * @param integer $row_id Starting row number. - * - * @return void - */ - function addData($data, $col_id = 0, $row_id = 0) - { - foreach ($data as $row) { - if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) { - $this->_data[$row_id] = CONSOLE_TABLE_HORIZONTAL_RULE; - $row_id++; - continue; - } - $starting_col = $col_id; - foreach ($row as $cell) { - $this->_data[$row_id][$starting_col++] = $cell; - } - $this->_updateRowsCols(); - $this->_max_cols = max($this->_max_cols, $starting_col); - $row_id++; - } - } - - /** - * Adds a horizontal seperator to the table. - * - * @return void - */ - function addSeparator() - { - $this->_data[] = CONSOLE_TABLE_HORIZONTAL_RULE; - } - - /** - * Returns the generated table. - * - * @return string The generated table. - */ - function getTable() - { - $this->_applyFilters(); - $this->_calculateTotals(); - $this->_validateTable(); - - return $this->_buildTable(); - } - - /** - * Calculates totals for columns. - * - * @return void - */ - function _calculateTotals() - { - if (empty($this->_calculateTotals)) { - return; - } - - $this->addSeparator(); - - $totals = array(); - foreach ($this->_data as $row) { - if (is_array($row)) { - foreach ($this->_calculateTotals as $columnID) { - $totals[$columnID] += $row[$columnID]; - } - } - } - - $this->_data[] = $totals; - $this->_updateRowsCols(); - } - - /** - * Applies any column filters to the data. - * - * @return void - */ - function _applyFilters() - { - if (empty($this->_filters)) { - return; - } - - foreach ($this->_filters as $filter) { - $column = $filter[0]; - $callback = $filter[1]; - - foreach ($this->_data as $row_id => $row_data) { - if ($row_data !== CONSOLE_TABLE_HORIZONTAL_RULE) { - $this->_data[$row_id][$column] = - call_user_func($callback, $row_data[$column]); - } - } - } - } - - /** - * Ensures that column and row counts are correct. - * - * @return void - */ - function _validateTable() - { - if (!empty($this->_headers)) { - $this->_calculateRowHeight(-1, $this->_headers[0]); - } - - for ($i = 0; $i < $this->_max_rows; $i++) { - for ($j = 0; $j < $this->_max_cols; $j++) { - if (!isset($this->_data[$i][$j]) && - (!isset($this->_data[$i]) || - $this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE)) { - $this->_data[$i][$j] = ''; - } - - } - $this->_calculateRowHeight($i, $this->_data[$i]); - - if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) { - ksort($this->_data[$i]); - } - - } - - $this->_splitMultilineRows(); - - // Update cell lengths. - for ($i = 0; $i < count($this->_headers); $i++) { - $this->_calculateCellLengths($this->_headers[$i]); - } - for ($i = 0; $i < $this->_max_rows; $i++) { - $this->_calculateCellLengths($this->_data[$i]); - } - - ksort($this->_data); - } - - /** - * Splits multiline rows into many smaller one-line rows. - * - * @return void - */ - function _splitMultilineRows() - { - ksort($this->_data); - $sections = array(&$this->_headers, &$this->_data); - $max_rows = array(count($this->_headers), $this->_max_rows); - $row_height_offset = array(-1, 0); - - for ($s = 0; $s <= 1; $s++) { - $inserted = 0; - $new_data = $sections[$s]; - - for ($i = 0; $i < $max_rows[$s]; $i++) { - // Process only rows that have many lines. - $height = $this->_row_heights[$i + $row_height_offset[$s]]; - if ($height > 1) { - // Split column data into one-liners. - $split = array(); - for ($j = 0; $j < $this->_max_cols; $j++) { - $split[$j] = preg_split('/\r?\n|\r/', - $sections[$s][$i][$j]); - } - - $new_rows = array(); - // Construct new 'virtual' rows - insert empty strings for - // columns that have less lines that the highest one. - for ($i2 = 0; $i2 < $height; $i2++) { - for ($j = 0; $j < $this->_max_cols; $j++) { - $new_rows[$i2][$j] = !isset($split[$j][$i2]) - ? '' - : $split[$j][$i2]; - } - } - - // Replace current row with smaller rows. $inserted is - // used to take account of bigger array because of already - // inserted rows. - array_splice($new_data, $i + $inserted, 1, $new_rows); - $inserted += count($new_rows) - 1; - } - } - - // Has the data been modified? - if ($inserted > 0) { - $sections[$s] = $new_data; - $this->_updateRowsCols(); - } - } - } - - /** - * Builds the table. - * - * @return string The generated table string. - */ - function _buildTable() - { - if (!count($this->_data)) { - return ''; - } - - $rule = $this->_border == CONSOLE_TABLE_BORDER_ASCII - ? '|' - : $this->_border; - $separator = $this->_getSeparator(); - - $return = array(); - for ($i = 0; $i < count($this->_data); $i++) { - for ($j = 0; $j < count($this->_data[$i]); $j++) { - if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE && - $this->_strlen($this->_data[$i][$j]) < - $this->_cell_lengths[$j]) { - $this->_data[$i][$j] = $this->_strpad($this->_data[$i][$j], - $this->_cell_lengths[$j], - ' ', - $this->_col_align[$j]); - } - } - - if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) { - $row_begin = $rule . str_repeat(' ', $this->_padding); - $row_end = str_repeat(' ', $this->_padding) . $rule; - $implode_char = str_repeat(' ', $this->_padding) . $rule - . str_repeat(' ', $this->_padding); - $return[] = $row_begin - . implode($implode_char, $this->_data[$i]) . $row_end; - } elseif (!empty($separator)) { - $return[] = $separator; - } - - } - - $return = implode("\r\n", $return); - if (!empty($separator)) { - $return = $separator . "\r\n" . $return . "\r\n" . $separator; - } - $return .= "\r\n"; - - if (!empty($this->_headers)) { - $return = $this->_getHeaderLine() . "\r\n" . $return; - } - - return $return; - } - - /** - * Creates a horizontal separator for header separation and table - * start/end etc. - * - * @return string The horizontal separator. - */ - function _getSeparator() - { - if (!$this->_border) { - return; - } - - if ($this->_border == CONSOLE_TABLE_BORDER_ASCII) { - $rule = '-'; - $sect = '+'; - } else { - $rule = $sect = $this->_border; - } - - $return = array(); - foreach ($this->_cell_lengths as $cl) { - $return[] = str_repeat($rule, $cl); - } - - $row_begin = $sect . str_repeat($rule, $this->_padding); - $row_end = str_repeat($rule, $this->_padding) . $sect; - $implode_char = str_repeat($rule, $this->_padding) . $sect - . str_repeat($rule, $this->_padding); - - return $row_begin . implode($implode_char, $return) . $row_end; - } - - /** - * Returns the header line for the table. - * - * @return string The header line of the table. - */ - function _getHeaderLine() - { - // Make sure column count is correct - for ($j = 0; $j < count($this->_headers); $j++) { - for ($i = 0; $i < $this->_max_cols; $i++) { - if (!isset($this->_headers[$j][$i])) { - $this->_headers[$j][$i] = ''; - } - } - } - - for ($j = 0; $j < count($this->_headers); $j++) { - for ($i = 0; $i < count($this->_headers[$j]); $i++) { - if ($this->_strlen($this->_headers[$j][$i]) < - $this->_cell_lengths[$i]) { - $this->_headers[$j][$i] = - $this->_strpad($this->_headers[$j][$i], - $this->_cell_lengths[$i], - ' ', - $this->_col_align[$i]); - } - } - } - - $rule = $this->_border == CONSOLE_TABLE_BORDER_ASCII - ? '|' - : $this->_border; - $row_begin = $rule . str_repeat(' ', $this->_padding); - $row_end = str_repeat(' ', $this->_padding) . $rule; - $implode_char = str_repeat(' ', $this->_padding) . $rule - . str_repeat(' ', $this->_padding); - - $separator = $this->_getSeparator(); - if (!empty($separator)) { - $return[] = $separator; - } - for ($j = 0; $j < count($this->_headers); $j++) { - $return[] = $row_begin - . implode($implode_char, $this->_headers[$j]) . $row_end; - } - - return implode("\r\n", $return); - } - - /** - * Updates values for maximum columns and rows. - * - * @param array $rowdata Data array of a single row. - * - * @return void - */ - function _updateRowsCols($rowdata = null) - { - // Update maximum columns. - $this->_max_cols = max($this->_max_cols, count($rowdata)); - - // Update maximum rows. - ksort($this->_data); - $keys = array_keys($this->_data); - $this->_max_rows = end($keys) + 1; - - switch ($this->_defaultAlign) { - case CONSOLE_TABLE_ALIGN_CENTER: - $pad = STR_PAD_BOTH; - break; - case CONSOLE_TABLE_ALIGN_RIGHT: - $pad = STR_PAD_LEFT; - break; - default: - $pad = STR_PAD_RIGHT; - break; - } - - // Set default column alignments - for ($i = count($this->_col_align); $i < $this->_max_cols; $i++) { - $this->_col_align[$i] = $pad; - } - } - - /** - * Calculates the maximum length for each column of a row. - * - * @param array $row The row data. - * - * @return void - */ - function _calculateCellLengths($row) - { - for ($i = 0; $i < count($row); $i++) { - if (!isset($this->_cell_lengths[$i])) { - $this->_cell_lengths[$i] = 0; - } - $this->_cell_lengths[$i] = max($this->_cell_lengths[$i], - $this->_strlen($row[$i])); - } - } - - /** - * Calculates the maximum height for all columns of a row. - * - * @param integer $row_number The row number. - * @param array $row The row data. - * - * @return void - */ - function _calculateRowHeight($row_number, $row) - { - if (!isset($this->_row_heights[$row_number])) { - $this->_row_heights[$row_number] = 1; - } - - // Do not process horizontal rule rows. - if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) { - return; - } - - for ($i = 0, $c = count($row); $i < $c; ++$i) { - $lines = preg_split('/\r?\n|\r/', $row[$i]); - $this->_row_heights[$row_number] = max($this->_row_heights[$row_number], - count($lines)); - } - } - - /** - * Returns the character length of a string. - * - * @param string $str A multibyte or singlebyte string. - * - * @return integer The string length. - */ - function _strlen($str) - { - static $mbstring, $utf8; - - // Strip ANSI color codes if requested. - if ($this->_ansiColor) { - $str = Console_Color::strip($str); - } - - // Cache expensive function_exists() calls. - if (!isset($mbstring)) { - $mbstring = function_exists('mb_strlen'); - } - if (!isset($utf8)) { - $utf8 = function_exists('utf8_decode'); - } - - if ($utf8 && - ($this->_charset == strtolower('utf-8') || - $this->_charset == strtolower('utf8'))) { - return strlen(utf8_decode($str)); - } - if ($mbstring) { - return mb_strlen($str, $this->_charset); - } - - return strlen($str); - } - - /** - * Returns part of a string. - * - * @param string $string The string to be converted. - * @param integer $start The part's start position, zero based. - * @param integer $length The part's length. - * - * @return string The string's part. - */ - function _substr($string, $start, $length = null) - { - static $mbstring; - - // Cache expensive function_exists() calls. - if (!isset($mbstring)) { - $mbstring = function_exists('mb_substr'); - } - - if (is_null($length)) { - $length = $this->_strlen($string); - } - if ($mbstring) { - $ret = @mb_substr($string, $start, $length, $this->_charset); - if (!empty($ret)) { - return $ret; - } - } - return substr($string, $start, $length); - } - - /** - * Returns a string padded to a certain length with another string. - * - * This method behaves exactly like str_pad but is multibyte safe. - * - * @param string $input The string to be padded. - * @param integer $length The length of the resulting string. - * @param string $pad The string to pad the input string with. Must - * be in the same charset like the input string. - * @param const $type The padding type. One of STR_PAD_LEFT, - * STR_PAD_RIGHT, or STR_PAD_BOTH. - * - * @return string The padded string. - */ - function _strpad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT) - { - $mb_length = $this->_strlen($input); - $sb_length = strlen($input); - $pad_length = $this->_strlen($pad); - - /* Return if we already have the length. */ - if ($mb_length >= $length) { - return $input; - } - - /* Shortcut for single byte strings. */ - if ($mb_length == $sb_length && $pad_length == strlen($pad)) { - return str_pad($input, $length, $pad, $type); - } - - switch ($type) { - case STR_PAD_LEFT: - $left = $length - $mb_length; - $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), - 0, $left, $this->_charset) . $input; - break; - case STR_PAD_BOTH: - $left = floor(($length - $mb_length) / 2); - $right = ceil(($length - $mb_length) / 2); - $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), - 0, $left, $this->_charset) . - $input . - $this->_substr(str_repeat($pad, ceil($right / $pad_length)), - 0, $right, $this->_charset); - break; - case STR_PAD_RIGHT: - $right = $length - $mb_length; - $output = $input . - $this->_substr(str_repeat($pad, ceil($right / $pad_length)), - 0, $right, $this->_charset); - break; - } - - return $output; - } - -} diff --git a/sites/all/modules/unl/unl_site_creation.php b/sites/all/modules/unl/unl_site_creation.php index 3bb3319dc95be85d8178ac1e0f2c51fd63d9481d..1c7b11d06de8efad74c61c60f685cec10d7a36b7 100644 --- a/sites/all/modules/unl/unl_site_creation.php +++ b/sites/all/modules/unl/unl_site_creation.php @@ -8,6 +8,7 @@ function unl_sites_page() { $page = array(); $page[] = drupal_get_form('unl_site_create'); $page[] = drupal_get_form('unl_site_list'); + $page[] = drupal_get_form('unl_site_updates'); return $page; } @@ -156,6 +157,37 @@ function unl_site_list_submit($form, &$form_state) { } +function unl_site_updates($form, &$form_state) { + $form['root'] = array( + '#type' => 'fieldset', + '#title' => 'Maintenance', + '#description' => 'Using drush, do database updates and clear the caches of all sites.', + ); + + $form['root']['submit'] = array( + '#type' => 'submit', + '#value' => 'Run Drush', + ); + + return $form; +} + +function unl_site_updates_submit($form, &$form_state) { + $sites = db_select('unl_sites', 's') + ->fields('s', array('site_id', 'db_prefix', 'installed', 'site_path', 'uri')) + ->execute() + ->fetchAll(); + + foreach ($sites as $site) { + $uri = escapeshellarg($site->uri); + $root = escapeshellarg(DRUPAL_ROOT); + $command = "sites/all/modules/drush/drush.php -y --token=secret --root={$root} --uri={$uri} updatedb"; + drupal_set_message('Messages from ' . $site->uri . ':<br />' . PHP_EOL . '<pre>' . shell_exec($command) . '</pre>', 'status'); + } +} + + + function unl_site_remove($site_id) { $uri = db_select('unl_sites', 's') ->fields('s', array('uri'))