first import

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-08 11:40:19 +02:00
commit 1bc61b12ad
8435 changed files with 1582817 additions and 0 deletions

View File

@@ -0,0 +1,311 @@
<?php
/**
* @file
* An early implementation of Site Archive dump/restore. See
* http://groups.drupal.org/site-archive-format.
*/
function archive_drush_command() {
$items['archive-dump'] = array(
'description' => 'Backup your code, files, and database into a single file.',
'arguments' => array(
'targets' => 'Optional. Site specifications, delimited by commas. Typically, list subdirectory name(s) under /sites.',
),
'options' => array(
'description' => 'Describe the archive contents.',
'tags' => 'Add tags to the archive manifest. Delimit multiple by commas.',
'destination' => 'The full path and filename in which the archive should be stored. If omitted, it will be saved to the drush-backups directory and a filename will be generated.',
'overwrite' => 'Do not fail if the destination file exists; overwrite it instead.',
'generator' => 'The generator name to store in the MANIFEST file. The default is "Drush archive-dump".',
'generatorversion' => 'The generator version number to store in the MANIFEST file. The default is ' . DRUSH_VERSION . '.',
'pipe' => 'Only print the destination of the archive. Useful for scripts that don\'t pass --destination.',
),
'examples' => array(
'drush archive-dump default,example.com,foo.com' => 'Write an archive containing 3 sites in it.',
'drush archive-dump @sites' => 'Save archive containing all sites in a multi-site.',
'drush archive-dump default --destination=/backups/mysite.tar' => 'Save archive to custom location.',
),
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
'aliases' => array('ard', 'archive-backup', 'arb'),
);
$items['archive-restore'] = array(
'description' => 'Expand a site archive into a Drupal web site.',
'arguments' => array(
'file' => 'The site archive file that should be expanded.',
'site name' => 'Optional. Which site within the archive you want to restore. Defaults to all.',
),
'options' => array(
'destination' => 'Specify where the Drupal site should be expanded. Defaults to the current working directory.',
'db-prefix' => 'An optional table prefix to use during restore.',
'db-url' => 'A Drupal 6 style database URL indicating the target for database restore. If not provided, the archived settings.php is used.',
'db-su' => 'Account to use when creating the new database. Optional.',
'db-su-pw' => 'Password for the "db-su" account. Optional.',
'overwrite' => 'Allow drush to overwrite any files in the destinion.',
),
'examples' => array(
'drush archive-restore ./example.tar.gz' => 'Restore the files and databases for all sites in the archive.',
'drush archive-restore ./example.tar.gz example.com' => 'Restore the files and database for example.com site.',
'drush archive-restore ./example.tar.gz --destination=/var/www/example.com/docroot' => 'Restore archive to a custom location.',
'drush archive-restore ./example.tar.gz --db-url=mysql://root:pass@127.0.0.1/dbname' => 'Restore archive to a new database (and customize settings.php to point there.).',
),
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'aliases' => array('arr'),
);
return $items;
}
/**
* Command callback. Generate site archive file.
*/
function drush_archive_dump($sites_subdirs = '@self') {
$aliases = drush_sitealias_resolve_sitespecs(explode(',', $sites_subdirs));
foreach ($aliases as $key => $alias) {
if (($db_record = sitealias_get_databases_from_record($alias))) {
$full[$key] = $alias += $db_record;
}
else {
drush_log(dt('DB connections not found for !alias', array('!alias' => $alias)), 'error');
return;
}
}
// The user can specify a destination filepath or not. That filepath might
// end with .gz, .tgz, or something else. At the end of this command we will
// gzip a file, and we want it to end up with the user-specified name (if
// any), but gzip renames files and refuses to compress files ending with
// .gz and .tgz, making our lives difficult. Solution:
//
// 1. Create a unique temporary base name to which gzip WILL append .gz.
// 2. If no destination is provided, set $dest_dir to a backup directory and
// $final_destination to be the unique name in that dir.
// 3. If a destination is provided, set $dest_dir to that directory and
// $final_destination to the exact name given.
// 4. Set $destination, the actual working file we will build up, to the
// unqiue name in $dest_dir.
// 5. After gzip'ing $destination, rename $destination.gz to
// $final_destination.
//
// Sheesh.
// Create the unique temporary name.
$date = gmdate('Ymd_his');
$first = current($full);
$prefix = count($sites_subdirs) > 1 ? 'multiple_sites' : $first['default']['default']['database'];
$temp_dest_name = "$prefix.$date.tar";
$final_destination = drush_get_option('destination');
if (!$final_destination) {
// No destination provided.
drush_include_engine('version_control', 'backup');
$backup = new drush_pm_version_control_backup();
// TODO: this standard drush pattern leads to a slightly obtuse directory structure.
$dest_dir = $backup->prepare_backup_dir('archive-dump');
if (empty($dest_dir)) {
$dest_dir = drush_tempdir();
}
$final_destination = "$dest_dir/$temp_dest_name.gz";
}
else {
// Use the supplied --destination. If it is relative, resolve it
// relative to the directory in which drush was invoked.
$command_cwd = getcwd();
drush_op('chdir', drush_get_context('DRUSH_OLDCWD', getcwd()));
// This doesn't perform realpath on the basename, but that's okay. This is
// not path-based security. We just use it for checking for perms later.
$dest_dir = realpath(dirname($final_destination));
$final_destination = $dest_dir . '/' . basename($final_destination);
drush_op('chdir', $command_cwd);
}
// $dest_dir is either the backup directory or specified directory. Set our
// working file.
$destination = "$dest_dir/$temp_dest_name";
// Validate the FINAL destination. It should be a file that does not exist
// (unless --overwrite) in a writable directory (and a writable file if
// it exists). We check all this up front to avoid failing after a long
// dump process.
$overwrite = drush_get_option('overwrite');
$dest_dir = dirname($final_destination);
$dt_args = array('!file' => $final_destination, '!dir' => $dest_dir);
if (is_dir($final_destination)) {
drush_set_error('DRUSH_ARCHIVE_DEST_IS_DIR', dt('destination !file must be a file, not a directory.', $dt_args));
return;
}
else if (file_exists($final_destination)) {
if (!$overwrite) {
drush_set_error('DRUSH_ARCHIVE_DEST_EXISTS', dt('destination !file exists; specify --overwrite to overwrite.', $dt_args));
return;
}
else if (!is_writable($final_destination)) {
drush_set_error('DRUSH_ARCHIVE_DEST_FILE_NOT_WRITEABLE', dt('destination !file is not writable.', $dt_args));
return;
}
}
else if (!is_writable(dirname($final_destination))) {
drush_set_error('DRUSH_ARCHIVE_DEST_DIR_NOT_WRITEABLE', dt('destination directory !dir is not writable.', $dt_args));
return;
}
$docroot_path = realpath(drush_get_context('DRUSH_DRUPAL_ROOT'));
$docroot = basename($docroot_path);
$workdir = dirname($docroot_path);
// Archive Drupal core, excluding sites dir.
drush_shell_cd_and_exec($workdir, "tar --exclude '{$docroot}/sites' --dereference -cf %s %s", $destination, $docroot);
// Add sites/all to the same archive.
drush_shell_cd_and_exec($workdir, "tar --dereference -rf %s %s", $destination, "{$docroot}/sites/all");
// Dump the database(s) for each site and add to the archive.
foreach ($full as $key => $alias) {
foreach ($alias['databases'] as $dbkey => $target) {
$tmp = drush_tempdir();
// Use a subdirectory name matching the docroot name.
drush_mkdir("{$tmp}/{$docroot}");
$result_file = "{$tmp}/{$target['default']['database']}.sql";
drush_set_option('result-file', $result_file);
$table_selection = drush_sql_get_table_selection();
list($dump_exec, $dump_file) = drush_sql_build_dump_command($table_selection, $target['default']);
drush_shell_exec($dump_exec);
drush_shell_cd_and_exec($tmp, 'tar --dereference -rf %s %s', $destination, basename($result_file));
}
}
// Build a manifest file AND add sites/$subdir to archive as we go.
$platform = array(
'datestamp' => time(),
'formatversion' => '1.0',
'generator' => drush_get_option('generator', 'Drush archive-dump'),
'generatorversion' => drush_get_option('generatorversion', DRUSH_VERSION),
'description' => drush_get_option('description', ''),
'tags' => drush_get_option('tags', ''),
);
$contents = drush_export_ini(array('Global' => $platform));
$i=0;
foreach ($full as $key => $alias) {
$status = drush_invoke_sitealias_args($alias, 'core-status', array(), array());
// Add the site specific directory to archive.
if (!empty($status['object']['%paths']['%site'])) {
drush_shell_cd_and_exec($workdir, "tar --dereference -rf %s %s", $destination, "{$docroot}/sites/" . basename($status['object']['%paths']['%site']));
}
$site = array(
'docroot' => DRUPAL_ROOT,
'sitedir' => @$status['object']['%paths']['%site'],
'files-public' => @$status['object']['%paths']['%files'],
'files-private' => @$status['object']['%paths']['%private'],
);
// Add info for each DB connection (usually only 1);
foreach ($alias['databases'] as $dbkey => $target) {
$site["database-$dbkey-file"] = './' . $target['default']['database'] . '.sql';
$site["database-$dbkey-driver"] = $target['default']['driver'];
}
// The section title is the sites subdirectory name.
$info[basename($site['sitedir'])] = $site;
$contents .= "\n" . drush_export_ini($info);
unset($info);
$i++;
}
file_put_contents("{$tmp}/MANIFEST.ini", $contents);
// Add manifest to archive.
drush_shell_cd_and_exec($tmp, 'tar --dereference -rf %s %s', $destination, "MANIFEST.ini");
// Compress the archive
drush_shell_exec("gzip --no-name -f %s", $destination);
// gzip appends .gz unless the name already ends in .gz, .tgz, or .taz.
if ("{$destination}.gz" != $final_destination) {
drush_move_dir("{$destination}.gz", $final_destination, $overwrite);
}
drush_log(dt('Archive saved to !dest', array('!dest' => $final_destination)), 'ok');
drush_print_pipe($final_destination);
return $final_destination;
}
/**
* Command callback. Restore web site(s) from a site archive file.
*/
function drush_archive_restore($file, $site_id = NULL) {
$tmp = drush_tempdir();
if (!$files = drush_tarball_extract($file, $tmp)) {
return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_EXTRACT', dt('Unable to extract site archive tarball to !tmp.', array('!tmp' => $tmp)));
}
$manifest = $tmp . '/MANIFEST.ini';
if (file_exists($manifest)) {
if (!$ini = parse_ini_file($manifest, TRUE)) {
return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_PARSE_MANIFEST', dt('Unable to parse MANIFEST.ini in the archive.'));
}
}
else {
// No manifest. Try to find docroot and DB dump file.
$db_file = drush_scan_directory($tmp, '/\.sql$/', array('.', '..', 'CVS'), 0, 0);
$directories = glob($tmp . '/*' , GLOB_ONLYDIR);
$ini = array(
'Global' => array(),
'default' => array(
'docroot' => reset($directories),
'sitedir' => 'sites/default',
'database-default-file' => key($db_file),
),
);
}
// Grab the first site in the Manifest and move docroot to destination.
$ini_tmp = $ini;
unset($ini_tmp['Global']);
$first = array_shift($ini_tmp);
$docroot = basename($first['docroot']);
$destination = drush_get_option('destination', realpath('.') . "/$docroot");
if (!drush_move_dir("$tmp/$docroot", $destination, drush_get_option('overwrite'))) {
return drush_set_error('DRUSH_ARCHIVE_UNABLE _RESTORE_FILES', dt('Unable to restore files to !dest', array('!dest' => $destination)));
}
// Loop over sites and restore databases and append to settings.php.
foreach ($ini as $section => $site) {
if ($section != 'Global' && (is_null($site_id) || $section == $site_id) && !empty($site['database-default-file'])) {
// Restore database
$sql_file = $tmp . '/' . $site['database-default-file'];
if ($db_url = drush_get_option('db-url')) {
if (empty($site_id) && count($ini) >= 3) {
// TODO: Use drushrc to provide multiple db-urls for multi-restore?
return drush_set_error('DRUSH_ARCHIVE_UNABLE_MULTI_RESTORE', dt('You must specify a site to restore when the archive contains multiple sites and a db-url is provided.'));
}
$db_spec = drush_convert_db_from_db_url($db_url);
}
else {
$site_specification = $destination . '#' . $section;
if ($return = drush_invoke_sitealias_args($site_specification, 'sql-conf', array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE))) {
$databases = $return['object'];
$db_spec = $databases['default']['default'];
}
else {
return drush_set_error('DRUSH_ARCHIVE_UNABLE_DISCOVER_DB', dt('Unable to get database details from db-url option or settings.php', array('!key' => $key)));
}
}
drush_sql_empty_db($db_spec);
_drush_sql_query(NULL, $db_spec, $sql_file);
// Append new DB info to settings.php.
if ($db_url) {
$settingsfile = $destination . '/' . $site['sitedir'] . '/settings.php';
chmod($settingsfile, 0755); // Need to do something here or else we can't write.
file_put_contents($settingsfile, "\n// Appended by drush archive-restore command.\n", FILE_APPEND);
if (drush_drupal_major_version() >= 7) {
file_put_contents($settingsfile, "\n" . '$databases = \'' . drush_sitealias_convert_db_from_db_url($db_url) . "';\n", FILE_APPEND);
}
else {
file_put_contents($settingsfile, "\n" . '$db_url = \'' . $db_url . "';\n", FILE_APPEND);
}
}
}
}
return $destination;
}

View File

@@ -0,0 +1,227 @@
<?php
/**
* Implementation of hook_drush_command().
*/
function cache_drush_command() {
$items = array();
$items['cache-get'] = array(
'description' => 'Fetch a cached object and display it.',
'examples' => array(
'drush cache-get schema' => 'Display the data for the cache id "schema" from the "cache" bin.',
'drush cache-get update_available_releases update' => 'Display the data for the cache id "update_available_releases" from the "update" bin.',
),
'arguments' => array(
'cid' => 'The id of the object to fetch.',
'bin' => 'Optional. The cache bin to fetch from.',
),
'options' => array(
'format' => 'Format to output the object. Use "print_r" for print_r (default), "export" for var_export, and "json" for JSON.',
),
'aliases' => array('cg'),
);
$items['cache-clear'] = array(
'description' => 'Clear a specific cache, or all drupal caches.',
'arguments' => array(
'type' => 'The particular cache to clear. Omit this argument to choose from available caches.',
),
'aliases' => array('cc'),
);
$items['cache-set'] = array(
'description' => 'Cache an object expressed in JSON or var_export() format.',
'arguments' => array(
'cid' => 'The id of the object to set.',
'data' => 'The object to set in the cache. Use \'-\' to read the object from STDIN.',
'bin' => 'Optional. The cache bin to store the object in.',
'expire' => 'Optional. CACHE_PERMANENT, CACHE_TEMPORARY, or a Unix timestamp.',
),
'options' => array(
'format' => 'Format to parse the object. Use "string" for string (default), and "json" for JSON.',
'cache-get' => 'If the object is the result a previous fetch from the cache, only store the value in the "data" property of the object in the cache.',
),
'aliases' => array('cs'),
);
return $items;
}
/**
* Command callback for drush cache-clear.
*/
function drush_cache_clear($type = NULL) {
switch (drush_drupal_major_version()) {
case 5:
// clear preprocessor cache
drupal_clear_css_cache();
// clear core tables
$core = array('cache', 'cache_filter', 'cache_menu', 'cache_page');
$alltables = array_merge($core, module_invoke_all('devel_caches'));
foreach ($alltables as $table) {
cache_clear_all('*', $table, TRUE);
}
drush_print(dt('Cache cleared.'));
break;
case 6:
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($types[$type]);
drush_log(dt("'!name' cache was cleared", array('!name' => $type)), 'success');
}
else {
$choice = drush_choice($types, 'Enter a number to choose which cache to clear.', '!key');
if ($choice !== FALSE) {
call_user_func($types[$choice]);
drush_log(dt("'!name' cache was cleared", array('!name' => $choice)), 'success');
}
}
break;
}
}
/**
* Print an object returned from the cache.
*
* @param $cid
* The cache ID of the object to fetch.
* @param $bin
* Optional parameter to specify a specific bin to fetch from.
*/
function drush_cache_get($cid = NULL, $bin = NULL) {
if (!$cid) {
drush_log(dt('You must specify a cache id to fetch.'), 'error');
return;
}
if (!$bin) {
$bin = 'cache';
}
$result = cache_get($cid, $bin);
if (!empty($result)) {
switch (drush_get_option('format', 'print_r')) {
case 'export':
drush_print(var_export($result, TRUE));
return;
case 'json':
drush_print(drush_json_encode($result));
return;
case 'print_r':
default:
drush_print_r($result);
return;
}
}
else {
drush_log(dt('The !cid object in the !bin cache bin was not found.', array('!cid' => $cid, '!bin' => $bin)), 'error');
}
}
/**
* Set an object in the cache.
*
* @param $cid
* The cache ID of the object to fetch.
* @param $data
* The data to save to the cache, or '-' to read from STDIN.
* @param $bin
* Optional parameter to specify a specific bin to fetch from.
* @param $expire
* Optional parameter to specify the expiry of the cached object.
*/
function drush_cache_set($cid = NULL, $data = '', $bin = NULL, $expire = CACHE_PERMANENT) {
if (!$cid) {
drush_log(dt('You must specify a cache id to set.'), 'error');
return;
}
if ($data === '') {
drush_log(dt('The data to set must be non-empty.'), 'error');
return;
}
if (!$bin) {
$bin = 'cache';
}
if ($data == '-') {
$data = stream_get_contents(STDIN);
}
// Now, we parse the object.
switch (drush_get_option('format', 'string')) {
case 'json':
$data = drush_json_decode($data);
break;
}
if (drush_get_option('cache-get')) {
$data = $data->data;
}
switch(drush_drupal_major_version()) {
case 5:
cache_set($cid, $bin, $data, $expire);
break;
case 6:
case 7:
default:
cache_set($cid, $data, $bin, $expire);
}
}
function drush_cache_clear_types() {
$types = array(
'all' => 'drupal_flush_all_caches',
'theme registry' => 'drush_cache_clear_theme_registry',
'menu' => 'menu_rebuild',
'css+js' => 'drush_cache_clear_css_js',
'block' => 'drush_cache_clear_block',
'module list' => 'drush_get_modules',
'theme list' => 'drush_get_themes',
);
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';
}
// Include the appropriate environment engine, so callbacks can use core
// version specific cache clearing functions directly.
drush_include_engine('drupal', 'environment');
// Command files may customize $types as desired.
drush_command_invoke_all_ref('drush_cache_clear', $types);
return $types;
}
function drush_cache_clear_theme_registry() {
drush_db_delete('cache', 'cid LIKE :theme_registry', array(':theme_registry' => 'theme_registry%'));
}
function drush_cache_clear_css_js() {
_drupal_flush_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');
}

View File

@@ -0,0 +1 @@

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,244 @@
<?php
/**
* @file
* Documentation commands providing various topics.
*/
/**
* Implementation of hook_drush_help().
*/
function docs_drush_help($section) {
switch ($section) {
case 'meta:docs:title':
return dt('Documentation commands');
case 'meta:docs:summary':
return dt('Show information on various drush topics.');
}
}
/**
* Implementation of hook_drush_command().
*
* @return
* An associative array describing your command(s).
*/
function docs_drush_command() {
$docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH);
//
// Topic commands.
// Any commandfile may add topics.
// Set 'topic' => TRUE to indicate the command is a topic (REQUIRED)
// Begin the topic name with the name of the commandfile (just like
// any other command).
//
$items['docs-readme'] = array(
'description' => 'README.txt',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/README.txt'),
);
$items['docs-configuration'] = array(
'description' => 'Drush configuration overview with examples',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/example.drushrc.php'),
);
$items['docs-aliases'] = array(
'description' => 'Site aliases overview with examples',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/example.aliases.drushrc.php'),
);
$items['docs-ini-files'] = array(
'description' => 'Configuring php.ini or drush.ini for use with drush.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/example.drush.ini'),
);
$items['docs-bootstrap'] = array(
'description' => 'Information about the drush bootstrap process.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/bootstrap.html'),
);
$items['docs-scripts'] = array(
'description' => 'Overview on how to write drush shell scripts.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/shellscripts.html'),
);
$items['docs-commands'] = array(
'description' => 'Overview on how to write drush commands.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/commands.html'),
);
$items['docs-errorcodes'] = array(
'description' => 'Summary of drush error codes.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
);
$items['docs-api'] = array(
'description' => 'Drush API',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/drush.api.php'),
);
$items['docs-context'] = array(
'description' => 'Drush contexts',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/context.html'),
);
$items['docs-examplescript'] = array(
'description' => 'Example drush script',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/helloworld.script'),
);
$items['docs-examplecommand'] = array(
'description' => 'Example drush command file.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/sandwich.drush.inc'),
);
$items['docs-policy'] = array(
'description' => 'Example policy file.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/policy.drush.inc'),
);
$items['docs-upgrading'] = array(
'description' => 'Upgrading Drupal using the drush site-upgrade command.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/upgrade.html'),
);
return $items;
}
/**
* docs-error-codes command. Print a list of all error codes
* that can be found.
*/
function drush_docs_errorcodes() {
$header = <<<EOD
==== Drush Error Codes ====
Drush error codes are alphanumeric constants that represent an unrecoverable error condition that may arise during the execution of some command. They are set by the following function:
return drush_set_error('DRUSH_ERROR_CODE', dt('Error message.'));
In general, any drush command that calls drush_set_error is expected to also return FALSE as its function result. The drush_set_error function returns FALSE to make it easy to exit with an error code. Error codes are returned as part of the drush backend invoke process, which is used by drush API functions such as drush_invoke_process. An example of how to test for a specific error code is shown below:
\$result = drush_invoke_process('some-command');
if (array_key_exists('DRUSH_ERROR_CODE', \$result['error_log'])) {
// handle ocurrances of DRUSH_ERROR_CODE here
}
Some of the available drush error codes are listed in the table below.
EOD;
// Find all of the files that we will search for error messages.
// Start with all of the commandfiles.
$commandfiles = drush_commandfile_list();
$files = array_flip($commandfiles);
// In addition to the commandfiles, we will also look for files
// that drush will load when executing a command; examples include
// updatecode.pm.inc and sync.sql.inc.
$commands = drush_get_commands();
foreach ($commands as $command_name => $command) {
$files = array_merge($files, drush_command_get_includes($command_name));
}
// We will also search through all of the .inc files in the
// drush includes directory
$drush_include_files = drush_scan_directory(DRUSH_BASE_PATH . '/includes', '/.*\.inc$/', array('.', '..', 'CVS'), 0, FALSE);
foreach ($drush_include_files as $filename => $info) {
$files[$filename] = 'include';
}
// Extract error messages from all command files
$error_list = array();
foreach ($files as $file => $commandfile) {
_drush_docs_find_set_error_calls($error_list, $file, $commandfile);
}
// Order error messages alphabetically by key
ksort($error_list);
// Convert to a table
$data = array();
foreach ($error_list as $error_code => $error_messages) {
$data[] = array($error_code, '-', implode("\n", $error_messages));
}
$tmpfile = drush_tempnam('drush-errorcodes.');
file_put_contents($tmpfile, $header);
drush_print_table($data, FALSE, array(0 => 35), $tmpfile);
drush_print_file($tmpfile);
}
/**
* Search through a php source file looking for calls to
* the function drush_set_error. If found, and if the
* first parameter is an uppercase alphanumeric identifier,
* then record the error code and the error message in our table.
*/
function _drush_docs_find_set_error_calls(&$error_list, $filename, $shortname) {
$lines = file($filename);
foreach ($lines as $line) {
$matches = array();
// Find the error code after the drush_set_error call. The error code
// should consist of uppercase letters and underscores only (numbers thrown in just in case)
$match_result = preg_match("/.*drush_set_error[^'\"]['\"]([A-Z0-9_]*)['\"][^,]*,[^'\"]*(['\"])/", $line, $matches);
if ($match_result) {
$error_code = $matches[1];
$quote_char = $matches[2];
$error_message = "";
$message_start = strlen($matches[0]) - 1;
// Regex adapted from http://stackoverflow.com/questions/1824325/regex-expression-for-escaped-quoted-string-wont-work-in-phps-preg-match-allif ($quote_char == '"') {
if ($quote_char == '"') {
$regex = '/"((?:[^\\\]*?(?:\\\")?)*?)"/';
}
else {
$regex = "/'((?:[^\\\]*?(?:\\\')?)*?)'/";
}
$match_result = preg_match($regex, $line, $matches, 0, $message_start);
if ($match_result) {
$error_message = $matches[1];
}
$error_list[$error_code] = array_key_exists($error_code, $error_list) ? array_merge($error_list[$error_code], array($error_message)) : array($error_message);
}
}
}

View File

@@ -0,0 +1,257 @@
<?php
/**
* @file
* Drupal 7 engine for the Batch API
*/
/**
* Main loop for the Drush batch API.
*
* Saves a record of the batch into the database, and progressively call $command to
* process the operations.
*
* @param command
* The command to call to process the batch.
*
*/
function _drush_backend_batch_process($command = 'batch-process') {
global $user;
$batch =& batch_get();
if (isset($batch)) {
$process_info = array(
'current_set' => 0,
);
$batch += $process_info;
// The batch is now completely built. Allow other modules to make changes
// to the batch so that it is easier to reuse batch processes in other
// enviroments.
drupal_alter('batch', $batch);
// Assign an arbitrary id: don't rely on a serial column in the 'batch'
// table, since non-progressive batches skip database storage completely.
$batch['id'] = db_next_id();
$batch['progressive'] = TRUE;
// Move operations to a job queue. Non-progressive batches will use a
// memory-based queue.
foreach ($batch['sets'] as $key => $batch_set) {
_batch_populate_queue($batch, $key);
}
// Store the batch.
db_insert('batch')
->fields(array(
'bid' => $batch['id'],
'timestamp' => REQUEST_TIME,
'token' => drupal_get_token($batch['id']),
'batch' => serialize($batch),
))
->execute();
$finished = FALSE;
while (!$finished) {
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);
}
}
}
/**
* Initialize the batch command and call the worker function.
*
* Loads the batch record from the database and sets up the requirements
* for the worker, such as registering the shutdown function.
*
* @param id
* The batch id of the batch being processed.
*/
function _drush_batch_command($id) {
$batch =& batch_get();
$data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(
':bid' => $id))->fetchField();
if ($data) {
$batch = unserialize($data);
}
else {
return FALSE;
}
if (!isset($batch['running'])) {
$batch['running'] = TRUE;
}
// Register database update for end of processing.
register_shutdown_function('_drush_batch_shutdown');
if (_drush_batch_worker()) {
_drush_batch_finished();
}
}
/**
* Process batch operations
*
* Using the current $batch process each of the operations until the batch
* has been completed or half of the available memory for the process has been
* reached.
*/
function _drush_batch_worker() {
$batch =& batch_get();
$current_set =& _batch_current_set();
$set_changed = TRUE;
timer_start('batch_processing');
if (empty($current_set['start'])) {
$current_set['start'] = microtime(TRUE);
}
$queue = _batch_queue($current_set);
while (!$current_set['success']) {
// If this is the first time we iterate this batch set in the current
// request, we check if it requires an additional file for functions
// definitions.
if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
include_once DRUPAL_ROOT . '/' . $current_set['file'];
}
$task_message = '';
// Assume a single pass operation and set the completion level to 1 by
// default.
$finished = 1;
if ($item = $queue->claimItem()) {
list($function, $args) = $item->data;
// Build the 'context' array and execute the function call.
$batch_context = array(
'sandbox' => &$current_set['sandbox'],
'results' => &$current_set['results'],
'finished' => &$finished,
'message' => &$task_message,
);
call_user_func_array($function, array_merge($args, array(&$batch_context)));
if ($finished == 1) {
// Make sure this step is not counted twice when computing $current.
$finished = 0;
// Remove the processed operation and clear the sandbox.
$queue->deleteItem($item);
$current_set['count']--;
$current_set['sandbox'] = array();
}
}
// When all operations in the current batch set are completed, browse
// through the remaining sets, marking them 'successfully processed'
// along the way, until we find a set that contains operations.
// _batch_next_set() executes form submit handlers stored in 'control'
// sets (see form_execute_handlers()), which can in turn add new sets to
// the batch.
$set_changed = FALSE;
$old_set = $current_set;
while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
$current_set = &_batch_current_set();
$current_set['start'] = microtime(TRUE);
$set_changed = TRUE;
}
// At this point, either $current_set contains operations that need to be
// processed or all sets have been completed.
$queue = _batch_queue($current_set);
// If we are in progressive mode, break processing after 1 second.
if ((memory_get_usage() * 2) >= drush_memory_limit()) {
drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), "batch");
// Record elapsed wall clock time.
$current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2);
break;
}
}
// Reporting 100% progress will cause the whole batch to be considered
// processed. If processing was paused right after moving to a new set,
// we have to use the info from the new (unprocessed) set.
if ($set_changed && isset($current_set['queue'])) {
// Processing will continue with a fresh batch set.
$remaining = $current_set['count'];
$total = $current_set['total'];
$progress_message = $current_set['init_message'];
$task_message = '';
}
else {
// Processing will continue with the current batch set.
$remaining = $old_set['count'];
$total = $old_set['total'];
$progress_message = $old_set['progress_message'];
}
$current = $total - $remaining + $finished;
$percentage = _batch_api_percentage($total, $current);
return ($percentage == 100);
}
/**
* End the batch processing:
* Call the 'finished' callbacks to allow custom handling of results,
* and resolve page redirection.
*/
function _drush_batch_finished() {
$batch = &batch_get();
// Execute the 'finished' callbacks for each batch set, if defined.
foreach ($batch['sets'] as $batch_set) {
if (isset($batch_set['finished'])) {
// Check if the set requires an additional file for function definitions.
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
include_once DRUPAL_ROOT . '/' . $batch_set['file'];
}
if (function_exists($batch_set['finished'])) {
$queue = _batch_queue($batch_set);
$operations = $queue->getAllItems();
$batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000));
}
}
}
// Clean up the batch table and unset the static $batch variable.
db_delete('batch')
->condition('bid', $batch['id'])
->execute();
foreach ($batch['sets'] as $batch_set) {
if ($queue = _batch_queue($batch_set)) {
$queue->deleteQueue();
}
}
$_batch = $batch;
$batch = NULL;
drush_set_option('drush_batch_process_finished', TRUE);
}
/**
* Shutdown function: store the batch data for next request,
* or clear the table if the batch is finished.
*/
function _drush_batch_shutdown() {
if ($batch = batch_get()) {
db_update('batch')
->fields(array('batch' => serialize($batch)))
->condition('bid', $batch['id'])
->execute();
}
}

View File

@@ -0,0 +1,200 @@
<?php
/**
* @file
* Drupal 6 engine for the Batch API
*/
/**
* Main loop for the Drush batch API.
*
* Saves a record of the batch into the database, and progressively call $command to
* process the operations.
*
* @param command
* The command to call to process the batch.
*
*/
function _drush_backend_batch_process($command = 'batch-process') {
global $user;
$batch =& batch_get();
if (isset($batch)) {
$process_info = array(
'current_set' => 0,
);
$batch += $process_info;
// Initiate db storage in order to get a batch id. We have to provide
// at least an empty string for the (not null) 'token' column.
db_query("INSERT INTO {batch} (token, timestamp) VALUES ('', %d)", time());
$batch['id'] = db_last_insert_id('batch', 'bid');
// Actually store the batch data and the token generated form the batch id.
db_query("UPDATE {batch} SET token = '%s', batch = '%s' WHERE bid = %d", drupal_get_token($batch['id']), serialize($batch), $batch['id']);
$finished = FALSE;
while (!$finished) {
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 || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE);
}
}
}
/**
* Initialize the batch command and call the worker function.
*
* Loads the batch record from the database and sets up the requirements
* for the worker, such as registering the shutdown function.
*
* @param id
* The batch id of the batch being processed.
*/
function _drush_batch_command($id) {
$batch =& batch_get();
// Retrieve the current state of batch from db.
if ($data = db_result(db_query("SELECT batch FROM {batch} WHERE bid = %d", $id))) {
$batch = unserialize($data);
}
else {
return FALSE;
}
if (!isset($batch['running'])) {
$batch['running'] = TRUE;
}
// Register database update for end of processing.
register_shutdown_function('_drush_batch_shutdown');
if (_drush_batch_worker()) {
_drush_batch_finished();
}
}
/**
* Process batch operations
*
* Using the current $batch process each of the operations until the batch
* has been completed or half of the available memory for the process has been
* reached.
*/
function _drush_batch_worker() {
$batch =& batch_get();
$current_set =& _batch_current_set();
$set_changed = TRUE;
timer_start('batch_processing');
while (!$current_set['success']) {
// If this is the first time we iterate this batch set in the current
// request, we check if it requires an additional file for functions
// definitions.
if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
include_once($current_set['file']);
}
$finished = 1;
$task_message = '';
if ((list($function, $args) = reset($current_set['operations'])) && function_exists($function)) {
// Build the 'context' array, execute the function call,
// and retrieve the user message.
$batch_context = array('sandbox' => &$current_set['sandbox'], 'results' => &$current_set['results'], 'finished' => &$finished, 'message' => &$task_message);
// Process the current operation.
call_user_func_array($function, array_merge($args, array(&$batch_context)));
}
if ($finished == 1) {
// Make sure this step isn't counted double when computing $current.
$finished = 0;
// Remove the operation and clear the sandbox.
array_shift($current_set['operations']);
$current_set['sandbox'] = array();
}
// If the batch set is completed, browse through the remaining sets,
// executing 'control sets' (stored form submit handlers) along the way -
// this might in turn insert new batch sets.
// Stop when we find a set that actually has operations.
$set_changed = FALSE;
$old_set = $current_set;
while (empty($current_set['operations']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
$current_set =& _batch_current_set();
$set_changed = TRUE;
}
// At this point, either $current_set is a 'real' batch set (has operations),
// or all sets have been completed.
// TODO - replace with memory check!
// If we're in progressive mode, stop after 1 second.
if ((memory_get_usage() * 2) >= drush_memory_limit()) {
drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), "batch");
break;
}
}
// Gather progress information.
// Reporting 100% progress will cause the whole batch to be considered
// processed. If processing was paused right after moving to a new set,
// we have to use the info from the new (unprocessed) one.
if ($set_changed && isset($current_set['operations'])) {
// Processing will continue with a fresh batch set.
$remaining = count($current_set['operations']);
$total = $current_set['total'];
$task_message = '';
}
else {
$remaining = count($old_set['operations']);
$total = $old_set['total'];
}
$current = $total - $remaining + $finished;
$percentage = $total ? floor($current / $total * 100) : 100;
return ($percentage == 100);
}
/**
* End the batch processing:
* Call the 'finished' callbacks to allow custom handling of results,
* and resolve page redirection.
*/
function _drush_batch_finished() {
$batch =& batch_get();
// Execute the 'finished' callbacks for each batch set.
foreach ($batch['sets'] as $key => $batch_set) {
if (isset($batch_set['finished'])) {
// Check if the set requires an additional file for functions definitions.
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
include_once($batch_set['file']);
}
if (function_exists($batch_set['finished'])) {
$batch_set['finished']($batch_set['success'], $batch_set['results'], $batch_set['operations']);
}
}
}
// Cleanup the batch table and unset the global $batch variable.
db_query("DELETE FROM {batch} WHERE bid = %d", $batch['id']);
$_batch = $batch;
$batch = NULL;
drush_set_option('drush_batch_process_finished', TRUE);
}
/**
* Shutdown function: store the batch data for next request,
* or clear the table if the batch is finished.
*/
function _drush_batch_shutdown() {
if ($batch = batch_get()) {
db_query("UPDATE {batch} SET batch = '%s' WHERE bid = %d", serialize($batch), $batch['id']);
}
}

View File

@@ -0,0 +1,198 @@
<?php
/**
* @file
* 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.
*/
/**
* Get complete information for all available modules.
*
* @return
* An array containing module info for all available modules.
*/
function drush_get_modules() {
return system_rebuild_module_data();
}
/**
* Returns drupal required modules, including modules declared as required dynamically.
*/
function _drush_drupal_required_modules($module_info) {
$required = drupal_required_modules();
foreach ($module_info as $name => $module) {
if (isset($module->info['required']) && $module->info['required']) {
$required[] = $name;
}
}
return array_unique($required);
}
/**
* Return dependencies and its status for modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependencies and status for $modules
*/
function drush_check_module_dependencies($modules, $module_info) {
$status = array();
foreach ($modules as $key => $module) {
$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(
'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
);
}
else {
// check for version incompatibility
foreach ($dependencies as $dependency_name => $v) {
$current_version = $module_info[$dependency_name]->info['version'];
$incompatibility = drupal_check_incompatibility($v, $current_version);
if (!is_null($incompatibility)) {
$status[$key]['error'] = array(
'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_VERSION_MISMATCH',
'message' => dt('Module !module cannot be enabled because it depends on !dependency !required_version but !current_version is available', array('!module' => $module, '!dependency' => $dependency_name, '!required_version' => $incompatibility, '!current_version' => $current_version))
);
}
}
}
$status[$key]['unmet-dependencies'] = $unmet_dependencies;
$status[$key]['dependencies'] = array_keys($dependencies);
}
return $status;
}
/**
* Return dependents of modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependents for each one of $modules
*/
function drush_module_dependents($modules, $module_info) {
$dependents = array();
foreach ($modules as $module) {
$dependents = array_merge($dependents, drupal_map_assoc(array_keys($module_info[$module]->required_by)));
}
return array_unique($dependents);
}
/**
* Enable a list of modules. It is assumed the list contains all the dependencies not already enabled.
*
* @param $modules
* Array of module names
*/
function drush_module_enable($modules) {
// The list of modules already have all the dependencies, but they might not
// be in the correct order. Still pass $enable_dependencies = TRUE so that
// Drupal will enable the modules in the correct order.
module_enable($modules);
}
/**
* Disable a list of modules. It is assumed the list contains all dependents not already disabled.
*
* @param $modules
* Array of module names
*/
function drush_module_disable($modules) {
// The list of modules already have all the dependencies, but they might not
// be in the correct order. Still pass $enable_dependencies = TRUE so that
// Drupal will enable the modules in the correct order.
module_disable($modules);
}
/**
* Uninstall a list of modules.
*
* @param $modules
* Array of module names
*/
function drush_module_uninstall($modules) {
require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc';
drupal_uninstall_modules($modules);
}
/**
* Submit the system modules form.
*
* The modules should already be fully enabled/disabled before calling this
* function. Calling this function just makes sure any activities triggered by
* the form submit (such as admin_role) are completed.
*/
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();
}
/**
* Get complete information for all available themes.
*
* @return
* An array containing theme info for all available themes.
*/
function drush_get_themes() {
return system_rebuild_theme_data();
}
/**
* Enable a list of themes.
*
* @param $themes
* Array of theme names.
*/
function drush_theme_enable($themes) {
theme_enable($themes);
}
/**
* Disable a list of themes.
*
* @param $themes
* Array of theme names.
*/
function drush_theme_disable($themes) {
theme_disable($themes);
}
/**
* Helper function to obtain the severity levels based on Drupal version.
*
* This is a copy of watchdog_severity_levels() without t().
*
* Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt.
*
* @return
* Array of watchdog severity levels.
*/
function core_watchdog_severity_levels() {
return array(
WATCHDOG_EMERGENCY=> 'emergency',
WATCHDOG_ALERT => 'alert',
WATCHDOG_CRITICAL => 'critical',
WATCHDOG_ERROR => 'error',
WATCHDOG_WARNING => 'warning',
WATCHDOG_NOTICE => 'notice',
WATCHDOG_INFO => 'info',
WATCHDOG_DEBUG => 'debug',
);
}

View File

@@ -0,0 +1,229 @@
<?php
/**
* @file
* Specific functions for a drupal 5 environment.
* drush_include_engine() magically includes either this file
* or environment_X.inc depending on which version of drupal drush
* 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');
}
/**
* Returns drupal required modules, including their dependencies.
*/
function _drush_drupal_required_modules($module_info) {
return drupal_required_modules();
}
/**
* Get complete information for all available modules.
*
* We need to set the type for those modules that are not already in the system table.
* Also In Drupal 5, system_modules() returns NULL for the dependency list of the module if there are no dependencies.
* We will override this to be an empty array instead to be compatible to Drupal 6 and 7.
*
* @return
* An array containing module info for all available modules.
*/
function drush_get_modules() {
$modules = module_rebuild_cache();
foreach ($modules as $module) {
if (!isset($module->type)) {
$module->type = 'module';
}
if (empty($module->info['dependencies'])) {
$module->info['dependencies'] = array();
}
if (empty($module->info['dependents'])) {
$module->info['dependents'] = array();
}
}
return $modules;
}
/**
* Return dependencies and its status for modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependencies and status for $modules
*/
function drush_check_module_dependencies($modules, $module_info) {
$status = array();
foreach ($modules as $key => $module) {
$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(
'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
);
}
else {
$status[$key]['dependencies'] = $dependencies;
}
}
return $status;
}
/**
* Return dependents of modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependents for each one of $modules
*/
function drush_module_dependents($modules, $module_info) {
$dependents = array();
foreach ($modules as $module) {
$dependents = array_merge($dependents, $module_info[$module]->info['dependents']);
}
return array_unique($dependents);
}
/**
* Enable a list of modules. It is assumed the list contains all the dependencies not already enabled.
*
* @param $modules
* Array of module names
*/
function drush_module_enable($modules) {
// In Drupal 5, drupal_install_modules() only installs new modules,
// and does not enable previously installed and disabled modules.
$install_modules = array();
$enable_modules = array();
foreach ($modules as $module) {
if (drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED) {
$install_modules[] = $module;
}
else {
$enable_modules[] = $module;
}
}
drupal_install_modules($install_modules);
module_enable($enable_modules);
}
/**
* Disable a list of modules. It is assumed the list contains all dependents not already disabled.
*
* @param $modules
* Array of module names
*/
function drush_module_disable($modules) {
module_disable($modules);
}
/**
* Uninstall a list of modules.
*
* @param $modules
* Array of module names
*/
function drush_module_uninstall($modules) {
require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc';
foreach ($modules as $module) {
drupal_uninstall_module($module);
}
}
/**
* Submit the system modules form.
*
* The modules should already be fully enabled/disabled before calling this
* function. Calling this function just makes sure any activities triggered by
* 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';
$form_state = array('values' => array('status' => $active_modules));
drupal_execute('system_modules', $form_state);
}
/**
* Get complete information for all available themes.
*
* We need to set the type for those themes that are not already in the system table.
*
* @return
* An array containing theme info for all available themes.
*/
function drush_get_themes() {
$themes = system_theme_data();
foreach ($themes as $theme) {
if (!isset($theme->type)) {
$theme->type = 'theme';
}
}
return $themes;
}
/**
* Enable a list of themes.
*
* This function is based on system_themes_submit().
*
* @see system_themes_submit()
* @param $themes
* Array of theme names.
*/
function drush_theme_enable($themes) {
foreach ($themes as $theme) {
system_initialize_theme_blocks($theme);
}
$placeholder = implode(',', array_fill(0, count($themes), "'%s'"));
db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND name IN (".$placeholder.")", $themes);
menu_rebuild();
}
/**
* Disable a list of themes.
*
* This function is based on system_themes_submit().
*
* @see system_themes_submit()
* @param $themes
* Array of theme names.
*/
function drush_theme_disable($themes) {
$placeholder = implode(',', array_fill(0, count($themes), "'%s'"));
db_query("UPDATE {system} SET status = 0 WHERE type = 'theme' AND name IN (".$placeholder.")", $themes);
menu_rebuild();
}
/**
* Helper function to obtain the severity levels based on Drupal version.
*
* This is a copy of watchdog_severity_levels() without t().
*
* Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt.
*
* @return
* Array of watchdog severity levels.
*/
function core_watchdog_severity_levels() {
return array(
WATCHDOG_NOTICE => 'notice',
WATCHDOG_WARNING => 'warning',
WATCHDOG_ERROR => 'error'
);
}

View File

@@ -0,0 +1,218 @@
<?php
/**
* @file
* 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.
*/
/**
* Get complete information for all available modules.
*
* We need to set the type for those modules that are not already in the system table.
*
* @return
* An array containing module info for all available modules.
*/
function drush_get_modules() {
$modules = module_rebuild_cache();
foreach ($modules as $module) {
if (!isset($module->type)) {
$module->type = 'module';
}
}
return $modules;
}
/**
* Returns drupal required modules, including their dependencies.
*
* A module may alter other module's .info to set a dependency on it.
* See for example http://drupal.org/project/phpass
*/
function _drush_drupal_required_modules($module_info) {
$required = drupal_required_modules();
foreach ($required as $module) {
$required = array_merge($required, $module_info[$module]->info['dependencies']);
}
return $required;
}
/**
* Return dependencies and its status for modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependencies and status for $modules
*/
function drush_check_module_dependencies($modules, $module_info) {
$status = array();
foreach ($modules as $key => $module) {
$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(
'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
);
}
$status[$key]['unmet-dependencies'] = $unmet_dependencies;
$status[$key]['dependencies'] = $dependencies;
}
return $status;
}
/**
* Return dependents of modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependents for each one of $modules
*/
function drush_module_dependents($modules, $module_info) {
$dependents = array();
foreach ($modules as $module) {
$dependents = array_merge($dependents, $module_info[$module]->info['dependents']);
}
return array_unique($dependents);
}
/**
* Enable a list of modules. It is assumed the list contains all the dependencies not already enabled.
*
* @param $modules
* Array of module names
*/
function drush_module_enable($modules) {
// Try to install modules previous to enabling.
foreach ($modules as $module) {
_drupal_install_module($module);
}
module_enable($modules);
}
/**
* Disable a list of modules. It is assumed the list contains all dependents not already disabled.
*
* @param $modules
* Array of module names
*/
function drush_module_disable($modules) {
module_disable($modules, FALSE);
}
/**
* Uninstall a list of modules.
*
* @param $modules
* Array of module names
*/
function drush_module_uninstall($modules) {
require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc';
foreach ($modules as $module) {
drupal_uninstall_module($module);
}
}
/**
* Submit the system modules form.
*
* The modules should already be fully enabled/disabled before calling this
* function. Calling this function just makes sure any activities triggered by
* the form submit (such as admin_role) are completed.
*/
function drush_system_modules_form_submit($active_modules) {
module_load_include('inc', 'system', 'system.admin');
$form_state = array('values' => array('status' => $active_modules));
drupal_execute('system_modules', $form_state);
}
/**
* Get complete information for all available themes.
*
* We need to set the type for those themes that are not already in the system table.
*
* @return
* An array containing theme info for all available themes.
*/
function drush_get_themes() {
$themes = system_theme_data();
foreach ($themes as $theme) {
if (!isset($theme->type)) {
$theme->type = 'theme';
}
}
return $themes;
}
/**
* Enable a list of themes.
*
* This function is based on system_themes_form_submit().
*
* @see system_themes_form_submit()
* @param $themes
* Array of theme names.
*/
function drush_theme_enable($themes) {
drupal_clear_css_cache();
foreach ($themes as $theme) {
system_initialize_theme_blocks($theme);
}
db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND name IN (".db_placeholders($themes, 'text').")", $themes);
list_themes(TRUE);
menu_rebuild();
module_invoke('locale', 'system_update', $themes);
}
/**
* Disable a list of themes.
*
* This function is based on system_themes_form_submit().
*
* @see system_themes_form_submit()
* @param $themes
* Array of theme names.
*/
function drush_theme_disable($themes) {
drupal_clear_css_cache();
db_query("UPDATE {system} SET status = 0 WHERE type = 'theme' AND name IN (".db_placeholders($themes, 'text').")", $themes);
list_themes(TRUE);
menu_rebuild();
drupal_rebuild_theme_registry();
module_invoke('locale', 'system_update', $themes);
}
/**
* Helper function to obtain the severity levels based on Drupal version.
*
* This is a copy of watchdog_severity_levels() without t().
*
* Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt.
*
* @return
* Array of watchdog severity levels.
*/
function core_watchdog_severity_levels() {
return array(
WATCHDOG_EMERG => 'emergency',
WATCHDOG_ALERT => 'alert',
WATCHDOG_CRITICAL => 'critical',
WATCHDOG_ERROR => 'error',
WATCHDOG_WARNING => 'warning',
WATCHDOG_NOTICE => 'notice',
WATCHDOG_INFO => 'info',
WATCHDOG_DEBUG => 'debug',
);
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* Install Drupal 7+
*/
function drush_core_site_install_version($profile, array $additional_form_options = array()) {
if (is_null($profile)) {
$profile = 'standard';
}
define('MAINTENANCE_MODE', 'install');
require_once DRUPAL_ROOT . '/includes/install.core.inc';
$db_spec = drush_sql_read_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(
'driver' => $db_spec['driver'],
$db_spec['driver'] => $db_spec,
'op' => dt('Save and continue'),
),
'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),
'op' => dt('Save and continue'),
),
),
);
// Merge in the additional options.
foreach ($additional_form_options as $key => $value) {
$current = &$settings['forms'];
foreach (explode('.', $key) as $param) {
$current = &$current[$param];
}
$current = $value;
}
drush_log(dt('Starting Drupal installation. This takes a few seconds ...'), 'ok');
install_drupal($settings);
}

View File

@@ -0,0 +1,126 @@
<?php
/**
* 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');
// We need to disable reporting of E_NOTICE if we want to read the command's output
// on Windows, because of how Windows is handling output order when using 2>&1
// redirect added to the command in drush_shell_exec(). We will actually take out
// all but fatal errors. See http://drupal.org/node/985716 for more information.
$phpcode = 'error_reporting(E_ERROR);' . _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);
// We need to bootstrap the database to be able to check the progress of the
// install batch process since we're not duplicating the install process using
// drush_batch functions, but calling the process directly.
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);
$status = _drush_site_install6_stage($profile, $cli_cookie, "start");
if ($status === FALSE) {
return FALSE;
}
$status = _drush_site_install6_stage($profile, $cli_cookie, "do_nojs");
if ($status === FALSE) {
return FALSE;
}
$status = _drush_site_install6_stage($profile, $cli_cookie, "finished");
if ($status === FALSE) {
return FALSE;
}
$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);
}
/**
* Submit a given op to install.php; if a meta "Refresh" tag
* is returned in the result, then submit that op as well.
*/
function _drush_site_install6_stage($profile, $cli_cookie, $initial_op) {
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
// Remember the install task at the start of the stage
$install_task = _drush_site_install6_install_task();
$op = $initial_op;
while (!empty($op)) {
$phpcode = _drush_site_install6_cookies($profile, $cli_cookie). ' $_GET["op"]="' . $op . '"; include("'. $drupal_root .'/install.php");';
drush_shell_exec('php -r %s', $phpcode);
$output = implode("\n", drush_shell_exec_output());
// Check for a "Refresh" back to the do_nojs op; e.g.:
// <meta http-equiv="Refresh" content="0; URL=http://default/install.php?locale=en&profile=wk_profile6&id=1&op=do_nojs">
// If this pattern is NOT found, then go on to the "finished" step.
$matches = array();
$match_result = preg_match('/http-equiv="Refresh".*op=([a-zA-Z0-9_]*)/', $output, $matches);
if ($match_result) {
$op = $matches[1];
}
else {
$op = '';
}
}
if (($install_task == _drush_site_install6_install_task()) && ($initial_op != "finished")) {
return drush_set_error('DRUSH_SITE_INSTALL_FAILED', dt("The site install task '!task' failed.", array('!task' => $install_task)));
}
return TRUE;
}
/**
* Utility function to grab/set current "cli cookie".
*/
function _drush_site_install6_cookies($profile, $cookie = NULL) {
$drupal_base_url = parse_url(drush_get_option('uri', 'http://default'));
$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;';
$output .= '$_SERVER["SCRIPT_NAME"] = "/install.php";';
$output .= '$_SERVER["HTTP_HOST"] = "'.$drupal_base_url['host'].'";';
$output .= '$_SERVER["REMOTE_ADDR"] = "127.0.0.1";';
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;
}
/**
* Utility function to check the install_task. We are
* not bootstrapped to a high enough level to use variable_get.
*/
function _drush_site_install6_install_task() {
if ($data = db_result(db_query("SELECT value FROM {variable} WHERE name = 'install_task'",1))) {
$result = unserialize($data);
}
return $result;
}

View File

@@ -0,0 +1,321 @@
<?php
/**
* @file
* Update.php for provisioned sites.
* This file is a derivative of the standard drupal update.php,
* which has been modified to allow being run from the command
* line.
*/
/**
* Global flag to identify update.php run, and so avoid various unwanted
* operations, such as hook_init() and hook_exit() invokes, css/js preprocessing
* and translation, and solve some theming issues. This flag is checked on several
* places in Drupal code (not just update.php).
*/
define('MAINTENANCE_MODE', 'update');
/**
* Drupal's update.inc has functions that are in previous update_X.inc files
* for example, update_check_incompatibility() which can prove useful when
* enabling modules.
*/
require_once DRUPAL_ROOT . '/includes/update.inc';
/**
* Returns (and optionally stores) extra requirements that only apply during
* particular parts of the update.php process.
*/
function update_extra_requirements($requirements = NULL) {
static $extra_requirements = array();
if (isset($requirements)) {
$extra_requirements += $requirements;
}
return $extra_requirements;
}
/**
* Perform one update and store the results which will later be displayed on
* the finished page.
*
* An update function can force the current and all later updates for this
* module to abort by returning a $ret array with an element like:
* $ret['#abort'] = array('success' => FALSE, 'query' => 'What went wrong');
* The schema version will not be updated in this case, and all the
* aborted updates will continue to appear on update.php as updates that
* have not yet been run.
*
* @param $module
* The module whose update will be run.
* @param $number
* The update number to run.
* @param $context
* The batch context array
*/
function drush_update_do_one($module, $number, $dependency_map, &$context) {
$function = $module . '_update_' . $number;
// If this update was aborted in a previous step, or has a dependency that
// was aborted in a previous step, go no further.
if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) {
return;
}
$context['log'] = FALSE;
$ret = array();
if (function_exists($function)) {
try {
if ($context['log']) {
Database::startLog($function);
}
drush_log("Executing " . $function);
$ret['results']['query'] = $function($context['sandbox']);
$ret['results']['success'] = TRUE;
}
// @TODO We may want to do different error handling for different exception
// types, but for now we'll just print the message.
catch (Exception $e) {
$ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage());
drush_set_error('DRUPAL_EXCEPTION', $e->getMessage());
}
if ($context['log']) {
$ret['queries'] = Database::getLog($function);
}
}
if (isset($context['sandbox']['#finished'])) {
$context['finished'] = $context['sandbox']['#finished'];
unset($context['sandbox']['#finished']);
}
if (!isset($context['results'][$module])) {
$context['results'][$module] = array();
}
if (!isset($context['results'][$module][$number])) {
$context['results'][$module][$number] = array();
}
$context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);
if (!empty($ret['#abort'])) {
// Record this function in the list of updates that were aborted.
$context['results']['#abort'][] = $function;
}
// Record the schema update if it was completed successfully.
if ($context['finished'] == 1 && empty($ret['#abort'])) {
drupal_set_installed_schema_version($module, $number);
}
$context['message'] = 'Updating ' . check_plain($module) . ' module';
}
/**
* Check update requirements and report any errors.
*/
function update_check_requirements() {
$warnings = FALSE;
// Check the system module and update.php requirements only.
$requirements = system_requirements('update');
$requirements += update_extra_requirements();
// If there are issues, report them.
foreach ($requirements as $requirement) {
if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) {
$message = isset($requirement['description']) ? $requirement['description'] : '';
if (isset($requirement['value']) && $requirement['value']) {
$message .= ' (Currently using ' . $requirement['title'] . ' ' . $requirement['value'] . ')';
}
$warnings = TRUE;
drupal_set_message($message, 'warning');
}
}
return $warnings;
}
function update_main_prepare() {
// Some unavoidable errors happen because the database is not yet up-to-date.
// Our custom error handler is not yet installed, so we just suppress them.
drush_errors_off();
// We prepare a minimal bootstrap for the update requirements check to avoid
// reaching the PHP memory limit.
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
require_once DRUPAL_ROOT . '/includes/common.inc';
require_once DRUPAL_ROOT . '/includes/file.inc';
require_once DRUPAL_ROOT . '/includes/entity.inc';
include_once DRUPAL_ROOT . '/includes/unicode.inc';
update_prepare_d7_bootstrap();
drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
require_once DRUPAL_ROOT . '/includes/install.inc';
require_once DRUPAL_ROOT . '/modules/system/system.install';
// Load module basics.
include_once DRUPAL_ROOT . '/includes/module.inc';
$module_list['system']['filename'] = 'modules/system/system.module';
module_list(TRUE, FALSE, FALSE, $module_list);
drupal_load('module', 'system');
// Reset the module_implements() cache so that any new hook implementations
// in updated code are picked up.
module_implements('', FALSE, TRUE);
// Set up $language, since the installer components require it.
drupal_language_initialize();
// Set up theme system for the maintenance page.
drupal_maintenance_theme();
// Check the update requirements for Drupal.
update_check_requirements();
// update_fix_d7_requirements() needs to run before bootstrapping beyond path.
// So bootstrap to DRUPAL_BOOTSTRAP_LANGUAGE then include unicode.inc.
drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE);
update_fix_d7_requirements();
// Now proceed with a full bootstrap.
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
drupal_maintenance_theme();
drush_errors_on();
include_once DRUPAL_ROOT . '/includes/batch.inc';
drupal_load_updates();
update_fix_compatibility();
// Change query-strings on css/js files to enforce reload for all users.
_drupal_flush_css_js();
// Flush the cache of all data for the update status module.
if (db_table_exists('cache_update')) {
cache_clear_all('*', 'cache_update', TRUE);
}
module_list(TRUE, FALSE, TRUE);
}
function update_main() {
update_main_prepare();
$pending = update_get_update_list();
$start = array();
// Ensure system module's updates run first
$start['system'] = array();
// Print a list of pending updates for this module and get confirmation.
if (sizeof($pending)) {
drush_print(dt('The following updates are pending:'));
drush_print();
foreach ($pending as $module => $updates) {
if (isset($updates['start'])) {
drush_print($module . ' module : ');
if (isset($updates['start'])) {
$start[$module] = $updates['start'];
foreach ($updates['pending'] as $update) {
drush_print($update, 2);
}
}
drush_print();
}
}
if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
return drush_user_abort();
}
drush_update_batch($start);
}
else {
drush_log(dt("No database updates required"), 'success');
}
}
function _update_batch_command($id) {
update_main_prepare();
drush_batch_command($id);
}
/**
* Start the database update batch process.
*
* @param $start
* An array of all the modules and which update to start at.
* @param $redirect
* Path to redirect to when the batch has finished processing.
* @param $url
* URL of the batch processing page (should only be used for separate
* scripts like update.php).
* @param $batch
* Optional parameters to pass into the batch API.
* @param $redirect_callback
* (optional) Specify a function to be called to redirect to the progressive
* processing page.
*/
function drush_update_batch($start) {
// Resolve any update dependencies to determine the actual updates that will
// be run and the order they will be run in.
$updates = update_resolve_dependencies($start);
// Store the dependencies for each update function in an array which the
// batch API can pass in to the batch operation each time it is called. (We
// do not store the entire update dependency array here because it is
// potentially very large.)
$dependency_map = array();
foreach ($updates as $function => $update) {
$dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array();
}
$operations = array();
foreach ($updates as $update) {
if ($update['allowed']) {
// Set the installed version of each module so updates will start at the
// correct place. (The updates are already sorted, so we can simply base
// this on the first one we come across in the above foreach loop.)
if (isset($start[$update['module']])) {
drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
unset($start[$update['module']]);
}
// Add this update function to the batch.
$function = $update['module'] . '_update_' . $update['number'];
$operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
}
}
$batch['operations'] = $operations;
$batch += array(
'title' => 'Updating',
'init_message' => 'Starting updates',
'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
'finished' => 'drush_update_finished',
'file' => 'includes/update.inc',
);
batch_set($batch);
drush_backend_batch_process('updatedb-batch-process');
}
function drush_update_finished($success, $results, $operations) {
// Nothing to do here. All caches already cleared. Kept as documentation of 'finished' callback.
}

View File

@@ -0,0 +1,119 @@
<?php
/**
* @file
* Update.php for provisioned sites.
* This file is a derivative of the standard drupal update.php,
* which has been modified to allow being run from the command
* line.
*/
ob_start();
include_once("update.php");
ob_end_clean();
function update_main() {
// Updates only run reliably if user ID #1 is logged in. For example, node_delete() requires elevated perms in D5/6.
if (!drush_get_context('DRUSH_USER')) {
drush_set_option('user', 1);
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_LOGIN);
}
include_once './includes/install.inc';
drupal_load_updates();
update_fix_schema_version();
update_fix_watchdog_115();
update_fix_watchdog();
update_fix_sessions();
$has_updates = FALSE;
$start = array();
foreach (module_list() as $module) {
$updates = drupal_get_schema_versions($module);
if ($updates !== FALSE) {
$pending[$module] = array();
$updates = drupal_map_assoc($updates);
$schema_version = drupal_get_installed_schema_version($module);
$default = $schema_version;
foreach (array_keys($updates) as $update) {
if ($update > $default) {
$start[$module] = $update;
break;
}
}
// Record any pending updates. Used for confirmation prompt.
foreach (array_keys($updates) as $update) {
if ($update > $schema_version) {
if (class_exists('ReflectionFunction')) {
// The description for an update comes from its Doxygen.
$func = new ReflectionFunction($module. '_update_'. $update);
$description = str_replace(array("\n", '*', '/'), '', $func->getDocComment());
}
if (empty($description)) {
$description = dt('description not available');
}
$pending[$module][] = array("$update - ". trim($description));
$has_updates = TRUE;
}
}
}
}
// Print a list of pending updates for this module and get confirmation.
if ($has_updates) {
drush_print(dt('The following updates are pending:'));
drush_print();
foreach ($pending as $module => $updates) {
if (sizeof($updates)) {
array_unshift($updates, array($module . ' module'));
drush_print_table($updates, TRUE);
drush_print();
}
}
if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
return drush_user_abort();
}
$update_results = array();
foreach ($start as $module => $version) {
drupal_set_installed_schema_version($module, $version - 1);
$updates = drupal_get_schema_versions($module);
$max_version = max($updates);
if ($version <= $max_version) {
drush_log(dt('Updating module @module from schema version @start to schema version @max', array('@module' => $module, '@start' => $version - 1, '@max' => $max_version)));
foreach ($updates as $update) {
$finished = FALSE;
if ($update >= $version) {
while (!$finished) {
// do update
$ret = module_invoke($module, 'update_' . $update);
_drush_log_update_sql($ret);
$finished = 1;
if (isset($ret['#finished'])) {
$finished = $ret['#finished'];
unset($ret['#finished']);
}
}
drupal_set_installed_schema_version($module, $update);
}
}
}
else {
drush_log(dt('No database updates for @module module', array('@module' => $module)), 'success');
}
}
}
else {
drush_log(dt("No database updates required"), 'success');
}
}

View File

@@ -0,0 +1,504 @@
<?php
/**
* @file
* Update.php for provisioned sites.
* This file is a derivative of the standard drupal update.php,
* which has been modified to allow being run from the command
* line.
*/
define('MAINTENANCE_MODE', 'update');
/**
* Add a column to a database using syntax appropriate for PostgreSQL.
* Save result of SQL commands in $ret array.
*
* Note: when you add a column with NOT NULL and you are not sure if there are
* already rows in the table, you MUST also add DEFAULT. Otherwise PostgreSQL
* won't work when the table is not empty, and db_add_column() will fail.
* To have an empty string as the default, you must use: 'default' => "''"
* in the $attributes array. If NOT NULL and DEFAULT are set the PostgreSQL
* version will set values of the added column in old rows to the
* DEFAULT value.
*
* @param $ret
* Array to which results will be added.
* @param $table
* Name of the table, without {}
* @param $column
* Name of the column
* @param $type
* Type of column
* @param $attributes
* Additional optional attributes. Recognized attributes:
* not null => TRUE|FALSE
* default => NULL|FALSE|value (the value must be enclosed in '' marks)
* @return
* nothing, but modifies $ret parameter.
*/
function db_add_column(&$ret, $table, $column, $type, $attributes = array()) {
if (array_key_exists('not null', $attributes) and $attributes['not null']) {
$not_null = 'NOT NULL';
}
if (array_key_exists('default', $attributes)) {
if (is_null($attributes['default'])) {
$default_val = 'NULL';
$default = 'default NULL';
}
elseif ($attributes['default'] === FALSE) {
$default = '';
}
else {
$default_val = "$attributes[default]";
$default = "default $attributes[default]";
}
}
$ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column $type");
if (!empty($default)) {
$ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET $default");
}
if (!empty($not_null)) {
if (!empty($default)) {
$ret[] = update_sql("UPDATE {". $table ."} SET $column = $default_val");
}
$ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET NOT NULL");
}
}
/**
* Change a column definition using syntax appropriate for PostgreSQL.
* Save result of SQL commands in $ret array.
*
* Remember that changing a column definition involves adding a new column
* and dropping an old one. This means that any indices, primary keys and
* sequences from serial-type columns are dropped and might need to be
* recreated.
*
* @param $ret
* Array to which results will be added.
* @param $table
* Name of the table, without {}
* @param $column
* Name of the column to change
* @param $column_new
* New name for the column (set to the same as $column if you don't want to change the name)
* @param $type
* Type of column
* @param $attributes
* Additional optional attributes. Recognized attributes:
* not null => TRUE|FALSE
* default => NULL|FALSE|value (with or without '', it won't be added)
* @return
* nothing, but modifies $ret parameter.
*/
function db_change_column(&$ret, $table, $column, $column_new, $type, $attributes = array()) {
if (array_key_exists('not null', $attributes) and $attributes['not null']) {
$not_null = 'NOT NULL';
}
if (array_key_exists('default', $attributes)) {
if (is_null($attributes['default'])) {
$default_val = 'NULL';
$default = 'default NULL';
}
elseif ($attributes['default'] === FALSE) {
$default = '';
}
else {
$default_val = "$attributes[default]";
$default = "default $attributes[default]";
}
}
$ret[] = update_sql("ALTER TABLE {". $table ."} RENAME $column TO ". $column ."_old");
$ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column_new $type");
$ret[] = update_sql("UPDATE {". $table ."} SET $column_new = ". $column ."_old");
if ($default) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET $default"); }
if ($not_null) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET NOT NULL"); }
$ret[] = update_sql("ALTER TABLE {". $table ."} DROP ". $column ."_old");
}
/**
* Disable anything in the {system} table that is not compatible with the
* current version of Drupal core.
*/
function update_fix_compatibility() {
$ret = array();
$incompatible = array();
$query = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')");
while ($result = db_fetch_object($query)) {
if (update_check_incompatibility($result->name, $result->type)) {
$incompatible[] = $result->name;
drush_log(dt("%type %name is incompatible with this release of Drupal, and will be disabled.",
array("%type" => $result->type, '%name' => $result->name)), "warning");
}
}
if (!empty($incompatible)) {
$ret[] = update_sql("UPDATE {system} SET status = 0 WHERE name IN ('". implode("','", $incompatible) ."')");
}
return $ret;
}
/**
* Helper function to test compatibility of a module or theme.
*/
function update_check_incompatibility($name, $type = 'module') {
static $themes, $modules;
// Store values of expensive functions for future use.
if (empty($themes) || empty($modules)) {
drush_include_engine('drupal', 'environment');
$themes = _system_theme_data();
$modules = module_rebuild_cache();
}
if ($type == 'module' && isset($modules[$name])) {
$file = $modules[$name];
}
else if ($type == 'theme' && isset($themes[$name])) {
$file = $themes[$name];
}
if (!isset($file)
|| !isset($file->info['core'])
|| $file->info['core'] != DRUPAL_CORE_COMPATIBILITY
|| version_compare(phpversion(), $file->info['php']) < 0) {
return TRUE;
}
return FALSE;
}
/**
* Perform Drupal 5.x to 6.x updates that are required for update.php
* to function properly.
*
* This function runs when update.php is run the first time for 6.x,
* even before updates are selected or performed. It is important
* that if updates are not ultimately performed that no changes are
* made which make it impossible to continue using the prior version.
* Just adding columns is safe. However, renaming the
* system.description column to owner is not. Therefore, we add the
* system.owner column and leave it to system_update_6008() to copy
* the data from description and remove description. The same for
* renaming locales_target.locale to locales_target.language, which
* will be finished by locale_update_6002().
*/
function update_fix_d6_requirements() {
$ret = array();
if (drupal_get_installed_schema_version('system') < 6000 && !variable_get('update_d6_requirements', FALSE)) {
$spec = array('type' => 'int', 'size' => 'small', 'default' => 0, 'not null' => TRUE);
db_add_field($ret, 'cache', 'serialized', $spec);
db_add_field($ret, 'cache_filter', 'serialized', $spec);
db_add_field($ret, 'cache_page', 'serialized', $spec);
db_add_field($ret, 'cache_menu', 'serialized', $spec);
db_add_field($ret, 'system', 'info', array('type' => 'text'));
db_add_field($ret, 'system', 'owner', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
if (db_table_exists('locales_target')) {
db_add_field($ret, 'locales_target', 'language', array('type' => 'varchar', 'length' => 12, 'not null' => TRUE, 'default' => ''));
}
if (db_table_exists('locales_source')) {
db_add_field($ret, 'locales_source', 'textgroup', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => 'default'));
db_add_field($ret, 'locales_source', 'version', array('type' => 'varchar', 'length' => 20, 'not null' => TRUE, 'default' => 'none'));
}
variable_set('update_d6_requirements', TRUE);
// Create the cache_block table. See system_update_6027() for more details.
$schema['cache_block'] = array(
'fields' => array(
'cid' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
'data' => array('type' => 'blob', 'not null' => FALSE, 'size' => 'big'),
'expire' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
'created' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
'headers' => array('type' => 'text', 'not null' => FALSE),
'serialized' => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0)
),
'indexes' => array('expire' => array('expire')),
'primary key' => array('cid'),
);
db_create_table($ret, 'cache_block', $schema['cache_block']);
// Create the semaphore table now -- the menu system after 6.15 depends on
// this table, and menu code runs in updates prior to the table being
// created in its original update function, system_update_6054().
$schema['semaphore'] = array(
'fields' => array(
'name' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => ''),
'value' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => ''),
'expire' => array(
'type' => 'float',
'size' => 'big',
'not null' => TRUE),
),
'indexes' => array('expire' => array('expire')),
'primary key' => array('name'),
);
db_create_table($ret, 'semaphore', $schema['semaphore']);
}
return $ret;
}
/**
* Check update requirements and report any errors.
*/
function update_check_requirements() {
// Check the system module requirements only.
$requirements = module_invoke('system', 'requirements', 'update');
$severity = drupal_requirements_severity($requirements);
// If there are issues, report them.
if ($severity != REQUIREMENT_OK) {
foreach ($requirements as $requirement) {
if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) {
$message = isset($requirement['description']) ? $requirement['description'] : '';
if (isset($requirement['value']) && $requirement['value']) {
$message .= ' (Currently using '. $requirement['title'] .' '. $requirement['value'] .')';
}
drush_log($message, 'warning');
}
}
}
}
/**
* Create the batch table.
*
* This is part of the Drupal 5.x to 6.x migration.
*/
function update_create_batch_table() {
// If batch table exists, update is not necessary
if (db_table_exists('batch')) {
return;
}
$schema['batch'] = array(
'fields' => array(
'bid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
'token' => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE),
'timestamp' => array('type' => 'int', 'not null' => TRUE),
'batch' => array('type' => 'text', 'not null' => FALSE, 'size' => 'big')
),
'primary key' => array('bid'),
'indexes' => array('token' => array('token')),
);
$ret = array();
db_create_table($ret, 'batch', $schema['batch']);
return $ret;
}
function update_main_prepare() {
global $profile;
// Some unavoidable errors happen because the database is not yet up-to-date.
// Our custom error handler is not yet installed, so we just suppress them.
drush_errors_off();
require_once './includes/bootstrap.inc';
// Minimum load of components.
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
require_once './includes/install.inc';
require_once './includes/file.inc';
require_once './modules/system/system.install';
// Load module basics.
include_once './includes/module.inc';
$module_list['system']['filename'] = 'modules/system/system.module';
$module_list['filter']['filename'] = 'modules/filter/filter.module';
module_list(TRUE, FALSE, FALSE, $module_list);
drupal_load('module', 'system');
drupal_load('module', 'filter');
// Set up $language, since the installer components require it.
drupal_init_language();
// Set up theme system for the maintenance page.
drupal_maintenance_theme();
// Check the update requirements for Drupal.
update_check_requirements();
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
$profile = variable_get('install_profile', 'default');
// Updates only run reliably if user ID #1 is logged in. For example, node_delete() requires elevated perms in D5/6.
if (!drush_get_context('DRUSH_USER')) {
drush_set_option('user', 1);
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_LOGIN);
}
// This must happen *after* drupal_bootstrap(), since it calls
// variable_(get|set), which only works after a full bootstrap.
_drush_log_update_sql(update_create_batch_table());
// Turn error reporting back on. From now on, only fatal errors (which are
// not passed through the error handler) will cause a message to be printed.
drush_errors_on();
// Perform Drupal 5.x to 6.x updates that are required for update.php to function properly.
_drush_log_update_sql(update_fix_d6_requirements());
// Must unset $theme->status in order to safely rescan and repopulate
// the system table to ensure we have a full picture of the platform.
// This is needed because $theme->status is set to 0 in a call to
// list_themes() done by drupal_maintenance_theme().
// It is a issue with _system_theme_data() that returns its own cache
// variable and can be modififed by others. When this is fixed in
// drupal core we can remove this unset.
// For reference see: http://drupal.org/node/762754
$themes = _system_theme_data();
foreach ($themes as $theme) {
unset($theme->status);
}
drush_get_extensions();
include_once './includes/batch.inc';
drupal_load_updates();
// Disable anything in the {system} table that is not compatible with the current version of Drupal core.
_drush_log_update_sql(update_fix_compatibility());
}
function update_main() {
update_main_prepare();
$start = array();
$has_updates = FALSE;
$modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
foreach ($modules as $module => $schema_version) {
$updates = drupal_get_schema_versions($module);
// Skip incompatible module updates completely, otherwise test schema versions.
if (!update_check_incompatibility($module) && $updates !== FALSE && $schema_version >= 0) {
// module_invoke returns NULL for nonexisting hooks, so if no updates
// are removed, it will == 0.
$last_removed = module_invoke($module, 'update_last_removed');
if ($schema_version < $last_removed) {
drush_set_error('PROVISION_DRUPAL_UPDATE_FAILED', dt( $module .' module can not be updated. Its schema version is '. $schema_version .'. Updates up to and including '. $last_removed .' have been removed in this release. In order to update '. $module .' module, you will first <a href="http://drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.'));
continue;
}
$updates = drupal_map_assoc($updates);
foreach (array_keys($updates) as $update) {
if ($update > $schema_version) {
$start[$module] = $update;
break;
}
}
// Record any pending updates. Used for confirmation prompt.
foreach (array_keys($updates) as $update) {
if ($update > $schema_version) {
if (class_exists('ReflectionFunction')) {
// The description for an update comes from its Doxygen.
$func = new ReflectionFunction($module. '_update_'. $update);
$description = str_replace(array("\n", '*', '/'), '', $func->getDocComment());
}
if (empty($description)) {
$description = dt('description not available');
}
$pending[$module][] = array("$update - ". trim($description));
$has_updates = TRUE;
}
}
}
}
// Print a list of pending updates for this module and get confirmation.
if ($has_updates) {
drush_print(dt('The following updates are pending:'));
drush_print();
foreach ($pending as $module => $updates) {
if (sizeof($updates)) {
array_unshift($updates, array($module . ' module'));
drush_print_table($updates, TRUE);
drush_print();
}
}
if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
return drush_user_abort();
}
// Proceed with running all pending updates.
$operations = array();
foreach ($start as $module => $version) {
drupal_set_installed_schema_version($module, $version - 1);
$updates = drupal_get_schema_versions($module);
$max_version = max($updates);
if ($version <= $max_version) {
drush_log(dt('Updating module @module from schema version @start to schema version @max', array('@module' => $module, '@start' => $version - 1, '@max' => $max_version)));
foreach ($updates as $update) {
if ($update >= $version) {
$operations[] = array('_update_do_one', array($module, $update));
}
}
}
else {
drush_log(dt('No database updates for module @module', array('@module' => $module)), 'success');
}
}
$batch = array(
'operations' => $operations,
'title' => 'Updating',
'init_message' => 'Starting updates',
'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
'finished' => 'update_finished',
);
batch_set($batch);
$batch =& batch_get();
$batch['progressive'] = FALSE;
drush_backend_batch_process('updatedb-batch-process');
}
else {
drush_log(dt("No database updates required"), 'success');
}
}
/**
* A simplified version of the batch_do_one function from update.php
*
* This does not mess with sessions and the like, as it will be used
* from the command line
*/
function _update_do_one($module, $number, &$context) {
// If updates for this module have been aborted
// in a previous step, go no further.
if (!empty($context['results'][$module]['#abort'])) {
return;
}
$function = $module .'_update_'. $number;
drush_log("Executing $function", 'success');
if (function_exists($function)) {
$ret = $function($context['sandbox']);
$context['results'][$module] = $ret;
_drush_log_update_sql($ret);
}
if (isset($ret['#finished'])) {
$context['finished'] = $ret['#finished'];
unset($ret['#finished']);
}
if ($context['finished'] == 1 && empty($context['results'][$module]['#abort'])) {
drupal_set_installed_schema_version($module, $number);
}
}
function _update_batch_command($id) {
update_main_prepare();
drush_batch_command($id);
}

View File

@@ -0,0 +1,303 @@
<?php
/**
* @file
* Field API's drush integration
*/
/**
* Implementation of hook_drush_help().
*/
function field_drush_help($section) {
switch ($section) {
case 'meta:field:title':
return dt('Field commands');
case 'meta:field:summary':
return dt('Manipulate Drupal 7+ fields.');
}
}
/**
* 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.',
),
'options' => array(
'pipe' => 'Return field information table as CSV.',
),
'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($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(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $name, array('absolute' => TRUE));
}
drush_print(implode(' ', $urls));
}
// Copy of function _field_ui_bundle_admin_path() since we don't want to load UI module.
function drush_field_ui_bundle_admin_path($entity_type, $bundle_name) {
$bundles = field_info_bundles($entity_type);
$bundle_info = $bundles[$bundle_name];
if (isset($bundle_info['admin'])) {
return isset($bundle_info['admin']['real path']) ? $bundle_info['admin']['real path'] : $bundle_info['admin']['path'];
}
}
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(drush_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_user_abort();
}
}
}
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(drush_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);
}
$row = array(
$field_name,
$field['type'],
implode(' ', $bundle_strs),
);
$rows[] = $row;
$pipe[] = implode(',', $row);
}
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);
$row = array(
$type_name,
$type['default_widget'],
implode(', ', array_keys($widgets)),
);
$rows[] = $row;
$pipe[] = implode(',', $row);
}
break;
}
drush_print_table($rows, TRUE);
drush_print_pipe($pipe);
return $rows;
}
/**
* 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;
}
}
}
}

View File

@@ -0,0 +1,261 @@
<?php
/**
* Build a fake command for the purposes of showing examples and options.
*/
function drush_global_options_command($brief = FALSE) {
$global_options_help = array(
'description' => 'Execute a drush command. Run `drush help [command]` to view command-specific help. Run `drush topic` to read even more documentation.',
'sections' => array(
'options' => 'Global options (see `drush topic core-global-options` for the full list).',
),
'options' => drush_get_global_options($brief),
'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.',
),
);
$global_options_help += drush_command_defaults('global-options', 'global_options', __FILE__);
return $global_options_help;
}
/**
* Command callback for help command. This is the default command, when none
* other has been specified.
*/
function drush_core_help() {
$commands = func_get_args();
if (empty($commands)) {
// For speed, only bootstrap up to DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION.
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
$implemented = drush_get_commands();
// Organize all commands into categories
$command_categories = array();
$category_map = array();
foreach ($implemented as $key => $candidate) {
if ((!array_key_exists('is_alias', $candidate) || !$candidate['is_alias']) && !$candidate['hidden']) {
$category = $candidate['commandfile'];
// If we have decided to remap a category, remap every command
if (array_key_exists($category, $category_map)) {
$category = $category_map[$category];
}
if (!array_key_exists($category, $command_categories)) {
$title = drush_command_invoke_all('drush_help', "meta:$category:title");
$alternate_title = '';
if (!$title) {
// If there is no title, then check to see if the
// command file is stored in a folder with the same
// name as some other command file (e.g. 'core') that
// defines a title.
$alternate = basename($candidate['path']);
$alternate_title = drush_command_invoke_all('drush_help', "meta:$alternate:title");
}
if (!empty($alternate_title)) {
$category_map[$category] = $alternate;
$category = $alternate;
$title = $alternate_title;
}
$command_categories[$category]['title'] = empty($title) ? '' : $title[0];
$summary = drush_command_invoke_all('drush_help', "meta:$category:summary");
if ($summary) {
$command_categories[$category]['summary'] = $summary[0];
}
}
$command_categories[$category]['commands'][] = $key;
}
}
// Sort the command categories; make sure that 'core' is
// always first in the list
$core_category = array('core' => $command_categories['core']);
unset($command_categories['core']);
// Post-process the categories that have no title.
// Any that have fewer than 4 commands will go into a
// section called "other".
$processed_categories = array();
$misc_categories = array();
$other_commands = array();
$other_categories = array();
foreach ($command_categories as $key => $info) {
if (empty($info['title'])) {
$one_category = $key;
if (count($info['commands']) < 4) {
$other_commands = array_merge($other_commands, $info['commands']);
$other_categories[] = $one_category;
}
else {
$info['title'] = dt("All commands in !category", array('!category' => $key));
$misc_categories[$one_category] = $info;
}
}
else {
$processed_categories[$key] = $info;
}
}
$other_category = array();
if (!empty($other_categories)) {
$other_category[implode(',', $other_categories)] = array('title' => dt("Other commands"), 'commands' => $other_commands);
}
asort($processed_categories);
asort($misc_categories);
$command_categories = array_merge($core_category, $processed_categories, $misc_categories, $other_category);
// If the user specified --filter w/out a value, then
// present a list of help options.
if (drush_get_option('filter', FALSE) === TRUE) {
$help_categories = array();
foreach ($command_categories as $key => $info) {
$description = $info['title'];
if (array_key_exists('summary', $info)) {
$description .= ": " . $info['summary'];
}
$help_categories[$key] = $description;
}
$result = drush_choice($help_categories, 'Select a help category:');
if (!$result) {
return drush_user_abort();
}
drush_set_option('filter', $result);
}
if (drush_get_option('html')) {
drush_print(drush_help_html_header());
}
// Make a fake command section to hold the global options, then print it.
$global_options_help = drush_global_options_command(TRUE);
if ((!drush_get_option('html')) && (!drush_get_option('filter'))) {
drush_print_help($global_options_help);
}
// Filter out categories that the user does not want to see
if ($filter_category = drush_get_option('filter')) {
if (!array_key_exists($filter_category, $command_categories)) {
return drush_set_error('DRUSH_NO_CATEGORY', dt("The specified command category !filter does not exist.", array('!filter' => $filter_category)));
}
$command_categories = array($filter_category => $command_categories[$filter_category]);
}
// If the user specified --sort, then merge all of the remaining
// categories together
if (drush_get_option('sort', FALSE)) {
$combined_commands = array();
foreach ($command_categories as $key => $info) {
$combined_commands = array_merge($combined_commands, $info['commands']);
}
$command_categories = array('all' => array('commands' => $combined_commands, 'title' => dt("Commands:")));
}
// Next, print out the table of commands by category.
$all_commands = array();
foreach ($command_categories as $key => $info) {
// Get the commands in this category and sort them
$commands = $info['commands'];
sort($commands);
// Remove hidden commands and build output for drush_print_table.
$rows = array();
foreach($commands as $cmd) {
$command = $implemented[$cmd];
$all_commands[$cmd] = $command;
$name = $command['aliases'] ? $cmd . ' (' . implode(', ', $command['aliases']) . ')': $cmd;
$rows[$cmd] = array($name, $command['description']);
$pipe[] = $cmd;
}
// Vary the output by mode: text or html
if (drush_get_option('html')) {
$sorted_commands = array();
foreach($commands as $cmd) {
$sorted_commands[$cmd] = $implemented[$cmd];
}
drush_print("<h3>" . $info['title'] . "</h3>");
drush_print(drush_help_html_command_list($sorted_commands));
}
else {
drush_print($info['title'] . ": (" . $key . ")");
drush_print_table($rows, FALSE, array(0 => 20));
}
}
// Print out the long-form help for all commands
if (drush_get_option('html')) {
drush_print(drush_help_html_global_options($global_options_help));
drush_print(drush_help_html($all_commands));
}
// Newline-delimited list for use by other scripts. Set the --pipe option.
if (drush_get_option('pipe') && isset($pipe)) {
sort($pipe);
drush_print_pipe(implode(" ", $pipe));
}
return;
}
else {
$result = TRUE;
while ((count($commands) > 0) && !drush_get_error()) {
$result = drush_show_help(array_shift($commands));
}
return $result;
}
return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => implode(" ", $commands))));
}
/**
* Return an HTML page header that includes all global options.
*/
function drush_help_html_header() {
$output = "<html><head><title>drush help</title><style>dt {font-size: 110%; font-weight: bold}</style></head><body>\n";
}
function drush_help_html_global_options($global_options_help) {
// Global options
$global_option_rows = drush_format_help_section($global_options_help, 'options');
$output = '<h3>Global Options (see `drush topic core-global-options` for the full list)</h3><table>';
foreach ($global_option_rows as $row) {
$output .= "<tr>";
foreach ($row as $value) {
$output .= "<td>" . htmlspecialchars($value) . "</td>\n";
}
$output .= "</tr>";
}
$output .= "</table>\n";
return $output;
}
function drush_help_html_command_list($commands) {
// Command table
$output = "<table>\n";
foreach ($commands as $key => $command) {
$output .= " <tr><td><a href=\"#$key\">$key</a></td><td>" . $command['description'] . "</td></tr>\n";
}
$output .= "</table>\n";
return $output;
}
/**
* Return an HTML page documenting all available commands and global options.
*/
function drush_help_html($commands) {
// Command details
$output = '<h3>Command detail</h3><dl>';
foreach ($commands as $key => $command) {
$output .= "\n<a name=\"$key\"></a><dt>$key</dt><dd><pre>\n";
ob_start();
drush_show_help($key);
$output .= ob_get_clean();
$output .= "</pre></dd>\n";
}
$output .= "</body></html>\n";
return $output;
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* @file
* Image module's drush integration.
*
* @todo image-build($field_name, $bundle, $style_name)
*/
/**
* Implementation of hook_drush_command().
*/
function image_drush_command() {
$items['image-flush'] = array(
'description' => 'Flush all derived images for a given style.',
'core' => array('7+'),
'drupal_dependencies' => array('image'),
'arguments' => array(
'style' => 'An image style machine name. If not provided, user may choose from a list of names.',
),
'options' => array(
'all' => 'Flush all derived images',
),
'examples' => array(
'drush image-flush' => 'Pick an image style and then delete its images.',
'drush image-flush thumbnail' => 'Delete all thumbnail images.',
'drush image-flush --all' => 'Flush all derived images. They will be regenerated on the fly.',
),
);
return $items;
}
function drush_image_flush($style_name = NULL) {
if (drush_get_option('all')) {
drush_image_flush_all();
}
elseif (empty($style_name)) {
$choices = drupal_map_assoc(array_keys(image_styles()));
$choices = array_merge(array('all' => 'all'), $choices);
$style_name = drush_choice($choices, dt("Choose a style to flush."));
if ($style_name == 'all') {
drush_image_flush_all();
}
else {
$commands = drush_get_commands();
return drush_dispatch($commands['image-flush'], array($style_name));
}
}
else {
if ($style = image_style_load($style_name)) {
image_style_flush($style);
drush_log(dt('Image style !style_name flushed', array('!style_name' => $style_name)), 'success');
}
else {
return drush_set_error(dt('Image style !style not recognized.', array('!style' => $style_name)));
}
}
}
function drush_image_flush_all() {
foreach (image_styles() as $style) {
image_style_flush($style);
}
drush_log(dt('All image styles flushed'), 'success');
}

View File

@@ -0,0 +1,272 @@
<?php
/**
* Entrypoint for drush rsync.
*
* @param source
* A site alias ("@dev") or site specification ("/path/to/drupal#mysite.com")
* followed by an optional path (":path/to/sync"), or any path
* that could be passed to rsync ("user@server.com:/path/to/dir/").
* @param destination
* Same format as source.
* @param additional_options
* An array of options that overrides whatever was passed in on
* the command line (like the 'process' context, but only for
* the scope of this one call).
*/
function drush_core_rsync($source, $destination, $additional_options = array()) {
// Preflight destination in case it defines aliases used by the source
_drush_sitealias_preflight_path($destination);
// After preflight, evaluate file paths
$source_settings = drush_sitealias_evaluate_path($source, $additional_options);
$destination_settings = drush_sitealias_evaluate_path($destination, $additional_options);
$source_path = $source_settings['evaluated-path'];
$destination_path = $destination_settings['evaluated-path'];
if (!isset($source_settings)) {
return drush_set_error('DRUSH_BAD_PATH', dt('Could not evaluate source path !path.', array('!path' => $source)));
}
if (!isset($destination_settings)) {
return drush_set_error('DRUSH_BAD_PATH', dt('Could not evaluate destination path !path.', array('!path' => $destination)));
}
// Check to see if this is an rsync multiple command (multiple sources and multiple destinations)
$is_multiple = drush_do_multiple_command('rsync', $source_settings, $destination_settings, TRUE);
if ($is_multiple === FALSE) {
// If the user path is the same for the source and the destination, then
// always add a slash to the end of the source. If the user path is not
// the same in the source and the destination, then you need to know how
// rsync paths work, and put on the trailing '/' if you want it.
if ($source_settings['user-path'] == $destination_settings['user-path']) {
$source_path .= '/';
}
// Prompt for confirmation. This is destructive.
if (!drush_get_context('DRUSH_SIMULATE')) {
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.');
return drush_user_abort();
}
}
// Exclude settings is the default only when both the source and
// the destination are aliases or site names. Therefore, include
// settings will be the default whenever either the source or the
// destination contains a : or a /.
$include_settings_is_default = (strpos($source . $destination, ':') !== FALSE) || (strpos($source . $destination, '/') !== FALSE);
// Go ahead and call rsync with the paths we determined
return drush_core_call_rsync($source_path, $destination_path, $additional_options, $include_settings_is_default);
}
}
/**
* Make a direct call to rsync after the source and destination paths
* have been evaluated.
*
* @param $source
* Any path that can be passed to rsync.
* @param $destination
* Any path that can be passed to rsync.
* @param $additional_options
* An array of options that overrides whatever was passed in on the command
* line (like the 'process' context, but only for the scope of this one
* call).
* @param $include_settings_is_default
* If TRUE, then settings.php will be transferred as part of the rsync unless
* --exclude-conf is specified. If FALSE, then settings.php will be excluded
* from the transfer unless --include-conf is specified.
* @param $live_output
* If TRUE, output goes directly to the terminal using system(). If FALSE,
* rsync is executed with drush_shell_exec() with output in
* drush_shell_exec_output().
*
* @return
* TRUE on success, FALSE on failure.
*/
function drush_core_call_rsync($source, $destination, $additional_options = array(), $include_settings_is_default = TRUE, $live_output = TRUE) {
// Exclude vcs reserved files.
$options = '';
if (!_drush_rsync_option_exists('include-vcs', $additional_options)) {
$vcs_files = drush_version_control_reserved_files();
foreach ($vcs_files as $file) {
$options .= ' --exclude="'.$file.'"';
}
}
$mode = '-az';
// Process --include-path and --exclude-path options the same way
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(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 . '"';
}
}
}
// drush_core_rsync passes in $include_settings_is_default such that
// 'exclude-conf' is the default when syncing from one alias to
// another, and 'include-conf' is the default when a path component
// is included.
if ($include_settings_is_default ? _drush_rsync_option_exists('exclude-conf', $additional_options) : !_drush_rsync_option_exists('include-conf', $additional_options)) {
$options .= ' --exclude="settings.php"';
}
if (_drush_rsync_option_exists('exclude-sites', $additional_options)) {
$options .= ' --include="sites/all" --exclude="sites/*"';
}
if (_drush_rsync_option_exists('mode', $additional_options)) {
$mode = "-" . drush_get_option_override($additional_options, 'mode');
}
if (drush_get_context('DRUSH_VERBOSE')) {
// the drush_op() will be verbose about the command that gets executed.
$mode .= 'v';
$options .= ' --stats --progress';
}
$rsync_available_options = array(
// unary options
'archive', // -a
'recursive', // -r
'relative', // -R
'backup', // -b
'update', // -u
'checksum', // -c
'dirs', // -d
'links', // -l
'copy-links', // -L
'copy-dirlinks', // -k
'keep-dirlinks', // -K
'hard-links', // -H
'perms', // -p
'executability', // -E
'acls', // -A
'xattrs', // -X
'owner', // -o
'group', // -g
'times', // -t
'omit-dir-times', // -O
'sparse', // -S
'dry-run', // -n
'whole-file', // -W
'one-file-system', // -x
'prune-empty-dirs', // -m
'ignore-times', // -I
'fuzzy', // -y
'cvs-exclude', // -C
'compress', // -Z
'protect-args', // -s
'8-bit-output', // -8
'human-readable', // -h
'itemize-changes', // -i
'copy-unsafe-links',
'safe-links',
'no-implied-dirs',
'inplace',
'append',
'append-verify',
'existing',
'remove-source-files',
'delete',
'delete-before',
'delete-during',
'delete-delay',
'delete-after',
'delete-excluded',
'ignore-errors',
'force',
'ignore-existing',
'partial',
'delay-updates',
'numeric-ids',
'size-only',
'blocking-io',
'stats',
'progress',
'list-only',
// options with values
'block-size',
'backup-dir',
'suffix',
'chmod',
'rsync-path',
'modify-window',
'compare-dest',
'copy-dest',
'link-dest',
'skip-compress',
'filter',
'exclude',
'exclude-from',
'include',
'include-from',
'files-from',
'address',
'port',
'sockopts',
'out-format',
'bwlimit',
'iconv',
'checksum-seed',
'max-delete',
'max-size',
'min-size',
'partial-dir',
'timeout',
'temp-dir',
'compress-level',
'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);
// 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";
}
else {
$options .= " --$test_option=" . escapeshellarg($value);
}
}
}
$ssh_options = drush_get_option_override($additional_options, 'ssh-options', '');
$exec = "rsync -e 'ssh $ssh_options' $mode$options $source $destination";
if ($live_output) {
$exec_result = drush_op_system($exec);
$result = ($exec_result == 0);
}
else {
$result = drush_shell_exec($exec);
}
if (!$result) {
drush_set_error('DRUSH_RSYNC_FAILED', dt("Could not rsync from !source to !dest", array('!source' => $source, '!dest' => $destination)));
}
return $result;
}
function _drush_rsync_option_exists($option, $additional_options) {
if (array_key_exists($option, $additional_options)) {
return TRUE;
}
else {
return drush_get_option($option, FALSE);
}
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* @file
* Use this file as a php scratchpad for your Drupal site. You might want to
* load a node, change it, and call node_save($node), for example. If you have
* used the Execute PHP feature of devel.module, this is the drush equivalent.
*
* You may edit this file with whatever php you choose. Then execute the file
* using `drush script scratch.php`. That command will bootstrap your drupal
* site and then run the php below.
*
* 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.
*
*/
// Just some ideas to get the juices flowing.
drush_print_r(user_roles());
drush_print_r($GLOBALS['user']);

View File

@@ -0,0 +1,88 @@
<?php
function drush_core_search_status() {
list($remaining, $total) = _drush_core_search_status();
drush_print(dt('There are @remaining items out of @total still to be indexed.', array(
'@remaining' => $remaining,
'@total' => $total,
)));
drush_print_pipe("$remaining/$total\n");
}
function _drush_core_search_status() {
$remaining = 0;
$total = 0;
if (drush_drupal_major_version() >= 7) {
foreach (module_implements('search_status') as $module) {
$status = module_invoke($module, 'search_status');
$remaining += $status['remaining'];
$total += $status['total'];
}
}
else {
foreach (module_implements('search') as $module) {
// Special case. Apachesolr recommends disabling core indexing with
// search_cron_limit = 0. Need to avoid infinite status loop.
if ($module == 'node' && variable_get('search_cron_limit', 10) == 0) {
continue;
}
$status = module_invoke($module, 'search', 'status');
$remaining += $status['remaining'];
$total += $status['total'];
}
}
return array($remaining, $total);
}
function drush_core_search_index() {
drush_print(dt("Building the index may take a long time."));
if (!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
drush_op('_drush_core_search_index');
drush_log(dt('The search index has been built.'), 'ok');
}
function _drush_core_search_index() {
list($remaining, ) = _drush_core_search_status();
register_shutdown_function('search_update_totals');
while ($remaining > 0) {
drush_log(dt('Remaining items to be indexed: ' . $remaining), 'ok');
// Use drush_backend_invoke() to start subshell. Avoids out of memory issue.
$eval = "register_shutdown_function('search_update_totals');";
if (drush_drupal_major_version() >= 7) {
foreach (variable_get('search_active_modules', array('node', 'user')) as $module) {
$eval .= " module_invoke($module, 'update_index');";
}
}
else {
$eval .= " module_invoke_all('update_index');";
}
drush_backend_invoke('php-eval', array($eval));
list($remaining, ) = _drush_core_search_status();
}
}
function drush_core_search_reindex() {
drush_print(dt("The search index must be fully rebuilt before any new items can be indexed."));
if (drush_get_option('immediate')) {
drush_print(dt("Rebuilding the index may take a long time."));
}
if (!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
if (drush_drupal_major_version() >= 7) {
drush_op('search_reindex');
}
else {
drush_op('search_wipe');
}
if (drush_get_option('immediate')) {
drush_op('_drush_core_search_index');
drush_log(dt('The search index has been rebuilt.'), 'ok');
}
else {
drush_log(dt('The search index will be rebuilt.'), 'ok');
}
}

View File

@@ -0,0 +1,133 @@
<?php
/**
* Command validate.
*/
function drush_core_site_install_validate() {
if ($sites_subdir = drush_get_option('sites-subdir')) {
$lower = strtolower($sites_subdir);
if ($sites_subdir != $lower) {
drush_log(dt('Only lowercase sites-subdir are valid. Switching to !lower.', array('!lower' => $lower)), 'warning');
drush_set_option('sites-subdir', $lower);
}
}
}
/**
* Perform setup tasks for installation.
*/
function drush_core_pre_site_install() {
if (!$db_spec = drush_sql_read_db_spec()) {
drush_set_error(dt('Could not determine database connection parameters. Pass --db-url option.'));
return;
}
if ($sites_subdir = drush_get_option('sites-subdir')) {
// Needed so that we later bootstrap into the right site.
drush_set_option('uri', 'http://'.$sites_subdir);
}
else {
$sites_subdir = 'default';
}
$conf_path = "sites/$sites_subdir";
$files = "$conf_path/files";
$settingsfile = "$conf_path/settings.php";
if (!file_exists($files)) {
$msg[] = dt('create a @files directory', array('@files' => $files));
}
if (!file_exists($settingsfile)) {
$msg[] = dt('create a @settingsfile file', array('@settingsfile' => $settingsfile));
}
$msg[] = dt("DROP your '@db' database and then CREATE a new one.", array('@db' => $db_spec['database']));
if (!drush_confirm(dt('You are about to ') . implode(dt(' and '), $msg) . ' Do you want to continue?')) {
return drush_user_abort();
}
// Can't install without sites directory and settings.php.
if (!file_exists($conf_path)) {
if (!drush_op('mkdir', $conf_path) && !drush_get_context('DRUSH_SIMULATE')) {
drush_set_error(dt('Failed to create directory @conf_path', array('@conf_path' => $conf_path)));
return;
}
}
else {
drush_log(dt('Sites directory @subdir already exists - proceeding.', array('@subdir' => $conf_path)));
}
if (!file_exists($settingsfile)) {
if (!drush_op('copy', 'sites/default/default.settings.php', $settingsfile) && !drush_get_context('DRUSH_SIMULATE')) {
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. In D7+, the installer does it.
file_put_contents($settingsfile, "\n" . '$db_url = \'' . drush_get_option('db-url') . "';\n", FILE_APPEND);
// Instead of parsing and performing string replacement on the configuration file,
// the options are appended and override the defaults.
// Database table prefix
if (!empty($db_spec['db_prefix'])) {
if (is_array($db_spec['db_prefix'])) {
// Write db_prefix configuration as an array
$db_prefix_config = '$db_prefix = ' . var_export($db_spec['db_prefix'], TRUE) . ';';
}
else {
// Write db_prefix configuration as a string
$db_prefix_config = '$db_prefix = \'' . $db_spec['db_prefix'] . '\';';
}
file_put_contents($settingsfile, "\n" . $db_prefix_config . "\n", FILE_APPEND);
}
}
}
// Add a files dir if needed
if (!file_exists($files)) {
if (!drush_op('mkdir', $files) && !drush_get_context('DRUSH_SIMULATE')) {
drush_set_error(dt('Failed to create directory @name', array('@name' => $files)));
return;
}
}
// Now we can bootstrap up to the specified site.
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
// Drop and create DB if needed.
$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)));
}
}
}
else {
drush_sql_empty_db($db_spec);
}
return TRUE;
}
/**
* Command callback.
*/
function drush_core_site_install($profile = NULL) {
$args = func_get_args();
$form_options = array();
if ($args) {
// The first argument is the profile.
$profile = array_shift($args);
// Subsequent arguments are additional form values.
foreach ($args as $arg) {
list($key, $value) = explode('=', $arg);
$form_options[$key] = $value;
}
}
drush_include_engine('drupal', 'site_install', drush_drupal_major_version());
drush_core_site_install_version($profile, $form_options);
}

View File

@@ -0,0 +1,285 @@
<?php
/**
* @file
* Site alias commands. @see example.drushrc.php for details.
*/
function sitealias_drush_help($section) {
switch ($section) {
case 'drush:site-alias':
return dt('Print an alias record.');
}
}
function sitealias_drush_command() {
$items = array();
$items['site-alias'] = array(
'callback' => 'drush_sitealias_print',
'description' => 'Print site alias records for all known site aliases and local sites.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'arguments' => array(
'site' => 'Site specification alias to print',
),
'options' => array(
'full' => 'Print the full alias record for each site. Default when aliases are specified on the command line.',
'component' => 'Print only the specified element from the full alias record.',
'short' => 'Print only the site alias name. Default when not command line arguments are specified.',
'pipe' => 'Print the long-form site specification for each site.',
'with-db' => 'Include the databases structure in the full alias record.',
'with-db-url' => 'Include the short-form db-url in the full alias record.',
'no-db' => 'Do not include the database record in the full alias record (default).',
'with-optional' => 'Include optional default items.',
),
'aliases' => array('sa'),
'examples' => array(
'drush site-alias' => 'List all alias records known to drush.',
'drush site-alias @dev' => 'Print an alias record for the alias \'dev\'.',
),
'topics' => array('docs-aliases'),
);
return $items;
}
/**
* Return a list of all site aliases known to drush.
*
* The array key is the site alias name, and the array value
* is the site specification for the given alias.
*/
function _drush_sitealias_alias_list() {
return drush_get_context('site-aliases');
}
/**
* Return a list of all of the local sites at the current drupal root.
*
* The array key is the site folder name, and the array value
* is the site specification for that site.
*/
function _drush_sitealias_site_list() {
$site_list = array();
$base_path = drush_get_context('DRUSH_DRUPAL_ROOT') . '/sites';
$files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all'));
foreach ($files as $filename => $info) {
if ($info->basename == 'settings.php') {
$alias_record = drush_sitealias_build_record_from_settings_file($filename);
if (!empty($alias_record)) {
$site_list[drush_sitealias_uri_to_site_dir($alias_record['uri'])] = $alias_record;
}
}
}
return $site_list;
}
/**
* Return the list of all site aliases and all local sites.
*/
function _drush_sitealias_all_list() {
drush_sitealias_load_all();
return array_merge(_drush_sitealias_alias_list(), _drush_sitealias_site_list());
}
/**
* Return the list of sites (aliases or local) that the
* user specified on the command line. If none were specified,
* then all are returned.
*/
function _drush_sitealias_user_specified_list() {
$command = drush_get_command();
$specifications = $command['arguments'];
$site_list = array();
// Did the user specify --short or --full output?
$specified_output_style = drush_get_option(array('full', 'short'), FALSE);
// Iterate over the arguments and convert them to alias records
if (!empty($specifications)) {
$site_list = drush_sitealias_resolve_sitespecs($specifications);
if (!$specified_output_style) {
drush_set_option('full', TRUE);
}
}
// 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;
}
/**
* Print out the specified site aliases using the format
* specified.
*/
function drush_sitealias_print() {
// Call bootstrap max, unless the caller requested short output
if (!drush_get_option('short', FALSE)) {
drush_bootstrap_max();
}
$site_list = _drush_sitealias_user_specified_list();
$full_output = drush_get_option('full');
$long_output = drush_get_option('long');
$with_db = (drush_get_option('with-db') != null) || (drush_get_option('with-db-url') != null);
$site_specs = array();
foreach ($site_list as $site => $alias_record) {
if (!array_key_exists('site-list', $alias_record)) {
$site_specs[] = drush_sitealias_alias_record_to_spec($alias_record, $with_db);
}
if (isset($full_output)) {
$component = drush_get_option('component');
if ($component) {
if (array_key_exists($component, $alias_record)) {
drush_print($alias_record[$component]);
}
else {
drush_set_error('DRUSH_NO_SUCH_ELEMENT', dt('The element @component was not found in the alias record for @site.', array('@component' => $component, '@site' => $site)));
}
}
else {
_drush_sitealias_print_record($alias_record, $site);
}
}
else {
drush_print($site);
}
}
drush_print_pipe(array_unique($site_specs));
}
/**
* Given a site alias name, print out a php-syntax
* representation of it.
*
* @param alias_record
* The name of the site alias to print
*/
function _drush_sitealias_print_record($alias_record, $site_alias = '') {
$output_db = drush_get_option('with-db');
$output_db_url = drush_get_option('with-db-url');
$output_optional_items = drush_get_option('with-optional');
// Make sure that the default items have been added for all aliases
_drush_sitealias_add_static_defaults($alias_record);
// Include the optional items, if requested
if ($output_optional_items) {
_drush_sitealias_add_transient_defaults($alias_record);
}
drush_sitealias_resolve_path_references($alias_record);
if (isset($output_db_url)) {
drush_sitealias_add_db_url($alias_record);
}
if (isset($output_db_url) || isset($output_db)) {
drush_sitealias_add_db_settings($alias_record);
}
// If the user specified --with-db-url, then leave the
// 'db-url' entry in the alias record (unless it is not
// set, in which case we will leave the 'databases' record instead).
if (isset($output_db_url)) {
if (isset($alias_record['db-url'])) {
unset($alias_record['databases']);
}
}
// If the user specified --with-db, then leave the
// 'databases' entry in the alias record.
else if (isset($output_db)) {
unset($alias_record['db-url']);
}
// If neither --with-db nor --with-db-url were specified,
// then remove both the 'db-url' and the 'databases' entries.
else {
unset($alias_record['db-url']);
unset($alias_record['databases']);
}
// The alias name will be the same as the site alias name,
// unless the user specified some other name on the command line.
$alias_name = drush_get_option('alias-name');
if (!isset($alias_name)) {
$alias_name = $site_alias;
if (empty($alias_name) || is_numeric($alias_name)) {
$alias_name = drush_sitealias_uri_to_site_dir($alias_record['uri']);
}
}
// We don't want the name to go into the output
unset($alias_record['#name']);
unset($alias_record['#hidden']);
// We only want to output the 'root' item; don't output the '%root' path alias
if (array_key_exists('path-aliases', $alias_record) && array_key_exists('%root', $alias_record['path-aliases'])) {
unset($alias_record['path-aliases']['%root']);
// If there is nothing left in path-aliases, then clear it out
if (count($alias_record['path-aliases']) == 0) {
unset($alias_record['path-aliases']);
}
}
// Alias names contain an '@' when referenced, but do
// not contain an '@' when defined.
if (substr($alias_name,0,1) == '@') {
$alias_name = substr($alias_name,1);
}
if (!drush_get_option('show-passwords', FALSE)) {
drush_unset_recursive($alias_record, 'password');
}
$exported_alias = var_export($alias_record, TRUE);
drush_print('$aliases[\'' . $alias_name . '\'] = ' . $exported_alias . ';');
}
/**
* Use heuristics to attempt to convert from a site directory to a URI.
* This function should only be used when the URI really is unknown, as
* the mapping is not perfect.
*
* @param site_dir
* A directory, such as domain.com.8080.drupal
*
* @return string
* A uri, such as http://domain.com:8080/drupal
*/
function _drush_sitealias_site_dir_to_uri($site_dir) {
// Protect IP addresses NN.NN.NN.NN by converting them
// temporarily to NN_NN_NN_NN for now.
$uri = preg_replace("/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/", "$1_$2_$3_$4", $site_dir);
// Convert .[0-9]+. into :[0-9]+/
$uri = preg_replace("/\.([0-9]+)\./", ":$1/", $uri);
// Convert .[0-9]$ into :[0-9]
$uri = preg_replace("/\.([0-9]+)$/", ":$1", $uri);
// Convert .(com|net|org|info). into .(com|net|org|info)/
$uri = str_replace(array('.com.', '.net.', '.org.', '.info.'), array('.com/', '.net/', '.org/', '.info/'), $uri);
// If there is a / then convert every . after the / to /
// Then again, if we did this we would break if the path contained a "."
// I hope that the path would never contain a "."...
$pos = strpos($uri, '/');
if ($pos !== false) {
$uri = substr($uri, 0, $pos + 1) . str_replace('.', '/', substr($uri, $pos + 1));
}
// n.b. this heuristic works all the time if there is a port,
// it also works all the time if there is a port and no path,
// but it does not work for domains such as .co.jp with no path,
// and it can fail horribly if someone makes a domain like "info.org".
// Still, I think this is the best we can do short of consulting DNS.
// Convert from NN_NN_NN_NN back to NN.NN.NN.NN
$uri = preg_replace("/([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+)/", "$1.$2.$3.$4", $site_dir);
return 'http://' . $uri;
}

View File

@@ -0,0 +1,268 @@
<?php
/**
* @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.',
'xml' => 'Output verbose test results to a specified directory using the JUnit test reporting format. Useful for integrating with Jenkins.'
),
'drupal dependencies' => array('simpletest'),
// If you DRUSH_BOOTSTRAP_DRUPAL_LOGIN, you fall victim to http://drupal.org/node/974768. We'd like
// to not bootstrap at all but simpletest uses Drupal to discover test classes,
// cache the lists of tests, file_prepare_directory(), variable lookup like
// httpauth creds, copy pre-built registry table from testing side, etc.
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
);
$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')) {
// No longer needed?
// 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 ($dir = drush_get_option('xml')) {
drush_test_xml_results($test_id, $dir);
}
// 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);
}
/*
* Display test results.
*/
function drush_test_xml_results($test_id, $dir) {
$dir = is_string($dir) ? $dir : '.';
$results_map = array(
'pass' => 'Pass',
'fail' => 'Fail',
'exception' => 'Exception',
);
if (drush_drupal_major_version() >= 7) {
$results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
}
else {
$result = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $test_id);
$results = array();
while ($row = db_fetch_object($result)) {
$results[] = $row;
}
}
$test_class = '';
$xml_files = array();
foreach ($results as $result) {
if (isset($results_map[$result->status])) {
if ($result->test_class != $test_class) {
// Display test class every time results are for new test class.
if (isset($xml_files[$test_class])) {
file_put_contents($dir . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
unset($xml_files[$test_class]);
}
$test_class = $result->test_class;
if (!isset($xml_files[$test_class])) {
$doc = new DomDocument('1.0');
$root = $doc->createElement('testsuite');
$root = $doc->appendChild($root);
$xml_files[$test_class] = array('doc' => $doc, 'suite' => $root);
}
}
// Save the result into the XML:
$case = $xml_files[$test_class]['doc']->createElement('testcase');
$case->setAttribute('classname', $test_class);
list($class, $name) = explode('->', $result->function, 2);
$case->setAttribute('name', $name);
if ($result->status == 'fail') {
$fail = $xml_files[$test_class]['doc']->createElement('failure');
$fail->setAttribute('type', 'failure');
$fail->setAttribute('message', $result->message_group);
$text = $xml_files[$test_class]['doc']->createTextNode($result->message);
$fail->appendChild($text);
$case->appendChild($fail);
}
$xml_files[$test_class]['suite']->appendChild($case);
}
}
// Save the last one:
if (isset($xml_files[$test_class])) {
file_put_contents($dir . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
unset($xml_files[$test_class]);
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* @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 docs-context' => 'Show documentation for the drush context API',
'drush docs-context' => 'Show documentation for the drush context API',
),
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'aliases' => array('topic'),
'topics' => array('docs-readme'),
);
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'] = 'Topics';
$command['topic_section'][$topic_name] = $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_topic_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'];
}
natcasesort($choices);
if (!$topic_name = drush_choice($choices, dt('Choose a topic'), '!value (!key)')) {
return;
}
}
// If the topic name is not found, check for
// "docs-$topic_name". This allows users to be
// just a bit lazy when selecting core topics by name.
if (!isset($commands[$topic_name]) && isset($commands["docs-$topic_name"])) {
$topic_name = "docs-$topic_name";
}
return drush_dispatch($commands[$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;
}

View File

@@ -0,0 +1,703 @@
<?php
/**
* @file
* Refine your Drupal major version upgrade.
*/
/**
* Implement hook_drush_command().
*/
function upgrade_drush_command() {
$items = array();
$items['site-upgrade'] = array(
'description' => 'Run a major version upgrade for Drupal (e.g. Drupal 6 to Drupal 7). A copy of the site is made, and then upgraded; the source site is not changed.',
'drupal dependencies' => array('update'),
'drush dependencies' => array('sql', 'pm', 'core'),
'core' => array(6), // Add 7 once drush supports 7 -> 8 upgrades.
'arguments' => array(
'target' => "The name of a sitealias, which points to the destination site. 'root' and 'uri' keys are required; db-url is recommended. 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.',
),
'options' => array(
'structure-tables-key' => 'A key in the structure-tables array. @see example.drushrc.php. Defaults to \'common\'.',
'source-dump' => 'Path to dump file. Medium or large sized sites should set this. Optional; default is to create a temporary file.',
'db-su' => 'DB username to use when dropping and creating the target database. Optional.',
'db-su-pw' => 'DB password to use when dropping and creating the target database. Optional.',
'no-cache' => 'Transfer a fresh database from source site. Otherwise, DB dump is re-used for 24 hours.',
'core-only' => 'Stop after upgrading Drupal core; do not download and enable new versions of the site\'s modules.',
'force-sites-default' => 'Forces settings.php to be written in sites/default folder, even if source settings.php is not.',
'replace' => 'Replace target if it already exists. Default is to prompt.',
'reuse' => 'Reuse target if it already exists. Default is to prompt.',
'uninstall' => 'Comma-separated list of modules to uninstall in the target database prior to upgrade. n.b. The source site is not affected.',
),
'aliases' => array('sup'),
'topics' => array('docs-aliases'),
);
$items['site-upgrade-prepare'] = array(
'description' => 'Prior to running updatedb on Drupal core, disable all contrib modules and uninstall any module the user specified should be uninstalled. Called automatically by site-upgrade.',
'hidden' => TRUE,
'arguments' => array(
'modules' => 'The modules to disable.',
),
'options' => array(
'uninstall' => 'Comma-separated list of modules to uninstall prior to upgrade.',
),
);
$items['site-upgrade-modules'] = array(
'description' => 'Download, enable, and run updatedb on all contrib modules after an upgrade. Called automatically by site-upgrade.',
'hidden' => TRUE,
'arguments' => array(
'modules' => 'The modules to download and enable.',
),
'options' => array(
'projects' => 'The projects that must be downloaded to provide the specified module list.',
),
);
return $items;
}
/**
* Implement hook_drush_help().
*/
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. 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). Note that this command uses pm-download and sql-sync internally so most options for those commands are valid here too.");
}
}
/**
* Do some sanity checks to make sure that we are ready to perform an upgrade, and
* that the command is being called with reasonable-looking parameters.
*/
function drush_upgrade_site_upgrade_validate($target_key = NULL) {
if (empty($target_key)) {
return drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Missing argument: target'));
}
if (!$target_alias = drush_sitealias_get_record($target_key)) {
return drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Site alias not found: @target-key. See example.drushrc.php.', array('@target-key' => $target_key)));
}
if (!file_exists(dirname($target_alias['root']))) {
drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Parent directory for site alias root not found: @root; this folder must exist before running site-upgrade. See example.drushrc.php.', array('@root' => dirname($target_alias['root']))));
}
if (realpath($target_alias['root']) == realpath(DRUPAL_ROOT)) {
drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Target site alias must have a different Drupal root directory than the source site. Both are at @root.', array('@root' => $target_alias['root'])));
}
}
/**
* Main command hook for site-upgrade.
*
* This runs bootstrapped to the SOURCE site.
*/
function drush_upgrade_site_upgrade($target_key) {
// Presume we are ready to go (n.b. some checks already performed in 'validate')
$ready_to_upgrade = TRUE;
$result = TRUE;
// PREPARE: Find the target version and determine the contrib projects and enabled modules installed.
$source_version = drush_drupal_major_version();
$target_version = $source_version + 1;
$target_alias = drush_sitealias_get_record($target_key);
if (empty($target_alias)) {
return drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt("Could not find target site for upgrade: !target", array("!target" => $target_key)));
}
$destination_core = $target_alias['root'];
$destination_conf_path = conf_path();
if (drush_get_option('force-sites-default')) {
$destination_conf_path = 'sites/default';
}
// Get a list of enabled contrib extensions.
$values = drush_invoke_process_args('pm-list', array(), array('status'=>'enabled','no-core'=>TRUE, '#integrate' => FALSE, '#override-simulated' => TRUE));
if ($values['error_status'] != 0) return FALSE;
$contrib_extensions = array_keys($values['object']);
// Get a list of enabled core extensions.
$values = drush_invoke_process_args('pm-list', array(), array('status'=>'enabled','core'=>TRUE, '#integrate' => FALSE, '#override-simulated' => TRUE));
if ($values['error_status'] != 0) return FALSE;
$core_extensions = array_keys($values['object']);
// Make a list of modules that are not available to be enabled in the target
// version of Drupal, either because they have not been ported yet, or because
// they have been rolled into core.
$unavailable_extensions = array();
// Get the list of modules the user would like to uninstall (if any).
$uninstall_extensions = drush_get_option('uninstall', '');
if ($uninstall_extensions == "all") {
$uninstall_extensions = $contrib_extensions;
}
else {
$uninstall_extensions = explode(',', $uninstall_extensions);
}
// WARN: Tell the user about any special situations that might exist with contrib modules.
$project_download_list = array();
$extension_info = drush_pm_get_extensions();
if (!empty($contrib_extensions) && !drush_get_option('core-only')) {
// Make a list of all of the extensions to download. We will
// download everything in the contrib extension list, but we
// will skip projects that already exist.
$special_projects = drush_upgrade_project_map($target_version);
$in_core = array();
$special_warning = array();
foreach ($contrib_extensions as $extension_name) {
// Only check extensions that are NOT in our uninstall list.
if (!in_array($extension_name, $uninstall_extensions) && array_key_exists($extension_name, $extension_info)) {
$project = $extension_info[$extension_name]->info['project'];
// Check our lookup table to see if a project has been renamed.
if (array_key_exists($project, $special_projects['project-remap'])) {
$project = $special_projects['project-remap'][$project];
}
// If the module has been rolled into core in the next major release of
// Drupal, then we do not need to download it. Add it to an array for
// reporting purposes.
if ((in_array($project, $special_projects['projects-in-core'])) || (in_array($extension_name, $special_projects['modules-in-core']))) {
$in_core[$extension_name] = $extension_name;
// Might some of these need to be enabled?
$unavailable_extensions[] = $extension_name;
}
elseif (($extension_info[$extension_name]->type == 'module') && !is_dir($destination_core . '/sites/all/modules/' . $project) && !is_dir($destination_core . '/' . $destination_conf_path . '/modules/' . $project)) {
$project_download_list[$project][] = $extension_name;
}
// If there is a special warning about a project, then add it
// to the warning list for later reporting.
if (array_key_exists($project . ':' . $extension_name, $special_projects['warning'])) {
$special_warning[$project] = $special_projects['warning'][$project . ':' . $extension_name];
}
elseif ((array_key_exists($project, $special_projects['warning'])) && (!array_key_exists($project, $special_warning))) {
$special_warning[$project] = $special_projects['warning'][$project];
}
}
}
// Consider each project from the contrib extensions and check with PM to see if there is
// a recommended release available to download. If there is NO release available, then
// we are not ready to upgrade (but still can, without that project); if there is no RECOMMENDED
// release, then we might not be ready to upgrade (but still can, with a non-recommended release).
if (!empty($project_download_list)) {
$result = drush_invoke_sitealias_args(array('root' => '', 'uri' => ''), 'pm-releases', array_keys($project_download_list), array('default-major' => $target_version, '#integrate' => FALSE, '#override-simulated' => TRUE));
$project_releases = $result['object'];
foreach ($project_download_list as $project => $extension_list) {
if (!array_key_exists($project, $project_releases)) {
drush_log(dt('The project !project has no releases in version !version', array('!project' => $project, '!version' => $target_version)), 'warning');
$unavailable_extensions = array_merge($unavailable_extensions, $extension_list);
$ready_to_upgrade = FALSE;
}
else {
if (empty($project_releases[$project]['recommended'])) {
drush_log(dt('The project !project has no recommended release in version !version', array('!project' => $project, '!version' => $target_version)), 'warning');
$ready_to_upgrade = 'maybe';
}
}
}
}
// Print out some messages about projects that migrated to core, or modules that will require special processing.
if (!empty($in_core)) {
drush_log(dt('The following contrib modules were enabled in your Drupal site, but are now standard in core: !in_core. These modules may need to be reconfigured after the upgrade is complete.', array('!in_core' => implode(', ', $in_core))), 'ok');
}
foreach ($special_warning as $project => $warning) {
if ($warning === TRUE) {
$warning = 'Please see !project_page and !source for more information on how to do this.';
}
if ($warning === FALSE) {
$warning = 'So far there is no indication of when a migration path will be provided. Check !project_page for updates.';
$ready_to_upgrade = 'maybe';
}
drush_log(dt("You are using the project !project, which requires data migration or other special processing. $warning", array('!project' => $project, '!project_page' => 'http://drupal.org/project/' . $project, '!source' => $special_projects['source'])), 'warning');
}
}
// CONFIRM: Ask the user before overwriting an exsiting site, and ask if an upgrade is really decided if the site is not ready yet.
// Summarize whether or not there is a good chance that the site can be upgraded.
if ($ready_to_upgrade !== TRUE) {
drush_log(dt("Based on the contrib modules enabled in this site, it is possible that the site-upgrade command might fail. See warnings above."), (($ready_to_upgrade === FALSE) ? 'warning' : 'notice'));
}
// Check to see what we should do if the target Drupal folder already exists.
$options = array(
'replace' => dt("Delete the existing site and start over."),
'reuse' => dt("Re-use the existing code, re-import the database from the source site and run updatedb again."),
);
$selection = NULL;
foreach ($options as $option => $msg) {
if (drush_get_option($option, FALSE)) {
$selection = $option;
}
}
if (!isset($selection) && (file_exists($destination_core))) {
$selection = drush_choice($options, dt("Drupal site already exists at !root. Would you like to:", array('!root' => $destination_core)));
if (!$selection) {
return drush_user_abort();
}
}
elseif($ready_to_upgrade !== TRUE) {
if (!drush_confirm('Would you like to continue anyway?')) {
return drush_user_abort();
}
}
// User has already been prompted; skip further confirms.
drush_set_context('DRUSH_AFFIRMATIVE', TRUE);
// We need to know where our destination settings file is regardless of which
// code path we take; therefore, we will precompute it here.
$settings_destination = $destination_core . '/' . $destination_conf_path . '/settings.php';
// STEP 1: Download the next major version of Drupal.
if (($selection == 'replace') || (!is_dir($destination_core))) {
drush_upgrade_download_drupal($target_version, $destination_core);
if (drush_get_error()) return FALSE; // Early exit if we see an error.
drush_upgrade_copy_settings($target_alias, $settings_destination);
}
else {
// Move sites/all/modules and $conf_path()/modules out of the way
// so that updatedb can be run on core only.
if (_drush_upgrade_preserve_modules($destination_core) === FALSE) {
return FALSE;
}
}
// Copy source database to target database. The source DB is not changed.
// Always set 'common' at minimum. Sites that want other can create other key in drushrc.php.
if (!drush_get_option('structure-tables-key')) {
drush_set_option('structure-tables-key', 'common');
}
// Always blow away the target database so we start fresh.
// We still have DRUSH_AFFIRMATIVE set from above, so this will not prompt.
drush_set_option('create-db', TRUE);
drush_include(DRUSH_BASE_PATH . '/commands/sql', 'sync.sql');
drush_invoke('sql-sync', '@self', $target_key);
if (drush_get_error()) return FALSE; // Early exit if we see an error.
if (!empty($contrib_extensions)) {
$target_alias_databases = sitealias_get_databases_from_record($target_alias);
$modify_site_conf_path = NULL;
// Make an alias record that uses the CODE from @self and the DATABASE from $target.
// Since we just did an sql-sync from @self to @target, we can use this hybrid specification
// to do manipulations on the target database before runing updatedb. In brief, we are going
// to disable all contrib modules to prevent problems with updatedb.
$modify_site = array (
'root' => DRUPAL_ROOT,
'uri' => $target_alias_databases['default']['default']['database'],
);
if (!drush_get_context('DRUSH_SIMULATE')) {
// In theory, if the sql-sync worked, this should never be empty.
if (empty($modify_site['uri'])) {
return drush_set_error('DRUSH_UPGRADE_DATABASE_SPEC_UNKNOWN', dt('Failed to look up database spec for @target', array('@target' => $target_key)));
}
$modify_site_conf_path = dirname(conf_path()) . '/' . $modify_site['uri'];
$modify_site_settings = $modify_site_conf_path . '/settings.php';
drush_log('set up a fake site by copying ' . $settings_destination . ' to ' . $modify_site_settings);
if (!file_exists($modify_site_settings)) {
if ((drush_mkdir($modify_site_conf_path) === FALSE) || drush_op('copy', $settings_destination, $modify_site_settings) !== TRUE) {
return drush_set_error('DRUSH_UPGRADE_COULD_NOT_DISABLE', dt("Could not create a temporary multisite "));
}
}
}
$result = (drush_invoke_sitealias_args($modify_site, 'site-upgrade-prepare', $contrib_extensions, array('uninstall' => implode(',', $uninstall_extensions), 'yes' => TRUE, '#interactive' => TRUE)) == 0);
// Delete the temporary site now that we're done with it.
if (isset($modify_site_conf_path)) {
drush_delete_dir($modify_site_conf_path);
}
if ($result === FALSE) return FALSE;
}
// STEP 2: Call updatedb for Drupal core.
// Run update.php in a subshell. It is run on @target site whereas this request was on @self.
drush_log(dt('About to perform updatedb for Drupal core on !target', array('!target' => $target_key)), 'ok');
// When we call drush_invoke_sitealias_args in #interactive mode, the result code comes from drush_op_system, where 0 == success.
$result = drush_invoke_sitealias_args($target_alias, 'updatedb', array(), array('yes' => TRUE, '#interactive' => TRUE)) == 0;
if ($result === FALSE) {
return drush_set_error('DRUSH_DRUPAL_CORE_UPGRADE_FAILED', dt("The call to updatedb failed for Drupal core. This may be caused by a contrib module that is not yet ready for upgrade. Try running site-upgrade again with '--uninstall={module list}' to remove all contrib extensions prior to upgrade. Add modules back in until the problematic one is isolated. Please report problems in the issue queue of the module that is causing problems."));
}
drush_log(dt('updatedb complete for Drupal core'), 'ok');
// If we moved our modules out of the way, bring them back now.
_drush_upgrade_restore_preserved_modules();
// STEP 3: Download and re-enable the contrib modules.
if (!empty($contrib_extensions) && !drush_get_option('core-only')) {
$options = array('#interactive' => TRUE);
if (!empty($project_download_list)) {
$projects = implode(',', array_keys($project_download_list));
$options['projects'] = $projects;
}
// If a module changed name, then rename it prior to calling pm-enable.
foreach ($contrib_extensions as $extension_name) {
if (array_key_exists($extension_name, $special_projects['module-remap'])) {
$unavailable_extensions[] = $extension_name;
$contrib_extensions[] = $special_projects['module-remap'][$extension_name];
}
}
// Redispatch to site-upgrade-modules command, so that we will be
// bootstrapped to the target site.
$result = (drush_invoke_sitealias_args($target_alias, 'site-upgrade-modules', array_merge($core_extensions, array_diff($contrib_extensions, $unavailable_extensions, $uninstall_extensions)), $options) == 0);
}
return $result;
}
/**
* http://drupal.org/node/895314 lists projects that are now in
* core; it also has a list of projects that require special handling.
* Keep a record here too.
*
* @param $target_version
* The version of Drupal being upgraded to.
* @return @array
* - source URL to the page where more information about this upgrade can be found.
* - target-version The version of Drupal being upgraded to.
* - projects-in-core An array containing projects that were once contrib that are now in core.
* - modules-in-core An array containing modules that were once in contrib that are now in core.
* Use 'modules-in-core' in place of 'projects-in-core' only if there is a
* project where only some of its subcomponents were moved to core.
* - warning An array of warning messages to display to the user related to this upgrade.
* The key should be the name of the project that the warning applies to.
* use 'project:module' as the key if the warning only applies when the given
* module in the project is enabled; otherwise, the warning will be shown whenever
* any module in the specific project is enabled. The value can either be the
* warning string to display, or TRUE to tell the user to see the project page
* for information on the migration path, or FALSE to tell the user that there
* currently is no migration path for the given project.
*/
function drush_upgrade_project_map($target_version) {
$result = array(
'source' => '',
'target-version' => $target_version,
'projects-in-core' => array(),
'modules-in-core' => array(),
'warning' => array(),
);
if ($target_version == 7) {
$result = array(
'source' => 'http://drupal.org/node/895314',
'projects-in-core' => array(
'abssrc',
'admin_hover',
'adminrole',
'ahah_helper',
'autoload',
'automaticmenu',
'automenu',
'auto_menutitle',
'block_edit',
'block_node_visibility',
'blocks404',
'canonical_url',
'checkbox_validate',
'comment_display',
'config_perms',
'ctm',
'dbtng',
'documentation',
'drupal_queue',
'edit_term',
'elements',
'element_themehook',
'filefield',
'filter_default',
'hook_file',
'imageapi',
'imagecache',
'imagefield',
'input_format_permissions',
'jq',
'jqp',
'jquery_cookie',
'jquery_form_update',
'jquery_ui',
'jsalter',
'login_security',
'menuless_nodetype',
'nodepreview_by_type',
'parallel',
'permissions_api',
'phpass',
'plugin_manager',
'plugins',
'poormanscron',
'preview',
'rdf',
'shortlink',
'simplecdn',
'simpletest',
'storage_api',
'tar',
'taxidermy',
'tinymce_ahah',
'tinymce_dragdrop',
'transaction',
'upload_element',
'upload_preview',
'url_alter',
'user_cancellation',
'user_default_filter',
'user_delete',
'vertical_tabs',
'view_unpublished',
'vocabperms',
'wysiwygcck',
),
'modules-in-core' => array(
// 'date' project is still contrib, but date_timezone, one of its modules, has moved to core. See http://drupal.org/node/868028.
'date_timezone',
),
'project-remap' => array(),
'module-remap' => array(
'content' => 'cck',
),
'warning' => array(
'token' => dt('In Drupal 7, the contrib token module handles UI, as well as field and profile tokens; all other functionality has been migrated to core.'),
'cck' => TRUE,
'content_taxonomy' => dt('The project page for this module (!project_page) says that an upgrade path will be provided for this module at a future time.'),
'date:date_api' => dt("The d6 version of the date_api module in the date project defines a table called date_formats, which is also defined by system/system.install schema in d7. If this problem has not been fixed yet, then the updatedb function will fail, and it will not be possible to upgrade to d7. If this happens, disable and uninstall the date_api module before running site-upgrade (i.e. add '--uninstall=date_api' to site-upgrade call). See http://drupal.org/node/1013034."),
'field_taxonomy' => FALSE,
'filefield' => dt('Data migration for this module will be provided by the Content Migrate submodule of cck. Enable content_migrate after upgrading; see http://drupal.org/node/781088.'),
'imagefield' => dt('Data migration for this module will be provided by the Content Migrate submodule of cck. Enable content_migrate after upgrading; see http://drupal.org/node/781088.'),
'taxonomy_delegate' => FALSE,
'taxonomy_intro' => FALSE,
'term_fields' => FALSE,
),
);
}
drush_command_invoke_all_ref('drush_upgrade_project_map_alter', $result);
return $result;
}
/**
* Prepare to upgrade; the first step is to disable all contrib modules.
*/
function drush_upgrade_site_upgrade_prepare() {
$contrib_extensions = func_get_args();
$uninstall_extensions = explode(',', drush_get_option('uninstall', ''));
// Set theme back to garland per Upgrade.txt.
variable_set('theme_default', 'garland');
// http://drupal.org/node/724102 recommends using "seven" as your admin theme. Don't switch it to garland if it is already seven.
$admin_theme = variable_get('admin_theme', NULL);
if ($admin_theme != "seven") {
variable_set('admin_theme', 'garland');
}
else {
drush_log(dt("Admin theme is already set to 'seven'."), 'ok');
}
// Disable all contrib modules per Upgrade.txt.
drush_log(dt("Disabling !list", array('!list' => implode(", ", $contrib_extensions))), 'ok');
call_user_func_array('drush_pm_disable', $contrib_extensions);
if (drush_get_error()) return FALSE; // Early exit if we see an error.
// Uninstall any modules specified via the --uninstall flag.
if (!empty($uninstall_extensions)) {
drush_log(dt("Uninstalling !list", array('!list' => implode(", ", $uninstall_extensions))), 'ok');
call_user_func_array('drush_pm_uninstall', $uninstall_extensions);
if (drush_get_error()) return FALSE; // Early exit if we see an error.
}
}
/**
* Upgrade all of the contrib modules of the site being upgraded.
*
* This runs bootstrapped to the TARGET site, after the new version
* of Drupal has been downloaded, and after updatedb has been run
* for Drupal core.
*/
function drush_upgrade_site_upgrade_modules() {
$extensions_to_enable = func_get_args();
$project_download_list = drush_get_option('projects', '');
if (!empty($project_download_list)) {
// Download our contrib extensions.
drush_log(dt('Download projects: !projects', array('!projects' => $project_download_list)), 'ok');
drush_set_option('destination', NULL);
// User has already been prompted; if there is no recommended release,
// we will just take the most recent.
drush_set_option('choice', '1');
call_user_func_array('drush_pm_download', explode(',', $project_download_list));
}
// Run updatedb to update all of the contrib extensions.
drush_log(dt('About to perform updatedb for extensions'), 'ok');
$result = drush_invoke_process_args('updatedb', array(), array('yes' => TRUE, '#interactive' => TRUE));
if ($result === FALSE) {
return drush_set_error('DRUSH_DRUPAL_CONTRIB_UPGRADE_FAILED', dt("The call to updatedb failed for the enabled contrib modules. Try running site-upgrade again with '--uninstall={module list}' to remove all contrib extensions prior to upgrade. Add modules back in until the problematic one is isolated. Please report problems in the issue queue of the module that is causing problems."));
}
drush_log(dt('updatedb complete for extensions'), 'ok');
// Finally, enable the modules that site-upgrade previously disabled.
// We will set the option --resolve-dependencies to pick up new modules
// that may now be required; for example, views-7.x picked up a dependency
// on ctools that views-6.x did not have. We also set DRUSH_AFFIRMATIVE,
// so everything from here on out will be processed with --yes.
drush_set_option('resolve-dependencies', TRUE);
drush_set_context('DRUSH_AFFIRMATIVE', TRUE);
$result = drush_invoke_args('pm-enable', $extensions_to_enable);
return $result;
}
/**
* Download the upgraded version of Drupal for site-upgrade.
*/
function drush_upgrade_download_drupal($target_version, $destination_core) {
// Fetch target core and place as per target alias root.
drush_set_option('destination', dirname($destination_core));
drush_set_option('drupal-project-rename', basename($destination_core));
// No need for version control in this command.
drush_set_option('version-control', 'backup');
if (drush_get_context('DRUSH_SIMULATE')) {
drush_log(dt("Simulated download of drupal-!version", array('!version' => $target_version)));
}
else {
drush_pm_download('drupal-' . $target_version);
if (drush_get_error()) return FALSE; // Early exit if we see an error.
// Check and see if there is a Drupal site at the target.
if (!file_exists($destination_core . '/includes/bootstrap.inc')) {
return drush_set_error('DRUSH_UPGRADE_NO_DRUPAL', dt('Drupal could not be downloaded to the target directory, @root. Move existing content out of the way first.', array('@root' => $target_alias['root'])));
}
}
}
/**
* Copy the settings.php file from the source site to the target site,
* and fix it up so that it will have its own database settings.
*/
function drush_upgrade_copy_settings(&$target_alias, $settings_destination) {
$settings_destination_folder = dirname($settings_destination);
// Create sites subdirectory in target if needed.
$settings_source = conf_path() . '/settings.php';
if (!file_exists($settings_destination_folder)) {
if (!drush_op('mkdir', $settings_destination_folder) && !drush_get_context('DRUSH_SIMULATE')) {
return drush_set_error('DRUSH_UPGRADE_MKDIR_FAILED', dt('Failed to create directory @settings_destination', array('@settings_destination' => $settings_destination_folder)));
}
}
// Copy settings.php to target.
if (!file_exists($settings_destination)) {
if (!drush_op('copy', $settings_source, $settings_destination) && !drush_get_context('DRUSH_SIMULATE')) {
return drush_set_error('DRUSH_UPGRADE_COPY_FAILED', dt('Failed to copy @source to @dest', array('@source' => $settings_source, 'dest' => $settings_destination)));
}
}
// Append new $db_url with new DB name in target's settings.php.
return drush_upgrade_fix_db_url($target_alias, $settings_destination);
}
/**
* Replace db_url with DB name from target. updatedb will later append a DBTNG compatible version.
*/
function drush_upgrade_fix_db_url(&$target_alias, $settings_destination) {
$old_url = $GLOBALS['db_url'];
if (is_array($old_url)) {
$old_url = $old_url['default'];
}
$old_databases = $GLOBALS['databases'];
if (empty($old_databases)) {
$old_databases = drush_sitealias_convert_db_from_db_url($old_url);
}
$target_alias_databases = sitealias_get_databases_from_record($target_alias);
$database_name = $target_alias_databases['default']['default']['database'];
if (empty($database_name)) {
$database_name = str_replace("@", "", $target_alias['name']) . "db";
drush_log(dt("No database name specified; defaulting to !dbname", array("!dbname" => $database_name)), 'notice');
}
$append = "\n# Added by drush site-upgrade.";
if (drush_drupal_major_version() <= 6) {
$new_url = substr($old_url, 0, strrpos(trim($old_url), '/')) . '/'. $database_name;
$append .= "\n" . '$db_url = \'' . $new_url . '\';';
$databases = drush_sitealias_convert_db_from_db_url($new_url);
}
else {
$databases = $GLOBALS['databases'];
$databases['default']['default']['database'] = $target_alias_databases['default']['default']['database'];
$append .= "\n" . '$databases = ' . var_export($databases, TRUE) . ';';
}
// Caching the database record in the alias record allows sql-sync to work
// before updatedb is called. updatedb is what converts from a db_url to a
// DBTNG array; this conversion is required by sql-sync.
drush_sitealias_cache_db_settings($target_alias, $databases);
// Also append the new configuration options to the end of settings.php.
drush_op('file_put_contents', $settings_destination, $append, FILE_APPEND);
}
/**
* Rollback function: restore our modules if updatedb fails.
*/
function drush_upgrade_site_upgrade_rollback($target_key) {
_drush_upgrade_restore_preserved_modules();
}
/**
* Preserve existing modules. Move them out of the way prior
* to updatedb of Drupal core. We will move them back afterwards.
*/
function _drush_upgrade_preserve_modules($destination_core) {
$modules_preserve['root'] = $destination_core;
$modules_preserve['list'] = array();
$moduledir_list = array(
'sites_all_modules' => $destination_core . '/sites/all/modules',
'sites_conf_path_modules' => $destination_core . conf_path() . '/modules',
);
foreach ($moduledir_list as $moduledir_name => $moduledir) {
if (is_dir($moduledir)) {
$preserved_moduledir = drush_tempnam($moduledir_name, dirname($moduledir));
$result = drush_move_dir($moduledir, $preserved_moduledir, TRUE);
if ($result) {
$modules_preserve['list'][$moduledir] = $preserved_moduledir;
drush_log(dt('Move !src to !dest prior to updatedb on Drupal core.', array('!src' => $moduledir, '!dest' => $preserved_moduledir)), 'ok');
}
else {
return drush_set_error('DRUSH_MODULE_PRESERVE_FAILED', dt('Failed to move !src to !dest.', array('!src' => $moduledir, '!dest' => $preserved_moduledir)));
}
}
}
drush_set_context('DRUSH_MODULES_PRESERVE', $modules_preserve);
return TRUE;
}
/**
* Restore modules that were preserved by _drush_upgrade_preserve_modules.
*/
function _drush_upgrade_restore_preserved_modules() {
$modules_preserve = drush_get_context('DRUSH_MODULES_PRESERVE', array());
if (!empty($modules_preserve) && array_key_exists('list', $modules_preserve)) {
foreach ($modules_preserve['list'] as $moduledir => $preserved_moduledir) {
drush_move_dir($preserved_moduledir, $moduledir, TRUE);
}
}
drush_set_context('DRUSH_MODULES_PRESERVE', array());
}

View File

@@ -0,0 +1,187 @@
<?php
/**
* Implementation of hook_drush_command().
*
* In this hook, you specify which commands your
* drush module makes available, what it does and
* description.
*
* Notice how this structure closely resembles how
* you define menu hooks.
*
* @return
* An associative array describing your command(s).
*/
function variable_drush_command() {
$items['variable-get'] = array(
'description' => 'Get a list of some or all site variables and values.',
'arguments' => array(
'name' => 'A string to filter the variables by. Only variables beginning with the string will be listed.',
),
'examples' => array(
'drush vget' => 'List all variables and values.',
'drush vget user' => 'List all variables beginning with the string "user".',
),
'options' => array(
'pipe' => 'Use var_export() to emit executable PHP. Useful for pasting into code.',
),
'aliases' => array('vget'),
);
$items['variable-set'] = array(
'description' => "Set a variable.",
'arguments' => array(
'name' => 'The name of a variable or the first few letters of its name.',
'value' => 'The value to assign to the variable.',
),
'options' => array(
'yes' => 'Skip confirmation if only one variable name matches.',
'always-set' => 'Always skip confirmation.',
),
'examples' => array(
'drush vset --yes preprocess_css 1' => 'Set the preprocess_css variable to true. Skip confirmation if variable already exists.',
'drush vset --always-set site_offline 1' => 'Take the site offline; Skips confirmation even if site_offline variable does not exist.',
'drush vset pr 1' => 'Choose from a list of variables beginning with "pr" to set to true.',
),
'aliases' => array('vset'),
);
$items['variable-delete'] = array(
'description' => "Delete a variable.",
'arguments' => array(
'name' => 'The name of a variable or the first few letters of its name.',
),
'options' => array(
'yes' => 'Skip confirmation if only one variable name matches.',
),
'examples' => array(
'drush vdel user_pictures' => 'Delete the user_pictures variable.',
'drush vdel u' => 'Choose from a list of variables beginning with "u" to delete.',
),
'aliases' => array('vdel'),
);
return $items;
}
/**
* Command callback.
* List your site's variables.
*/
function drush_variable_get() {
global $conf;
$found = FALSE;
$keys = array_keys($conf);
if ($args = func_get_args()) {
$keys = preg_grep("/{$args[0]}/", $keys);
}
foreach ($keys as $name) {
$value = $conf[$name];
drush_print_pipe("\$variables['$name'] = ". var_export($value, TRUE). ";\n");
if ($value === TRUE) {
$value = 'TRUE';
}
elseif ($value === FALSE) {
$value = 'FALSE';
}
elseif (is_string($value)) {
$value = '"' . $value . '"';
}
elseif (is_array($value) || is_object($value)) {
$value = print_r($value, TRUE);
}
drush_print($name . ': ' . $value);
$found = TRUE;
}
if (!$found) {
return drush_set_error('No matching variable found.');
}
}
/**
* Command callback.
* Set a variable.
*/
function drush_variable_set() {
$args = func_get_args();
if (!isset($args[0])) {
return drush_set_error('DRUSH_VARIABLE_ERROR', dt('No variable specified.'));
}
$value = $args[1];
if (!isset($value)) {
return drush_set_error('DRUSH_VARIABLE_ERROR', dt('No value specified.'));
}
$result = drush_variable_like($args[0]);
$options[] = "$args[0] ". dt('(new variable)');
$match = FALSE;
while (!$match && $name = drush_db_result($result)) {
if ($name == $args[0]) {
$options[0] = $name;
$match = TRUE;
}
else {
$options[] = $name;
}
}
if ((drush_get_option('always-set', FALSE)) || ((count($options) == 1 || $match) && drush_get_context('DRUSH_AFFIRMATIVE'))) {
variable_set($args[0], $value);
drush_log(dt('!name was set to !value.', array('!name' => $args[0], '!value' => $value)), 'success');
return '';
}
else {
$choice = drush_choice($options, 'Enter a number to choose which variable to set.');
if ($choice !== FALSE) {
$choice = $options[$choice];
$choice = str_replace(' ' . dt('(new variable)'), '', $choice);
drush_op('variable_set', $choice, $value);
drush_log(dt('!name was set to !value', array('!name' => $choice, '!value' => $value)), 'success');
}
}
}
/**
* Command callback.
* Delete a variable.
*/
function drush_variable_delete() {
$args = func_get_args();
if (!isset($args[0])) {
drush_set_error('DRUSH_VARIABLE_ERROR', dt('No variable specified'));
}
// Look for similar variable names.
$result = drush_variable_like($args[0]);
$options = array();
while ($name = drush_db_result($result)) {
$options[] = $name;
}
if (count($options) == 0) {
drush_print(dt('!name not found.', array('!name' => $args[0])));
return '';
}
if ((count($options) == 1) && drush_get_context('DRUSH_AFFIRMATIVE')) {
drush_op('variable_del', $args[0]);
drush_log(dt('!name was deleted.', array('!name' => $args[0])), 'success');
return '';
}
else {
$choice = drush_choice($options, 'Enter a number to choose which variable to delete.');
if ($choice !== FALSE) {
$choice = $options[$choice];
drush_op('variable_del', $choice);
drush_log(dt('!choice was deleted.', array('!choice' => $choice)), 'success');
}
}
}
// Query for similar variable names.
function drush_variable_like($arg) {
return drush_db_select('variable', 'name', 'name LIKE :keyword', array(':keyword' => $arg . '%'), NULL, NULL, 'name');
}

View File

@@ -0,0 +1,375 @@
<?php
/**
* Implementation of hook_drush_help().
*/
function watchdog_drush_help($section) {
switch ($section) {
case 'drush:watchdog-list':
return dt('Show available message types and severity levels. A prompt will ask for a choice to show watchdog messages.');
case 'drush:watchdog-show':
return dt('Show watchdog messages. Arguments and options can be combined to configure which messages to show.');
case 'drush:watchdog-delete':
return dt('Delete watchdog messages. Arguments or options must be provided to specify which messages to delete.');
}
}
/**
* Implementation of hook_drush_command().
*/
function watchdog_drush_command() {
$items['watchdog-list'] = array(
'description' => 'Show available message types and severity levels. A prompt will ask for a choice to show watchdog messages.',
'drupal dependencies' => drush_drupal_major_version() >= 6 ? array('dblog') : array('watchdog'),
'aliases' => array('wd-list'),
);
$items['watchdog-show'] = array(
'description' => 'Show watchdog messages.',
'drupal dependencies' => drush_drupal_major_version() >= 6 ? array('dblog') : array('watchdog'),
'arguments' => array(
'wid' => 'Optional id of a watchdog message to show in detail. If not provided, a listing of most recent 10 messages will be displayed. Alternatively if a string is provided, watchdog messages will be filtered by it.',
),
'options' => array(
'count' => 'The number of messages to show. Defaults to 10.',
'severity' => 'Restrict to messages of a given severity level.',
'type' => 'Restrict to messages of a given type.',
'tail' => 'Continuously show new watchdog messages until interrupted.',
'sleep-delay' => 'To be used in conjunction with --tail. This is the number of seconds to wait between each poll to the database. Delay is 1 second by default.'
),
'examples' => array(
'drush watchdog-show' => 'Show a listing of most recent 10 messages.',
'drush watchdog-show 64' => 'Show in detail message with id 64.',
'drush watchdog-show "cron run succesful"' => 'Show a listing of most recent 10 messages containing the string "cron run succesful".',
'drush watchdog-show --count=46' => 'Show a listing of most recent 46 messages.',
'drush watchdog-show --severity=notice' => 'Show a listing of most recent 10 messages with a severity of notice.',
'drush watchdog-show --type=php' => 'Show a listing of most recent 10 messages of type php.',
'drush watchdog-show --tail' => 'Show a listing of most recent 10 messages and continue showing messages as they are registered in the watchdog.',
'drush watchdog-show --tail --sleep-delay=2' => 'Do a tail of the watchdog with a delay of two seconds between each poll to the database.',
),
'aliases' => array('wd-show', 'ws'),
);
$items['watchdog-delete'] = array(
'description' => 'Delete watchdog messages.',
'drupal dependencies' => drush_drupal_major_version() >= 6 ? array('dblog') : array('watchdog'),
'arguments' => array(
'severity' => 'Delete messages of a given severity level.',
'type' => 'Delete messages of a given type.',
),
'examples' => array(
'drush watchdog-delete all' => 'Delete all messages.',
'drush watchdog-delete 64' => 'Delete messages with id 64.',
'drush watchdog-delete "cron run succesful"' => 'Delete messages containing the string "cron run succesful".',
'drush watchdog-delete --severity=notice' => 'Delete all messages with a severity of notice.',
'drush watchdog-delete --type=cron' => 'Delete all messages of type cron.',
),
'aliases' => array('wd-del', 'wd-delete'),
'deprecated-aliases' => array('wd'),
);
return $items;
}
/**
* Command callback.
*/
function drush_core_watchdog_list() {
$options['-- types --'] = dt('== message types ==');
$types = core_watchdog_message_types();
foreach ($types as $type) {
$options[] = $type;
}
$options['-- levels --'] = dt('== severity levels ==');
drush_include_engine('drupal', 'environment');
$severities = core_watchdog_severity_levels();
foreach ($severities as $key => $value) {
$options[] = "$value($key)";
}
$option = drush_choice($options, dt('Select a message type or severity level.'));
if ($option === FALSE) {
return drush_user_abort();
}
$ntypes = count($types);
if ($option < $ntypes) {
drush_set_option('type', $types[$option]);
}
else {
drush_set_option('severity', $option - $ntypes);
}
drush_core_watchdog_show_many();
}
/**
* Command callback.
*/
function drush_core_watchdog_show($arg = NULL) {
if (is_numeric($arg)) {
drush_core_watchdog_show_one($arg);
}
else {
drush_core_watchdog_show_many($arg);
}
}
/**
* Print a watchdog message.
*
* @param $wid
* The id of the message to show.
*/
function drush_core_watchdog_show_one($wid) {
$rsc = drush_db_select('watchdog', '*', 'wid = :wid', array(':wid' => $wid), 0, 1);
$result = drush_db_fetch_object($rsc);
if (!$result) {
return drush_set_error(dt('Watchdog message #!wid not found.', array('!wid' => $wid)));
}
$result = core_watchdog_format_result($result, TRUE);
foreach ($result as $key => $value) {
$uc = ucfirst($key);
$upper->$uc = $value;
}
drush_print_table(drush_key_value_to_array_table($upper));
print "\n";
}
/**
* Print a table of watchdog messages.
*
* @param $filter
* String to filter the message's text by.
*/
function drush_core_watchdog_show_many($filter = NULL) {
$count = drush_get_option('count', 10);
$type = drush_get_option('type');
$severity = drush_get_option('severity');
$tail = drush_get_option('tail', FALSE);
$where = core_watchdog_query($type, $severity, $filter);
if ($where === FALSE) {
return drush_log(dt('Aborting.'));
}
$rsc = drush_db_select('watchdog', '*', $where['where'], $where['args'], 0, $count, 'wid', 'DESC');
if ($rsc === FALSE) {
return drush_log(dt('Aborting.'));
}
$header = array(dt('Id'), dt('Date'), dt('Severity'), dt('Type'), dt('Message'));
while ($result = drush_db_fetch_object($rsc)) {
$row = core_watchdog_format_result($result);
$table[] = array($row->wid, $row->date, $row->severity, $row->type, $row->message);
}
if (empty($table)) {
return drush_log(dt('No log messages available.'), 'ok');
}
else {
drush_log(dt('Most recent !count watchdog log messages:', array('!count' => $count)));
if ($tail) {
$table = array_reverse($table);
}
array_unshift($table, $header);
$tbl = drush_print_table($table, TRUE);
}
if ($tail) {
// We will reuse the table object to display each line generated while in tail mode.
// To make it possible some hacking is done on the object: remove the header and reset the rows on each iteration.
$tbl->_headers = NULL;
// Obtain the last wid.
$last = array_pop($table);
$last_wid = $last[0];
// Adapt the where snippet.
if ($where['where'] != '') {
$where['where'] .= ' AND ';
}
$where['where'] .= 'wid > :wid';
// sleep-delay
$sleep_delay = drush_get_option('sleep-delay', 1);
while (TRUE) {
$where['args'][':wid'] = $last_wid;
$table = array();
// Reset table rows.
$tbl->_data = array();
$rsc = drush_db_select('watchdog', '*', $where['where'], $where['args'], NULL, NULL, 'wid', 'ASC');
while ($result = drush_db_fetch_object($rsc)) {
$row = core_watchdog_format_result($result);
$table[] = array($row->wid, $row->date, $row->severity, $row->type, $row->message);
#$tbl->addRow(array($row->wid, $row->date, $row->severity, $row->type, $row->message));
$last_wid = $row->wid;
}
$tbl->addData($table);
print $tbl->_buildTable();
sleep($sleep_delay);
}
}
else {
print "\n";
}
}
/**
* Format a watchdog database row.
*
* @param $result
* Array. A database result object.
* @return
* Array. The result object with some attributes themed.
*/
function core_watchdog_format_result($result, $full = FALSE) {
// Severity.
drush_include_engine('drupal', 'environment');
$severities = core_watchdog_severity_levels();
$result->severity = $severities[$result->severity];
// Date.
$result->date = format_date($result->timestamp, 'custom', 'd/M H:i');
unset($result->timestamp);
// Message.
if (drush_drupal_major_version() > 5) {
$variables = unserialize($result->variables);
if (is_array($variables)) {
$result->message = strtr($result->message, $variables);
}
unset($result->variables);
}
$result->message = truncate_utf8(strip_tags(decode_entities($result->message)), 188, FALSE, FALSE);
// This data is only used in a detailed view.
if ($full) {
// Possible empty values.
if (empty($result->link)) {
unset($result->link);
}
if (empty($result->referer)) {
unset($result->referer);
}
// Username.
if ($account = user_load($result->uid)) {
$result->username = $account->name;
}
else {
$result->username = dt('Anonymous');
}
unset($result->uid);
}
return $result;
}
/**
* Command callback.
*
* @param $arg
* The id of the message to delete or 'all'.
*/
function drush_core_watchdog_delete($arg = NULL) {
if ($arg == 'all') {
drush_print(dt('All watchdog messages will be deleted.'));
if (!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
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)));
if(!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
$affected_rows = drush_db_delete('watchdog', 'wid=:wid', array(':wid' => $arg));
if ($affected_rows == 1) {
drush_log(dt('Watchdog message #!wid has been deleted.', array('!wid' => $arg)), 'ok');
}
else {
return drush_set_error(dt('Watchdog message #!wid does not exist.', array('!wid' => $arg)));
}
}
else {
$type = drush_get_option('type');
$severity = drush_get_option('severity');
if ((is_null($arg))&&(is_null($type))&&(is_null($severity))) {
return drush_set_error(dt('No options provided.'));
}
$where = core_watchdog_query($type, $severity, $arg, 'OR');
if ($where === FALSE) {
// Drush set error was already called by core_watchdog_query
return FALSE;
}
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_user_abort();
}
$affected_rows = drush_db_delete('watchdog', $where['where'], $where['args']);
drush_log(dt('!affected_rows watchdog messages have been deleted.', array('!affected_rows' => $affected_rows)), 'ok');
}
}
/**
* Build a WHERE snippet based on given parameters.
*
* @param $type
* String. Valid watchdog type.
* @param $severity
* Int or String for a valid watchdog severity message.
* @param $filter
* String. Value to filter watchdog messages by.
* @param $criteria
* ('AND', 'OR'). Criteria for the WHERE snippet.
* @return
* False or array with structure ('where' => string, 'args' => array())
*/
function core_watchdog_query($type = NULL, $severity = NULL, $filter = NULL, $criteria = 'AND') {
$args = array();
$conditions = array();
if ($type) {
$types = core_watchdog_message_types();
if (array_search($type, $types) === FALSE) {
$msg = "Unrecognized message type: !type.\nRecognized types are: !types.";
return drush_set_error('WATCHDOG_UNRECOGNIZED_TYPE', dt($msg, array('!type' => $type, '!types' => implode(', ', $types))));
}
$conditions[] = "type = :type";
$args[':type'] = $type;
}
if (!is_null($severity)) {
drush_include_engine('drupal', 'environment');
$severities = core_watchdog_severity_levels();
if (isset($severities[$severity])) {
$level = $severity;
}
elseif (($key = array_search($severity, $severities)) !== FALSE) {
$level = $key;
}
else {
$level = FALSE;
}
if ($level === FALSE) {
foreach ($severities as $key => $value) {
$levels[] = "$value($key)";
}
$msg = "Unknown severity level: !severity.\nValid severity levels are: !levels.";
return drush_set_error(dt($msg, array('!severity' => $severity, '!levels' => implode(', ', $levels))));
}
$conditions[] = 'severity = :severity';
$args[':severity'] = $level;
}
if ($filter) {
$conditions[] = "message LIKE :filter";
$args[':filter'] = '%'.$filter.'%';
}
$where = implode(" $criteria ", $conditions);
return array('where' => $where, 'args' => $args);
}
/**
* Helper function to obtain the message types based on drupal version.
*
* @return
* Array of watchdog message types.
*/
function core_watchdog_message_types() {
if (drush_drupal_major_version() == 5) {
return _watchdog_get_message_types();
}
else {
return _dblog_get_message_types();
}
}

View File

@@ -0,0 +1,148 @@
<?php
/**
* @file Drush PM CVS extension
*/
/**
* Validate this package handler can run.
*/
function package_handler_validate() {
// Check cvs command exists. Disable possible output.
$debug = drush_get_context('DRUSH_DEBUG');
drush_set_context('DRUSH_DEBUG', FALSE);
$success = drush_shell_exec('cvs --version');
drush_set_context('DRUSH_DEBUG', $debug);
if (!$success) {
return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('cvs executable not found.'));
}
// Check cvs_deploy is enabled. Only for bootstrapped sites.
if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
if (!module_exists('cvs_deploy')) {
drush_log(dt('cvs package handler needs cvs_deploy module enabled to work properly.'), 'warning');
}
}
}
/**
* 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_download_project(&$project, $release) {
// Check it out.
drush_pm_cvs($project, $release);
if (!drush_get_context('DRUSH_SIMULATE')) {
if (is_dir($project['full_project_path'])) {
drush_log("Checking out " . $project['name'] . " was successful.");
return TRUE;
}
else {
return drush_set_error('DRUSH_PM_CVS_CHECKOUT_PROBLEMS', dt("Unable to check out !project to !destination from cvs.drupal.org", array('!project' => $project['name'], '!destination' => $project['full_project_path'])));
}
}
}
/**
* 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'] . ' ...');
// Check out a fresh copy, or update an existing one.
drush_pm_cvs($project, $release);
if (is_dir($project['full_project_path']) && !drush_get_context('DRUSH_SIMULATE')) {
drush_log("Updating of " . $project['name'] . " was successful.");
return TRUE;
}
else {
return drush_set_error('DRUSH_PM_CVS_UPDATE_PROBLEMS', dt("Unable to update !project from cvs.drupal.org", array('!project' => $project['name'])));
}
}
/**
* 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) {
// Build the cvs command to execute.
$command = array('cvs');
// 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);
if (empty($cvsmethod)) {
$cvsmethod = 'checkout';
// 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 a VCS is competent enough to deal with this!
$reserved_files = drush_version_control_reserved_files();
$overwrite = TRUE;
foreach ($reserved_files as $file) {
if (file_exists($project['full_project_path'] . '/' . $file)) {
$overwrite = FALSE;
break;
}
}
$command[] = $overwrite?'-dPC':'-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.
if (!drush_shell_cd_and_exec($project['base_project_path'], implode(' ', $command))) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', dt('Unable to !op !project from cvs.drupal.org.', array('!op' => $cvsmethod, '!project' => $project['name'])));
}
}
/**
* Post download action.
*
* This action take place once the project is placed in its final location.
*/
function package_handler_post_download($project) {
}

View File

@@ -0,0 +1,191 @@
<?php
/**
* @file Drush PM drupal.org Git extension.
*/
/**
* Validate this package handler can run.
*/
function package_handler_validate() {
// Check git command exists. Disable possible output.
$debug = drush_get_context('DRUSH_DEBUG');
drush_set_context('DRUSH_DEBUG', FALSE);
$success = drush_shell_exec('git --version');
drush_set_context('DRUSH_DEBUG', $debug);
if (!$success) {
return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('git executable not found.'));
}
// Check git_deploy is enabled. Only for bootstrapped sites.
if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
if (!module_exists('git_deploy')) {
drush_log(dt('git package handler needs git_deploy module enabled to work properly.'), 'warning');
}
}
}
/**
* Download a project.
*
* @param $request
* The project array with name, base and full (final) paths.
* @param $release
* The release details array from drupal.org.
*/
function package_handler_download_project(&$request, $release) {
if ($username = drush_get_option('gitusername')) {
// Uses SSH, which enables pushing changes back to git.drupal.org.
$repository = $username . '@git.drupal.org:project/' . $request['name'] . '.git';
}
else {
$repository = 'git://git.drupal.org/project/' . $request['name'] . '.git';
}
$request['repository'] = $repository;
$tag = $release['tag'];
// If the --cache option was given, create a new git reference cache of the
// remote repository, or update the existing cache to fetch recent changes.
if (drush_get_option('cache') && ($cachedir = drush_directory_cache())) {
$gitcache = $cachedir . '/git';
$projectcache = $gitcache . '/' . $request['name'] . '.git';
drush_mkdir($gitcache);
// Setup a new cache, if we don't have this project yet.
if (!file_exists($projectcache)) {
// --mirror works similar to --bare, but retrieves all tags, local
// branches, remote branches, and any other refs (notes, stashes, etc).
// @see http://stackoverflow.com/questions/3959924
$command = 'git clone --mirror';
if (drush_get_context('DRUSH_VERBOSE')) {
$command .= ' --verbose --progress';
}
$command .= ' %s %s';
drush_shell_cd_and_exec($gitcache, $command, $repository, $request['name'] . '.git');
}
// If we already have this project, update it to speed up subsequent clones.
else {
// A --mirror clone is fully synchronized with `git remote update` instead
// of `git fetch --all`.
// @see http://stackoverflow.com/questions/6150188
drush_shell_cd_and_exec($projectcache, 'git remote update');
}
$gitcache = $projectcache;
}
// Clone the repo into its appropriate target location.
$command = 'git clone';
$command .= ' ' . drush_get_option('gitcloneparams');
if (drush_get_option('cache')) {
$command .= ' --reference ' . drush_escapeshellarg($gitcache);
}
if (drush_get_context('DRUSH_VERBOSE')) {
$command .= ' --verbose --progress';
}
$command .= ' ' . drush_escapeshellarg($repository);
$command .= ' ' . drush_escapeshellarg($request['full_project_path']);
if (!drush_shell_exec($command)) {
return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupal.org.', array('!name' => $request['name'])));
}
// Check if the 'tag' from the release feed is a tag or a branch.
// If the tag exists, git will return it
if (!drush_shell_cd_and_exec($request['full_project_path'], 'git tag -l ' . drush_escapeshellarg($tag))) {
return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupal.org.', array('!name' => $request['name'])));
}
$output = drush_shell_exec_output();
if (isset($output[0]) && ($output[0] == $tag)) {
// If we want a tag, simply checkout it. The checkout will end up in
// "detached head" state.
$command = 'git checkout ' . drush_get_option('gitcheckoutparams');
$command .= ' ' . drush_escapeshellarg($tag);
if (!drush_shell_cd_and_exec($request['full_project_path'], $command)) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
}
}
else {
// Else, we want to checkout a branch.
// First check if we are not already in the correct branch.
if (!drush_shell_cd_and_exec($request['full_project_path'], 'git symbolic-ref HEAD')) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
}
$output = drush_shell_exec_output();
$current_branch = preg_replace('@^refs/heads/@', '', $output[0]);
// If we are not on the correct branch already, switch to the correct one.
if ($current_branch != $tag) {
$command = 'git checkout';
$command .= ' ' . drush_get_option('gitcheckoutparams');
$command .= ' --track ' . drush_escapeshellarg('origin/' . $tag) . ' -b ' . drush_escapeshellarg($tag);
if (!drush_shell_cd_and_exec($request['full_project_path'], $command)) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
}
}
}
return TRUE;
}
/**
* Update a project (so far, only modules are supported).
*
* @param $request
* The project array with name, base and full (final) paths.
* @param $release
* The release details array from drupal.org.
*/
function package_handler_update_project($request, $release) {
drush_log('Updating project ' . $request['name'] . ' ...');
$commands = array();
if ($release['version_extra'] == 'dev') {
// Update the branch of the development repository.
$commands[] = 'git pull';
$commands[] = drush_get_option('gitpullparams');
}
else {
// Use a stable repository.
$commands[] = 'git fetch';
$commands[] = drush_get_option('gitfetchparams');
$commands[] = ';';
$commands[] = 'git checkout';
$commands[] = drush_get_option('gitcheckoutparams');
$commands[] = $release['version'];
}
if (!drush_shell_cd_and_exec($request['full_project_path'], implode(' ', $commands))) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to update ' . $request['name'] . ' from git.drupal.org.');
}
return TRUE;
}
/**
* Post download action.
*
* This action take place once the project is placed in its final location.
*
* Here we add the project as a git submodule.
*/
function package_handler_post_download($project) {
if (drush_get_option('gitsubmodule', FALSE)) {
// Obtain the superproject path, then add as submodule.
if (drush_shell_cd_and_exec(dirname($project['full_project_path']), 'git rev-parse --show-toplevel')) {
$output = drush_shell_exec_output();
$superproject = $output[0];
// Add the downloaded project as a submodule of its git superproject.
$command = array();
$command[] = 'git submodule add';
$command[] = drush_get_option('gitsubmoduleaddparams');
$command[] = $project['repository'];
// We need the submodule relative path.
$command[] = substr($project['full_project_path'], strlen($superproject) + 1);
if (!drush_shell_cd_and_exec($superproject, implode(' ', $command))) {
return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to add !name as a git submodule of !super.', array('!name' => $project['name'], '!super' => $superproject)));
}
}
else {
return drush_set_error('DRUSH_PM_GIT_SUBMODULE_PROBLEMS', dt('Unable to create !project as a git submodule: !dir is not in a Git repository.', array('!project' => $project['name'], '!dir' => dirname($project['full_project_path']))));
}
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* @file Drush PM Wget extension
*/
/**
* Validate this package handler can run.
*/
function package_handler_validate() {
// Check wget or curl command exists. Disable possible output.
$debug = drush_get_context('DRUSH_DEBUG');
drush_set_context('DRUSH_DEBUG', FALSE);
$success = drush_shell_exec('wget --version');
if (!$success) {
$success = drush_shell_exec('curl --version');
// Old version of curl shipped in darwin returns error status for --version
// and --help. Give the chance to use it.
if (!$success) {
$success = drush_shell_exec('which curl');
}
}
drush_set_context('DRUSH_DEBUG', $debug);
if (!$success) {
return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('wget nor curl executables found.'));
}
}
/**
* Download a project.
*
* @param $request Array with information on the request to download.
* @param $release The release details array from drupal.org.
*/
function package_handler_download_project(&$request, $release) {
// Install profiles come in several variants. User may specify which one she wants.
if ($request['project_type'] == 'profile') {
// @todo Use xpath to get the right file url.
$files = $release['files'];
foreach ($files as $key => $file) {
if ((string)$file->variant == drush_get_option('variant', 'full') && (string)$file->archive_type == 'tar.gz') {
$release['download_link'] = (string)$file->url;
$release['mdhash'] = (string)$file->md5;
break;
}
}
}
$filename = explode('/', $release['download_link']);
$filename = array_pop($filename);
// Download the project.
if (!drush_shell_exec("wget -P . %s", $release['download_link'])) {
drush_shell_exec("curl -O %s", $release['download_link']);
}
if (file_exists($filename) || drush_get_context('DRUSH_SIMULATE')) {
drush_log("Downloading " . $filename . " was successful.");
}
else {
return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', 'Unable to download ' . $filename . ' to ' . $request['base_project_path'] . ' from '. $release['download_link']);
}
// Check Md5 hash.
if (drush_op('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);
return FALSE;
}
else {
drush_log("Md5 checksum of $filename verified.");
}
// Extract the tarball.
$file_list = drush_tarball_extract($filename, $request['base_project_path'], TRUE);
drush_op('unlink', $filename);
// 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.
$project_dir = rtrim($file_list[0], DIRECTORY_SEPARATOR);
if ($request['project_dir'] != $project_dir) {
$path = $request['base_project_path'];
drush_move_dir($path . '/'. $project_dir, $path . '/' . $request['project_dir']);
}
}
return TRUE;
}
/**
* This is an alias of the download function, since they are identical
*/
function package_handler_update_project(&$request, $release) {
return package_handler_download_project($request, $release);
}
/**
* Post download action.
*
* This action take place once the project is placed in its final location.
*/
function package_handler_post_download($project) {
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,145 @@
<?php
function pm_update_filter(&$project) {
$update = FALSE;
switch($project['status']) {
case UPDATE_CURRENT:
$status = dt('Up to date');
$project['candidate_version'] = $project['recommended'];
break;
case UPDATE_NOT_CURRENT:
$status = dt('Update available');
pm_release_recommended($project);
break;
case UPDATE_NOT_SECURE:
$status = dt('SECURITY UPDATE available');
pm_release_recommended($project);
break;
case UPDATE_REVOKED:
$status = dt('Installed version REVOKED');
pm_release_recommended($project);
break;
case UPDATE_NOT_SUPPORTED:
$status = dt('Installed version not supported');
pm_release_recommended($project);
break;
case UPDATE_NOT_CHECKED:
$status = dt('Unable to check status');
break;
default:
$status = dt('Unknown');
break;
}
return $status;
}
function pm_update_last_check() {
return variable_get('update_last_check', 0);
}
/**
* Command callback. Refresh update status information.
*/
function _pm_refresh() {
drush_print(dt("Refreshing update status information ..."));
update_refresh();
drush_print(dt("Done."));
}
/**
* Get update information for all installed projects.
*
* @see update_get_available().
* @see update_manual_status().
*
* @return An array containing remote and local versions for all installed
* projects
*/
function _pm_get_update_info($projects) {
// Force to invalidate some caches that are only cleared
// when visiting update status report page. This allow to detect changes in
// .info files.
_update_cache_clear('update_project_data');
_update_cache_clear('update_project_projects');
// From update_get_available(): Iterate all projects and create a fetch task
// for those we have no information or is obsolete.
module_load_include('inc', 'update', 'update.compare');
$available = _update_get_cached_available_releases();
$update_projects = update_get_projects();
foreach ($update_projects as $key => $project) {
if (empty($available[$key])) {
update_create_fetch_task($project);
continue;
}
if ($project['info']['_info_file_ctime'] > $available[$key]['last_fetch']) {
$available[$key]['fetch_status'] = UPDATE_FETCH_PENDING;
}
if (empty($available[$key]['releases'])) {
$available[$key]['fetch_status'] = UPDATE_FETCH_PENDING;
}
if (!empty($available[$key]['fetch_status']) && $available[$key]['fetch_status'] == UPDATE_FETCH_PENDING) {
update_create_fetch_task($project);
}
}
// Set a batch to process all pending tasks.
$batch = array(
'operations' => array(
array('update_fetch_data_batch', array()),
),
'finished' => 'update_fetch_data_finished',
'file' => drupal_get_path('module', 'update') . '/update.fetch.inc',
);
batch_set($batch);
drush_backend_batch_process();
// Calculate update status data.
$available = _update_get_cached_available_releases();
$data = update_calculate_project_data($available);
foreach ($data as $project_name => $project) {
// Discard custom projects.
if ($project['status'] == UPDATE_UNKNOWN) {
unset($data[$project_name]);
continue;
}
// Allow to update disabled projects.
if (in_array($project['project_type'], array('module-disabled', 'theme-disabled'))) {
$data[$project_name]['project_type'] = substr($project['project_type'], 0, strpos($project['project_type'], '-'));
}
// Set 'includes' key to all extensions. By default it only contain enabled
// extensions and _pm_get_project_path() needs all of them.
$data[$project_name]['includes'] = drupal_map_assoc($projects[$project_name]['extensions']);
// Store all releases, not just the ones selected by update.module.
$data[$project_name]['releases'] = $available[$project_name]['releases'];
}
$data = _pm_get_project_path($data, 'includes');
return $data;
}
function pm_get_project_info($projects) {
$data = array();
include_once drupal_get_path('module', 'update') .'/update.fetch.inc';
foreach ($projects as $project_name => $project) {
$url = UPDATE_DEFAULT_URL. "/$project_name/". drush_drupal_major_version() . '.x';
$xml = drupal_http_request($url);
if (isset($xml->error)) {
drush_set_error(dt(
'HTTP Request to @request has failed. @error',
array('@request' => $xml->request, '@error' => $xml->error)
));
}
elseif (!$info = update_parse_xml($xml->data)) {
drush_set_error(dt(
'No release history found for @project_name',
array('@project_name' => $project_name)
));
}
else {
$data[$project_name] = $info;
}
}
return $data;
}

View File

@@ -0,0 +1,100 @@
<?php
function pm_update_filter(&$project) {
$update = FALSE;
switch($project['status']) {
case UPDATE_STATUS_CURRENT:
$status = dt('OK');
$project['candidate_version'] = $project['recommended'];
break;
case UPDATE_STATUS_NOT_CURRENT:
$status = dt('Update available');
pm_release_recommended($project);
break;
case UPDATE_STATUS_NOT_SECURE:
$status = dt('SECURITY UPDATE available');
pm_release_recommended($project);
break;
case UPDATE_STATUS_REVOKED:
$status = dt('Installed version REVOKED');
pm_release_recommended($project);
break;
case UPDATE_STATUS_NOT_SUPPORTED:
$status = dt('Installed version not supported');
pm_release_recommended($project);
break;
default:
$status = dt('Ignored: !reason', array('!reason' => $project['reason']));
$project['title'] = $project['name'];
$project['candidate_version'] = dt('Unknown');
break;
}
return $status;
}
function pm_update_last_check() {
return variable_get('update_status_last', 0);
}
/**
* Command callback. Refresh update status information.
*/
function _pm_refresh() {
drush_print(dt("Refreshing update status information ..."));
update_status_refresh();
drush_print(dt("Done."));
}
/**
* Get update information for all installed projects.
*
* @return An array containing remote and local versions for all installed projects
*/
function _pm_get_update_info($projects) {
// We force a refresh if the cache is not available.
if (!cache_get('update_status_info', 'cache')) {
_pm_refresh();
}
$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[$project_name]['modules'] = drupal_map_assoc($projects[$project_name]['extensions']);
}
$data = _pm_get_project_path($data, 'modules');
return $data;
}
/**
* Get project information from drupal.org.
*
* @param $projects An array of project names/**
* Get project information from drupal.org.
*
* @param $projects An array of project names
*/
function pm_get_project_info($projects) {
$info = array();
$data = array();
foreach ($projects as $project_name => $project) {
$url = UPDATE_STATUS_DEFAULT_URL. "/$project_name/". UPDATE_STATUS_CORE_VERSION;
$xml = drupal_http_request($url);
$data[] = $xml->data;
}
if ($data) {
$parser = new update_status_xml_parser;
$info = $parser->parse($data);
}
return $info;
}

View File

@@ -0,0 +1,103 @@
<?php
function pm_update_filter(&$project) {
$update = FALSE;
switch($project['status']) {
case UPDATE_CURRENT:
$status = dt('Up to date');
$project['candidate_version'] = $project['recommended'];
break;
case UPDATE_NOT_CURRENT:
$status = dt('Update available');
pm_release_recommended($project);
break;
case UPDATE_NOT_SECURE:
$status = dt('SECURITY UPDATE available');
pm_release_recommended($project);
break;
case UPDATE_REVOKED:
$status = dt('Installed version REVOKED');
pm_release_recommended($project);
break;
case UPDATE_NOT_SUPPORTED:
$status = dt('Installed version not supported');
pm_release_recommended($project);
break;
case UPDATE_NOT_CHECKED:
$status = dt('Unable to check status');
break;
default:
$status = dt('Unknown');
break;
}
return $status;
}
function pm_update_last_check() {
return variable_get('update_last_check', 0);
}
/**
* Command callback. Refresh update status information.
*/
function _pm_refresh() {
drush_print(dt("Refreshing update status information ..."));
update_refresh();
drush_print(dt("Done."));
}
/**
* Get update information for all installed projects.
*
* @return An array containing remote and local versions for all installed projects
*/
function _pm_get_update_info($projects) {
// We force a refresh if the cache is not available.
if (!cache_get('update_available_releases', 'cache_update')) {
_pm_refresh();
}
$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);
foreach ($data as $project_name => $project) {
// Discard custom projects.
if ($project['status'] == UPDATE_UNKNOWN) {
unset($data[$project_name]);
continue;
}
if (in_array($project['project_type'], array('disabled-module', 'disabled-theme'))) {
$data[$project_name]['project_type'] = substr($project['project_type'], strpos($project['project_type'], '-') + 1);
}
$data[$project_name]['includes'] = drupal_map_assoc($projects[$project_name]['extensions']);
}
$data = _pm_get_project_path($data, 'includes');
return $data;
}
/**
* Get project information from drupal.org.
*
* @param $projects An array of project names
*/
function pm_get_project_info($projects) {
$info = array();
$data = array();
foreach ($projects as $project_name => $project) {
$url = UPDATE_DEFAULT_URL. "/$project_name/". drush_drupal_major_version() . '.x';
$xml = drupal_http_request($url);
$data[] = $xml->data;
}
if ($data) {
include_once drupal_get_path('module', 'update') .'/update.fetch.inc';
$parser = new update_xml_parser;
$info = $parser->parse($data);
}
return $info;
}

View File

@@ -0,0 +1,640 @@
<?php
/**
* 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.
*/
function drush_pm_updatecode() {
// 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');
// Find only security updates?
$security_only = drush_get_option('security-only');
// Get specific requests.
$requests = _convert_csv_to_array(func_get_args());
// Parse out project name and version.
$requests = pm_parse_project_version($requests);
// Get installed extensions and projects.
$extensions = drush_get_extensions();
$projects = drush_get_projects($extensions);
// Get update status information.
$update_info = _pm_get_update_info($projects);
// Process locks specified on the command line.
$locked_list = drush_pm_update_lock($update_info, drush_get_option_list('lock'), drush_get_option_list('unlock'), drush_get_option('lock-message'));
foreach ($extensions as $name => $extension) {
// Add an item to $update_info for each enabled extension which was obtained
// from cvs or git and its project is unknown (because of cvs_deploy or
// git_deploy is not enabled).
if (!isset($extension->info['project'])) {
if ((isset($extension->vcs)) && ($extension->status)) {
$update_info[$name] = array(
'title' => $extension->info['name'],
'existing_version' => 'Unknown',
'status' => DRUSH_PM_REQUESTED_PROJECT_NOT_PACKAGED,
'status_msg' => dt('Project was not packaged by drupal.org but obtained from !vcs. You need to enable !vcs_deploy module', array('!vcs' => $extension->vcs))
);
// The user may have requested to update a project matching this
// extension. If it was by coincidence or error we don't mind as we've
// already added an item to $update_info. Just clean up $requests.
if (isset($requests[$name])) {
unset($requests[$name]);
}
}
}
// Aditionally if the extension name is distinct to the project name and
// the user asked to update the extension, fix the request.
elseif ((isset($requests[$name])) && ($extension->name != $extension->info['project'])) {
$requests[$extension->info['project']] = $requests[$name];
unset($requests[$name]);
}
}
// Add an item to $update_info for each request not present in $update_info.
foreach ($requests as $name => $request) {
if (!isset($update_info[$name])) {
// Disabled projects.
if ((isset($projects[$name])) && ($projects[$name]['status'] == 0)) {
$update_info[$name] = array(
'title' => $name,
'existing_version' => $projects[$name]['version'],
'status' => DRUSH_PM_REQUESTED_PROJECT_NOT_UPDATEABLE
);
unset($requests[$name]);
}
// At this point we are unable to find matching installed project.
// It does not exist at all or it is mispelled,...
else {
$update_info[$name] = array(
'title' => $name,
'existing_version' => 'Unknown',
'status'=> DRUSH_PM_REQUESTED_PROJECT_NOT_FOUND,
);
}
}
}
// If specific versions were requested, match the requested release.
foreach ($requests as $name => $request) {
if (!empty($request['version'])) {
$release = pm_get_release($request, $update_info[$name]);
if (!$release) {
$update_info[$name]['status'] = DRUSH_PM_REQUESTED_VERSION_NOT_FOUND;
}
else if ($release['version'] == $update_info[$name]['existing_version']) {
$update_info[$name]['status'] = DRUSH_PM_REQUESTED_CURRENT;
}
else {
$update_info[$name]['status'] = DRUSH_PM_REQUESTED_UPDATE;
}
// Set the candidate version to the requested release.
$update_info[$name]['candidate_version'] = $release['version'];
}
}
// Table headers.
$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($update_info, $rows, $security_only);
// Pipe preparation.
if (drush_get_context('DRUSH_PIPE')) {
$pipe = "";
foreach($updateable as $project) {
$pipe .= $project['name']. " ";
$pipe .= $project['existing_version']. " ";
$pipe .= $project['candidate_version']. " ";
$pipe .= str_replace(' ', '-', pm_update_filter($project)). "\n";
}
drush_print_pipe($pipe);
// Automatically curtail update process if in pipe mode.
$updateable = array();
}
$tmpfile = drush_tempnam('pm-updatecode.');
$last = pm_update_last_check();
drush_print(dt('Update information last refreshed: ') . ($last ? format_date($last) : dt('Never')));
drush_print();
drush_print(dt("Update status information on all installed and enabled Drupal projects:"));
drush_print_table($rows, TRUE, array(3 => 40), $tmpfile);
$contents = file_get_contents($tmpfile);
drush_print($contents);
drush_print();
// If specific project updates were requested then remove releases for all
// others.
if (!empty($requests)) {
foreach ($updateable as $name => $project) {
if (!isset($requests[$name])) {
unset($updateable[$name]);
}
}
}
// Prevent update of core if --no-core was specified.
if (isset($updateable['drupal']) && drush_get_option('no-core', FALSE)) {
unset($updateable['drupal']);
drush_print(dt('Skipping core update (--no-core specified).'));
}
// 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]);
}
}
}
// First check to see if there is a newer drush.
$drush_update_available = NULL;
if (drush_get_option('self-update', TRUE)) {
$drush_update_available = drush_check_self_update();
}
// 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;
}
$core_update_available = FALSE;
if (isset($updateable['drupal'])) {
$drupal_project = $updateable['drupal'];
unset($update_info['drupal']);
unset($updateable['drupal']);
// At present we need to update drupal core after non-core projects
// are updated.
if (empty($updateable)) {
return _pm_update_core($drupal_project, $tmpfile);
}
// If there are modules other than drupal core enabled, then update them
// first.
else {
$core_update_available = TRUE;
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."));
}
drush_print(dt("Drupal core will be updated after all of the non-core modules are updated.\n"));
}
}
// If there are no releases to update, then print a final
// exit message. Supress the message if we already printed
// a message about a drush update being available.
if (empty($updateable)) {
if ($drush_update_available === TRUE) {
return FALSE;
}
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.
if (!pm_update_packages($updateable, $tmpfile)) {
return FALSE;
}
// After projects are updated we can update core.
if ($core_update_available) {
drush_print();
return _pm_update_core($drupal_project, $tmpfile);
}
}
/**
* Update drupal core, following interactive confirmation from the user.
*
* @param $project
* The drupal project information from the drupal.org update service,
* copied from $update_info['drupal']. @see drush_pm_updatecode.
*/
function _pm_update_core(&$project, $tmpfile) {
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 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();
if (drush_get_option('notes', FALSE)) {
drush_print('Obtaining release notes for above projects...');
_drush_pm_releasenotes(array('drupal'), TRUE, $tmpfile);
}
if(!drush_confirm(dt('Do you really want to continue?'))) {
drush_print(dt('Rolling back all changes. Run again with --no-core to update modules only.'));
return 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.
$project['path'] = 'drupal-' . $project['candidate_version'];
$project['full_project_path'] = $drupal_root . '/' . $project['path'];
if (!is_dir($project['full_project_path'])) {
drush_mkdir($project['full_project_path']);
}
// Create a list of directories to exclude from the update process.
$skip_list = array('sites', $project['path']);
// 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
_pm_update_move_files($drupal_root, $project['full_project_path'], $project['skip_list']);
// Set a context variable to indicate that rollback should reverse
// the _pm_update_move_files above.
drush_set_context('DRUSH_PM_DRUPAL_CORE', $project);
if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
return FALSE;
}
$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;
}
// Take the updated files in the 'core' directory that have been updated,
// and move all except for the items in the skip list back to
// the drupal root
_pm_update_move_files($project['full_project_path'], $drupal_root, $project['skip_list']);
drush_delete_dir($project['full_project_path']);
// Version control engines expect full_project_path to exist and be accurate.
$project['full_project_path'] = $project['base_project_path'];
// If there is a backup target, then find items
// in the backup target that do not exist at the
// 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);
return TRUE;
}
/**
* Move some files from one location to another
*/
function _pm_update_move_files($src_dir, $dest_dir, $skip_list, $remove_conflicts = TRUE) {
$items_to_move = drush_scan_directory($src_dir, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'filename', 0, TRUE);
foreach ($items_to_move as $filename => $info) {
if ($remove_conflicts) {
drush_delete_dir($dest_dir . '/' . basename($filename));
}
if (!file_exists($dest_dir . '/' . basename($filename))) {
$move_result = drush_move_dir($filename, $dest_dir . '/' . basename($filename));
}
}
return TRUE;
}
/**
* Update projects according to an array of releases and print the release notes
* for each project, following interactive confirmation from the user.
*
* @param $update_info
* An array of projects from the drupal.org update service, with an additional
* array key candidate_version that specifies the version to be installed.
*/
function pm_update_packages($update_info, $tmpfile) {
drush_include_engine('package_handler', drush_get_option('package-handler', 'wget'));
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
$print = '';
$status = array();
foreach($update_info 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])) {
$title = (dt('Security and code updates will be made to the following projects:'));
}
else {
$title = (dt('Security updates will be made to the following projects:'));
}
}
else {
$title = (dt('Code updates will be made to the following projects:'));
}
$print = "$title " . (substr($print, 0, strlen($print)-2));
drush_print($print);
file_put_contents($tmpfile, "\n\n$print\n\n", FILE_APPEND);
// Print the release notes for projects to be updated.
if (drush_get_option('notes', FALSE)) {
drush_print('Obtaining release notes for above projects...');
_drush_pm_releasenotes(array_keys($update_info), TRUE, $tmpfile);
}
// We print some warnings before the user confirms the update.
drush_print();
if (drush_get_option('no-backup', FALSE)) {
drush_print(dt("Note: You have selected to not store backups."));
}
else {
drush_print(dt("Note: A backup of your project 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 with the update process?'))) {
return drush_user_abort();
}
// Now we start the actual updating.
foreach($update_info as $project) {
if (empty($project['path'])) {
return drush_set_error('DRUSH_PM_UPDATING_NO_PROJECT_PATH', dt('The !project project path is not available, perhaps the !type is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'])));
}
drush_log(dt('Starting to update !project code at !dir...', array('!project' => $project['title'], '!dir' => $project['path'])));
// Create the projects directory and base (parent) directory.
$project['full_project_path'] = $drupal_root . '/' . $project['path'];
// Check that the directory exists, and is where we expect it to be.
if (stripos($project['path'], $project['project_type']) === FALSE || !is_dir($project['full_project_path'])) {
return drush_set_error('DRUSH_PM_UPDATING_PATH_NOT_FOUND', dt('The !project directory could not be found within the !types directory at !full_project_path, perhaps the project is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'], '!full_project_path' => $project['full_project_path'])));
}
if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
return FALSE;
}
$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)) {
return FALSE;
}
// 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;
}
pm_update_complete($project, $version_control);
}
return TRUE;
}
/**
* Update one project -- a module, theme or Drupal core.
*
* @param $project
* The project to upgrade. $project['full_project_path'] must be set
* to the location where this project is stored.
*/
function pm_update_project($project, $version_control) {
// 1. If the version control engine is a proper vcs we need to remove project
// files in order to not have orphan files after update.
// 2. If the package-handler is cvs or git, it will remove upstream removed
// files and no orphans will exist after update.
// So, we must remove all files previous update if the directory is not a
// working copy of cvs or git but we don't need to remove them if the version
// control engine is backup, as it did already move the project out to the
// backup directory.
if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) {
// Find and unlink all files but the ones in the vcs control directories.
$skip_list = array('.', '..');
$skip_list = array_merge($skip_list, drush_version_control_reserved_files());
drush_scan_directory($project['full_project_path'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE);
}
// Add the project to a context so we can roll back if needed.
$updated = drush_get_context('DRUSH_PM_UPDATED');
$updated[] = $project;
drush_set_context('DRUSH_PM_UPDATED', $updated);
if (!package_handler_update_project($project, $project['releases'][$project['candidate_version']])) {
return drush_set_error('DRUSH_PM_UPDATING_FAILED', dt('Updating project !project failed. Attempting to roll back to previously installed version.', array('!project' => $project['name'])));
}
// If the version control engine is a proper vcs we also need to remove
// orphan directories.
if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) {
$files = drush_find_empty_directories($project['full_project_path'], $version_control->reserved_files());
array_map('drush_delete_dir', $files);
}
return TRUE;
}
/**
* Run the post-update hooks after updatecode is complete for one project.
*/
function pm_update_complete($project, $version_control) {
drush_print(dt('Project !project was updated successfully. Installed version is now !version.', array('!project' => $project['name'], '!version' => $project['candidate_version'])));
drush_command_invoke_all('pm_post_update', $project['name'], $project['releases'][$project['candidate_version']]);
$version_control->post_update($project);
}
function drush_pm_updatecode_rollback() {
$projects = array_reverse(drush_get_context('DRUSH_PM_UPDATED', array()));
foreach($projects as $project) {
drush_log(dt('Rolling back update of !project code ...', array('!project' => $project['title'])));
// Check we have a version control system, and it clears it's pre-flight.
if (!$version_control = drush_pm_include_version_control($project['path'])) {
return FALSE;
}
$version_control->rollback($project);
}
// Post rollback, we will do additional repair if the project is drupal core.
$drupal_core = drush_get_context('DRUSH_PM_DRUPAL_CORE', FALSE);
if ($drupal_core) {
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
_pm_update_move_files($drupal_core['full_project_path'], $drupal_root, $drupal_core['skip_list']);
drush_delete_dir($drupal_core['full_project_path']);
}
}
/**
* Return an array of updateable projects and fill $rows.
*
* Array of updateable projects is obtained from calculated project update
* status and $security_only flag.
*/
function pm_project_filter(&$update_info, &$rows, $security_only) {
$updateable = array();
foreach ($update_info as $key => $project) {
if (empty($project['title'])) {
continue;
}
switch($project['status']) {
case DRUSH_PM_REQUESTED_UPDATE:
$status = dt('Specified version available');
$project['updateable'] = TRUE;
break;
case DRUSH_PM_REQUESTED_CURRENT:
$status = dt('Specified version already installed');
break;
case DRUSH_PM_REQUESTED_PROJECT_NOT_PACKAGED:
$status = $project['status_msg'];
break;
case DRUSH_PM_REQUESTED_VERSION_NOT_FOUND:
$status = dt('Specified version not found');
break;
case DRUSH_PM_REQUESTED_PROJECT_NOT_FOUND:
$status = dt('Specified project not found');
break;
case DRUSH_PM_REQUESTED_PROJECT_NOT_UPDATEABLE:
$status = dt('Project has no enabled extensions and can\'t be updated');
break;
default:
$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 (isset($project['candidate_version'], $project['releases'][$project['candidate_version']], $project['releases'][$project['existing_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 $update_info (plural).
if (empty($project['candidate_version'])) {
$update_info[$key]['candidate_version'] = $project['existing_version']; // Default to no change
}
else {
$update_info[$key]['candidate_version'] = $project['candidate_version'];
}
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'], $update_info[$key]['candidate_version'], $status);
}
return $updateable;
}
/**
* Set a release to a recommended version (if available), and set as updateable.
*/
function pm_release_recommended(&$project) {
if (isset($project['recommended'])) {
$project['candidate_version'] = $project['recommended'];
$project['updateable'] = TRUE;
}
}
/**
* 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_extensions()
*/
function pm_get_release($request, $project) {
$minor = '';
$version_patch_changed = '';
if ($request['version']) {
// The user specified a specific version - try to find that exact version
foreach($project['releases'] as $version => $release) {
// Ignore unpublished releases.
if ($release['status'] != 'published') {
continue;
}
// Straight match
if (!isset($recommended_version) && $release['version'] == $request['version']) {
$recommended_version = $version;
}
}
}
else {
// No version specified - try to find the best version we can
foreach($project['releases'] as $version => $release) {
// Ignore unpublished releases.
if ($release['status'] != 'published') {
continue;
}
// If we haven't found a recommended version yet, put the dev
// version as recommended and hope it gets overwritten later.
// Look for the 'latest version' if we haven't found it yet.
// Latest version is defined as the most recent version for the
// default major version.
if (!isset($latest_version) && $release['version_major'] == $project['default_major']) {
$latest_version = $version;
}
if (!isset($recommended_version) && $release['version_major'] == $project['default_major']) {
if ($minor != $release['version_patch']) {
$minor = $release['version_patch'];
$version_patch_changed = $version;
}
if (empty($release['version_extra']) && $minor == $release['version_patch']) {
$recommended_version = $version_patch_changed;
}
continue;
}
}
}
if (isset($recommended_version)) {
return $project['releases'][$recommended_version];
}
else if (isset($latest_version)) {
return $project['releases'][$latest_version];
}
else {
return false;
}
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* @file Drush pm directory copy backup extension
*/
class drush_pm_version_control_backup implements drush_pm_version_control {
/**
* Implementation of pre_update().
*/
public function pre_update(&$project, $items_to_test = array()) {
if (drush_get_option('no-backup', FALSE)) {
return TRUE;
}
if ($backup_target = $this->prepare_backup_dir()) {
if ($project['project_type'] != 'core') {
$backup_target .= '/' . $project['project_type'] . 's';
drush_mkdir($backup_target);
}
$backup_target .= '/'. $project['name'];
// Save for rollback or notifications.
$project['backup_target'] = $backup_target;
// Move or copy to backup target based in package-handler.
if (drush_get_option('package-handler', 'wget') == 'wget') {
if (drush_move_dir($project['full_project_path'], $backup_target)) {
return TRUE;
}
}
// cvs or git.
elseif (drush_copy_dir($project['full_project_path'], $backup_target)) {
return TRUE;
}
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)));
}
}
/**
* Implementation of rollback().
*/
public function rollback($project) {
if (drush_get_option('no-backup', FALSE)) {
return;
}
if (drush_move_dir($project['backup_target'], $project['full_project_path'], TRUE)) {
return drush_log(dt("Backups were restored successfully."), 'ok');
}
return drush_set_error('DRUSH_PM_BACKUP_ROLLBACK_FAILED', dt('Could not restore backup and rollback from failed upgrade. You will need to resolve manually.'));
}
/**
* Implementation of post_update().
*/
public function post_update($project) {
if (drush_get_option('no-backup', FALSE)) {
return;
}
if ($project['backup_target']) {
drush_log(dt("Backups were saved into the directory !backup_target.", array('!backup_target' => $project['backup_target'])), 'ok');
}
}
/**
* Implementation of post_download().
*/
public function post_download($project) {
// NOOP
}
// Helper for pre_update.
public function prepare_backup_dir($subdir = NULL) {
return drush_prepare_backup_dir($subdir);
}
public static function reserved_files() {
return array();
}
}

View File

@@ -0,0 +1,134 @@
<?php
/**
* @file Drush pm Bazaar extension
*/
class drush_pm_version_control_bzr implements drush_pm_version_control {
/**
* Implementation of pre_update().
*
* Check that the project or drupal core directory looks clean
*/
public function pre_update(&$project, $items_to_test = array()) {
// Bazaar needs a list of items to test within the given project.
// If $items_to_test is empty we need to force it to test the project
// directory itself --once we've cd'ed to it.
if (empty($items_to_test)) {
$items_to_test = array('.' => '.');
}
$args = array_keys($items_to_test);
array_unshift($args, 'bzr status --short' . str_repeat(' %s', count($args)));
array_unshift($args, $project['full_project_path']);
if (call_user_func_array('drush_shell_cd_and_exec', $args)) {
$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 uncommitted 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 rollback().
*/
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 uncommitted 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 ($this->sync($project)) {
// Only attempt commit on a sucessful sync
$this->commit($project);
}
}
/**
* Implementation of post_download().
*/
public function post_download($project) {
if ($this->sync($project)) {
// Only attempt commit on a sucessful sync
$this->commit($project);
}
}
/**
* 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 = '';
$root = array();
if (drush_shell_exec('bzr status --short %s', $project['full_project_path'])) {
$output = drush_shell_exec_output();
// 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();
}
foreach ($output as $line) {
if (preg_match('/^\?\s+(.*)/', $line, $matches)) {
$path = $root[0] .'/'. $matches[1];
if (!drush_shell_exec('bzr add --no-recurse %s', $path)) {
$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)));
}
}
else {
return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status. Check that you have Bazaar \ninstalled and that the site is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
}
return TRUE;
}
}
/**
* Automatically commit changes to the repository
*/
private function commit($project) {
if (drush_get_option('bzrcommit')) {
$message = drush_get_option('bzrmessage');
if (empty($message)) {
$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 --message=%s %s', $message, $project['full_project_path'])) {
drush_log(dt('Project committed to Bazaar successfully'), 'ok');
}
else {
drush_set_error('DRUSH_PM_BZR_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
}
}
else {
drush_print(dt("You should consider committing the new code to your Bazaar repository.\nIf this version becomes undesireable, use Bazaar to roll back."));
}
}
public static function reserved_files() {
return array('.bzr', '.bzrignore', '.bzrtags');
}
}

View File

@@ -0,0 +1,135 @@
<?php
/**
* @file Drush pm SVN extension
*/
class drush_pm_version_control_svn implements drush_pm_version_control {
/**
* Implementation of pre_update().
*/
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 svn status.
$args = array_keys($items_to_test);
array_unshift($args, 'svn status '. drush_get_option('svnstatusparams') . str_repeat('%s ', count($args)));
array_unshift($args, $project['full_project_path']);
if (call_user_func_array('drush_shell_cd_and_exec', $args)) {
$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 uncommitted 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_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
}
// Check for incoming updates
$args = array_keys($items_to_test);
array_unshift($args, 'svn status -u '. drush_get_option('svnstatusparams') . str_repeat('%s ', count($args)));
array_unshift($args, $project['full_project_path']);
if (call_user_func_array('drush_shell_cd_and_exec', $args)) {
$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))));
}
}
else {
return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn remote status on !path. Check that you have connectivity to the repository.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
}
return TRUE;
}
/**
* Implementation of rollback().
*/
public function rollback($project) {
if (drush_shell_exec('svn revert '. drush_get_option('svnrevertparams') .' '. $project['full_project_path'])) {
$output = 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 uncommitted 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_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion 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 ($this->sync($project)) {
// Only attempt commit on a sucessful sync
$this->commit($project);
}
}
/**
* Implementation of post_download().
*/
public function post_download($project) {
if ($this->sync($project)) {
// Only attempt commit on a sucessful sync
$this->commit($project);
}
}
/**
* Automatically add any unversioned files to Subversion and remove any files
* that have been deleted on the file system
*/
private function sync($project) {
if (drush_get_option('svnsync')) {
$errors = '';
if (drush_shell_exec('svn status '. drush_get_option('svnstatusparams') .' '. $project['full_project_path'])) {
$output = drush_shell_exec_output();
foreach ($output as $line) {
if (preg_match('/^\? *(.*)/', $line, $matches)) {
if (!drush_shell_exec('svn add '. drush_get_option('svnaddparams') .' '. $matches[1])) {
$errors .= implode("\n", drush_shell_exec_output());
}
}
if (preg_match('/^\! *(.*)/', $line, $matches)) {
if (!drush_shell_exec('svn remove '. drush_get_option('svnremoveparams') .' '. $matches[1])) {
$errors .= implode("\n", drush_shell_exec_output());
}
}
}
if (!empty($errors)) {
return drush_set_error('DRUSH_PM_SVN_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from this SVN working copy.\nThe specific errors are below:\n!errors", array('!errors' => $errors)));
}
}
else {
return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
}
return TRUE;
}
}
/**
* Automatically commit changes to the repository
*/
private function commit($project) {
if (drush_get_option('svncommit')) {
$message = drush_get_option('svnmessage');
if (empty($message)) {
$message = dt("Drush automatic commit: \n") . implode(' ', $_SERVER['argv']);
}
if (drush_shell_exec('svn commit '. drush_get_option('svncommitparams') .' -m "'. $message .'" '. $project['full_project_path'])) {
drush_log(dt('Project committed to Subversion successfully'), 'ok');
}
else {
drush_set_error('DRUSH_PM_SVN_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Subversion.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
}
}
else {
drush_print(dt("You should consider committing the new code to your Subversion repository.\nIf this version becomes undesireable, use Subversion to roll back."));
}
}
public static function reserved_files() {
return array('.svn');
}
}

View File

@@ -0,0 +1,999 @@
<?php
/**
* @file Drush sql commands
*/
/**
* Implementation of hook_drush_help().
*/
function sql_drush_help($section) {
switch ($section) {
case 'meta:sql:title':
return dt('SQL commands');
case 'meta:sql:summary':
return dt('Examine and modify your Drupal database.');
}
}
/**
* Implementation of hook_drush_command().
*/
function sql_drush_command() {
$options['database'] = 'The DB connection key if using multiple connections in settings.php.';
if (drush_drupal_major_version() >= 7) {
$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_CONFIGURATION,
'options' => array(
'yes' => 'Skip confirmation and proceed.',
'result-file' => 'Save to a file. The file should be relative to Drupal root. Recommended.',
) + $options,
'topics' => array('docs-policy'),
);
$items['sql-conf'] = array(
'description' => 'Print database connection details using print_r().',
'hidden' => TRUE,
'arguments' => array(
'all' => 'Show all database connections, instead of just one.',
'show-passwords' => 'Show database password.',
),
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION,
'options' => $options,
);
$items['sql-connect'] = array(
'description' => 'A string for connecting to the DB.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION,
'options' => $options,
'examples' => array(
'`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 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.',
'drush sql-dump --skip-tables-key=common' => 'Skip standard tables. @see example.drushrc.com',
),
'options' => array(
'result-file' => 'Save to a file. The file should be relative to Drupal root. If --result-file is provided with no value, then date based filename will be created under ~/drush-backups directory.',
'skip-tables-key' => 'A key in the $skip_tables array. @see example.drushrc.php. Optional.',
'structure-tables-key' => 'A key in the $structure_tables array. @see example.drushrc.php. Optional.',
'tables-key' => 'A key in the $tables array. Optional.',
'tables-list' => 'A comma-separated list of tables to transfer. Optional.',
'ordered-dump' => 'Use this option to output ordered INSERT statements in the sql-dump.Useful when backups are managed in a Version Control System. Optional.',
'create-db' => 'Wipe existing tables.',
'data-only' => 'Omit CREATE TABLE statements. Postgres only.',
'ordered-dump' => 'Order by primary key and add line breaks for efficient diff in revision control. Also, faster rsync. Slows down the dump. Mysql only.',
'gzip' => 'Compress the dump using the gzip program which must be in your $PATH.',
) + $options,
);
$items['sql-query'] = array(
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE,
'description' => 'Execute a query against the site database.',
'examples' => array(
'drush sql-query "SELECT * FROM {users} WHERE uid=1"' => 'Browse user record. Table prefixes are honored.',
'`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.',
'drush sql-query --input-file=example.sql' => 'Alternate way to import sql statements from a file.',
),
'arguments' => array(
'query' => 'An SQL query. Ignored if \'file\' is provided.',
),
'options' => array(
'result-file' => 'Save to a file. The file should be relative to Drupal root. Optional.',
'input-file' => 'Path to a file containing the SQL to be run.',
'extra' => 'Add custom options to the mysql command.',
) + $options,
'aliases' => array('sqlq'),
);
$items['sql-sync'] = array(
'description' => 'Copy and import source database to target database. Transfers via rsync.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'drush dependencies' => array('core'), // core-rsync.
'examples' => array(
'drush sql-sync @dev @prod' => 'Copy the DB defined in sites/dev to the DB in sites/prod.',
),
'arguments' => array(
'from' => 'Name of subdirectory within /sites or a site-alias.',
'to' => 'Name of subdirectory within /sites or a site-alias.',
),
'options' => array(
'skip-tables-key' => 'A key in the $skip_tables array. @see example.drushrc.php. Optional.',
'structure-tables-key' => 'A key in the $structure_tables array. @see example.drushrc.php. Optional.',
'tables-key' => 'A key in the $tables array. Optional.',
'tables-list' => 'A comma-separated list of tables to transfer. Optional.',
'cache' => 'Skip dump if result file exists and is less than "cache" hours old. Optional; default is 24 hours.',
'no-cache' => 'Do not cache the sql-dump file.',
'no-dump' => 'Do not dump the sql database; always use an existing dump file.',
'source-db-url' => 'Database specification for source system to dump from.',
'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.',
'no-ordered-dump' => 'Do not pass --ordered-dump to sql-dump. sql-sync orders the dumpfile by default in order to increase the efficiency of rsync.',
'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',
),
),
'topics' => array('docs-aliases', 'docs-policy'),
);
if (drush_drupal_major_version() >= 7) {
$items['sql-sync']['options'] += array(
'source-target' => 'The name of a target within the SOURCE database.',
'destination-target' => 'The name of a target within the specified DESTINATION database.',
);
}
$items['sql-cli'] = array(
'description' => "Open a SQL command-line interface using Drupal's credentials.",
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION,
'options' => $options,
'aliases' => array('sqlc'),
);
$items['sql-sanitize'] = array(
'description' => "Run sanitization operations on the current database.",
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE,
'hidden' => TRUE,
'options' => 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".',
),
'aliases' => array('sqlsan'),
);
return $items;
}
/**
* Command callback. Displays the Drupal site's database connection string.
*/
function drush_sql_conf() {
if (drush_get_option('db-url', FALSE)) {
$db_spec['db-url'] = $GLOBALS['db_url'];
}
elseif (drush_get_option('all', FALSE)) {
$db_spec = _drush_sql_get_all_db_specs();
}
if (!isset($db_spec)) {
$db_spec = _drush_sql_get_db_spec();
}
drush_backend_set_result($db_spec);
if (!drush_get_option('show-passwords', FALSE)) {
drush_unset_recursive($db_spec, 'password');
}
drush_print_r($db_spec);
}
/**
* Command callback. Emits a connect string for mysql or pgsql.
*/
function _drush_sql_connect($db_spec = NULL) {
switch (_drush_sql_get_scheme($db_spec)) {
case 'mysql':
$command = 'mysql';
break;
case 'pgsql':
$command = 'psql';
break;
case 'sqlite':
$command = 'sqlite3';
break;
}
$command .= _drush_sql_get_credentials($db_spec);
return $command;
}
function drush_sql_connect() {
drush_print(_drush_sql_connect());
}
/**
* Command callback. Outputs the entire Drupal database in SQL format using mysqldump.
*/
function drush_sql_dump_execute() {
list($exec, $file) = drush_sql_dump();
// Avoid the php memory of the $output array in drush_shell_exec().
if (!$return = drush_op_system($exec)) {
if ($file) {
drush_log(dt('Database dump saved to !path', array('!path' => $file)), 'success');
}
}
return $return;
}
function drush_sql_get_table_selection() {
// Skip large core tables if instructed. Also used by 'sql-sync' command.
$skip_tables = _drush_sql_get_table_list('skip-tables');
// Skip any structure-tables as well.
$structure_tables = _drush_sql_get_table_list('structure-tables');
// Dump only the specified tables. Takes precedence over skip-tables and structure-tables.
$tables = _drush_sql_get_table_list('tables');
return array('skip' => $skip_tables, 'structure' => $structure_tables, 'tables' => $tables);
}
/**
* Build a mysqldump/pg_dump/sqlite statement.
*
* @param db_spec
* For D5/D6, a $db_url. For D7, a target in the default DB connection.
* @return array
* An array with items.
* 1. A mysqldump/pg_dump/sqlite statement that is ready for executing.
* 2. The filepath where the dump will be saved.
*/
function drush_sql_dump($db_spec = NULL) {
return drush_sql_build_dump_command(drush_sql_get_table_selection(), $db_spec);
}
/**
* Build a mysqldump/pg_dump/sqlite statement.
*
* @param array $table_selection
* Supported keys: 'skip', 'structure', 'tables'.
* @param db_spec
* For D5/D6, a $db_url. For D7, a target in the default DB connection.
* @return array
* An array with items.
* 1. A mysqldump/pg_dump/sqlite statement that is ready for executing.
* 2. The filepath where the dump will be saved.
*/
function drush_sql_build_dump_command($table_selection, $db_spec = NULL) {
$skip_tables = $table_selection['skip'];
$structure_tables = $table_selection['structure'];
$tables = $table_selection['tables'];
$ignores = array();
$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.
// @see drush_get_option_help() in drush.inc
$ordered_dump = drush_get_option('ordered-dump');
if (is_null($db_spec)) {
$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.
$file = NULL;
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($db_spec['database']);
if (empty($backup_dir)) {
$backup_dir = "/tmp";
}
$file = $backup_dir . '/@DATABASE_@DATE.sql';
}
$file = str_replace(array('@DATABASE', '@DATE'), array($database, gmdate('Ymd_his')), $file);
}
switch (_drush_sql_get_scheme($db_spec)) {
case 'mysql':
$exec = 'mysqldump';
if ($file) {
$exec .= ' --result-file '. $file;
}
// 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';
}
if (isset($ordered_dump)) {
$extra .= ' --skip-extended-insert --order-by-primary';
}
$exec .= $extra;
if (!empty($tables)) {
$exec .= ' ' . implode(' ', $tables);
}
else {
// Append the ignore-table options.
foreach ($skip_tables as $table) {
$ignores[] = "--ignore-table=$database.$table";
}
$exec .= ' '. implode(' ', $ignores);
// Run mysqldump again and append output if we need some structure only tables.
if (!empty($structure_tables)) {
$exec .= "; mysqldump --no-data $extra " . implode(' ', $structure_tables);
if ($file) {
$exec .= " >> $file";
}
}
}
break;
case 'pgsql':
$create_db = drush_get_option('create-db');
$exec = 'pg_dump ';
if ($file) {
$exec .= ' --file '. $file;
}
// Unlike psql, pg_dump does not take a '--dbname=' before the database name.
$extra = str_replace('--dbname=', ' ', _drush_sql_get_credentials($db_spec));
if (isset($data_only)) {
$extra .= ' --data-only';
}
$exec .= $extra;
$exec .= (!isset($create_db) && !isset($data_only) ? ' --clean' : '');
if (!empty($tables)) {
foreach ($tables as $table) {
$exec .= " --table=$table";
}
}
else {
foreach ($skip_tables as $table) {
$ignores[] = "--exclude-table=$table";
}
$exec .= ' '. implode(' ', $ignores);
// Run pg_dump again and append output if we need some structure only tables.
if (!empty($structure_tables)) {
$schemaonlies = array();
foreach ($structure_tables as $table) {
$schemaonlies[] = "--table=$table";
}
$exec .= "; pg_dump --schema-only " . implode(' ', $schemaonlies) . $extra;
if ($file) {
$exec .= " >> $file";
}
}
}
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;
}
if (drush_get_option('gzip')) {
if ($file) {
// Gzip the result-file
$exec .= "; gzip $file";
$file .= '.gz';
}
else {
// gzip via pipe since user has not specified a file.
$exec .= "| gzip";
}
}
return array($exec, $file);
}
/**
* Consult the specified options and return the list of tables
* specified.
*
* @param option_name
* 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.
* @returns array
* Returns an array of tables based on the first option
* found, or an empty array if there were no matches.
*/
function _drush_sql_get_table_list($option_name) {
foreach(array('' => 'cli', 'target-,,source-' => NULL) as $prefix_list => $context) {
foreach(explode(',',$prefix_list) as $prefix) {
$key_list = drush_get_option($prefix . $option_name . '-key', NULL, $context);
foreach(explode(',', $key_list) as $key) {
$all_tables = drush_get_option($option_name, array());
if (array_key_exists($key, $all_tables)) {
return $all_tables[$key];
}
if ($option_name != 'tables') {
$all_tables = drush_get_option('tables', array());
if (array_key_exists($key, $all_tables)) {
return $all_tables[$key];
}
}
}
$table_list = drush_get_option($prefix . $option_name . '-list', NULL, $context);
if (isset($table_list)) {
return empty($table_list) ? array() : explode(',', $table_list);
}
}
}
return array();
}
/**
* Command callback. Executes the given SQL query on the Drupal database.
*/
function drush_sql_query($query) {
$filename = drush_get_option('file', NULL);
return _drush_sql_query($query, NULL, $filename);
}
/*
* Execute a SQL query.
*
* @param string $query
* The SQL to be executed. Should be NULL if $file is provided.
* @param array $db_spec
* A database target.
* @param string $filename
* A path to a file containing the SQL to be executed.
*/
function _drush_sql_query($query, $db_spec = NULL, $filename = NULL) {
$scheme = _drush_sql_get_scheme($db_spec);
// Inject table prefixes as needed.
if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_DATABASE)) {
if ($filename) {
$query = file_get_contents($filename);
}
if (drush_drupal_major_version() >= 7) {
$query = Database::getConnection()->prefixTables($query);
}
else {
$query = db_prefix_tables($query);
}
}
// Convert mysql 'show tables;' query into something pgsql understands
if (($scheme == 'pgsql') && ($query == 'show tables;')) {
$query = drush_sql_show_tables_pgsql();
}
// Save $query to a tmp file if needed. We will redirect it in.
if (!$filename) {
$filename = drush_save_data_to_temp_file($query);
}
$exec = drush_sql_build_exec($db_spec, $filename);
if ($output_file = drush_get_option('result-file')) {
$exec .= ' > '. drush_escapeshellarg($output_file);
}
// 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) == 0;
return $return;
}
function drush_sql_drop() {
if (!drush_confirm(dt('Do you really want to drop all tables?'))) {
return drush_user_abort();
}
_drush_sql_drop();
}
// n.b. site-install uses _drush_sql_drop as a fallback technique if
// drop database; create database fails. If _drush_sql_drop
// is rewritten to also use that technique, it should maintain
// the drop tables code here as a fallback.
function _drush_sql_drop($db_spec = NULL) {
// TODO: integrate with _drush_sql_get_table_list?
$scheme = _drush_sql_get_scheme($db_spec);
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($db_spec, $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);
if ($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($db_spec);
$exec .= " '{$sql}'";
return drush_op_system($exec) == 0;
}
else {
// Shift off the header of the column of data returned.
array_shift($tables);
$sql = 'DROP TABLE '. implode(', ', $tables);
return _drush_sql_query($sql, $db_spec);
}
}
else {
drush_log(dt('No tables to drop.'), 'ok');
}
return TRUE;
}
function drush_sql_cli() {
proc_close(proc_open(_drush_sql_connect(), array(0 => STDIN, 1 => STDOUT, 2 => STDERR), $pipes));
}
/**
* Command callback. Run's the sanitization operations on the current database.
*/
function drush_sql_sanitize() {
if (!drush_confirm(dt('Do you really want to sanitize the current database?'))) {
return drush_user_abort();
}
drush_include(DRUSH_BASE_PATH . '/commands/sql', 'sync.sql');
drush_command_invoke_all('drush_sql_sync_sanitize', 'default');
$options = drush_get_context('post-sync-ops');
if (!empty($options)) {
if (!drush_get_context('DRUSH_SIMULATE')) {
$messages = _drush_sql_get_post_sync_messages();
if ($messages) {
drush_print();
drush_print($messages);
}
}
}
$sanitize_query = '';
foreach($options as $id => $data) {
$sanitize_query .= $data['query'] . " ";
}
if ($sanitize_query) {
if (!drush_get_context('DRUSH_SIMULATE')) {
drush_sql_query($sanitize_query);
}
else {
drush_print("Executing: $sanitize_query");
}
}
}
//////////////////////////////////////////////////////////////////////////////
// SQL SERVICE HELPERS
/**
* Get a database specification for the active DB connection. Honors the
* 'database' and 'target command' line options.
*
* @return
* An info array describing a database target.
*/
function _drush_sql_get_db_spec() {
$database = drush_get_option('database', 'default');
$target = drush_get_option('target', 'default');
switch (drush_drupal_major_version()) {
case 5:
case 6:
$url = $GLOBALS['db_url'];
// TODO: array version not working?
$url = is_array($url) ? $url[$database] : $url;
return drush_convert_db_from_db_url($url);
default:
// We don't use DB API here `sql-sync` would have to messily addConnection.
if (!isset($GLOBALS['databases']) || !array_key_exists($database, $GLOBALS['databases']) || !array_key_exists($target, $GLOBALS['databases'][$database])) {
return NULL;
}
return $GLOBALS['databases'][$database][$target];
}
}
function _drush_sql_get_all_db_specs() {
switch (drush_drupal_major_version()) {
case 5:
case 6:
return drush_sitealias_convert_db_from_db_url($GLOBALS['db_url']);
default:
if (!isset($GLOBALS['databases'])) {
return NULL;
}
return $GLOBALS['databases'];
}
}
function _drush_sql_get_spec_from_options($prefix, $default_to_self = TRUE) {
$db_spec = NULL;
$databases = drush_get_option($prefix . 'databases');
if (isset($databases) && !empty($databases)) {
$database = drush_get_option($prefix . 'database', 'default');
$target = drush_get_option($prefix . 'target', 'default');
if (array_key_exists($database, $databases) && array_key_exists($target, $databases[$database])) {
$db_spec = $databases[$database][$target];
}
}
else {
$db_url = drush_get_option($prefix . 'db-url');
if (isset($db_url)) {
$db_spec = drush_convert_db_from_db_url($db_url);
}
elseif ($default_to_self) {
$db_spec = _drush_sql_get_db_spec();
}
}
if (isset($db_spec)) {
$remote_host = drush_get_option($prefix . 'remote-host');
if (!drush_is_local_host($remote_host)) {
$db_spec['remote-host'] = $remote_host;
$db_spec['port'] = drush_get_option($prefix . 'remote-port', $db_spec['port']);
}
}
return $db_spec;
}
/**
* Determine where to store an sql dump file. This
* function is called by sql-sync if the caller did
* not explicitly specify a dump file to use.
*
* @param db_spec
* Information about the database being dumped; used
* to generate the filename.
* @return string
* The path to the dump file
*/
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 --{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;
}
function _drush_sql_get_scheme($db_spec = NULL) {
if (is_null($db_spec)) {
$db_spec = _drush_sql_get_db_spec();
}
return $db_spec['driver'];
}
/**
* Build a fragment containing credentials and mysql-connection parameters.
*
* @param $db_spec
* @return string
*/
function _drush_sql_get_credentials($db_spec = NULL) {
if (is_null($db_spec)) {
$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':
// 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':
// Some drush commands (e.g. site-install) want to connect to the
// server, but not the database. Connect to the built-in 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";
}
// Join the parameters and return.
return ' ' . implode(' ', $parameter_strings);
}
function _drush_sql_get_invalid_url_msg($db_spec = NULL) {
if (is_null($db_spec)) {
$db_spec = _drush_sql_get_db_spec();
}
switch (drush_drupal_major_version()) {
case 5:
case 6:
return dt('Unable to parse DB connection string');
default:
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';";
}
/*
* Drop all tables or DROP+CREATE target database.
*
* return boolean
* TRUE or FALSE depending on success.
*/
function drush_sql_empty_db($db_spec = NULL) {
if (is_null($db_spec)) {
$db_spec = drush_sql_read_db_spec();
}
$sql = drush_sql_build_createdb_sql($db_spec, TRUE);
// Get credentials to connect to the server, but not the database which we
// are about to DROP. @see _drush_sql_get_credentials().
$create_db_spec = $db_spec;
unset($create_db_spec['database']);
$create_db_su = drush_sql_su($create_db_spec);
if (!_drush_sql_query($sql, $create_db_su)) {
// If we could not drop the database, try instead to drop all
// of the tables in the database (presuming it exists...).
// If we cannot do either operation, then fail with an error.
if (!_drush_sql_drop($db_spec)) {
return drush_set_error(dt('Could not drop and create database: @name', array('@name' => $db_name)));
}
}
return TRUE;
}
/**
* Return a db_spec based on supplied db_url/db_prefix options or an existing
* settings.php.
*/
function drush_sql_read_db_spec() {
if ($db_url = drush_get_option('db-url')) {
// We were passed a db_url. Usually a fresh site.
$db_spec = drush_convert_db_from_db_url($db_url);
$db_spec['db_prefix'] = drush_get_option('db-prefix');
return $db_spec;
}
elseif (drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION)) {
// We have an existing settings.php.
$db_spec = _drush_sql_get_db_spec();
$db_spec['db_prefix'] = $GLOBALS['db_prefix'];
return $db_spec;
}
else {
return FALSE;
}
}
/*
* Build DB connection array with superuser credentials if provided.
*/
function drush_sql_su($db_spec) {
$create_db_target = $db_spec;
$create_db_target['database'] = '';
$db_superuser = drush_get_option(array('db-su', 'target-db-su'));
if (isset($db_superuser)) {
$create_db_target['username'] = $db_superuser;
}
$db_su_pw = drush_get_option(array('db-su-pw', 'target-db-su-pw'));
if (isset($db_su_pw)) {
$create_db_target['password'] = $db_su_pw;
}
return $create_db_target;
}
/*
* Build a SQL string for dropping and creating a database.
*
* @param array $db_spec
* A database specification array.
*
* @param boolean $quoted
* Quote the database name. Mysql uses backticks to quote which can cause problems
* in a Windows shell. Set TRUE of the CREATE is not running on the bash command line.
*/
function drush_sql_build_createdb_sql($db_spec, $quoted = FALSE) {
switch (_drush_sql_get_scheme($db_spec)) {
case 'mysql':
$dbname = $quoted ? '`' . $db_spec['database'] . '`' : $db_spec['database'];
$sql[] = sprintf('DROP DATABASE IF EXISTS %s; ', $dbname);
$sql[] = sprintf('CREATE DATABASE %s /*!40100 DEFAULT CHARACTER SET utf8 */;', $dbname);
$sql[] = sprintf('GRANT ALL PRIVILEGES ON %s.* TO \'%s\'@\'%s\'', $dbname, $db_spec['username'], $db_spec['host']);
$sql[] = sprintf("IDENTIFIED BY '%s';", $db_spec['password']);
$sql[] = 'FLUSH PRIVILEGES;';
break;
case 'pgsql':
$dbname = $quoted ? '"' . $db_spec['database'] . '"' : $db_spec['database'];
$sql[] = sprintf('drop database if exists %s;', $dbname);
$sql[] = sprintf("create database %s ENCODING 'UTF8';", $dbname);
break;
}
return implode(' ', $sql);
}
function drush_sql_build_exec($db_spec, $filepath) {
$scheme = _drush_sql_get_scheme($db_spec);
switch ($scheme) {
case 'mysql':
$exec = 'mysql';
$exec .= _drush_sql_get_credentials($db_spec);
$exec .= ' ' . drush_get_option('extra');
$exec .= " < $filepath";
break;
case 'pgsql':
$exec = 'psql -q ';
$exec .= _drush_sql_get_credentials($db_spec);
$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;
}

View File

@@ -0,0 +1,488 @@
<?php
require_once DRUSH_BASE_PATH . '/commands/core/rsync.core.inc';
/**
* 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);
// Insure that we have database records for the source and destination
// alias records. sitealias_get_databases_from_record will cache the
// database info inside the alias records, and drush_sitealias_set_alias_context
// will copy the database record into the 'alias' context. We do not
// 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);
}
}
// By default, sql-sync will do an ordered dump.
// Set --no-ordered-dump to override.
if (!drush_get_option('no-ordered-dump', FALSE)) {
drush_set_option('ordered-dump', 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 passwords.
$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 email addresses.
$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, '@', '_') || '", '%name' => "' || replace(name, ' ', '_') || '");
$newmail = "'" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "'";
}
else {
$email_map = array('%uid' => "', uid, '", '%mail' => "', replace(mail, '@', '_'), '", '%name' => "', replace(name, ' ', '_'), '");
$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);
if ($is_multiple === FALSE) {
// Evaluate the source and destination specifications into options.
// The options from the 'source-*' and 'target-*' aliases are set
// in a drush context that has a lower priority than the command-line
// options; this allows command-line options to override the default
// values specified in a site-alias.
drush_sitealias_set_alias_context($source_settings, 'source-');
drush_sitealias_set_alias_context($destination_settings, 'target-');
// Get the options for the source and target databases
$source_db_url = _drush_sql_get_spec_from_options('source-', FALSE);
// The host may have special ssh requirements
$source_remote_ssh_options = drush_get_option('source-ssh-options');
// rsync later will also have to know this option
$source_rsync_options = array('ssh-options' => $source_remote_ssh_options);
$target_db_url = _drush_sql_get_spec_from_options('target-', FALSE);
// The host may have special ssh requirements
$target_remote_ssh_options = drush_get_option('target-ssh-options');
// rsync later will also have to know this option
$target_rsync_options = array('ssh-options' => $target_remote_ssh_options);
if (empty($source_db_url)) {
return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for !source', array('!source' => $source)));
}
if (empty($target_db_url)) {
return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for !destination', array('!destination' => $destination)));
}
// Set up the result file and the remote file.
// 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_sql_dump_file($source_db_url, 'source-');
$target_dump = drush_sql_dump_file($target_db_url, 'target-');
$use_temp_files = drush_get_option('temp');
// 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 ((!$target_db_url['dump-is-temp']) && ($source_db_url['dump-is-temp'])) {
$source_dump = $target_dump;
$source_db_url['dump-is-temp'] = FALSE;
}
else {
$target_dump = $source_dump;
$target_db_url['dump-is-temp'] = $source_db_url['dump-is-temp'];
}
$local_file = $source_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
// If source is local, call drush_sql_dump to dump the database to local machine
// In either case, the '--no-dump' option will cause the sql-dump step to be skipped, and
// we will import from the existing local file (first using rsync to fetch it if it does not exist)
//
// No dump affects both local and remote sql-dumps; it prevents drush sql-sync
// from calling sql-dump when the local cache file is newer than the cache threshhold
// No sync affects the remote sql-dump; it will prevent drush sql-sync from
// rsyncing the local sql-dump file with the remote sql-dump file.
$no_sync = drush_get_option(array('no-sync', 'source-no-sync'));
$no_dump = drush_get_option(array('no-dump', 'source-no-dump'));
$no_cache = drush_get_option(array('no-cache', 'source-no-cache'));
if (!isset($no_cache)) {
$cache = drush_get_option(array('cache', 'source-cache'));
if (!isset($cache)) {
$cache = 24; // Default cache is 24 hours if nothing else is specified.
}
}
// If the 'cache' option is set, then we will set the no-dump option iff the
// 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 ((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;
}
else {
drush_log(dt('Local sql cache file exists but is greater than !cache hours old.', array('!cache' => $cache)));
}
}
else {
drush_log('Local sql cache file does not exist.');
}
}
$table_selection = array();
if (!isset($no_dump)) {
$table_selection = drush_sql_get_table_selection();
}
// Prompt for confirmation. This is destructive.
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)));
}
}
else {
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)));
}
// 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);
}
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();
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?'))) {
return 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') : '';
}
if (!isset($no_dump)) {
$source_intermediate = $source_dump;
$mv_intermediate = '';
// If we are doing a remote dump and the source is not a temporary file,
// 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 (!$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);
list($dump_exec, $dump_file) = drush_sql_build_dump_command($table_selection, $source_db_url);
$dump_exec .= $mv_intermediate;
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 = "ssh $source_remote_ssh_options $source_remote_user$source_at" . $source_db_url['remote-host'] . " " . escapeshellarg($dump_exec);
}
}
else {
if (!isset($no_dump)) {
drush_set_option('result-file', $local_file);
list($dump_exec, $dump_file) = drush_sql_build_dump_command($table_selection, $source_db_url);
}
$no_sync = TRUE;
}
// Call sql-dump, either on the local machine or remotely via ssh, as appropriate.
if (!empty($dump_exec)) {
drush_op_system($dump_exec);
// TODO: IF FAILURE THEN ABORT
}
// If the sql-dump was remote, then rsync the file over to the local machine.
if (!isset($no_sync)) {
// If the source file is a temporary file, then we will have rsync
// delete it for us (remove-source-files option set above).
if (!drush_core_call_rsync($source_remote_user . $source_at . $source_db_url['remote-host'] . ':' . $source_dump, $local_file, $source_rsync_options)) {
return FALSE;
}
}
// We will handle lists of destination sites differently from
// single source-to-destination syncs.
if (array_key_exists('site-list', $destination_settings)) {
// Insure that we will not dump the source sql database
// repeatedly, but will instead re-use it each time through
// the redispatch loop.
drush_set_option('no-dump', TRUE);
drush_set_option('no-sync', TRUE);
drush_set_option('source-dump', $source_dump);
// Call sql-sync for each destination to push the $source_dump
// to each target in turn.
foreach ($destination_settings['site-list'] as $one_destination) {
drush_do_command_redispatch('sql-sync', array($source, $one_destination));
}
}
else {
// Prior to database import, we will generate a "create database" command
// if the '--create-db' option was specified. Note that typically the
// web server user will not have permissions to create a database; to specify
// a different user to use with the create db command, the '--db-su' option
// may be used.
// Under postgres, "alter role username with createdb;" will give create database
// permissions to the specified user if said user was not created with this right.
$pre_import_commands = '';
$create_db = drush_get_option('create-db');
if (isset($create_db)) {
$create_db_su = drush_sql_su($target_db_url);
$db_su_connect = _drush_sql_connect($create_db_su);
$pre_import_sql = drush_sql_build_createdb_sql($target_db_url);
$pre_import_commands = sprintf('echo "%s" | %s; ', $pre_import_sql, $db_su_connect);
}
// Generate the import command
$import_command = _drush_sql_connect($target_db_url);
switch (_drush_sql_get_scheme($target_db_url)) {
case 'mysql':
$import_command .= ' --silent';
break;
case 'pgsql':
$import_command .= ' -q';
break;
}
// If destination is remote, then use rsync to push the database, then use ssh to import the database
// 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') : '';
}
if (!drush_core_call_rsync($local_file, $target_remote_user . $target_at . $target_db_url['remote-host'] . ':' . $target_dump, $target_rsync_options)) {
return FALSE;
}
$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_db_url['dump-is-temp']) {
$import_exec .= '; rm -f ' . escapeshellarg($target_dump);
}
}
else {
$import_exec = $pre_import_commands . $import_command . ' < ' . $local_file;
}
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?'))) {
// Do not abort or return FALSE; that would trigger a rollback.
// Just skip the sanitizations and signal that all is ok.
drush_log(dt('Sanitizations skipped.'), 'ok');
return TRUE;
}
}
}
}
}
$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_invoke_sitealias_args($destination_settings, "sql-query", array($sanitize_query));
}
else {
drush_print("Executing on $destination: $sanitize_query");
}
}
}
}

View File

@@ -0,0 +1,572 @@
<?php
// $Id:
/**
* @file Drush User Management commands
*/
function user_drush_help($section) {
switch ($section) {
case 'meta:user:title':
return dt('User commands');
case 'meta:user:summary':
return dt('Add, modify and delete users.');
}
}
/**
* 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);
return $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) {
$link = user_pass_reset_url($user);
drush_print($link);
return $link;
}
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)'));
}
}