',
),
),
'examples' => array(
'drush [command] cck --svncommitparams=\"--username joe\"' => 'Commit changes as the user \'joe\' (Quotes are required).'
),
),
);
}
/**
* @} End of "Engine types".
*/
/**
* Interface for version control systems.
* We use a simple object layer because we conceivably need more than one
* loaded at a time.
*/
interface drush_version_control {
function pre_update(&$project);
function rollback($project);
function post_update($project);
function post_download($project);
static function reserved_files();
}
/**
* A simple factory function that tests for version control systems, in a user
* specified order, and return the one that appears to be appropriate for a
* specific directory.
*/
function drush_pm_include_version_control($directory = '.') {
$engine_info = drush_get_engines('version_control');
$version_controls = drush_get_option('version-control', FALSE);
// If no version control was given, use a list of defaults.
if (!$version_controls) {
// Backup engine is the last option.
$version_controls = array_reverse(array_keys($engine_info['engines']));
}
else {
$version_controls = array($version_controls);
}
// Find the first valid engine in the list, checking signatures if needed.
$engine = FALSE;
while (!$engine && count($version_controls)) {
$version_control = array_shift($version_controls);
if (isset($engine_info['engines'][$version_control])) {
if (!empty($engine_info['engines'][$version_control]['signature'])) {
drush_log(dt('Verifying signature for !vcs version control engine.', array('!vcs' => $version_control)), 'debug');
if (drush_shell_exec($engine_info['engines'][$version_control]['signature'], $directory)) {
$engine = $version_control;
}
}
else {
$engine = $version_control;
}
}
}
if (!$engine) {
return drush_set_error('DRUSH_PM_NO_VERSION_CONTROL', dt('No valid version control or backup engine found (the --version-control option was set to "!version-control").', array('!version-control' => $version_control)));
}
$instance = drush_include_engine('version_control', $version_control);
$instance->engine = $engine;
return $instance;
}
/**
* Update the locked status of all of the candidate projects
* to be updated.
*
* @param array &$projects
* The projects array from pm_updatecode. $project['locked'] will
* be set for every file where a persistent lockfile can be found.
* The 'lock' and 'unlock' operations are processed first.
* @param array $projects_to_lock
* A list of projects to create peristent lock files for
* @param array $projects_to_unlock
* A list of projects to clear the persistent lock on
* @param string $lock_message
* The reason the project is being locked; stored in the lockfile.
*
* @return array
* A list of projects that are locked.
*/
function drush_pm_update_lock(&$projects, $projects_to_lock, $projects_to_unlock, $lock_message = NULL) {
$locked_result = array();
// Warn about ambiguous lock / unlock values
if ($projects_to_lock == array('1')) {
$projects_to_lock = array();
drush_log(dt('Ignoring --lock with no value.'), 'warning');
}
if ($projects_to_unlock == array('1')) {
$projects_to_unlock = array();
drush_log(dt('Ignoring --unlock with no value.'), 'warning');
}
// Log if we are going to lock or unlock anything
if (!empty($projects_to_unlock)) {
drush_log(dt('Unlocking !projects', array('!projects' => implode(',', $projects_to_unlock))), 'ok');
}
if (!empty($projects_to_lock)) {
drush_log(dt('Locking !projects', array('!projects' => implode(',', $projects_to_lock))), 'ok');
}
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
foreach ($projects as $name => $project) {
if ($name == 'drupal') {
continue;
}
$message = NULL;
$lockfile = $drupal_root . '/' . $project['path'] . '/.drush-lock-update';
// Remove the lock file if the --unlock option was specified
if (((in_array($name, $projects_to_unlock)) || (in_array('all', $projects_to_unlock))) && (file_exists($lockfile))) {
drush_op('unlink', $lockfile);
}
// Create the lock file if the --lock option was specified
if ((in_array($name, $projects_to_lock)) || (in_array('all', $projects_to_lock))) {
drush_op('file_put_contents', $lockfile, $lock_message != NULL ? $lock_message : "Locked via drush.");
// Note that the project is locked. This will work even if we are simulated,
// or if we get permission denied from the file_put_contents.
// If the lock is -not- simulated or transient, then the lock message will be
// read from the lock file below.
$message = drush_get_context('DRUSH_SIMULATE') ? 'Simulated lock.' : 'Transient lock.';
}
// If the persistent lock file exists, then mark the project as locked.
if (file_exists($lockfile)) {
$message = trim(file_get_contents($lockfile));
}
// If there is a message set, then mark the project as locked.
if (isset($message)) {
$projects[$name]['locked'] = !empty($message) ? $message : "Locked.";
$locked_result[$name] = $project;
}
}
return $locked_result;
}
function _drush_pm_extension_cache_file() {
return drush_get_context('DRUSH_PER_USER_CONFIGURATION') . "/drush-extension-cache.inc";
}
function _drush_pm_get_extension_cache() {
$extension_cache = array();
$cache_file = _drush_pm_extension_cache_file();
if (file_exists($cache_file)) {
include $cache_file;
}
if (!array_key_exists('extension-map', $extension_cache)) {
$extension_cache['extension-map'] = array();
}
return $extension_cache;
}
function drush_pm_lookup_extension_in_cache($extension) {
$result = NULL;
$extension_cache = _drush_pm_get_extension_cache();
if (!empty($extension_cache) && array_key_exists($extension, $extension_cache)) {
$result = $extension_cache[$extension];
}
return $result;
}
function drush_pm_put_extension_cache($extension_cache) {
}
function drush_pm_cache_project_extensions($project, $found) {
$extension_cache = _drush_pm_get_extension_cache();
foreach($found as $extension) {
// Simple cache does not handle conflicts
// We could keep an array of projects, and count
// how many times each one has been seen...
$extension_cache[$extension] = $project['name'];
}
drush_pm_put_extension_cache($extension_cache);
}
/**
* Print out all extensions (modules/themes/profiles) found in specified project.
*
* Find .info files in the project path and identify modules, themes and
* profiles. It handles two kind of projects: drupal core/profiles and
* modules/themes.
* It does nothing with theme engine projects.
*/
function drush_pm_extensions_in_project($project) {
// Mask for drush_scan_directory, to avoid tests directories.
$nomask = array('.', '..', 'CVS', 'tests');
// Drupal core and profiles can contain modules, themes and profiles.
if (in_array($project['project_type'], array('core', 'profile'))) {
$found = array('profile' => array(), 'theme' => array(), 'module' => array());
// Find all of the .info files
foreach (drush_scan_directory($project['project_install_location'], "/.*\.info$/", $nomask) as $filename => $info) {
// Find the project type corresponding the .info file.
// (Only drupal >=7.x has .info for .profile)
$base = dirname($filename) . '/' . $info->name;
if (is_file($base . '.module')) {
$found['module'][] = $info->name;
}
else if (is_file($base . '.profile')) {
$found['profile'][] = $info->name;
}
else {
$found['theme'][] = $info->name;
}
}
// Special case: find profiles for drupal < 7.x (no .info)
if ($project['drupal_version'][0] < 7) {
foreach (drush_find_profiles($project['project_install_location']) as $filename => $info) {
$found['profile'][] = $info->name;
}
}
// Log results.
$msg = "Project !project contains:\n";
$args = array('!project' => $project['name']);
foreach (array_keys($found) as $type) {
if ($count = count($found[$type])) {
$msg .= " - !count_$type !type_$type: !found_$type\n";
$args += array("!count_$type" => $count, "!type_$type" => $type, "!found_$type" => implode(', ', $found[$type]));
if ($count > 1) {
$args["!type_$type"] = $type.'s';
}
}
}
drush_log(dt($msg, $args), 'success');
drush_print_pipe(call_user_func_array('array_merge', array_values($found)));
}
// Modules and themes can only contain other extensions of the same type.
elseif (in_array($project['project_type'], array('module', 'theme'))) {
// Find all of the .info files
$found = array();
foreach (drush_scan_directory($project['project_install_location'], "/.*\.info$/", $nomask) as $filename => $info) {
$found[] = $info->name;
}
// Log results.
// If there is only one module / theme in the project, only print out
// the message if is different than the project name.
if (count($found) == 1) {
if ($found[0] != $project['name']) {
$msg = "Project !project contains a !type named !found.";
}
}
// If there are multiple modules or themes in the project, list them all.
else {
$msg = "Project !project contains !count !types: !found.";
}
if (isset($msg)) {
drush_print(dt($msg, array('!project' => $project['name'], '!count' => count($found), '!type' => $project['project_type'], '!found' => implode(', ', $found))));
}
drush_print_pipe($found);
// Cache results.
drush_pm_cache_project_extensions($project, $found);
}
}
/**
* Return an array of empty directories.
*
* Walk a directory and return an array of subdirectories that are empty. Will
* return the given directory if it's empty.
* If a list of items to exclude is provided, subdirectories will be condidered
* empty even if they include any of the items in the list.
*
* @param string $dir
* Path to the directory to work in.
* @param array $exclude
* Array of files or directory to exclude in the check.
*
* @return array
* A list of directory paths that are empty. A directory is deemed to be empty
* if it only contains excluded files or directories.
*/
function drush_find_empty_directories($dir, $exclude = array()) {
// Skip files.
if (!is_dir($dir)) {
return array();
}
$to_exclude = array_merge(array('.', '..'), $exclude);
$empty_dirs = array();
$dir_is_empty = TRUE;
foreach (scandir($dir) as $file) {
// Skip excluded directories.
if (in_array($file, $to_exclude)) {
continue;
}
// Recurse into sub-directories to find potentially empty ones.
$subdir = $dir . '/' . $file;
$empty_dirs += drush_find_empty_directories($subdir, $exclude);
// $empty_dir will not contain $subdir, if it is a file or if the
// sub-directory is not empty. $subdir is only set if it is empty.
if (!isset($empty_dirs[$subdir])) {
$dir_is_empty = FALSE;
}
}
if ($dir_is_empty) {
$empty_dirs[$dir] = $dir;
}
return $empty_dirs;
}
/**
* Inject metadata into all .info files for a given project.
*
* @param string $project_dir
* The full path to the root directory of the project to operate on.
* @param string $project_name
* The project machine name (AKA shortname).
* @param string $version
* The version string to inject into the .info file(s).
*
* @return boolean
* TRUE on success, FALSE on any failures appending data to .info files.
*/
function drush_pm_inject_info_file_metadata($project_dir, $project_name, $version) {
$info_files = drush_scan_directory($project_dir, '/.*\.info$/');
if (!empty($info_files)) {
// Construct the string of metadata to append to all the .info files.
// Taken straight from: http://drupalcode.org/project/drupalorg.git/blob/refs/heads/6.x-3.x:/drupalorg_project/plugins/release_packager/DrupalorgProjectPackageRelease.class.php#l192
$info = "\n\n; Information added by drush on " . date('Y-m-d') . "\n";
$info .= "version = \"$version\"\n";
// .info files started with 5.x, so we don't have to worry about version
// strings like "4.7.x-1.0" in this regular expression. If we can't parse
// the version (also from an old "HEAD" release), or the version isn't at
// least 6.x, don't add any "core" attribute at all.
$matches = array();
if (preg_match('/^((\d+)\.x)-.*/', $version, $matches) && $matches[2] >= 6) {
$info .= "core = \"$matches[1]\"\n";
}
$info .= "project = \"$project_name\"\n";
$info .= 'datestamp = "'. time() ."\"\n";
$info .= "\n";
foreach ($info_files as $info_file) {
if (!drush_file_append_data($info_file->filename, $info)) {
return FALSE;
}
}
}
return TRUE;
}
drush-5.10.0/commands/pm/release_info/ 0000775 0000000 0000000 00000000000 12221055461 0017570 5 ustar 00root root 0000000 0000000 drush-5.10.0/commands/pm/release_info/updatexml.inc 0000664 0000000 0000000 00000050247 12221055461 0022276 0 ustar 00root root 0000000 0000000 $release) {
$options[$version] = array($version, '-', gmdate('Y-M-d', $release['date']), '-', implode(', ', $release['release_status']));
}
$choice = drush_choice($options, dt('Choose one of the available releases for !project:', array('!project' => $request['name'])));
if ($choice) {
return $project_info['releases'][$choice];
}
return FALSE;
}
/**
* Obtain releases info for given requests and fill in status information.
*
* @param $requests
* An array of project names optionally with a version.
*/
function release_info_get_releases($requests) {
$info = array();
foreach ($requests as $name => $request) {
$xml = updatexml_get_release_history_xml($request);
if (!$xml) {
continue;
}
$project_info = updatexml_get_releases_from_xml($xml, $name);
$info[$name] = $project_info;
}
return $info;
}
/**
* Check if a project is available in a update service.
*
* Optionally check for consistency by comparing given project type and
* the type obtained from the update service.
*/
function release_info_check_project($request, $type = NULL) {
$xml = updatexml_get_release_history_xml($request);
if (!$xml) {
return FALSE;
}
if ($type) {
$project_type = updatexml_determine_project_type($xml);
if ($project_type != $type) {
return FALSE;
}
}
return TRUE;
}
/**
* Prints release notes for given projects.
*
* @param $requests
* An array of drupal.org project names optionally with a version.
* @param $print_status
* Boolean. Used by pm-download to not print a informative note.
* @param $tmpfile
* If provided, a file that contains contents to show before the
* release notes.
*/
function release_info_print_releasenotes($requests, $print_status = TRUE, $tmpfile = NULL) {
$info = release_info_get_releases($requests);
if (!$info) {
return drush_log(dt('No valid projects given.'), 'ok');
}
if (is_null($tmpfile)) {
$tmpfile = drush_tempnam('rln-' . implode('-', array_keys($requests)) . '.');
}
foreach ($info as $name => $project) {
$selected_versions = array();
// If the request includes version, show the release notes for this version.
if (isset($requests[$name]['version'])) {
$selected_versions[] = $requests[$name]['version'];
}
else {
// If requested project is installed,
// show release notes for the installed version and all newer versions.
if (isset($project['recommended'], $project['installed'])) {
$releases = array_reverse($project['releases']);
foreach($releases as $version => $release) {
if ($release['date'] >= $project['releases'][$project['installed']]['date']) {
$release += array('version_extra' => '');
$project['releases'][$project['installed']] += array('version_extra' => '');
if ($release['version_extra'] == 'dev' && $project['releases'][$project['installed']]['version_extra'] != 'dev') {
continue;
}
$selected_versions[] = $version;
}
}
}
else {
// Project is not installed and user did not specify a version,
// so show the release notes for the recommended version.
$selected_versions[] = $project['recommended'];
}
}
foreach ($selected_versions as $version) {
if (!isset($project['releases'][$version]['release_link'])) {
drush_log(dt("Project !project does not have release notes for version !version.", array('!project' => $name, '!version' => $version)), 'warning');
continue;
}
$release_link = $project['releases'][$version]['release_link'];
$filename = drush_download_file($release_link, drush_tempnam($name));
@$dom = DOMDocument::loadHTMLFile($filename);
if ($dom) {
drush_log(dt("Successfully parsed and loaded the HTML contained in the release notes' page for !project (!version) project.", array('!project' => $name, '!version' => $version)), 'notice');
}
else {
drush_log(dt("Error while requesting the release notes page for !project project.", array('!project' => $name)), 'error');
continue;
}
$xml = simplexml_import_dom($dom);
$node_content = $xml->xpath('//*/div[@class="node-content"]');
// Prepare the project notes to print.
$notes_last_update = $node_content[0]->div[1]->div[0]->asXML();
unset($node_content[0]->div);
$project_notes = $node_content[0]->asXML();
// Build the status message.
$status_msg = '> ' . implode(', ', $project['releases'][$version]['release_status']);
$break = '
';
$notes_header = dt("
> RELEASE NOTES FOR '!name' PROJECT, VERSION !version:!break
> !time.!break
!status
", array('!status' => $print_status ? $status_msg : '', '!name' => strtoupper($name), '!break' => $break, '!version' => $version, '!time' => $notes_last_update));
// Finally print the release notes for the requested project.
if (drush_get_option('html', FALSE)) {
$print = $notes_header . $project_notes;
}
else {
$print = drush_html_to_text($notes_header . $project_notes . "\n", array('br', 'p', 'ul', 'ol', 'li', 'hr'));
if (drush_drupal_major_version() < 7) { $print .= "\n"; }
}
file_put_contents($tmpfile, $print, FILE_APPEND);
}
}
drush_print_file($tmpfile);
}
/**
* Helper function for release_info_filter_releases().
*/
function _release_info_compare_date($a, $b) {
if ($a['date'] == $b['date']) {
return 0;
}
if ($a['version_major'] == $b['version_major']) {
return ($a['date'] > $b['date']) ? -1 : 1;
}
return ($a['version_major'] > $b['version_major']) ? -1 : 1;
}
/**
* Filter a list of releases.
*
* @param $releases
* Array of release information
* @param $all
* Show all releases. If FALSE, shows only the first release that is
* Recommended or Supported or Security or Installed.
* @param String $restrict_to
* If set to 'dev', show only development release.
* @param $show_all_until_installed
* If TRUE, then all releases will be shown until the INSTALLED release
* is found, at which point the algorithm will stop.
*/
function release_info_filter_releases($releases, $all = FALSE, $restrict_to = '', $show_all_until_installed = TRUE) {
// Start off by sorting releases by release date.
uasort($releases, '_release_info_compare_date');
// Find version_major for the installed release.
$installed_version_major = FALSE;
foreach ($releases as $version => $release_info) {
if (in_array("Installed", $release_info['release_status'])) {
$installed_version_major = $release_info['version_major'];
}
}
// Now iterate through and filter out the releases we're interested in.
$options = array();
$limits_list = array();
$dev = $restrict_to == 'dev';
foreach ($releases as $version => $release_info) {
if (!$dev || ((array_key_exists('version_extra', $release_info)) && ($release_info['version_extra'] == 'dev'))) {
$saw_unique_status = FALSE;
foreach ($release_info['release_status'] as $one_status) {
// We will show the first release of a given kind;
// after we show the first security release, we show
// no other. We do this on a per-major-version basis,
// though, so if a project has three major versions, then
// we will show the first security release from each.
// This rule is overridden by $all and $show_all_until_installed.
$test_key = $release_info['version_major'] . $one_status;
if (!array_key_exists($test_key, $limits_list)) {
$limits_list[$test_key] = TRUE;
$saw_unique_status = TRUE;
// Once we see the "Installed" release we will stop
// showing all releases
if ($one_status == "Installed") {
$show_all_until_installed = FALSE;
$installed_release_date = $release_info['date'];
}
}
}
if ($all || ($show_all_until_installed && ($installed_version_major == $release_info['version_major'])) || $saw_unique_status) {
$options[$release_info['version']] = $release_info;
}
}
}
// If "show all until installed" is still true, that means that
// we never encountered the installed release anywhere in releases,
// and therefore we did not filter out any releases at all. If this
// is the case, then call ourselves again, this time with
// $show_all_until_installed set to FALSE from the beginning.
// The other situation we might encounter is when we do not encounter
// the installed release, and $options is still empty. This means
// that there were no supported or recommented or security or development
// releases found. If this is the case, then we will force ALL to TRUE
// and show everything on the second iteration.
if (($all === FALSE) && ($show_all_until_installed === TRUE)) {
$options = release_info_filter_releases($releases, empty($options), $restrict_to, FALSE);
}
return $options;
}
/**
* Pick most appropriate release from XML list or ask the user if no one fits.
*
* @param array $request
* An array with project and version strings as returned by
* pm_parse_project_version().
* @param resource $xml
* A handle to the XML document.
* @param String $restrict_to
* One of:
* 'dev': Forces a -dev release.
* 'version': Forces a point release.
* '': No restriction (auto-selects latest recommended or supported release
if requested release is not found).
* Default is ''.
*/
function updatexml_parse_release($request, $xml, $restrict_to = '') {
if (!empty($request['version'])) {
$matches = array();
// See if we only have a branch version.
if (preg_match('/^\d+\.x-(\d+)$/', $request['version'], $matches)) {
$xpath_releases = "/project/releases/release[status='published'][version_major=" . (string)$matches[1] . "]";
$releases = @$xml->xpath($xpath_releases);
}
else {
// In some cases, the request only says something like '7.x-3.x' but the
// version strings include '-dev' on the end, so we need to append that
// here for the xpath to match below.
if (substr($request['version'], -2) == '.x') {
$request['version'] .= '-dev';
}
$releases = $xml->xpath("/project/releases/release[status='published'][version='" . $request['version'] . "']");
if (empty($releases)) {
if (empty($restrict_to)) {
drush_log(dt("Could not locate !project version !version, will try to download latest recommended or supported release.", array('!project' => $request['name'], '!version' => $request['version'])), 'warning');
}
else {
drush_log(dt("Could not locate !project version !version.", array('!project' => $request['name'], '!version' => $request['version'])), 'warning');
return FALSE;
}
}
}
}
if ($restrict_to == 'dev') {
$releases = @$xml->xpath("/project/releases/release[status='published'][version_extra='dev']");
if (empty($releases)) {
drush_print(dt('There is no development release for project !project.', array('!type' => $release_type, '!project' => $request['name'])));
return FALSE;
}
}
// If that did not work, we will get the first published release for the
// recommended major version or fallback to other supported major versions.
if (empty($releases)) {
foreach(array('recommended_major', 'supported_majors') as $release_type) {
if ($versions = $xml->xpath("/project/$release_type")) {
$xpath = "/project/releases/release[status='published'][version_major=" . (string)$versions[0] . "]";
$releases = @$xml->xpath($xpath);
if (!empty($releases)) {
break;
}
}
}
}
// If there are releases found, let's try first to fetch one with no
// 'version_extra'. Otherwise, use all.
if (!empty($releases)) {
$stable_releases = array();
foreach ($releases as $one_release) {
if (!array_key_exists('version_extra', $one_release)) {
$stable_releases[] = $one_release;
}
}
if (!empty($stable_releases)) {
$releases = $stable_releases;
}
}
if (empty($releases)) {
drush_print(dt('There are no releases for project !project.', array('!project' => $request['name'])));
return FALSE;
}
// First published release is just the first value in $releases.
return (array)$releases[0];
}
/**
* Download the release history xml for the specified request.
*/
function updatexml_get_release_history_xml($request) {
$status_url = isset($request['status url'])?$request['status url']:RELEASE_INFO_DEFAULT_URL;
$url = $status_url . '/' . $request['name'] . '/' . $request['drupal_version'];
drush_log('Downloading release history from ' . $url);
// Some hosts have allow_url_fopen disabled.
if ($path = drush_download_file($url, drush_tempnam($request['name']), drush_get_option('cache-duration-releasexml', 24*3600))) {
$xml = simplexml_load_file($path);
}
if (!$xml) {
// We are not getting here since drupal.org always serves an XML response.
return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Could not download project status information from !url', array('!url' => $url)));
}
if ($error = $xml->xpath('/error')) {
// Don't set an error here since it stops processing during site-upgrade.
drush_log($error[0], 'warning'); // 'DRUSH_PM_COULD_NOT_LOAD_UPDATE_FILE',
return FALSE;
}
// Unpublished project?
$project_status = $xml->xpath('/project/project_status');
if ($project_status[0][0] == 'unpublished') {
return drush_set_error('DRUSH_PM_PROJECT_UNPUBLISHED', dt("Project !project is unpublished and has no releases available.", array('!project' => $request['name'])), 'warning');
}
return $xml;
}
/**
* Obtain releases for a project's xml as returned by the update service.
*/
function updatexml_get_releases_from_xml($xml, $project) {
// If bootstraped, we can obtain which is the installed release of a project.
static $installed_projects = FALSE;
if (!$installed_projects) {
if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
$installed_projects = drush_get_projects();
}
else {
$installed_projects = array();
}
}
foreach (array('title', 'short_name', 'dc:creator', 'api_version', 'recommended_major', 'supported_majors', 'default_major', 'project_status', 'link') as $item) {
if (array_key_exists($item, $xml)) {
$value = $xml->xpath($item);
$project_info[$item] = (string)$value[0];
}
}
$recommended_major = @$xml->xpath("/project/recommended_major");
$recommended_major = empty($recommended_major)?"":(string)$recommended_major[0];
$supported_majors = @$xml->xpath("/project/supported_majors");
$supported_majors = empty($supported_majors)?array():array_flip(explode(',', (string)$supported_majors[0]));
$releases_xml = @$xml->xpath("/project/releases/release[status='published']");
$recommended_version = NULL;
$latest_version = NULL;
foreach ($releases_xml as $release) {
$release_info = array();
foreach (array('name', 'version', 'tag', 'version_major', 'version_extra', 'status', 'release_link', 'download_link', 'date', 'mdhash', 'filesize') as $item) {
if (array_key_exists($item, $release)) {
$value = $release->xpath($item);
$release_info[$item] = (string)$value[0];
}
}
$statuses = array();
if (array_key_exists($release_info['version_major'], $supported_majors)) {
$statuses[] = "Supported";
unset($supported_majors[$release_info['version_major']]);
}
if ($release_info['version_major'] == $recommended_major) {
if (!isset($latest_version)) {
$latest_version = $release_info['version'];
}
// The first stable version (no 'version extra') in the recommended major
// is the recommended release
if (empty($release_info['version_extra']) && (!isset($recommended_version))) {
$statuses[] = "Recommended";
$recommended_version = $release_info['version'];
}
}
if (!empty($release_info['version_extra']) && ($release_info['version_extra'] == "dev")) {
$statuses[] = "Development";
}
foreach ($release->xpath('terms/term/value') as $release_type) {
// There are three kinds of release types that we recognize:
// "Bug fixes", "New features" and "Security update".
// We will add "Security" for security updates, and nothing
// for the other kinds.
if (strpos($release_type, "Security") !== FALSE) {
$statuses[] = "Security";
}
}
// Add to status whether the project is installed.
if (isset($installed_projects[$project])) {
if ($installed_projects[$project]['version'] == $release_info['version']) {
$statuses[] = dt('Installed');
$project_info['installed'] = $release_info['version'];
}
}
$release_info['release_status'] = $statuses;
$releases[$release_info['version']] = $release_info;
}
// If there is no -stable- release in the recommended major,
// then take the latest verion in the recommended major to be
// the recommended release.
if (!isset($recommended_version) && isset($latest_version)) {
$recommended_version = $latest_version;
$releases[$recommended_version]['release_status'][] = "Recommended";
}
$project_info['releases'] = $releases;
$project_info['recommended'] = $recommended_version;
return $project_info;
}
/**
* Determine a project type from its update service xml.
*/
function updatexml_determine_project_type($xml) {
$project_types = array(
'core' => 'Drupal core',
'profile' => 'Distributions',
'profile-legacy' => 'Installation profiles',
'module' => 'Modules',
'theme' => 'Themes',
'theme engine' => 'Theme engines',
'translation' => 'Translations'
);
$project_types_xpath = '(value="' . implode('" or value="', $project_types) . '")';
$type = 'module';
if ($types = $xml->xpath('/project/terms/term[name="Projects" and ' . $project_types_xpath . ']')) {
$type = array_search($types[0]->value, $project_types);
$type = ($type == 'profile-legacy') ? 'profile' : $type;
}
return $type;
}
drush-5.10.0/commands/pm/update_info/ 0000775 0000000 0000000 00000000000 12221055461 0017432 5 ustar 00root root 0000000 0000000 drush-5.10.0/commands/pm/update_info/drupal.inc 0000664 0000000 0000000 00000011550 12221055461 0021416 0 ustar 00root root 0000000 0000000 $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();
// Clear any error set by a failed update fetch task. This avoid rollbacks.
drush_clear_error();
// 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;
}
// Discard projects with unknown installation path.
if ($project_name != 'drupal' && !isset($projects[$project_name]['path'])) {
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'], '-'));
}
// Add some info from the project to $data.
$data[$project_name] += array(
'path' => $projects[$project_name]['path'],
'label' => $projects[$project_name]['label'],
);
// Store all releases, not just the ones selected by update.module.
$data[$project_name]['releases'] = $available[$project_name]['releases'];
}
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;
}
drush-5.10.0/commands/pm/update_info/drupal_6.inc 0000664 0000000 0000000 00000006402 12221055461 0021643 0 ustar 00root root 0000000 0000000 $project) {
// Discard custom projects.
if ($project['status'] == UPDATE_UNKNOWN) {
unset($data[$project_name]);
continue;
}
// Discard projects with unknown installation path.
if ($project_name != 'drupal' && !isset($projects[$project_name]['path'])) {
unset($data[$project_name]);
continue;
}
// Allow to update disabled projects.
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);
}
// Add some info from the project to $data.
$data[$project_name] += array(
'path' => $projects[$project_name]['path'],
'label' => $projects[$project_name]['label'],
);
}
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;
}
drush-5.10.0/commands/pm/updatecode.pm.inc 0000664 0000000 0000000 00000062206 12221055461 0020366 0 ustar 00root root 0000000 0000000 $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(
'label' => $extension->label,
'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(
'label' => $projects[$name]['label'],
'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(
'label' => $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:"));
// Cache the output of drush_print_table in $tmpfile so that we can
// include it in the paginated output later.
$tmphandle = fopen($tmpfile, 'a');
drush_print_table($rows, TRUE, array(3 => 50), $tmphandle);
fclose($tmphandle);
$contents = file_get_contents($tmpfile);
drush_print($contents);
drush_print();
// If specific project updates were requested then remove releases for all
// others.
$requested = func_get_args();
if (!empty($requested)) {
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]);
}
}
}
// 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.
if (empty($updateable)) {
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) {
$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...');
$requests = pm_parse_project_version(array('drupal'));
release_info_print_releasenotes($requests, 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]);
}
elseif (is_link($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) {
$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...');
$requests = pm_parse_project_version(array_keys($update_info));
release_info_print_releasenotes($requests, 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'])));
// Define and check the full path to project directory and base (parent) directory.
$project['full_project_path'] = $drupal_root . '/' . $project['path'];
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) {
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;
}
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['label'], $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;
}
// If installed version is dev and the candidate version is older, choose
// latest dev as candidate.
if (($project['install_type'] == 'dev') && isset($project['candidate_version'])) {
if ($project['releases'][$project['candidate_version']]['date'] < $project['info']['datestamp']) {
$project['candidate_version'] = $project['latest_dev'];
if ($project['releases'][$project['candidate_version']]['date'] <= $project['info']['datestamp']) {
$project['candidate_version'] = $project['existing_version'];
$project['updateable'] = FALSE;
}
}
}
}
/**
* 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;
}
}
drush-5.10.0/commands/pm/version_control/ 0000775 0000000 0000000 00000000000 12221055461 0020362 5 ustar 00root root 0000000 0000000 drush-5.10.0/commands/pm/version_control/backup.inc 0000664 0000000 0000000 00000004610 12221055461 0022323 0 ustar 00root root 0000000 0000000 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();
}
}
drush-5.10.0/commands/pm/version_control/bzr.inc 0000664 0000000 0000000 00000013475 12221055461 0021664 0 ustar 00root root 0000000 0000000 '.');
}
$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');
}
}
drush-5.10.0/commands/pm/version_control/svn.inc 0000664 0000000 0000000 00000014604 12221055461 0021670 0 ustar 00root root 0000000 0000000 $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');
}
}
drush-5.10.0/commands/runserver/ 0000775 0000000 0000000 00000000000 12221055461 0016554 5 ustar 00root root 0000000 0000000 drush-5.10.0/commands/runserver/runserver-drupal.inc 0000664 0000000 0000000 00000003542 12221055461 0022573 0 ustar 00root root 0000000 0000000 env;
// Handle static files and php scripts accessed directly
$uri = $request->uri;
$doc_root = DRUPAL_ROOT;
$path = $doc_root . $uri;
if (is_file(realpath($path))) {
if (preg_match('#\.php$#', $uri)) {
// SCRIPT_NAME is equal to uri if it does exist on disk
$cgi_env['SCRIPT_NAME'] = $uri;
return $this->get_php_response($request, $path, $cgi_env);
}
return $this->get_static_response($request, $path);
}
// Rewrite clean-urls
$cgi_env['QUERY_STRING'] = 'q=' . ltrim($uri, '/');
if ($request->query_string != "") {
$cgi_env['QUERY_STRING'] .= '&' . $request->query_string;
}
$cgi_env['SCRIPT_NAME'] = '/index.php';
$cgi_env['HTTP_HOST'] = $cgi_env['SERVER_NAME'] = $this->site;
return $this->get_php_response($request, $doc_root . '/index.php', $cgi_env);
}
/**
* Override get started event.
*/
function listening() {
if (!empty($this->browse)) {
drush_start_browser($this->browse);
}
}
/**
* Override request done event.
*/
function request_done($request) {
drush_print(trim($this->get_log_line($request), "\n"));
if ($this->debug) {
drush_print_r($request);
}
}
}
drush-5.10.0/commands/runserver/runserver-prepend.php 0000664 0000000 0000000 00000004744 12221055461 0022764 0 ustar 00root root 0000000 0000000 strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])),
'!severity' => $log_entry['severity'],
'!type' => $log_entry['type'],
'!ip' => $log_entry['ip'],
'!request_uri' => $log_entry['request_uri'],
'!referer' => $log_entry['referer'],
'!uid' => $log_entry['user']->uid,
'!link' => strip_tags($log_entry['link']),
));
error_log($message);
}
}
function runserver_env($key) {
if (isset($_SERVER[$key])) {
return $_SERVER[$key];
}
else {
return getenv($key);
}
}
drush-5.10.0/commands/runserver/runserver.drush.inc 0000664 0000000 0000000 00000031213 12221055461 0022426 0 ustar 00root root 0000000 0000000 'Runs a lightweight built in http server for development.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
'arguments' => array(
'addr:port/path' => 'Host IP address and port number to bind to and path to open in web browser. Format is addr:port/path, default 127.0.0.1:8888, all elements optional. See examples for shorthand.',
),
'options' => array(
'server' => 'Which http server to use - either: "cgi" for a CGI based httpserver (default, requires php 5.3 and php-cgi binary) or "builtin" for php 5.4 built in http server.',
'php-cgi' => 'Name of the php-cgi binary. If it is not on your current $PATH you should include the full path. You can include command line parameters to pass into php-cgi.',
'variables' => 'Key-value array of variables to override in the $conf array for the running site. By default disables drupal_http_request_fails to avoid errors on Windows (which supports only one connection at a time). Comma delimited list of name=value pairs (or array in drushrc).',
'default-server' => 'A default addr:port/path to use for any values not specified as an argument.',
'user' => 'If opening a web browser, automatically log in as this user (user ID or username).',
'browser' => 'If opening a web browser, which browser to user (defaults to operating system default).',
'dns' => 'Resolve hostnames/IPs using DNS/rDNS (if possible) to determine binding IPs and/or human friendly hostnames for URLs and browser.',
),
'aliases' => array('rs'),
'examples' => array(
'drush rs 8080' => 'Start runserver on 127.0.0.1, port 8080.',
'drush rs 10.0.0.28:80' => 'Start runserver on 10.0.0.28, port 80.',
'drush rs --php-cgi=php5-cgi --dns localhost:8888/user' => 'Start runserver on localhost (using rDNS to determine binding IP), port 8888, and open /user in browser. Use "php5-cgi" as the php-cgi binary.',
'drush rs /' => 'Start runserver on default IP/port (127.0.0.1, port 8888), and open / in browser.',
'drush rs --default-server=127.0.0.1:8080/ -' => 'Use a default (would be specified in your drushrc) that starts runserver on port 8080, and opens a browser to the front page. Set path to a single hyphen path in argument to prevent opening browser for this session.',
'drush rs --server=builtin :9000/admin' => 'Start builtin php 5.4 runserver on 127.0.0.1, port 9000, and open /admin in browser. Note that you need a colon when you specify port and path, but no IP.',
),
);
return $items;
}
/**
* Validate callback for runserver command.
*/
function drush_core_runserver_validate() {
if (version_compare(PHP_VERSION, '5.3.0') < 0) {
return drush_set_error('RUNSERVER_PHP53_VERSION', dt('The runserver command requires php 5.3, which could not be found.'));
}
$php_cgi = drush_shell_exec('which ' . drush_get_option('php-cgi', 'php-cgi'));
$builtin = version_compare(PHP_VERSION, '5.4.0') >= 0;
$server = drush_get_option('server', FALSE);
if (!$server) {
// No server specified, try and find a valid server option, preferring cgi.
if ($php_cgi) {
$server = 'cgi';
drush_set_option('server', 'cgi');
}
else if ($builtin) {
$server = 'builtin';
drush_set_option('server', 'builtin');
}
else {
return drush_set_error('RUNSERVER_PHP_CGI54', dt('The runserver command requires either the php-cgi binary, or php 5.4 (or higher). Neither could not be found.'));
}
}
else if ($server == 'cgi' && !$php_cgi) {
// Validate user specified cgi server option.
return drush_set_error('RUNSERVER_PHP_CGI', dt('The runserver command with the "cgi" server requires the php-cgi binary, which could not be found.'));
}
else if ($server == 'builtin' && !$builtin) {
// Validate user specified builtin server option.
return drush_set_error('RUNSERVER_PHP_CGI', dt('The runserver command with the "builtin" server requires php 5.4 (or higher), which could not be found.'));
}
// Update with detected configuration.
$server = drush_get_option('server', FALSE);
if ($server == 'cgi') {
// Fetch httpserver cgi based server to our /lib directory, if needed.
$lib = drush_get_option('lib', DRUSH_BASE_PATH . '/lib');
$httpserverfile = $lib . '/' . DRUSH_HTTPSERVER_DIR_BASE . substr(DRUSH_HTTPSERVER_VERSION, 0, 7) . '/httpserver.php';
if (!drush_file_not_empty($httpserverfile)) {
// Download and extract httpserver, and confirm success.
drush_lib_fetch(DRUSH_HTTPSERVER_BASE_URL . DRUSH_HTTPSERVER_VERSION);
if (!drush_file_not_empty($httpserverfile)) {
// Something went wrong - the library is still not present.
return drush_set_error('RUNSERVER_HTTPSERVER_LIB_NOT_FOUND', dt("The runserver command needs a copy of the httpserver library in order to function, and the attempt to download this file automatically failed. To continue you will need to download the package from !url, extract it into the !lib directory, such that httpserver.php exists at !httpserverfile.", array('!version' => DRUSH_HTTPSERVER_VERSION, '!url' => DRUSH_HTTPSERVER_BASE_URL . DRUSH_HTTPSERVER_VERSION, '!httpserverfile' => $httpserverfile, '!lib' => $lib)));
}
}
}
// Check we have a valid server.
if (!in_array($server, array('cgi', 'builtin'))) {
return drush_set_error('RUNSERVER_INVALID', dt('Invalid server specified.'));
}
}
/**
* Callback for runserver command.
*/
function drush_core_runserver($uri = NULL) {
global $user, $base_url;
// Determine active configuration.
$drush_default = array(
'host' => '127.0.0.1',
'port' => '8888',
'path' => '',
);
$user_default = runserver_parse_uri(drush_get_option('default-server', '127.0.0.1:8888'));
$uri = runserver_parse_uri($uri) + $user_default + $drush_default;
if (ltrim($uri['path'], '/') == '-') {
// Allow a path of a single hyphen to clear a default path.
$uri['path'] = '';
}
// Remove any leading slashes from the path, since that is what url() expects.
$uri['path'] = ltrim($uri['path'], '/');
// Determine and set the new URI.
$hostname = $addr = $uri['host'];
if (drush_get_option('dns', FALSE)) {
if (ip2long($hostname)) {
$hostname = gethostbyaddr($hostname);
}
else {
$addr = gethostbyname($hostname);
}
}
drush_set_context('DRUSH_URI', 'http://' . $hostname . ':' . $uri['port']);
// We pass in the currently logged in user (if set via the --user option),
// which will automatically log this user in the browser during the first
// request.
if (drush_get_option('user', FALSE)) {
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_LOGIN);
}
// We delete any registered files here, since they are not caught by Ctrl-C.
_drush_delete_registered_files();
// We set the effective base_url, since we have now detected the current site,
// and need to ensure generated URLs point to our runserver host.
// We also pass in the effective base_url to our auto_prepend_script via the
// CGI environment. This allows Drupal to generate working URLs to this http
// server, whilst finding the correct multisite from the HTTP_HOST header.
$base_url = 'http://' . $addr . ':' . $uri['port'];
$env['RUNSERVER_BASE_URL'] = $base_url;
// We pass in an array of $conf overrides using the same approach.
// By default we set drupal_http_request_fails to FALSE, as the httpserver
// is unable to process simultaneous requests on some systems.
// This is available as an option for developers to pass in their own
// favorite $conf overrides (e.g. disabling css aggregation).
$current_override = drush_get_option_list('variables', array('drupal_http_request_fails' => FALSE));
foreach ($current_override as $name => $value) {
if (is_numeric($name) && (strpos($value, '=') !== FALSE)) {
list($name, $value) = explode('=', $value, 2);
}
$override[$name] = $value;
}
$env['RUNSERVER_CONF'] = urlencode(serialize($override));
// We log in with the specified user ID (if set) via the password reset URL.
$user_message = '';
if ($user->uid) {
$options = array('query' => array('destination' => $uri['path']));
$browse = url(user_pass_reset_url($user) . '/login', $options);
$user_message = ', logged in as ' . $user->name;
}
else {
$browse = url($uri['path']);
}
drush_print(dt('HTTP server listening on !addr, port !port (see http://!hostname:!port/!path), serving site !site!user...', array('!addr' => $addr, '!hostname' => $hostname, '!port' => $uri['port'], '!path' => $uri['path'], '!site' => drush_get_context('DRUSH_DRUPAL_SITE', 'default'), '!user' => $user_message)));
if (drush_get_option('server', FALSE) == 'builtin') {
// Start php 5.4 builtin server.
// Store data used by runserver-prepend.php in the shell environment.
foreach ($env as $key => $value) {
putenv($key . '=' . $value);
}
if (!empty($uri['path'])) {
// Start a browser if desired. Include a 2 second delay to allow the
// server to come up.
drush_start_browser($browse, 2);
}
// Start the server using 'php -S'.
$php = drush_get_option('php');
drush_shell_exec_interactive($php . ' -S ' . $addr . ':' . $uri['port'] . ' --define auto_prepend_file="' . DRUSH_BASE_PATH . '/commands/runserver/runserver-prepend.php"');
}
else {
// Include the library and our class that extends it.
$lib = drush_get_option('lib', DRUSH_BASE_PATH . '/lib');
$httpserverfile = $lib . '/' . DRUSH_HTTPSERVER_DIR_BASE . substr(DRUSH_HTTPSERVER_VERSION, 0, 7) . '/httpserver.php';
require_once $httpserverfile;
require_once 'runserver-drupal.inc';
// Create a new httpserver instance and start it running.
$server = new DrupalServer(array(
'addr' => $addr,
'port' => $uri['port'],
'browse' => $browse,
'hostname' => $hostname,
'site' => drush_get_context('DRUSH_DRUPAL_SITE', 'default'),
'server_id' => 'Drush runserver',
'php_cgi' => drush_get_option('php-cgi', 'php-cgi') . ' --define auto_prepend_file="' . DRUSH_BASE_PATH . '/commands/runserver/runserver-prepend.php"',
'env' => $env,
'debug' => drush_get_context('DRUSH_DEBUG'),
));
$server->run_forever();
}
}
/**
* Parse a URI or partial URI (including just a port, host IP or path).
*
* @param $uri
* String that can contain partial URI.
* @return array
* URI array as returned by parse_url.
*/
function runserver_parse_uri($uri) {
if ($uri[0] == ':') {
// ':port/path' shorthand, insert a placeholder hostname to allow parsing.
$uri = 'placeholder-hostname' . $uri;
}
$first_part = substr($uri, 0, strpos($uri, '/'));
if (ip2long($first_part)) {
// 'IP/path' shorthand, insert a schema to allow parsing.
$uri = 'http://' . $uri;
}
$uri = parse_url($uri);
if (empty($uri)) {
return drush_set_error('RUNSERVER_INVALID_ADDRPORT', dt('Invalid argument - should be in the "host:port/path" format, numeric (port only) or non-numeric (path only).'));
}
if (count($uri) == 1 && isset($uri['path'])) {
if (is_numeric($uri['path'])) {
// Port only shorthand.
$uri['port'] = $uri['path'];
unset($uri['path']);
}
else if (ip2long($uri['path'])) {
// IP only shorthand.
$uri['host'] = $uri['path'];
unset($uri['path']);
}
}
if (isset($uri['host']) && $uri['host'] == 'placeholder-hostname') {
unset($uri['host']);
}
return $uri;
}
drush-5.10.0/commands/sql/ 0000775 0000000 0000000 00000000000 12221055461 0015320 5 ustar 00root root 0000000 0000000 drush-5.10.0/commands/sql/sql.drush.inc 0000664 0000000 0000000 00000141754 12221055461 0017752 0 ustar 00root root 0000000 0000000 'The DB connection key if using multiple connections in settings.php.',
'example-value' => 'extra',
);
$options['target'] = array(
'description' => 'The name of a target within the specified database.',
'example-value' => 'key',
// Gets unhidden in help_alter(). We only want to show this to D7 users but have to
// declare it here since some commands do not bootstrap fully.
'hidden' => TRUE,
);
$db_url['db-url'] = array(
'description' => 'A Drupal 6 style database URL. Only required for initial install - not re-install.',
'example-value' => 'mysql://root:pass@127.0.0.1/db',
);
$items['sql-drop'] = array(
'description' => 'Drop all tables in a given database.',
'arguments' => array(
),
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'options' => array(
'yes' => 'Skip confirmation and proceed.',
'result-file' => array(
'description' => 'Save to a file. The file should be relative to Drupal root. Recommended.',
'example-value' => '/path/to/file',
),
) + $options + $db_url,
'topics' => array('docs-policy'),
);
$items['sql-conf'] = array(
'description' => 'Print database connection details using print_r().',
'hidden' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'options' => array(
'all' => 'Show all database connections, instead of just one.',
'show-passwords' => 'Show database password.',
) + $options,
);
$items['sql-connect'] = array(
'description' => 'A string for connecting to the DB.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'options' => $options + $db_url,
'examples' => array(
'`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.',
),
);
$items['sql-create'] = array(
'description' => 'Create a database.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'examples' => array(
'drush sql-create' => 'Create the database for the current site.',
'drush @site.test sql-create' => 'Create the database as specified for @site.test.',
'drush sql-create --db-su=root --db-su-pw=rootpassword --db-url="mysql://drupal_db_user:drupal_db_password@127.0.0.1/drupal_db"' =>
'Create the database as specified in the db-url option.'
),
'options' => array(
'db-su' => 'Account to use when creating a new database. Optional.',
'db-su-pw' => 'Password for the "db-su" account. Optional.',
) + $options + $db_url,
);
$items['sql-dump'] = array(
'callback' => 'drush_sql_dump_execute',
'description' => 'Exports the Drupal DB as SQL using mysqldump or equivalent.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'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.php',
),
'options' => array(
'result-file' => array(
'description' => '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.',
'example-value' => '/path/to/file',
'value' => 'optional',
),
'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.',
'skip-tables-list' => 'A comma-separated list of tables to exclude completely. Optional.',
'structure-tables-list' => 'A comma-separated list of tables to include for structure, but not data. 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' => array('hidden' => TRUE, 'description' => 'Omit DROP TABLE statements. Postgres and Oracle only. Used by sql-sync, since including the DROP TABLE statements interfere with the import when the database is created.'),
'data-only' => 'Dump data without statements to create any of the schema.',
'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 + $db_url,
);
$items['sql-query'] = array(
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'description' => 'Execute a query against the site database.',
'examples' => array(
'drush sql-query "SELECT * FROM users WHERE uid=1"' => 'Browse user record. Table prefixes, if used, must be added to table names by hand.',
'drush sql-query --db-prefix "SELECT * FROM {users} WHERE uid=1"' => 'Browse user record. Table prefixes are honored. Caution: curly-braces will be stripped from all portions of the query.',
'`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.',
'drush sql-query --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' => array(
'description' => 'Save to a file. The file should be relative to Drupal root. Optional.',
'example-value' => '/path/to/file',
),
'file' => 'Path to a file containing the SQL to be run.',
'extra' => 'Add custom options to the mysql command.',
'db-prefix' => 'Enable replacement of braces in your query.',
) + $options + $db_url,
'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 @prod @dev' => 'Copy the DB defined in sites/prod to the DB in sites/dev.',
),
'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.',
'skip-tables-list' => 'A comma-separated list of tables to exclude completely. Optional.',
'structure-tables-key' => 'A key in the $structure_tables array. @see example.drushrc.php. Optional.',
'structure-tables-list' => 'A comma-separated list of tables to include for structure, but not data. 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.',
'source-database' => 'A key in the $db_url (D6) or $databases (D7+) array which provides the data.',
'source-target' => array(
'description' => 'A key within the SOURCE database identifying a particular server in the database group.',
'example-value' => 'key',
// Gets unhidden in help_alter(). We only want to show to D7 users but have to
// declare it here since this command does not bootstrap fully.
'hidden' => TRUE,
),
'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' => array(
'description' => 'Oy. A key within the TARGET database identifying a particular server in the database group.',
'example-value' => 'key',
// Gets unhidden in help_alter(). We only want to show to D7 users but have to
// declare it here since this command does not bootstrap fully.
'hidden' => TRUE,
),
'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' => array(
'description' => 'Account to use when creating a new database. Optional.',
'example-value' => 'root',
),
'db-su-pw' => array(
'description' => 'Password for the "db-su" account. Optional.',
'example-value' => 'pass',
),
'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 pattern for test email addresses in the sanitization operation, or "no" to keep email addresses unchanged. May contain replacement patterns %uid, %mail or %name. 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', 'docs-example-sync-via-http', 'docs-example-sync-extension'),
);
$items['sql-cli'] = array(
'description' => "Open a SQL command-line interface using Drupal's credentials.",
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'options' => array(
'A' => 'Skip reading table information. This gives a quicker start of mysql.',
) + $db_url,
'aliases' => array('sqlc'),
'examples' => array(
'drush sql-cli' => "Open a SQL command-line interface using Drupal's credentials.",
'drush sql-cli -A' => "Open a SQL command-line interface using Drupal's credentials and skip reading table information.",
),
);
$items['sql-sanitize'] = array(
'description' => "Run sanitization operations on the current database.",
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'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 pattern for test email addresses in the sanitization operation, or "no" to keep email addresses unchanged. May contain replacement patterns %uid, %mail or %name. Default is "user+%uid@localhost".',
) + $db_url,
'aliases' => array('sqlsan'),
);
return $items;
}
/**
* Implements hook_drush_help_alter().
*/
function sql_drush_help_alter(&$command) {
// Drupal 7+ only options.
if (drush_drupal_major_version() >= 7) {
if ($command['command'] == 'sql-sync') {
unset($command['options']['source-target']['hidden'], $command['options']['target-target']['hidden']);
}
else {
unset($command['options']['target']['hidden']);
}
}
}
/**
* Command argument complete callback.
*
* @return
* Array of available site aliases.
*/
function sql_sql_sync_complete() {
return array('values' => array_keys(_drush_sitealias_all_list()));
}
/**
* Check whether further bootstrap is needed. If so, do it.
*/
function drush_sql_bootstrap_further() {
if (!drush_get_option('db-url')) {
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
}
}
/**
* Command callback. Displays the Drupal site's database connection string.
*/
function drush_sql_conf() {
// Under Drupal 7, if the database is configured but empty, then
// DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION will throw an exception.
// If this happens, we'll just catch it and continue.
// TODO: Fix this in the bootstrap, per http://drupal.org/node/1996004
try {
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
}
catch (Exception $e) {
}
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();
}
$return = $db_spec;
if (!drush_get_option('show-passwords', FALSE)) {
drush_unset_recursive($db_spec, 'password');
}
drush_print_r($db_spec);
return $return;
}
/**
* 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';
if (drush_get_option('A', FALSE)) {
$command .= ' -A';
}
break;
case 'pgsql':
$command = 'psql';
break;
case 'sqlite':
$command = 'sqlite3';
break;
case 'sqlsrv':
$command = 'sqlcmd';
break;
case 'oracle':
// use rlwrap if available for readline support
if ($handle = popen('rlwrap -v', 'r')) {
$command = 'rlwrap sqlplus';
pclose($handle);
}
else {
$command = 'sqlplus';
}
break;
}
$command .= _drush_sql_get_credentials($db_spec);
return $command;
}
function drush_sql_connect() {
drush_sql_bootstrap_further();
$connect = _drush_sql_connect();
drush_print($connect);
return $connect;
}
/**
* Command callback. Create a database.
*/
function drush_sql_create() {
$db_spec = _drush_sql_get_db_spec();
// Prompt for confirmation.
if (!drush_get_context('DRUSH_SIMULATE')) {
$txt_destination = (isset($db_spec['remote-host']) ? $db_spec['remote-host'] . '/' : '') . $db_spec['database'];
drush_print(dt("Creating database !target. Any possible existing database will be dropped!", array('!target' => $txt_destination)));
if (!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
}
return _drush_sql_create($db_spec);
}
function _drush_sql_create($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);
return _drush_sql_query($sql, $create_db_su);
}
/**
* Command callback. Outputs the entire Drupal database in SQL format using mysqldump.
*/
function drush_sql_dump_execute() {
drush_sql_bootstrap_further();
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');
}
}
else {
return drush_set_error('DRUSH_SQL_DUMP_FAIL', 'Database dump failed');
}
}
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 /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, drush_get_option('result-file', FALSE));
}
/**
* 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.
* @param file
* Destination for the dump file.
* @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, $file = FALSE) {
$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'];
// $file is passed in to us usually via --result-file. If the user
// has set $options['result-file'] = TRUE, then we
// will generate an SQL dump file in the same backup
// directory that pm-updatecode uses.
if ($file) {
if ($file === TRUE) {
// User did not pass a specific value for --result-file. Make one.
$backup = drush_include_engine('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 'mysqli':
case 'mysql':
$exec = 'mysqldump';
if ($file) {
$exec .= ' --result-file '. $file;
}
// mysqldump wants 'databasename' instead of 'database=databasename' for no good reason.
// We had --skip-add-locks here for a while to help people with insufficient permissions,
// but removed it because it slows down the import a lot. See http://drupal.org/node/1283978
$extra = ' --no-autocommit --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($db_spec);
// 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) {
$exec .= ' > '. $file;
}
break;
case 'sqlsrv':
// Change variable '$file' by reference in order to get drush_log() to report.
if (!$file) {
$file = $db_spec['database'] . '_' . date('Ymd_His') . '.bak';
}
$exec = "sqlcmd -U \"" . $db_spec['username'] . "\" -P \"" . $db_spec['password'] . "\" -S \"" . $db_spec['host'] . "\" -Q \"BACKUP DATABASE [" . $db_spec['database'] . "] TO DISK='" . $file . "'\"";
break;
case 'oracle':
$create_db = drush_get_option('create-db');
$exec = 'exp ' . _drush_sql_get_credentials($db_spec);
// Change variable '$file' by reference in order to get drush_log() to report.
if (!$file) {
$file = $db_spec['username'] . '.dmp';
}
$exec .= ' file=' . $file;
if (!empty($tables))
$exec .= ' tables="(' . implode(',', $tables) . ')"';
else
$exec .= ' owner=' . $db_spec['username'];
break;
}
if (drush_get_option('gzip')) {
if ($file) {
$escfile = drush_escapeshellarg($file);
if (drush_get_context('DRUSH_AFFIRMATIVE')) {
// Gzip the result-file without Gzip confirmation
$exec .= " && gzip -f $escfile";
$file .= '.gz';
}
else {
// Gzip the result-file
$exec .= " && gzip $escfile";
$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 function 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 = NULL) {
drush_sql_bootstrap_further();
$filename = drush_get_option('file', NULL);
// Enable prefix processing when db-prefix option is used.
if (drush_get_option('db-prefix')) {
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);
}
$result = _drush_sql_query($query, NULL, $filename);
if (!$result) {
return drush_set_error('DRUSH_SQL_NO_QUERY', dt('Query failed.'));
}
return TRUE;
}
/*
* 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) {
$suffix = '';
$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);
}
// Enable prefix processing which can be dangerous so off by default. See http://drupal.org/node/1219850.
if (drush_get_option('db-prefix')) {
if (drush_drupal_major_version() >= 7) {
$query = Database::getConnection()->prefixTables($query);
}
else {
$query = db_prefix_tables($query);
}
}
}
// is this an oracle query
if ($scheme == 'oracle') {
$query = drush_sql_format_oracle($query);
$suffix = '.sql';
}
// 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, $suffix);
}
$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);
if (!empty($exec)) {
drush_print('exec: ' . $exec);
}
return TRUE;
}
if (empty($scheme)) {
return drush_set_error('DRUSH_SQL_NO_DATABASE', dt("No database to operate on."));
}
if (empty($exec)) {
return drush_set_error('DRUSH_SQL_NO_QUERY', 'No query provided');
}
return (drush_op_system($exec) == 0);
}
function drush_sql_drop() {
drush_sql_bootstrap_further();
$db_spec = _drush_sql_get_db_spec();
if (!$db_spec) {
return drush_set_error('DRUSH_SQL_NO_DATABASE', dt("No database to operate on."));
}
if (!drush_confirm(dt('Do you really want to drop all tables in the database !db?', array('!db' => $db_spec['database'])))) {
return drush_user_abort();
}
_drush_sql_drop($db_spec);
}
// 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?
$suffix = '';
$scheme = _drush_sql_get_scheme($db_spec);
switch ($scheme) {
case 'pgsql':
$query = drush_sql_show_tables_pgsql();
break;
case 'sqlite':
$query = '.tables';
break;
case 'sqlsrv':
$query = 'SELECT TABLE_NAME FROM information_schema.tables';
break;
case 'oracle':
$query = "SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME NOT IN ('BLOBS','LONG_IDENTIFIERS')";
$suffix = '.sql';
break;
default:
$query = 'SHOW TABLES;';
}
$filename = drush_save_data_to_temp_file($query, $suffix);
$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;
}
elseif ($scheme === 'sqlsrv') {
// Shift off the header of the column of data returned.
array_pop($tables);
array_pop($tables);
$sql = 'DROP TABLE '. implode(', ', $tables);
return _drush_sql_query($sql, $db_spec);
}
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() {
drush_sql_bootstrap_further();
drush_shell_proc_open(_drush_sql_connect());
}
/**
* 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_sql_bootstrap_further();
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. Honors a --db-url option.
*
* @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');
if ($url = drush_get_option('db-url')) {
$url = is_array($url) ? $url[$database] : $url;
$db_spec = drush_convert_db_from_db_url($url);
$db_spec['db_prefix'] = drush_get_option('db-prefix');
return $db_spec;
}
elseif (($databases = drush_get_option('databases')) && (array_key_exists($database, $databases)) && (array_key_exists($target, $databases[$database]))) {
return $databases[$database][$target];
}
elseif (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION)) {
switch (drush_drupal_major_version()) {
case 6:
if ($url = isset($GLOBALS['db_url']) ? $GLOBALS['db_url'] : drush_get_option('db-url', NULL)) {
$url = is_array($url) ? $url[$database] : $url;
$db_spec = drush_convert_db_from_db_url($url);
$db_spec['db_prefix'] = isset($GLOBALS['db_prefix']) ? $GLOBALS['db_prefix'] : drush_get_option('db-prefix', NULL);
return $db_spec;
}
return NULL;
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 6:
if (!isset($GLOBALS['db_url'])) {
return NULL;
}
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', (isset($db_spec['port']) ? $db_spec['port'] : NULL));
}
}
return $db_spec;
}
/**
* Determine where to store an sql dump file. This
* function is called by sql-sync.
*/
function drush_sql_dump_file(&$site_record) {
$site_record['dump-is-temp'] = FALSE;
// If the user has set the --{prefix}-dump option, then
// use the exact name provided.
$dump_file = drush_sitealias_get_path_option($site_record, 'dump');
if (!isset($dump_file)) {
$databases = sitealias_get_databases_from_record($site_record);
if (isset($databases)) {
$db_spec = $databases['default']['default'];
// Make a base filename pattern to use to name the dump file
$filename_pattern = $db_spec['database'];
if (isset($db_spec['remote-host'])) {
$filename_pattern = $db_spec['remote-host'] . '_' . $filename_pattern;
}
}
// If the user has set the --dump-dir option, then
// store persistant sql dump files there.
$dump_dir = drush_sitealias_get_path_option($site_record, 'dump-dir');
if (!isset($dump_dir)) {
// If this is a remote site, try to find a writable tmpdir.
if (isset($site_record['remote-host'])) {
$result = drush_invoke_process($site_record, 'ev', array('drush_print(drush_find_tmp())'), array(), array('integrate' => FALSE, 'override-simulated' => TRUE));
// If the call to invoke process does not work for some reason
// (e.g. drush not installed on the target machine),
// then we will just presume that the tmp dir is '/tmp'.
if (!$result || empty($result['output'])) {
$dump_dir = '/tmp';
}
else {
$dump_dir = trim($result['output']);
}
$dump_file = $dump_dir . '/' . $filename_pattern . '.sql';
}
else {
$dump_file = drush_tempnam($filename_pattern . '.sql.');
}
$site_record['dump-is-temp'] = TRUE;
}
else {
$dump_file = $dump_dir . '/' . $filename_pattern . '.sql';
}
}
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'];
// Default to unix socket if configured.
if (!empty($db_spec['unix_socket'])) {
$parameters['socket'] = $db_spec['unix_socket'];
}
// EMPTY host is not the same as NO host, and is valid (see unix_socket).
elseif (isset($db_spec['host'])) {
$parameters['host'] = $db_spec['host'];
}
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;
case 'sqlsrv':
// Some drush commands (e.g. site-install) want to connect to the
// server, but not the database. Connect to the built-in database.
$database = empty($db_spec['database']) ? 'master' : $db_spec['database'];
// Host and port are optional but have defaults.
$host = empty($db_spec['host']) ? '.\SQLEXPRESS' : $db_spec['host'];
return ' -S ' . $host . ' -d ' . $database . ' -U ' . $db_spec['username'] . ' -P ' . $db_spec['password'];
break;
case 'oracle':
// Return an Oracle connection string
return ' ' . $db_spec['username'] .'/' . $db_spec['password'] . ($db_spec['host']=='USETNS' ? '@' . $db_spec['database'] : '@//' . $db_spec['host'] . ':' . ($db_spec['port'] ? $db_spec['port'] : '1521') . '/' . $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 = drush_escapeshellarg($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 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';";
}
// Format queries to work with Oracle and SqlPlus
function drush_sql_format_oracle($query) {
// remove trailing semicolon from query if we have it
$query = preg_replace('/\;$/', '', $query);
// some sqlplus settings
$settings[] = "set TRIM ON";
$settings[] = "set FEEDBACK OFF";
$settings[] = "set UNDERLINE OFF";
$settings[] = "set PAGES 0";
$settings[] = "set PAGESIZE 50000";
// are we doing a describe ?
if (!preg_match('/^ *desc/i', $query)) {
$settings[] = "set LINESIZE 32767";
}
// are we doing a show tables ?
if (preg_match('/^ *show tables/i', $query)) {
$settings[] = "set HEADING OFF";
$query = "select object_name from user_objects where object_type='TABLE' order by object_name asc";
}
// create settings string
$sqlp_settings = implode("\n", $settings)."\n";
// important for sqlplus to exit correctly
return "${sqlp_settings}${query};\nexit;\n";
}
/*
* Drop all tables (if DB exists) or 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_get_db_spec();
}
if (drush_sql_db_exists($db_spec)) {
_drush_sql_drop($db_spec);
}
else {
_drush_sql_create($db_spec);
}
}
/*
* Build DB connection array with superuser credentials if provided.
*
* The options 'db-su' and 'db-su-pw' will be retreived from the
* specified site alias record, if it exists and contains those items.
* If it does not, they will be fetched via drush_get_option.
*
* Note that in the context of sql-sync, the site alias record will
* be taken from the target alias (e.g. `drush sql-sync @source @target`),
* which will be overlayed with any options that begin with 'target-';
* therefore, the commandline options 'target-db-su' and 'target-db-su-pw'
* may also affect the operation of this function.
*/
function drush_sql_su($db_spec, $site_alias_record = NULL) {
$create_db_target = $db_spec;
$create_db_target['database'] = '';
$db_superuser = drush_sitealias_get_option($site_alias_record, 'db-su');
if (isset($db_superuser)) {
$create_db_target['username'] = $db_superuser;
}
$db_su_pw = drush_sitealias_get_option($site_alias_record, 'db-su-pw');
// If --db-su-pw is not provided and --db-su is, default to empty password.
// This way db cli command will take password from .my.cnf or .pgpass.
if (!empty($db_su_pw)) {
$create_db_target['password'] = $db_su_pw;
}
elseif (isset($db_superuser)) {
unset($create_db_target['password']);
}
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 if the CREATE is not running on the bash command line.
*/
function drush_sql_build_createdb_sql($db_spec, $quoted = FALSE) {
$sql = array();
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);
}
/*
* Does specified database exist on target server
*
* @return boolean
*/
function drush_sql_db_exists($db_spec) {
if ($db_spec['driver'] == 'sqlite') {
return file_exists($db_spec['database']);
}
$connect_yes_db = _drush_sql_connect($db_spec);
$database = $db_spec['database'];
unset($db_spec['database']);
$connect_no_db = _drush_sql_connect($db_spec);
// We need the output back so we can't use drush_sql_query().
switch ($db_spec['driver']) {
case 'mysql':
$sql = "SELECT 1;";
// Suppress ugly output. Redirect STDERR and STDOUT. We just need exit code.
$bit_bucket = drush_bit_bucket();
return drush_shell_exec("$connect_yes_db -e \"$sql\" 2> $bit_bucket > $bit_bucket");
case 'pgsql':
$sql = "SELECT 1 AS result FROM pg_database WHERE datname='$database'";
drush_shell_exec("$connect_no_db -t -c \"$sql\"");
$output = drush_shell_exec_output();
return (bool)$output[0];
case 'sqlsrv':
// TODO: untested, but the gist is here.
$sql = "if db_id('$database') IS NOT NULL print 1";
drush_shell_exec("$connect_no_db -Q \"$sql\"");
$output = drush_shell_exec_output();
return $output[0] == 1;
}
}
function drush_sql_build_exec($db_spec, $filepath) {
$scheme = _drush_sql_get_scheme($db_spec);
$exec = '';
switch ($scheme) {
case 'mysql':
$exec = 'mysql';
$exec .= _drush_sql_get_credentials($db_spec);
$exec .= ' ' . drush_get_option('extra');
$exec .= " < " . drush_escapeshellarg($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 " . drush_escapeshellarg($filepath);
break;
case 'sqlite':
$exec = 'sqlite3';
$exec .= ' ' . drush_get_option('extra');
$exec .= _drush_sql_get_credentials($db_spec);
$exec .= " < " . drush_escapeshellarg($filepath);
break;
case 'sqlsrv':
$exec = 'sqlcmd';
$exec .= ' ' . drush_get_option('extra');
$exec .= _drush_sql_get_credentials($db_spec);
$exec .= ' -h -1 -i "' . $filepath . '"';
break;
case 'oracle':
$exec = 'sqlplus';
$exec .= ' ' . drush_get_option('extra');
$exec .= _drush_sql_get_credentials($db_spec);
$exec .= " @" . drush_escapeshellarg($filepath);
break;
}
return $exec;
}
drush-5.10.0/commands/sql/sync.sql.inc 0000664 0000000 0000000 00000062307 12221055461 0017575 0 ustar 00root root 0000000 0000000 = 7) {
$core = DRUSH_DRUPAL_CORE;
include_once $core . '/includes/password.inc';
include_once $core . '/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 = $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, init = $newmail";
}
else {
$user_table_updates[] = "mail = '$newemail', init = '$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);
}
// Seems quite portable (SQLite?) - http://en.wikipedia.org/wiki/Truncate_(SQL)
$sql_sessions = 'TRUNCATE TABLE sessions;';
drush_sql_register_post_sync_op('sessions', dt('Truncate Drupal\'s sessions table'), $sql_sessions);
}
function drush_sql_sync($source = NULL, $destination = NULL) {
$source_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($source), 'source-');
$destination_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($destination), 'target-');
$source_os = drush_os($source_settings);
$target_os = drush_os($destination_settings);
// 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) {
// Get the options for the source and target databases
$source_db_url = drush_sitealias_get_db_spec($source_settings, FALSE, 'source-');
// The host may have special ssh requirements
$source_remote_ssh_options = drush_sitealias_get_option($source_settings, '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_sitealias_get_db_spec($destination_settings, FALSE, 'target-');
// The host may have special ssh requirements
$target_remote_ssh_options = drush_sitealias_get_option($destination_settings, '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)) {
if (empty($source_settings)) {
return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for !source', array('!source' => $source)));
}
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)) {
if (empty($destination_settings)) {
return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for !destination', array('!destination' => $destination)));
}
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_settings);
$target_dump = drush_sql_dump_file($destination_settings);
$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 ((!$destination_settings['dump-is-temp']) && ($source_settings['dump-is-temp'])) {
$source_dump = $target_dump;
$source_settings['dump-is-temp'] = FALSE;
}
else {
$target_dump = $source_dump;
$destination_settings['dump-is-temp'] = $source_settings['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_settings['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_sitealias_get_option($source_settings, 'no-sync');
$no_dump = drush_sitealias_get_option($source_settings, 'no-dump');
$no_cache = drush_sitealias_get_option($source_settings, 'no-cache');
if (!isset($no_cache)) {
$cache = drush_sitealias_get_option($source_settings, '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 --no-sync or --no-dump are already set, then this check is unnecessary.
if (isset($cache) && !isset($no_sync) && !isset($no_dump)) {
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_settings['dump-is-temp'] || $destination_settings['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 in !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($no_dump)) {
if (isset($source_db_url['remote-host'])) {
$source_remote_user = '';
$source_at = '';
if (array_key_exists('remote-user', $source_settings)) {
$source_remote_user = $source_settings['remote-user'];
$source_at ='@';
$source_remote_pass = array_key_exists('remote-pass', $source_settings) ? ':' . $source_settings['remote-pass'] : '';
}
$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 (!drush_is_windows($source_os) && (!$source_settings['dump-is-temp'])) {
$source_intermediate = $source_dump . '-' . date("U");
$mv_intermediate = '&& mv -f ' . $source_intermediate . ' ' . $source_dump;
}
list($dump_exec, $dump_file) = drush_sql_build_dump_command($table_selection, $source_db_url, $source_intermediate);
$dump_exec .= $mv_intermediate;
if (!drush_is_windows($source_os) && isset($cache) && !$source_settings['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';
}
}
else {
list($dump_exec, $dump_file) = drush_sql_build_dump_command($table_selection, $source_db_url, $local_file);
$no_sync = TRUE;
}
// Wrap the dump command in a remote call if the source site is remote.
$dump_exec = _drush_backend_generate_command($source_settings, $dump_exec);
}
// Call sql-dump, either on the local machine or remotely via ssh, as appropriate.
if (!empty($dump_exec)) {
if (drush_op_system($dump_exec)) {
return drush_set_error('DRUSH_SQL_DUMP_FAIL', 'Database dump failed');
}
}
// 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, drush_correct_absolute_path_for_exec($local_file, "RSYNC"), $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_sitealias_get_option($destination_settings, 'create-db');
if (isset($create_db)) {
$create_db_su = drush_sql_su($target_db_url, $destination_settings);
$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);
// Linux requires quotes around echo statements. Windows generally supports
// quotes for echo statements, but will complain if they are present
// when piping echo statements as input to other commands. Also,
// Windows multi-commands normally work when separated by '&&', '&', or ';'
// but will not function with ';' in this case.(http://drupal.org/node/1957020)
if (drush_is_windows($target_os)) {
$pre_import_commands = sprintf('echo %s | %s && ', $pre_import_sql, $db_su_connect);
}
else {
$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 = '';
$target_at = '';
if (array_key_exists('remote-user', $destination_settings)) {
$target_remote_user = $destination_settings['remote-user'];
$target_at ='@';
$target_remote_pass = array_key_exists('remote-pass', $destination_settings) ? ':' . $destination_settings['remote-pass'] : '';
}
if (!drush_core_call_rsync(drush_correct_absolute_path_for_exec($local_file, "RSYNC"), $target_remote_user . $target_at . $target_db_url['remote-host'] . ':' . $target_dump, $target_rsync_options)) {
return FALSE;
}
$import_exec = $pre_import_commands . $import_command . ' < ' . drush_escapeshellarg($target_dump, $target_os);
// Delete the remote target file if it is a temporary file.
if (!drush_is_windows($target_os) && $destination_settings['dump-is-temp']) {
$import_exec .= '; rm -f ' . drush_escapeshellarg($target_dump, $target_os);
}
// TODO: make sure that the remote tmp file is deleted on remote Windows machines.
}
else {
$import_exec = $pre_import_commands . $import_command . ' < ' . drush_escapeshellarg($local_file);
}
$import_exec = _drush_backend_generate_command($destination_settings, $import_exec);
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_process($destination_settings, "sql-query", array($sanitize_query));
}
else {
drush_print("Executing on $destination: $sanitize_query");
}
}
}
}
drush-5.10.0/commands/user/ 0000775 0000000 0000000 00000000000 12221055461 0015477 5 ustar 00root root 0000000 0000000 drush-5.10.0/commands/user/user.drush.inc 0000664 0000000 0000000 00000050662 12221055461 0020305 0 ustar 00root root 0000000 0000000 '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.',
),
'required-arguments' => TRUE,
'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.',
),
'required-arguments' => TRUE,
'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.',
),
'required-arguments' => TRUE,
'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.',
),
'required-arguments' => 1,
'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.',
),
'required-arguments' => 1,
'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'
),
'required-arguments' => TRUE,
'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',
),
'required-arguments' => TRUE,
'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.'
),
'required-arguments' => TRUE,
'options' => array(
'password' => array(
'description' => 'The new password for the account.',
'required' => TRUE,
'example-value' => 'foo',
),
),
'examples' => array(
'drush user-password someuser --password="correct horse battery staple"' =>
'Set the password for the username someuser. @see xkcd.com/936',
),
);
$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(
'user' => 'An optional uid, user name, or email address for the user to log in as. Default is to log in as uid 1. The uid/name/mail options take priority if specified.',
'path' => 'Optional path to redirect to after logging in.',
),
'options' => array(
'browser' => 'Optional value denotes which browser to use (defaults to operating system default). Set to 0 to suppress opening a browser.',
'uid' => 'A uid to log in as.',
'name' => 'A user name to log in as.',
'mail' => 'A user mail address to log in as.',
),
'examples' => array(
'drush user-login ryan node/add/blog' => 'Displays and opens default web browser (if configured or detected) for a one-time login link for the user with the username ryan and redirect to the path node/add/blog.',
'drush user-login --browser=firefox --mail=drush@example.org admin/settings/performance' => 'Open firefox web browser, login as the user with the e-mail address drush@example.org and redirect to the path admin/settings/performance.',
),
);
return $items;
}
/**
* Implements hook_drush_help_alter().
*/
function user_drush_help_alter(&$command) {
// Drupal 7+ only options.
if ($command['command'] == 'user-cancel' && drush_drupal_major_version() >= 7) {
$command['options']['delete-content'] = 'Delete all content created by the user';
$command['examples']['drush user-cancel --delete-content username'] =
'Cancel the user account with the name username and delete all content created by that user.';
}
}
/**
* Prints information about the specified user(s).
*/
function drush_user_information($users) {
$uids = _drush_user_get_users_from_arguments($users);
foreach($uids as $uid) {
_drush_user_print_info($uid);
}
}
/**
* Block the specified user(s).
*/
function drush_user_block($users = '') {
$uids = _drush_user_get_users_from_options_and_arguments($users);
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_user_get_users_from_options_and_arguments($users);
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_user_get_users_from_options_and_arguments($users);
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(dt("There is no role named: !role", array('!role' => $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_user_get_users_from_options_and_arguments($users);
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(dt("There is no role named: !role", array('!role' => $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')) {
if (drush_drupal_major_version() >= 8) {
$new_user_object = entity_create('user', $new_user);
$new_user_object->save();
}
else {
$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) {
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')) {
$pass = drush_get_option('password');
// If no password has been provided, prompt for one.
if (empty($pass)) $pass = drush_prompt(dt('Password'), NULL, TRUE, TRUE);
if (drush_drupal_major_version() >= 8) {
$user->pass = $pass;
$user_object = $user->save();
}
else {
$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($user = NULL, $path = NULL) {
$user_object = $uid = FALSE;
$args = func_get_args();
if (drush_get_option('uid', FALSE) || drush_get_option('name', FALSE) || drush_get_option('mail', FALSE)) {
// One of the user options was passed, so we prefer that to the user
// argument.
$user = NULL;
// If we only have a single argument and one of the user options is passed,
// then we assume the argument is the path to open.
if (count($args) == 1) {
$path = $args[0];
}
}
else if (empty($user)) {
// No user option or argument was passed, so we default to uid 1.
$uid = 1;
}
// Try and load a user from provided options and arguments.
if ($uid || $uid = reset(_drush_user_get_users_from_options_and_arguments($user))) {
$user_object = user_load($uid);
}
if ($user_object !== FALSE && $user_object->status) {
$options = array();
if ($path) {
$options['query']['destination'] = $path;
}
$link = url(user_pass_reset_url($user_object) . '/login', $options);
drush_start_browser($link);
drush_print($link);
return $link;
}
else {
drush_set_error("The user account could not be loaded or is blocked!");
}
}
/**
* 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");
}
}
/**
* Given a comma-separated list of users, return uids
* for users that match either by uid or email address.
*/
function _drush_user_get_users_from_arguments($users) {
$uids = array();
if ($users !== '') {
$users = explode(',', $users);
foreach($users as $user) {
$uid = _drush_user_get_uid($user);
if ($uid !== FALSE) {
$uids[] = $uid;
}
}
}
return $uids;
}
/**
* Return the list of matching uids given
*/
function _drush_user_get_users_from_options_and_arguments($users) {
$uids = drush_get_option_list('uids');
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)) {
$uids[] = $uid;
}
else {
drush_set_error("Could not find a uid for $user_attr = $search");
}
}
}
}
}
return array_merge($uids, _drush_user_get_users_from_arguments($users));
}
/**
* 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)'));
}
}
drush-5.10.0/docs/ 0000775 0000000 0000000 00000000000 12221055461 0013650 5 ustar 00root root 0000000 0000000 drush-5.10.0/docs/bastion.html 0000664 0000000 0000000 00000010021 12221055461 0016167 0 ustar 00root root 0000000 0000000 Remote Operations on Drupal Sites via a Bastion Server
Wikipedia defines a bastion server as "a special
purpose computer on a network specifically designed and configured
to withstand attacks." For the purposes of this documentation,
though, any server that you can ssh through to reach other servers
will do. Using standard ssh and drush techniques, it is possible
to make a two-hop remote command look and act as if the destination
machine is on the same network as the source machine.
Recap of Remote Site Aliases
Site aliases can refer to Drupal sites that are running on
remote machines simply including 'remote-host' and 'remote-user'
attributes:
$aliases['internal'] = array(
'remote-host' => 'internal.company.com',
'remote-user' => 'wwwadmin',
'uri' => 'http://internal.company.com',
'root' => '/path/to/remote/drupal/root',
);
With this alias defintion, you may use commands such as
`drush @internal status`, 'drush ssh @internal` and
`drush rsync @internal @dev` to operate remotely on the
internal machine. What if you cannot reach the server
that site is on from your current network? Enter the bastion server.
Setting up a Bastion server in .ssh/config
If you have access to a server, bastion.company.com, which
you can ssh to from the open internet, and if the bastion
server can in turn reach the internal server, then it
is possible to configure ssh to route all traffic to the
internal server through the bastion. The .ssh configuratin
file would look something like this:
In .ssh/config:
Host internal.company.com
ProxyCommand ssh user@bastion.company.com nc %h %p
That is all that is necessary; however, if the dev machine
you are configuring is a laptop that might sometimes be
inside the company intranet, you might want to optimize
this setup so that the bastion is not used when the internal
server can be reached directly. You could do this by
changing the contents of your .ssh/config file when your
network settings change -- or you could use drush.
Setting up a Bastion server via drush configuration
First, make sure that you do not have any configuration
options for the internal machine in your .ssh/config file.
The next step after that is to identify when you are connected
to your company intranet. I like to determine this by using the
`route` command to find my network gateway address, since
this is always the same on my intranet, and unlikely to be
encountered in other places.
In drushrc.php:
# Figure out if we are inside our company intranet by testing our gateway address against a known value
exec("route -n | grep '^0\.0\.0\.0' | awk '{ print $2; }' 2> /dev/null", $output);
if ($output[0] == '172.30.10.1') {
drush_set_context('MY_INTRANET', TRUE);
}
After this code runs, the 'MY_INTRANET' context will be set if our
gateway IP address matches the expected value, and unset otherwise.
We can make use of this in our alias files.
In aliases.drushrc.php:
if (drush_get_context('MY_INTRANET', FALSE) === FALSE) {
$aliases['intranet-proxy'] = array(
'ssh-options' => ' -o "ProxyCommand ssh user@bastion.company.com nc %h %p"',
);
}
$aliases['internal-server'] = array(
'parent' => '@intranet-proxy',
'remote-host' => 'internal.company.com',
'remote-user' => 'wwwadmin',
);
$aliases['internal'] = array(
'parent' => '@internal-server',
'uri' => 'http://internal.company.com',
'root' => '/path/to/remote/drupal/root',
);
The 'parent' term of the internal-server alias record is ignored
if the alias it references ('@intranet-proxy') is not defined;
the result is that 'ssh-options' will only be defined when
outside of the intranet, and the ssh ProxyCommand to the bastion
server will only be included when it is needed.
With this setup, you will be able to use your site alias
'@internal' to remotely operate on your internal intranet
Drupal site seemlessly, regardless of your location -- a handy
trick indeed.
drush-5.10.0/docs/bootstrap.html 0000664 0000000 0000000 00000010644 12221055461 0016560 0 ustar 00root root 0000000 0000000
The Drush Bootstrap Process
When preparing to run a command, drush works by "bootstrapping"
the Drupal environment in very much the same way that is done
during a normal page request from the web server, so most drush
commands run in the context of a fully-initialized website.
For efficiency and convenience, some drush commands can work
without first bootstrapping a Drupal site, or by only partially
bootstrapping a site. This is more efficient, because there is
sometimes a slight delay involved with bootstrapping, especially
in some of the later stages. It is also a matter of convenience,
because some commands are useful to use even when you do not
have a working Drupal site available to bootstrap. For example,
you can use drush to download Drupal with `drush dl drupal`. This
obviously does not require any bootstrapping to work.
Starting with Drush-5, the loading of configuration files is no
longer closely tied to the drush bootstrapping process. All configuration
files are now loaded upfront, during DRUSH_BOOTSTRAP_DRUSH, with
the Drupal root and site configuration files being loaded in advance
of the corresponding bootstrap phase. See `drush topic docs-configuration`
for details on drush configuration files.
DRUSH_BOOTSTRAP_DRUSH
Only bootstrap Drush, without any Drupal specific code.
Any code that operates on the Drush installation, and not specifically
any Drupal directory, should bootstrap to this phase.
DRUSH_BOOTSTRAP_DRUPAL_ROOT
Set up and test for a valid drupal root, either through the -r/--root options,
or evaluated based on the current working directory.
Any code that interacts with an entire Drupal installation, and not a specific
site on the Drupal installation should use this bootstrap phase.
DRUSH_BOOTSTRAP_DRUPAL_SITE
Set up a Drupal site directory and the correct environment variables to
allow Drupal to find the configuration file.
If no site is specified with the -l / --uri options, Drush will assume the
site is 'default', which mimics Drupal's behaviour.
If you want to avoid this behaviour, it is recommended that you use the
DRUSH_BOOTSTRAP_DRUPAL_ROOT bootstrap phase instead.
Any code that needs to modify or interact with a specific Drupal site's
settings.php file should bootstrap to this phase.
DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION
Load the settings from the Drupal sites directory.
This phase is analagous to the DRUPAL_BOOTSTRAP_CONFIGURATION bootstrap phase in Drupal
itself, and this is also the first step where Drupal specific code is included.
This phase is commonly used for code that interacts with the Drupal install API,
as both install.php and update.php start at this phase.
DRUSH_BOOTSTRAP_DRUPAL_DATABASE
Connect to the Drupal database using the database credentials loaded
during the previous bootstrap phase.
This phase is analogous to the DRUPAL_BOOTSTRAP_DATABASE bootstrap phase in
Drupal.
Any code that needs to interact with the Drupal database API needs to
be bootstrapped to at least this phase.
DRUSH_BOOTSTRAP_DRUPAL_FULL
Fully initialize Drupal.
This is analogous to the DRUPAL_BOOTSTRAP_FULL bootstrap phase in
Drupal.
Any code that interacts with the general Drupal API should be
bootstrapped to this phase.
DRUSH_BOOTSTRAP_DRUPAL_LOGIN
Log in to the initialiased Drupal site.
This bootstrap phase is used after the site has been
fully bootstrapped. This is the default bootstrap phase all
commands will try to reach, unless otherwise specified.
This phase will log you in to the drupal site with the username
or user ID specified by the --user/ -u option.
Use this bootstrap phase for your command if you need to have access
to information for a specific user, such as listing nodes that might
be different based on who is logged in.
DRUSH_BOOTSTRAP_MAX
This is not an actual bootstrap phase. Commands that use
DRUSH_BOOTSTRAP_MAX will cause drush to bootstrap as far
as possible, and then run the command regardless of the
bootstrap phase that was reached. This is useful for drush
commands that work without a bootstrapped site, but that
provide additional information or capabilities in the presence
of a bootstrapped site. For example, `drush pm-releases modulename`
works without a bootstrapped Drupal site, but will include
the version number for the installed module if a Drupal site
has been bootstrapped.
drush-5.10.0/docs/commands.html 0000664 0000000 0000000 00000024644 12221055461 0016351 0 ustar 00root root 0000000 0000000
Creating Custom Drush Commands
Creating a new drush command is very easy. There are
four simple steps.
- Create a command file called COMMANDFILE.drush.inc
- Implement the function COMMANDFILE_drush_command()
- Implement the functions that your commands will call.
These will usually be named drush_COMMANDFILE_COMMANDNAME().
For an example drush command, see examples/sandwich.drush.inc.
The steps for implementing your command are explained in more
detail below.
Create COMMANDFILE.drush.inc
The name of your drush command is very important. It must end
in ".drush.inc" to be recognized as a drush command. The part
of the filename that comes before the ".drush.inc" becomes the
name of the commandfile. Your commandfile name is used by
drush to compose the names of the functions it will call, so
choose wisely.
The example drush command, 'make-me-a-sandwich', is stored
in the 'sandwich' commandfile, 'sandwich.drush.inc'. You can
find this file in the 'examples' directory in the drush distribution.
Drush searches for commandfiles in the following locations:
- The "/path/to/drush/commands" folder.
- Folders listed in the 'include' option (see `drush topic docs-configuration`).
- The system-wide drush commands folder, e.g. /usr/share/drush/commands
- The ".drush" folder in the user's HOME folder.
- sites/all/drush in the current Drupal installation
- All enabled modules in the current Drupal installation
- Folders and files containing other versions of drush in their names will
be *skipped* (e.g. devel.drush4.inc or drush4/devel.drush.inc). Names
containing the current version of drush (e.g. devel.drush5.inc) will be loaded.
Note that modules in the current Drupal installation will only
be considered if drush has bootstrapped to at least the DRUSH_BOOSTRAP_SITE
level. Usually, when working with a Drupal site, drush will
bootstrap to DRUSH_BOOTSTRAP_FULL; in this case, only the drush
commandfiles in enabled modules will be considered eligible for
loading. If drush only bootstraps to DRUSH_BOOTSTRAP_SITE,
though, then all drush commandfiles will be considered, whether the
module is enabled or not. See `drush topic docs-bootstrap` for
more information on bootstrapping.
Additionally, drush commandfiles may optionally define a function
COMMANDFILE_drush_load() in the file COMMANDFILE.drush.load.inc.
If this function returns FALSE, then the commandfile will not be loaded.
Implement COMMANDFILE_drush_command()
The drush_command hook is the most important part of the
commandfile. It returns an array of items that define
how your commands should be called, and how they work.
Drush commands are very similar to the Drupal menu system.
The elements that can appear in a drush command definition
are shown below.
- 'aliases':
Provides a list of shorter names for the command.
For example, pm-download may also be called via `drush dl`.
If the alias is used, drush will substitute back in the
primary command name, so pm-download will still be used
to generate the command hook, etc.
- 'command hook':
Change the name of the function drush will
call to execute the command from drush_COMMANDFILE_COMMANDNAME()
to drush_COMMANDFILE_COMMANDHOOK(), where COMMANDNAME is the
original name of the command, and COMMANDHOOK is the value
of the 'command hook' item.
- 'callback':
Name of function to invoke for this command. The callback
function name _must_ begin with "drush_commandfile_", where commandfile
is from the file "commandfile.drush.inc", which contains the
commandfile_drush_command() function that returned this command.
Note that the callback entry is optional; it is preferable to
omit it, in which case drush_invoke() will generate the hook function name.
- 'callback arguments':
An array of arguments to pass to the callback.
The command line arguments, if any, will appear after the
callback arguments in the function parameters.
- 'description':
Description of the command.
- 'arguments':
An array of arguments that are understood by the command.
Used by `drush help` only.
- 'required-arguments':
Defaults to FALSE; TRUE if all of the arguments are required.
Set to an integer count of required arguments if only some are
required.
- 'options':
An array of options that are understood by the command.
Any option that the command expects to be able to query via
drush_get_option _must_ be listed in the options array.
If it is not, users will get an error about an "Unknown option"
when they try to specify the option on the command line.
The value of each option may be either a simple string containing
the option description, or an array containing the following
information:
- 'description': A description of the option.
- 'example_value': An example value to show in help.
- 'value': optional|required.
- 'required': This option must be passed.
- 'hidden': The option is not shown in the help output (rare).
- 'allow-additional-options':
If TRUE, then the ordinary testing to see if options exist
will be skipped. Examples of where this is done includes
the core-rsync command, which passes options along to the
rsync shell command.
This item may also contain a list of other commands that
are invoked as subcommands (e.g. the pm-update
command calls pm-updatecode and updatedb commands). When
this is done, the options from the subcommand may be used
on the commandline, and are also listed in the command's
`help` output.
Defaults to FALSE.
- 'examples':
An array of examples that are understood by the command.
Used by `drush help` only.
- 'scope':
One of 'system', 'project', 'site'. Not currently used.
- 'bootstrap':
Drupal bootstrap level. Valid values are:
- DRUSH_BOOTSTRAP_DRUSH
- DRUSH_BOOTSTRAP_DRUPAL_ROOT
- DRUSH_BOOTSTRAP_DRUPAL_SITE
- DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION
- DRUSH_BOOTSTRAP_DRUPAL_DATABASE
- DRUSH_BOOTSTRAP_DRUPAL_FULL
- DRUSH_BOOTSTRAP_DRUPAL_LOGIN
- DRUSH_BOOTSTRAP_MAX
The default value is DRUSH_BOOTSTRAP_DRUPAL_LOGIN.
See `drush topic docs-bootstrap`.
- 'core':
Drupal major version required. Append a '+' to indicate 'and later versions.'
- 'drupal dependencies':
Drupal modules required for this command.
- 'drush dependencies':
Other drush commandfiles required for this command.
- 'topics':
Provides a list of topic commands that are related in
some way to this command. Used by `drush help`.
- 'topic':
Set to TRUE if this command is a topic, callable from the
`drush docs-topics` command.
The 'sandwich' drush_command hook looks like this:
function sandwich_drush_command() {
$items = array();
$items['make-me-a-sandwich'] = array(
'description' => "Makes a delicious sandwich.",
'arguments' => array(
'filling' => 'The type of the sandwich (turkey, cheese, etc.)',
),
'options' => array(
'spreads' => 'Comma delimited list of spreads (e.g. mayonnaise, mustard)',
),
'examples' => array(
'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.',
),
'aliases' => array('mmas'),
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all.
);
return $items;
}
Most of the items in the 'make-me-a-sandwich' command
definition have no effect on execution, and are used only
by `drush help`. The exceptions are 'aliases' (described
above) and 'bootstrap'. As previously mentioned,
`drush topic docs-bootstrap` explains the drush bootstrapping
process in detail.
Implement drush_COMMANDFILE_COMMANDNAME()
The 'make-me-a-sandwich' command in sandwich.drush.inc
is defined as follows:
function drush_sandwich_make_me_a_sandwich($filling = 'ascii') {
... implementation here ...
}
If a user runs `drush make-me-a-sandwich` with no command line
arguments, then drush will call drush_sandwich_make_me_a_sandwich()
with no function parameters; in this case, $filling will take on
the provided default value, 'ascii'. (If there is no default
value provided, then the variable will be NULL, and a warning
will be printed.) Running `drush make-me-a-sandwich ham` will
cause drush to call drush_sandwich_make_me_a_sandwich('ham'). In
the same way, commands that take two command line arguments can
simply define two functional parameters, and a command that takes
a variable number of command line arguments can use the standard
php function func_get_args() to get them all in an array for easy
processing.
It is also very easy to query the command options using the
function drush_get_option(). For example, in the drush_sandwich_make_me_a_sandwich()
function, the --spreads option is retrieved as follows:
$str_spreads = '';
if ($spreads = drush_get_option('spreads')) {
$list = implode(' and ', explode(',', $spreads));
$str_spreads = ' with just a dash of ' . $list;
}
Note that drush will actually call a sequence of functions before
and after your drush command function. One of these hooks is the
"validate" hook. The 'sandwich' commandfile provides a validate
hook for the 'make-me-a-sandwich' command:
function drush_sandwich_make_me_a_sandwich_validate() {
$name = posix_getpwuid(posix_geteuid());
if ($name['name'] !== 'root') {
return drush_set_error('MAKE_IT_YOUSELF', dt('What? Make your own sandwich.'));
}
}
The validate function should call drush_set_error and return
its result if the command cannot be validated for some reason.
See `drush topic docs-policy` for more information on defining
policy functions with validate hooks, and `drush topic docs-api`
for information on how the command hook process works. Also,
the list of defined drush error codes can be found in
`drush topic docs-errorcodes`.
To see the full implementation of the sample 'make-me-a-sandwich'
command, see `drush topic docs-examplecommand`.
drush-5.10.0/docs/context.html 0000664 0000000 0000000 00000005753 12221055461 0016234 0 ustar 00root root 0000000 0000000
Drush Contexts
The drush contexts API acts as a storage mechanism for all options,
arguments and configuration settings that are loaded into drush.
This API also acts as an IPC mechanism between the different drush commands,
and provides protection from accidentally overriding settings that are
needed by other parts of the system.
It also avoids the necessity to pass references through the command chain
and allows the scripts to keep track of whether any settings have changed
since the previous execution.
This API defines several contexts that are used by default.
Argument contexts
These contexts are used by Drush to store information on the command.
They have their own access functions in the forms of
drush_set_arguments(), drush_get_arguments(), drush_set_command(),
drush_get_command().
- command : The drush command being executed.
- arguments : Any additional arguments that were specified.
Setting contexts
These contexts store options that have been passed to the drush.php
script, either through the use of any of the config files, directly from
the command line through --option='value' or through a JSON encoded string
passed through the STDIN pipe.
These contexts are accessible through the drush_get_option() and
drush_set_option() functions. See drush_context_names() for a description
of all of the contexts.
Drush commands may also choose to save settings for a specific context to
the matching configuration file through the drush_save_config() function.
Available Setting contexts
These contexts are evaluated in a certain order, and the highest priority value
is returned by default from drush_get_option. This allows scripts to check whether
an option was different before the current execution.
Specified by the script itself :
- process : Generated in the current process.
- cli : Passed as --option=value to the command line.
- stdin : Passed as a JSON encoded string through stdin.
- alias : Defined in an alias record, and set in the
alias context whenever that alias is used.
- specific : Defined in a command-specific option record, and
set in the command context whenever that command is used.
Specified by config files :
- custom : Loaded from the config file specified by --config or -c
- site : Loaded from the drushrc.php file in the Drupal site directory.
- drupal : Loaded from the drushrc.php file in the Drupal root directory.
- user : Loaded from the drushrc.php file in the user's home directory.
- drush : Loaded from the drushrc.php file in the $HOME/.drush directory.
- system : Loaded from the drushrc.php file in the system's $PREFIX/etc/drush directory.
- drush : Loaded from the drushrc.php file in the same directory as drush.php.
Specified by the script, but has the lowest priority :
- default : The script might provide some sensible defaults during init.
drush-5.10.0/docs/cron.html 0000664 0000000 0000000 00000007012 12221055461 0015477 0 ustar 00root root 0000000 0000000 Running Drupal cron tasks from drush
Drupal cron tasks are often set up to be run via a wget call
to cron.php; this same task can also be accomplished via the
`drush cron` command, which circumvents the need to provide
a webserver interface to cron.
Quickstart
If you just want to get started quickly, here is a crontab entry
that will run cron once every hour at ten minutes after the hour:
10 * * * * /usr/bin/env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin COLUMNS=72 /usr/local/drush/drush --root=/path/to/your/drupalroot --uri=your.drupalsite.org --quiet cron
You should set up crontab to run your cron tasks as the same
user that runs the web server; for example, if you run your
webserver as the user www-data:
sudo -u www-data crontab -e
You might need to edit the crontab entry shown above slightly
for your paricular setup; for example, if you have installed drush
to some directory other than /usr/local/drush, then you will need
to adjust the path to drush appropriately. We'll break down the
meaning of each section of the crontab entry in the documentation
that continues below.
Setting the schedule
See `man 5 crontab` for information on how to format the
information in a crontab entry. In the example above, the schedule
for the crontab is set by the string "10 * * * *". These fields are
the minute, hour, day of month, month and day of week; "*" means
essentially "all values", so "10 * * * *" will run any time the
minute == 10 (once every hour).
Setting the PATH
We use /usr/bin/env to run drush so that we can set up some
necessary environment variables that drush needs to execute.
By default, cron will run each command with an empty PATH, which
would not work well with drush. To find out what your PATH
needs to be, just type:
echo $PATH
Take the value that is output and place it into your crontab
entry in the place of the one shown above. You can remove
any entry that is known to not be of interest to drush (e.g. /usr/games),
or is only useful in a graphic environment (e.g. /usr/X11/bin).
Setting COLUMNS
When running drush in a terminal, the number of columns will be
automatically deteremined by drush by way of the tput command,
which queries the active terminal to determine what the width
of the screen is. When running drush from cron, there will not
be any terminal set, and the call to tput will produce an error
message. Spurrious error messages are undesirable, as cron is
often configured to send email whenever any output is produced,
so it is important to make an effort to insure that successful
runs of cron complete with no output.
In some cases, drush is smart enough to recognize that there is
no terminal -- if the terminal value is empty or "dumb", for
example. However, there are some "non-terminal" values that
drush does not recognize, such as "unknown." If you manually
set COLUMNS, then drush will repect your setting and will not
attempt to call tput.
Using --quiet
By default, drush will print a success message when the
run of cron is completed. The --quiet flag will suppress
these and other progress messages, again avoiding an unnecessary
email message.
Specifying the Drupal site to run
There are many ways to tell drush which Drupal site to select
for the active command, and any may be used here. The example
uses the --root and --uri flags, but you could also use an
alias record if you defined it in a global location, such as
/etc/drush/aliases.drushrc.php.
drush-5.10.0/docs/drush.api.php 0000664 0000000 0000000 00000025213 12221055461 0016261 0 ustar 00root root 0000000 0000000 0;");
}
/**
* Add help components to a command
*/
function hook_drush_help_alter(&$command) {
if ($command['command'] == 'sql-sync') {
$command['options']['myoption'] = "Description of modification of sql-sync done by hook";
$command['sub-options']['sanitize']['my-sanitize-option'] = "Description of sanitization option added by hook (grouped with --sanitize option)";
}
if ($command['command'] == 'global-options') {
// Recommended: don't show global hook options in brief global options help.
if ($command['#brief'] === FALSE) {
$command['options']['myglobaloption'] = 'Description of option used globally in all commands (e.g. in a commandfile init hook)';
}
}
}
/**
* Add/edit options to cache-clear command
*/
function hook_drush_cache_clear(&$types) {
$types['views'] = 'views_invalidate_cache';
}
/**
* Inform drush about one or more engine types.
*
* This hook allow to declare available engine types, the cli option to select
* between engine implementatins, which one to use by default, global options
* and other parameters. Commands may override this info when declaring the
* engines they use.
*
* @return
* An array whose keys are engine type names and whose values describe
* the characteristics of the engine type in relation to command definitions:
*
* - description: The engine type description.
* - option: The command line option to choose an implementation for
* this engine type.
* FALSE means there's no option. That is, the engine type is for internal
* usage of the command and thus an implementation is not selectable.
* - default: The default implementation to use by the engine type.
* - options: Engine options common to all implementations.
* - add-options-to-command: If there's a single implementation for this
* engine type, add its options as command level options.
*
* @see drush_get_engine_types_info()
* @see pm_drush_engine_type_info()
*/
function hook_drush_engine_type_info() {
return array(
'dessert' => array(
'description' => 'Choose a dessert while the sandwich is baked.',
'option' => 'dessert',
'default' => 'ice-cream',
'options' => 'sweetness',
'add-options-to-command' => FALSE,
),
);
}
/**
* Inform drush about one or more engines implementing a given engine type.
*
* This hook allow to declare implementations for an engine type.
*
* @see pm_drush_engine_package_handler()
* @see pm_drush_engine_version_control()
*/
function hook_drush_engine_ENGINE_TYPE() {
return array(
'ice-cream' => array(
'description' => 'Feature rich ice-cream with all kind of additives.',
'options' => array(
'flavour' => 'Choose your favorite flavour',
),
),
);
}
/**
* @} End of "addtogroup hooks".
*/
drush-5.10.0/docs/make.txt 0000664 0000000 0000000 00000032430 12221055461 0015330 0 ustar 00root root 0000000 0000000
Drush make
----------
Drush make is an extension to drush that can create a ready-to-use drupal site,
pulling sources from various locations. It does this by parsing a flat text file
(similar to a drupal `.info` file) and downloading the sources it describes. In
practical terms, this means that it is possible to distribute a complicated
Drupal distribution as a single text file.
Among drush make's capabilities are:
- Downloading Drupal core, as well as contrib modules from drupal.org.
- Checking code out from SVN, git, and bzr repositories.
- Getting plain `.tar.gz` and `.zip` files (particularly useful for libraries
that can not be distributed directly with drupal core or modules).
- Fetching and applying patches.
- Fetching modules, themes, and installation profiles, but also external
libraries.
Usage
-----
The `drush make` command can be executed from a path within a Drupal codebase or
independent of any Drupal sites entirely. See the examples below for instances
where `drush make` can be used within an existing Drupal site.
drush make [-options] [filename.make] [build path]
The `.make` file format
-----------------------
Each makefile is a plain text file that adheres to the Drupal `.info` file
syntax. See the included `example.make` for an example of a working makefile.
### Core version
The make file always begins by specifying the core version of Drupal for which
each package must be compatible. Example:
core = 6.x
### API version
The make file must specify which Drush Make API version it uses. This version
of Drush Make uses API version `2`
api = 2
### Projects
An array of the projects (e.g. modules, themes, libraries, and drupal) to be
retrieved. Each project name can be specified as a single string value. If
further options need to be provided for a project, the project should be
specified as the key.
**Project with no further options:**
projects[] = drupal
**Project using options (see below):**
projects[drupal][version] = 7.12
Do not use both types of declarations for a single project in your makefile.
### Project options
- `version`
Specifies the version of the project to retrieve.
This can be as loose as the major branch number or
as specific as a particular point release.
projects[views][version] = 3
projects[views][version] = 2.8
projects[views][version] = 3.0-alpha2
; Shorthand syntax for versions if no other options are to be specified
projects[views] = 3.0-alpha2
- `patch`
One or more patches to apply to this project. An array of URLs from which
each patch should be retrieved.
projects[calendar][patch][rfc-fixes][url] = "http://drupal.org/files/issues/cal-760316-rfc-fixes-2.diff"
projects[calendar][patch][rfc-fixes][md5] = "e4876228f449cb0c37ffa0f2142"
; shorthand syntax if no md5 checksum is specified
projects[adminrole][patch][] = "http://drupal.org/files/issues/adminrole_exceptions.patch"
- `subdir`
Place a project within a subdirectory of the `--contrib-destination`
specified. In the example below, `cck` will be placed in
`sites/all/modules/contrib` instead of the default `sites/all/modules`.
projects[cck][subdir] = "contrib"
- `location`
URL of an alternate project update XML server to use. Allows project XML data
to be retrieved from sites other than `updates.drupal.org`.
projects[tao][location] = "http://code.developmentseed.com/fserver"
- `type`
The project type. Must be provided if an update XML source is not specified
and/or using version control or direct retrieval for a project. May be one of
the following values: core, module, profile, theme.
projects[mytheme][type] = "theme"
- `directory_name`
Provide an alternative directory name for this project. By default, the
project name is used.
projects[mytheme][directory_name] = "yourtheme"
- `l10n_path`
Specific URL (can include tokens) to a translation. Allows translations to be
retrieved from l10n servers other than `localize.drupal.org`.
projects[mytheme][l10n_path] = "http://myl10nserver.com/files/translations/%project-%core-%version-%language.po"
- `l10n_url`
URL to an l10n server XML info file. Allows translations to be retrieved from
l10n servers other than `localize.drupal.org`.
projects[mytheme][l10n_url] = "http://myl10nserver.com/l10n_server.xml"
- `overwrite`
Allows the project to be installed in a directory that is not empty.
If not specified this is treated as FALSE, drush_make sets an error when the directory is not empty.
If specified TRUE, drush_make will continue and use the existing directory.
Useful when adding extra files and folders to existing folders in libraries or module extensions.
projects[myproject][overwrite] = TRUE
- `translations`
Retrieve translations for the specified language, if available, for all projects.
translations[] = es
### Project download options
Use an alternative download method instead of retrieval through update XML.
If no download type is specified, make defaults the type to
`git`. Additionally, if no url is specified, make defaults to use
Drupal.org.
The following methods are available:
- `download[type] = file`
Retrieve a project as a direct download. Options:
`url` - the URL of the file. Required.
`md5`, `sha1`, `sha256`, or `sha512` - one or more checksums for the file. Optional.
`request_type` - the request type - get or post. post depends on
http://drupal.org/project/make_post. Optional.
`data` - The post data to be submitted with the request. Should be a
valid URL query string. Requires http://drupal.org/project/make_post. Optional.
`filename` - What to name the file, if it's not an archive. Optional.
`subtree` - if the download is an archive, only this subtree within the
archive will be copied to the target destination. Optional.
- `download[type] = bzr`
Use a bazaar repository as the source for this project. Options:
`url` - the URL of the repository. Required.
- `download[type] = git`
Use a git repository as the source for this project. Options:
`url` - the URL of the repository. Required.
`branch` - the branch to be checked out. Optional.
`revision` - a specific revision identified by commit to check out. Optional.
`tag` - the tag to be checked out. Optional.
projects[mytheme][download][type] = "git"
projects[mytheme][download][url] = "git://github.com/jane_doe/mytheme.git"
Shorthand is available to pull a specific revision from a git
repository:
projects[context_admin][revision] = "eb9f05e"
is the same as:
projects[context_admin][download][revision] = "eb9f05e"
`refspec` - the git reference to fetch and checkout. Optional.
If this is set, it will have priority over tag, revision and branch options.
- `download[type] = svn`
Use an SVN repository as the source for this project. Options:
`url` - the URL of the repository. Required.
`interactive` - whether to prompt the user for authentication credentials
when using a private repository. Allows username and/or password options to
be omitted. Optional.
`username` - the username to use when retrieving an SVN project as a working
copy or from a private repository. Optional.
`password` - the password to use when retrieving an SVN project as a working
copy or from a private repository. Optional.
projects[mytheme][download][type] = "svn"
projects[mytheme][download][url] = "http://example.com/svnrepo/cool-theme/"
### Libraries
An array of non-Drupal-specific libraries to be retrieved (e.g. js, PHP or other
Drupal-agnostic components). Each library should be specified as the key of an
array of options in the libraries array.
**Example:**
libraries[jquery_ui][download][type] = "file"
libraries[jquery_ui][download][url] = "http://jquery- ui.googlecode.com/files/jquery.ui-1.6.zip"
libraries[jquery_ui][download][md5] = "c177d38bc7af59d696b2efd7dda5c605"
### Library options
Libraries share the `download`, `subdir`, and `directory_name` options with
projects. Additionally, they may specify a destination:
- `destination`
The target path to which this library should be moved. The path is relative to
that specified by the `--contrib-destination` option. By default, libraries
are placed in the `libraries` directory.
libraries[jquery_ui][destination] = "modules/contrib/jquery_ui
### Includes
An array of makefiles to include. Each include may be a local relative path
to the includer makefile directory or a direct URL to the makefile. Includes
are appended in order with the source makefile appended last, allowing latter
makefiles to override the keys/values of former makefiles.
**Example:**
includes[example] = "example.make"
includes[example_relative] = "../example_relative/example_relative.make"
includes[remote] = "http://www.example.com/remote.make"
### Defaults
If all projects or libraries have identical settings for a given
attribute, the `defaults` array can be used to specify these,
rather than specifying the attribute for each project.
**Example:**
; Specify common subdir of "contrib"
defaults[projects][subdir] = "contrib"
; Projects that don't specify subdir will go to the 'contrib' directory.
projects[views][version] = "3.3"
; Override default value
projects[devel][subdir] = "development"
### Overriding properties
Makefiles which include others may override the included makefiles properties.
Properties in the includer takes precedence over the includee.
**Example:**
`base.make`
core = "6.x"
projects[views][subdir] = "contrib"
projects[cck][subdir] = "contrib"
`extender.make`
includes[base] = "base.make"
; This line overrides the included makefile's 'subdir' option
projects[views][subdir] = "patched"
; This line overrides the included makefile, switching the download type
; to a git clone
projects[views][type] = "module"
projects[views][download][type] = "git"
projects[views][download][url] = "http://git.drupal.org/project/views.git"
A project or library entry of an included makefile can be removed entirely by
setting the corresponding key to FALSE:
; This line removes CCK entirely which was defined in base.make
projects[cck] = FALSE
Recursion
---------
If a project that is part of a build contains a `.make` itself, drush make will
automatically parse it and recurse into a derivative build.
For example, a full build tree may look something like this:
drush make distro.make distro
distro.make FOUND
- Drupal core
- Foo bar install profile
+ foobar.make FOUND
- CCK
- Token
- Module x
+ x.make FOUND
- External library x.js
- Views
- etc.
Recursion can be used to nest an install profile build in a Drupal site, easily
build multiple install profiles on the same site, fetch library dependencies
for a given module, or bundle a set of module and its dependencies together.
For Drush Make to recognize a makefile embedded within a project, the makefile
itself must have the same name as the project. For instance, the makefile
embedded within the managingnews profile must be called "managingnews.make".
The file should also be in the project's root directory. Subdirectories will
be ignored.
**Build a full Drupal site with the Managing News install profile:**
core = 6.x
projects[] = drupal
projects[] = managingnews
Testing
-------
Drush make also comes with testing capabilities, designed to test drush make
itself. Writing a new test is extremely simple. The process is as follows:
1. Figure out what you want to test. Write a makefile that will test
this out. You can refer to existing test makefiles for
examples. These are located in `DRUSH/tests/makefiles`.
2. Drush make your makefile, and use the --md5 option. You may also use other
options, but be sure to take note of which ones for step 4.
3. Verify that the result you got was in fact what you expected. If so,
continue. If not, tweak it and re-run step 2 until it's what you expected.
4. Using the md5 hash that was spit out from step 2, make a new entry in the
tests clase (DRUSH/tests/makeTest.php), following the example below.
'machine-readable-name' => array(
'name' => 'Human readable name',
'makefile' => 'tests/yourtest.make',
'messages' => array(
'Build hash: f68e6510-your-hash-e04fbb4ed',
),
'options' => array('any' => TRUE, 'other' => TRUE, 'options' => TRUE),
),
5. Test! Run drush test suite (see DRUSH/tests/README.md). To just
run the make tests:
`phpunit --filter=makeMake .`
You can check for any messages you want in the message array, but the most
basic tests would just check the build hash.
Generate
--------
Drush make has a primitive makefile generation capability. To use it, simply
change your directory to the Drupal installation from which you would like to
generate the file, and run the following command:
`drush generate-makefile /path/to/make-file.make`
This will generate a basic makefile. If you have code from other repositories,
the makefile will not complete - you'll have to fill in some information before
it is fully functional.
Maintainers
-----------
- Jonathan Hedstrom (jhedstrom)
- The rest of the Drush maintainers
Original Author
---------------
Dmitri Gaskin (dmitrig01)
drush-5.10.0/docs/shellaliases.html 0000664 0000000 0000000 00000004252 12221055461 0017212 0 ustar 00root root 0000000 0000000
Drush Shell Aliases
A Drush shell alias is a shortcut to any Drush command or
any shell command. Drush shell aliases are very similar
to git aliases.
See: https://git.wiki.kernel.org/index.php/Aliases#Advanced
A shell alias is defined in a Drush configuration file
called drushrc.php. See `drush topic docs-configuration`.
There are two kinds of shell aliases: an alias whose value
begins with a '!' will execute the rest of the line as
bash commands. Aliases that do not start with a '!' will
be interpreted as Drush commands.
Example:
$options['shell-aliases']['pull'] = '!git pull';
$options['shell-aliases']['noncore'] = 'pm-list --no-core';
With the above two aliases defined, `drush pull` will then be
equivalent to `git pull`, and `drush noncore` will be equivalent
to `drush pm-list --no-core`.
Shell Alias Replacements
Shell aliases are even more powerful when combined with shell alias
replacements and site aliases. Shell alias replacements take the
form of {{sitealias-item}} or {{%pathalias-item}}, and also the
special {{@target}}, which is replaced with the name of the site alias
used, or '@none' if none was used.
For example, given the following site alias:
$aliases['dev'] = array (
'root' => '/path/to/drupal',
'uri' => 'mysite.org',
'#live' => '@acme.live',
);
The alias below can be used for all your projects to fetch the database
and files from the client's live site via `drush @dev pull-data`.
Note that these aliases assume that the alias used defines an item named
'#live' (as shown in the above alias).
Shell aliases using replacements:
$options['shell-aliases']['pull-data'] = '!drush sql-sync {{#live}} {{@target}} && drush rsync {{#live}}:%files {{@target}}:%files';
If the user does not use these shell aliases with any site alias, then
an error will be returned and the script will not run.
These aliases with replacements can be used to quickly run combinations of drush sql-sync
and rsync commands on the "standard" source or target site, reducing the risk of
typos that might send information in the wrong direction or to the wrong site.
drush-5.10.0/docs/shellscripts.html 0000664 0000000 0000000 00000005611 12221055461 0017260 0 ustar 00root root 0000000 0000000 Drush Shell Scripts
A drush shell script is any Unix shell script file that has
its "execute" bit set (i.e., via `chmod +x myscript.drush`)
and that begins with a specific line:
#!/usr/bin/env drush
- or -
#!/full/path/to/drush
The former is the usual form, and is more convenient in that
it will allow you to run the script regardless of where drush
has been installed on your system, as long as it appears in
your PATH. The later form allows you to specify the drush
command add options to use, as in:
#!/full/path/to/drush php-script --some-option
Adding specific options is important only in certain cases,
described later; it is usually not necessary.
Drush scripts do not need to be named "*.drush" or "*.script";
they can be named anything at all. To run them, make sure they
are executable (`chmod +x helloworld.script`) and then run them
from the shell like any other script.
There are two big advantages to drush scripts over bash scripts:
- They are written in php
- drush can bootstrap your Drupal site before
running your script.
To bootstrap a Drupal site, provide an alias to the site to
bootstrap as the first commandline argument.
For example:
$ helloworld.script @dev a b c
If the first argument is a valid site alias, drush will remove
it from the arument list and bootstrap that site, then run
your script. The script itself will not see @dev on its
argument list. If you do not want drush to remove the first
site alias from your scripts argument list (e.g. if your script
wishes to syncronise two sites, specified by the first two
arguments, and does not want to bootstrap either of those
two sites), then fully specify the drush command (php-script)
and options to use, as shown above. By default, if the drush
command is not specified, drush will provide the following default
line:
#!/full/path/to/drush php-script --bootstrap-to-first-arg
It is the option --bootstrap-to-first-arg that causes drush to
pull off the first argument and bootstrap it. The way to get rid
of that option is to specify the php-script line to run, and leave
it off, like so:
#!/full/path/to/drush php-script
Note that 'php-script' is the only built-in drush command that
makes sense to put on the "shebang" ("#!" is pronounced "shebang")
line. However, if you wanted to, you could implement your own
custom version of php-script (e.g. to preprocess the script input,
perhaps), and specify that command on the shebang line.
Drush scripts can access their arguments via the drush_shift()
function:
while ($arg = drush_shift()) {
drush_print($arg);
}
Options are available via drush_get_option('option-name').
See the example drush script in `drush topic docs-examplescript`,
and the list of drush error codes in `drush topic docs-errorcodes`.
drush-5.10.0/docs/strict-options.html 0000664 0000000 0000000 00000003164 12221055461 0017543 0 ustar 00root root 0000000 0000000
Strict Option Handling
Some Drush commands use strict option handling; these commands require
that all Drush global option appear on the command line before the Drush
command name.
One example of this is the core-rsync command:
drush --simulate core-rsync -v @site1 @site2
The --simulate option is a Drush global option that causes Drush to
print out what it would do if the command is executed, without actually
taking any action. Commands such as core-rsync that use strict option
handling require that --simulate, if used, must appear before the command
name. Most Drush commands allow the --simulate to be placed anywhere,
such as at the end of the command line.
The -v option above is an rsync option. In this usage, it will cause
the rsync command to run in verbose mode. It will not cause Drush to
run in verbose mode, though, because it appears after the core-rsync
command name. Most Drush commands would be run in verbose mode if a
-v option appeared in the same location.
The advantage of strict option handling is that it allows Drush to
pass options and arguments through to a shell command. Some shell commands,
such as rsync and ssh, either have options that cannot be represented
in Drush. For example, rsync allows the --exclude option to appear multiple
times on the command line, but Drush only allows one instance of an
option at a time for most Drush commands. Strict option handling overcomes
this limitation, plus possible conflict between Drush options and
shell command options with the same name, at the cost of greater restriction
on where global options can be placed.
drush-5.10.0/drush 0000775 0000000 0000000 00000011222 12221055461 0013771 0 ustar 00root root 0000000 0000000 #!/usr/bin/env sh
# $Id$
#
# This script is a simple wrapper that will run Drush with the most appropriate
# php executable it can find.
#
# Solaris users: Add /usr/xpg4/bin to the head of your PATH
#
# Get the absolute path of this executable
SELF_DIRNAME="`dirname -- "$0"`"
SELF_PATH="`cd -P -- "$SELF_DIRNAME" && pwd -P`/`basename -- "$0"`"
# Decide if we are running a Unix shell on Windows
if [ `which uname` ]; then
case "`uname -a`" in
CYGWIN*)
CYGWIN=1 ;;
MINGW*)
MINGW=1 ;;
esac
fi
# Resolve symlinks - this is the equivalent of "readlink -f", but also works with non-standard OS X readlink.
while [ -h "$SELF_PATH" ]; do
# 1) cd to directory of the symlink
# 2) cd to the directory of where the symlink points
# 3) Get the pwd
# 4) Append the basename
DIR="`dirname -- "$SELF_PATH"`"
SYM="`readlink $SELF_PATH`"
SYM_DIRNAME="`dirname -- "$SYM"`"
SELF_PATH="`cd "$DIR" && cd "$SYM_DIRNAME" && pwd`/`basename -- "$SYM"`"
done
# Build the path to drush.php.
SCRIPT_PATH="`dirname "$SELF_PATH"`/drush.php"
if [ -n "$CYGWIN" ] ; then
SCRIPT_PATH="`cygpath -w -a -- "$SCRIPT_PATH"`"
fi
# If not exported, try to determine and export the number of columns.
# We do not want to run `tput cols` if $TERM is empty or "dumb", because
# if we do, tput will output an undesirable error message to stderr. If
# we redirect stderr in any way, e.g. `tput cols 2>/dev/null`, then the
# error message is suppressed, but tput cols becomes confused about the
# terminal and prints out the default value (80).
if [ -z $COLUMNS ] && [ -n "$TERM" ] && [ "$TERM" != dumb ] && [ -n "`which tput`" ] ; then
# Note to cygwin/mingw/msys users: install the ncurses package to get tput command.
# Note to mingw/msys users: there is no precompiled ncurses package.
if COLUMNS="`tput cols`"; then
export COLUMNS
fi
fi
if [ -n "$DRUSH_PHP" ] ; then
# Use the DRUSH_PHP environment variable if it is available.
php="$DRUSH_PHP"
else
# On MSYSGIT, we need to use "php", not the full path to php
if [ -n "$MINGW" ] ; then
php="php"
else
# Default to using the php that we find on the PATH.
# We check for a command line (cli) version of php, and if found use that.
# Note that we need the full path to php here for Dreamhost, which behaves oddly. See http://drupal.org/node/662926
php="`which php-cli 2>/dev/null`"
if [ ! -x "$php" ]; then
php="`which php 2>/dev/null`"
fi
if [ ! -x "$php" ]; then
echo "ERROR: can't find php."; exit 1
fi
fi
fi
# Check to see if the user has provided a php.ini file or drush.ini file in any conf dir
# Last found wins, so search in reverse priority order
for conf_dir in "`dirname "$SELF_PATH"`" /etc/drush "$HOME/.drush" ; do
if [ ! -d "$conf_dir" ] ; then
continue
fi
# Handle paths that don't start with a drive letter on MinGW shell. Equivalent to cygpath on Cygwin.
if [ -n "$MINGW" ] ; then
conf_dir=`sh -c "cd "$conf_dir"; pwd -W"`
fi
if [ -f "$conf_dir/php.ini" ] ; then
drush_php_ini="$conf_dir/php.ini"
fi
if [ -f "$conf_dir/drush.ini" ] ; then
drush_php_override="$conf_dir/drush.ini"
fi
done
# If the PHP_INI environment variable is specified, then tell
# php to use the php.ini file that it specifies.
if [ -n "$PHP_INI" ] ; then
drush_php_ini="$PHP_INI"
fi
# If the DRUSH_INI environment variable is specified, then
# extract all ini variable assignments from it and convert
# them into php '-d' options. These will override similarly-named
# options in the php.ini file
if [ -n "$DRUSH_INI" ] ; then
drush_php_override="$DRUSH_INI"
fi
# Add in the php file location and/or the php override variables as appropriate
if [ -n "$drush_php_ini" ] ; then
php_options="--php-ini $drush_php_ini"
fi
if [ -n "$drush_php_override" ] ; then
php_options=`grep '^[a-z_A-Z0-9.]\+ *=' $drush_php_override | sed -e 's|\([^ =]*\) *= *\(.*\)|\1="\2"|' -e 's| ||g' -e 's|^|-d |' | tr '\n\r' ' '`
fi
# If the PHP_OPTIONS environment variable is specified, then
# its contents will be passed to php on the command line as
# additional options to use.
if [ -n "$PHP_OPTIONS" ] ; then
php_options="$php_options $PHP_OPTIONS"
fi
# Always disable magic_quotes_gpc and friends
php_options="$php_options -d magic_quotes_gpc=Off -d magic_quotes_runtime=Off -d magic_quotes_sybase=Off"
# Pass in the path to php so that drush knows which one to use if it
# re-launches itself to run subcommands. We will also pass in the php options.
# Important note: Any options added here must be removed when Drush processes
# a #! (shebang) script. @see drush_adjust_args_if_shebang_script()
exec "$php" $php_options "$SCRIPT_PATH" --php="$php" --php-options="$php_options" "$@"
drush-5.10.0/drush.bat 0000664 0000000 0000000 00000001347 12221055461 0014542 0 ustar 00root root 0000000 0000000 @echo off
rem Drush automatically determines the user's home directory by checking for
rem HOME or HOMEDRIVE/HOMEPATH environment variables, and the temporary
rem directory by checking for TEMP, TMP, or WINDIR environment variables.
rem The home path is used for caching Drush commands and the git --reference
rem cache. The temporary directory is used by various commands, including
rem package manager for downloading projects.
rem You may want to specify a path that is not user-specific here; e.g., to
rem keep cache files on the same filesystem, or to share caches with other
rem users.
rem set HOME=H:/drush
rem set TEMP=H:/drush
REM See http://drupal.org/node/506448 for more information.
@php.exe "%~dp0drush.php" --php="php.exe" %*
drush-5.10.0/drush.complete.sh 0000664 0000000 0000000 00000002470 12221055461 0016213 0 ustar 00root root 0000000 0000000 #!/bin/bash
# BASH completion script for Drush.
#
# Place this in your /etc/bash_completion.d/ directory or source it from your
# ~/.bash_completion or ~/.bash_profile files. Alternatively, source
# examples/example.bashrc instead, as it will automatically find and source
# this file.
# Ensure drush is available.
which drush > /dev/null || alias drush &> /dev/null || return
__drush_ps1() {
f="${TMPDIR:-/tmp/}/drush-env/drush-drupal-site-$$"
if [ -f $f ]
then
DRUPAL_SITE=$(cat "$f")
fi
[[ -n "$DRUPAL_SITE" ]] && printf "${1:- (%s)}" "$DRUPAL_SITE"
}
# Completion function, uses the "drush complete" command to retrieve
# completions for a specific command line COMP_WORDS.
_drush_completion() {
# Set IFS to newline (locally), since we only use newline separators, and
# need to retain spaces (or not) after completions.
local IFS=$'\n'
# The '< /dev/null' is a work around for a bug in php libedit stdin handling.
# Note that libedit in place of libreadline in some distributions. See:
# https://bugs.launchpad.net/ubuntu/+source/php5/+bug/322214
COMPREPLY=( $(drush --early=includes/complete.inc "${COMP_WORDS[@]}" < /dev/null) )
}
# Register our completion function. We include common short aliases for Drush.
complete -o nospace -F _drush_completion d dr drush drush5 drush6 drush6 drush.php
drush-5.10.0/drush.info 0000664 0000000 0000000 00000000025 12221055461 0014717 0 ustar 00root root 0000000 0000000 drush_version=5.10.0
drush-5.10.0/drush.php 0000775 0000000 0000000 00000014037 12221055461 0014566 0 ustar 00root root 0000000 0000000 #!/usr/bin/env php
$command['command'], '!commandfile' => $command['commandfile'])), 'bootstrap');
$command_found = TRUE;
// Dispatch the command(s).
$return = drush_dispatch($command);
// prevent a '1' at the end of the output
if ($return === TRUE) {
$return = '';
}
if (drush_get_context('DRUSH_DEBUG') && !drush_get_context('DRUSH_QUIET')) {
drush_print_timers();
}
drush_log(dt('Peak memory usage was !peak', array('!peak' => drush_format_size(memory_get_peak_usage()))), 'memory');
break;
}
}
}
else {
break;
}
}
if (!$command_found) {
// If we reach this point, command doesn't fit requirements or we have not
// found either a valid or matching command.
// If no command was found check if it belongs to a disabled module.
if (!$command) {
$command = drush_command_belongs_to_disabled_module();
}
// Set errors related to this command.
$args = implode(' ', drush_get_arguments());
if (isset($command) && is_array($command)) {
foreach ($command['bootstrap_errors'] as $key => $error) {
drush_set_error($key, $error);
}
drush_set_error('DRUSH_COMMAND_NOT_EXECUTABLE', dt("The drush command '!args' could not be executed.", array('!args' => $args)));
}
elseif (!empty($args)) {
drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt("The drush command '!args' could not be found. Run `drush cache-clear drush` to clear the commandfile cache if you have installed new extensions.", array('!args' => $args)));
}
// Set errors that occurred in the bootstrap phases.
$errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS', array());
foreach ($errors as $code => $message) {
drush_set_error($code, $message);
}
}
return $return;
}
/**
* Check if the given command belongs to a disabled module
*
* @return
* Array with a command-like bootstrap error or FALSE if Drupal was not
* bootstrapped fully or the command does not belong to a diabled module.
*/
function drush_command_belongs_to_disabled_module() {
if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
_drush_find_commandfiles(DRUSH_BOOTSTRAP_DRUPAL_SITE, DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
$commands = drush_get_commands();
$arguments = drush_get_arguments();
$command_name = array_shift($arguments);
if (isset($commands[$command_name])) {
// We found it. Load its module name and set an error.
if (is_array($commands[$command_name]['drupal dependencies']) && count($commands[$command_name]['drupal dependencies'])) {
$modules = implode(', ', $commands[$command_name]['drupal dependencies']);
} else {
// The command does not define Drupal dependencies. Derive them.
$command_files = drush_get_context('DRUSH_COMMAND_FILES', array());
$command_path = $commands[$command_name]['path'] . DIRECTORY_SEPARATOR . $commands[$command_name]['commandfile'] . '.drush.inc';
$modules = array_search($command_path, $command_files);
}
return array(
'bootstrap_errors' => array(
'DRUSH_COMMAND_DEPENDENCY_ERROR' =>
dt('Command !command needs the following module(s) enabled to run: !dependencies.', array('!command' => $command_name, '!dependencies' => $modules)),
)
);
}
}
return FALSE;
}
drush-5.10.0/drush_logo-black.png 0000664 0000000 0000000 00000055360 12221055461 0016656 0 ustar 00root root 0000000 0000000 ‰PNG
IHDR Ú × |ƒO pHYs šœ tEXtSoftware Adobe ImageReadyqÉe<