array(
'version_control' => 'Integration with VCS in order to easily commit your changes to projects.',
'package_handler' => 'Determine how to download/checkout new projects and acquire updates to projects.',
),
);
$update_options = array(
'security-only' => 'Only update modules that have security updates available. However, if there were other releases of a module between the installed version the security update, other changes to features or functionality may occur.',
'lock' => 'Add a persistent lock to remove the specified projects from consideration during updates. Locks may be removed with the --unlock parameter, or overridden by specifically naming the project as a parameter to pm-update or pm-updatecode. The lock does not affect pm-download. See also the update-advanced project for similar and improved functionality.',
);
$update_suboptions = array(
'lock' => array(
'lock-message' => 'A brief message explaining why a project is being locked; displayed during pm-updatecode. Optional.',
'unlock' => 'Remove the persistent lock from the specified projects so that they may be updated again.',
),
);
$items['pm-enable'] = array(
'description' => 'Enable one or more extensions (modules or themes).',
'arguments' => array(
'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to enable all matches.',
),
'aliases' => array('en'),
'deprecated-aliases' => array('enable'),
);
$items['pm-disable'] = array(
'description' => 'Disable one or more extensions (modules or themes).',
'arguments' => array(
'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to disable multiple matches.',
),
'aliases' => array('dis'),
'deprecated-aliases' => array('disable'),
);
$items['pm-info'] = array(
'description' => 'Show detailed info for one or more extensions (modules or themes).',
'arguments' => array(
'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to show info for multiple matches. If no argument is provided it will show info for all available extensions.',
),
'aliases' => array('pmi'),
);
// Install command is reserved for the download and enable of projects including dependencies.
// @see http://drupal.org/node/112692 for more information.
// $items['install'] = array(
// 'description' => 'Download and enable one or more modules',
// );
$items['pm-uninstall'] = array(
'description' => 'Uninstall one or more modules.',
'arguments' => array(
'modules' => 'A list of modules.',
),
'deprecated-aliases' => array('uninstall'),
);
$items['pm-list'] = array(
'description' => 'Show a list of available extensions (modules and themes).',
'callback arguments' => array(array(), FALSE),
'options' => array(
'type' => 'Filter by extension type. Choices: module, theme.',
'status' => 'Filter by extension status. Choices: enabled, disable and/or \'not installed\'. You can use multiple comma separated values. (i.e. --status="disabled,not installed").',
'package' => 'Filter by project packages. You can use multiple comma separated values. (i.e. --package="Core - required,Other").',
'core' => 'Filter out extensions that are not in drupal core.',
'no-core' => 'Filter out extensions that are provided by drupal core.',
'pipe' => 'Returns a space delimited list of the names of the resulting extensions.',
),
'aliases' => array('pml'),
'deprecated-aliases' => array('sm'),
);
$items['pm-refresh'] = array(
'description' => 'Refresh update status information.',
'drupal dependencies' => array($update),
'aliases' => array('rf'),
'deprecated-aliases' => array('refresh'),
);
$items['pm-updatecode'] = array(
'description' => 'Update Drupal core and contrib projects to latest recommended releases.',
'drupal dependencies' => array($update),
'arguments' => array(
'projects' => 'Optional. A list of installed projects to update.',
),
'options' => array(
'pipe' => 'Returns a space delimited list of projects with any of its extensions enabled and their respective version and update information, one project per line. Order: project name, current version, recommended version, update status.',
'notes' => 'Show release notes for each project to be updated.',
'no-core' => 'Only update modules and skip the core update.',
'self-update' => 'Check for pending updates to drush itself. Set to 0 to avoid check.',
) + $update_options,
'sub-options' => $update_suboptions,
'aliases' => array('upc'),
'deprecated-aliases' => array('updatecode'),
'topics' => array('docs-policy'),
) + $engines;
// Merge all items from above.
$items['pm-update'] = array_merge($items['pm-updatecode'], array(
'description' => 'Update Drupal core and contrib projects and apply any pending database updates (Same as pm-updatecode + updatedb).',
'aliases' => array('up'),
'deprecated-aliases' => array('update'),
));
$items['pm-updatecode-postupdate'] = array(
'description' => 'Notify of pending db updates.',
'hidden' => TRUE
);
$items['pm-releasenotes'] = array(
'description' => 'Print release notes for given projects.',
'arguments' => array(
'projects' => 'A list of drupal.org project names, with optional version. Defaults to \'drupal\'',
),
'options' => array(
'html' => dt('Display releasenotes in HTML rather than plain text.'),
),
'examples' => array(
'drush rln cck' => 'Prints the release notes for the recommended version of CCK project.',
'drush rln token-1.13' => 'View release notes of a specfic version of the Token project for my version of Drupal.',
'drush rln pathauto zen' => 'View release notes for the recommended version of Pathauto and Zen projects.',
),
'aliases' => array('rln'),
'bootstrap' => DRUSH_BOOTSTRAP_MAX,
);
$items['pm-releases'] = array(
'description' => 'Print release information for given projects.',
'arguments' => array(
'projects' => 'A list of drupal.org project names. Defaults to \'drupal\'',
),
'options' => array(
'dev' => "Show only development releases.",
'all' => "Shows all available releases instead of the default short list of recent releases.",
),
'examples' => array(
'drush pm-releases cck zen' => 'View releases for cck and Zen projects for your Drupal version.',
),
'aliases' => array('rl'),
'bootstrap' => DRUSH_BOOTSTRAP_MAX,
);
$items['pm-download'] = array(
'description' => 'Download projects from drupal.org or other sources.',
'examples' => array(
'drush dl' => 'Download latest recommended release of Drupal core.',
'drush dl drupal' => 'Same as `drush dl`.',
'drush dl drupal-7.x' => 'Download latest 7.x development version of Drupal core.',
'drush dl drupal-6' => 'Download latest recommended release of Drupal 6.x.',
'drush dl cck zen' => 'Download latest versions of CCK and Zen projects.',
'drush dl og-1.3' => 'Download a specfic version of Organic groups module for my version of Drupal.',
'drush dl diff-6.x-2.x' => 'Download a specific development branch of diff module for a specific Drupal version.',
'drush dl views --select' => 'Show a list of recent releases of the views project, prompt for which one to download.',
'drush dl webform --dev' => 'Download the latest dev release of webform.',
),
'arguments' => array(
'projects' => 'A comma delimited list of drupal.org project names, with optional version. Defaults to \'drupal\'',
),
'options' => array(
'destination' => 'Path to which the project will be copied. If you\'re providing a relative path, note it is relative to the drupal root (if bootstrapped).',
'use-site-dir' => 'Force to use the site specific directory. It will create the directory if it doesn\'t exist. If --destination is also present this option will be ignored.',
'source' => 'The base URL which provides project release history in XML. Defaults to http://updates.drupal.org/release-history.',
'notes' => 'Show release notes after each project is downloaded.',
'variant' => "Only useful for install profiles. Possible values: 'full', 'projects', 'profile-only'.",
'dev' => "Download a development release.",
'select' => "Select the version to download interactively from a list of available releases.",
'all' => "Useful only with --select; shows all available releases instead of a short list of recent releases.",
'drupal-project-rename' => 'Alternate name for "drupal-x.y" directory when downloading Drupal project. Defaults to "drupal".',
'default-major' => 'Specify the default major version of modules to download when there is no bootstrapped Drupal site. Defaults to "7".',
'pipe' => 'Returns a list of the names of the extensions (modules and themes) contained in the downloaded projects.',
),
'bootstrap' => DRUSH_BOOTSTRAP_MAX,
'aliases' => array('dl'),
'deprecated-aliases' => array('download'),
) + $engines;
return $items;
}
/**
* @defgroup extensions Extensions management.
* @{
* Functions to manage extensions.
*/
/**
* Sort callback function for sorting extensions.
*
* It will sort first by type, second by package and third by name.
*/
function _drush_pm_sort_extensions($a, $b) {
if ($a->type == 'module' && $b->type == 'theme') {
return -1;
}
if ($a->type == 'theme' && $b->type == 'module') {
return 1;
}
$cmp = strcasecmp($a->info['package'], $b->info['package']);
if ($cmp == 0) {
$cmp = strcasecmp($a->info['name'], $b->info['name']);
}
return $cmp;
}
/**
* Calculate a extension status based on current status and schema version.
*
* @param $extension
* Object of a single extension info.
*
* @return
* String describing extension status. Values: enabled|disabled|not installed
*/
function drush_get_extension_status($extension) {
if (($extension->type == 'module')&&($extension->schema_version == -1)) {
$status = "not installed";
}
else {
$status = ($extension->status == 1)?'enabled':'disabled';
}
return $status;
}
/**
* Wrapper of drupal_get_extensions() with additional information used by
* pm- commands.
*
* @return
* An array containing info for all available extensions w/additional info.
*/
function drush_pm_get_extensions() {
$extensions = drush_get_extensions();
foreach ($extensions as $key => $extension) {
if (empty($extension->info['package'])) {
$extensions[$key]->info['package'] = dt('Other');
}
}
return $extensions;
}
/**
* Classify extensions as modules, themes or unknown.
*
* @param $extensions
* Array of extension names, by reference.
* @param $modules
* Empty array to be filled with modules in the provided extension list.
* @param $themes
* Empty array to be filled with themes in the provided extension list.
*/
function drush_pm_classify_extensions(&$extensions, &$modules, &$themes, $extension_info) {
_drush_pm_expand_extensions($extensions, $extension_info);
foreach ($extensions as $extension) {
if (!isset($extension_info[$extension])) {
continue;
}
if ($extension_info[$extension]->type == 'module') {
$modules[$extension] = $extension;
}
else if ($extension_info[$extension]->type == 'theme') {
$themes[$extension] = $extension;
}
}
}
/**
* Obtain an array of installed projects off the extensions available.
*
* A project is considered to be 'enabled' when any of its extensions is
* enabled.
* If any extension lacks project information and it is found that the
* extension was obtained from drupal.org's cvs or git repositories, a new
* 'vcs' attribute will be set on the extension. Example:
* $extensions[name]->vcs = 'cvs';
*
* @param array $extensions.
* Array of extensions as returned by drush_get_extensions().
* @return
* Array of installed projects with info of version, status and provided
* extensions.
*/
function drush_get_projects(&$extensions = NULL) {
if (is_null($extensions)) {
$extensions = drush_get_extensions();
}
$projects = array('drupal' => array('version' => VERSION));
foreach ($extensions as $extension) {
// The project name is not available in this cases:
// 1. the extension is part of drupal core.
// 2. the project was checked out from CVS/git and cvs_deploy/git_deploy
// is not installed.
// 3. it is not a project hosted in drupal.org.
if (empty($extension->info['project'])) {
if (isset($extension->info['version']) && ($extension->info['version'] == VERSION)) {
$project = 'drupal';
}
else {
if (is_dir(dirname($extension->filename) . '/CVS') && (!module_exists('cvs_deploy'))) {
$extension->vcs = 'cvs';
drush_log(dt('Extension !extension is fetched from cvs. Ignoring.', array('!extension' => $extension->name)), 'debug');
}
elseif (is_dir(dirname($extension->filename) . '/.git') && (!module_exists('git_deploy'))) {
$extension->vcs = 'git';
drush_log(dt('Extension !extension is fetched from git. Ignoring.', array('!extension' => $extension->name)), 'debug');
}
continue;
}
}
else {
$project = $extension->info['project'];
}
// Create/update the project in $projects with the project data.
if (!isset($projects[$project])) {
$projects[$project] = array(
'type' => $extension->type,
'version' => $extension->info['version'],
'status' => $extension->status,
'extensions' => array(),
);
if (isset($extension->info['project status url'])) {
$projects[$project]['status url'] = $extension->info['project status url'];
}
}
elseif ($extension->status != 0) {
$projects[$project]['status'] = $extension->status;
}
$projects[$project]['extensions'][] = $extension->name;
}
return $projects;
}
/**
* Returns a list of enabled modules.
*
* This is a simplified version of module_list().
*/
function pm_module_list() {
$enabled = array();
$rsc = drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1));
while ($row = drush_db_result($rsc)) {
$enabled[$row] = $row;
}
return $enabled;
}
/**
* @} End of "defgroup extensions".
*/
/**
* Command callback. Show a list of extensions with type and status.
*/
function drush_pm_list() {
//--package
$package_filter = array();
$package = strtolower(drush_get_option('package'));
if (!empty($package)) {
$package_filter = explode(',', $package);
}
if (empty($package_filter) || count($package_filter) > 1) {
$header[] = dt('Package');
}
$header[] = dt('Name');
//--type
$all_types = array('module', 'theme');
$type_filter = strtolower(drush_get_option('type'));
if (!empty($type_filter)) {
$type_filter = explode(',', $type_filter);
}
else {
$type_filter = $all_types;
}
if (count($type_filter) > 1) {
$header[] = dt('Type');
}
foreach ($type_filter as $type) {
if (!in_array($type, $all_types)) { //TODO: this kind of chck can be implemented drush-wide
return drush_set_error('DRUSH_PM_INVALID_PROJECT_TYPE', dt('!type is not a valid project type.', array('!type' => $type)));
}
}
//--status
$all_status = array('enabled', 'disabled', 'not installed');
$status_filter = strtolower(drush_get_option('status'));
if (!empty($status_filter)) {
$status_filter = explode(',', $status_filter);
}
else {
$status_filter = $all_status;
}
if (count($status_filter) > 1) {
$header[] = dt('Status');
}
foreach ($status_filter as $status) {
if (!in_array($status, $status_filter)) { //TODO: this kind of chck can be implemented drush-wide
return drush_set_error('DRUSH_PM_INVALID_PROJECT_TYPE', dt('!status is not a valid project status.', array('!status' => $status)));
}
}
$header[] = dt('Version');
$rows[] = $header;
$extension_info = drush_pm_get_extensions();
uasort($extension_info, '_drush_pm_sort_extensions');
$major_version = drush_drupal_major_version();
foreach ($extension_info as $key => $extension) {
if (!in_array($extension->type, $type_filter)) {
unset($extension_info[$key]);
continue;
}
$status = drush_get_extension_status($extension);
if (!in_array($status, $status_filter)) {
unset($extension_info[$key]);
continue;
}
if (($major_version >= 6) and (isset($extension->info['hidden']))) {
unset($extension_info[$key]);
continue;
}
// filter out core if --no-core specified
if (drush_get_option('no-core', FALSE)) {
if ($extension->info['version'] == VERSION) {
unset($extension_info[$key]);
continue;
}
}
// filter out non-core if --core specified
if (drush_get_option('core', FALSE)) {
if ($extension->info['version'] != VERSION) {
unset($extension_info[$key]);
continue;
}
}
// filter by package
if (!empty($package_filter)) {
if (!in_array(strtolower($extension->info['package']), $package_filter)) {
unset($extension_info[$key]);
continue;
}
}
if (empty($package_filter) || count($package_filter) > 1) {
$row[] = $extension->info['package'];
}
if (($major_version >= 6)||($extension->type == 'module')) {
$row[] = $extension->info['name'].' ('.$extension->name.')';
}
else {
$row[] = $extension->name;
}
if (count($type_filter) > 1) {
$row[] = ucfirst($extension->type);
}
if (count($status_filter) > 1) {
$row[] = ucfirst($status);
}
if (($major_version >= 6)||($extension->type == 'module')) {
// Suppress notice when version is not present.
$row[] = @$extension->info['version'];
}
$rows[] = $row;
$pipe[] = $extension->name;
unset($row);
}
drush_print_table($rows, TRUE);
if (isset($pipe)) {
// Newline-delimited list for use by other scripts. Set the --pipe option.
drush_print_pipe($pipe);
}
// Set the result for backend invoke
drush_backend_set_result($extension_info);
}
function drush_pm_find_project_from_extension($extension) {
$result = drush_pm_lookup_extension_in_cache($extension);
if (!isset($result)) {
// If we can find info on a project that has the same name
// as the requested extension, then we'll call that a match.
$info = _drush_pm_get_releases(array($extension));
if (!empty($info)) {
$result = $extension;
}
}
return $result;
}
/**
* Command callback. Enable one or more extensions from downloaded projects.
*/
function drush_pm_enable() {
// Include update engine so we can use update_check_incompatibility().
drush_include_engine('drupal', 'update');
$args = _convert_csv_to_array(func_get_args());
$extension_info = drush_get_extensions();
$recheck = TRUE;
while ($recheck) {
$recheck = FALSE;
// Classify $args in themes, modules or unknown.
$modules = array();
$themes = array();
drush_pm_classify_extensions($args, $modules, $themes, $extension_info);
$extensions = array_merge($modules, $themes);
$unknown = array_diff($args, $extensions);
// Discard and set an error for each unknown extension.
foreach ($unknown as $name) {
drush_log(dt('!extension was not found and will not be enabled.', array('!extension' => $name)), 'warning');
}
// Discard already enabled and incompatible extensions.
foreach ($extensions as $name) {
if ($extension_info[$name]->status) {
drush_log(dt('!extension is already enabled.', array('!extension' => $name)), 'ok');
}
// Check if is compatible with Drupal core.
if (update_check_incompatibility($name, $extension_info[$name]->type)) {
drush_set_error('DRUSH_PM_ENABLE_MODULE_INCOMPATIBLE', dt('!name is incompatible with the Drupal version.', array('!name' => $name)));
if ($extension_info[$name]->type == 'module') {
unset($modules[$name]);
}
else {
unset($themes[$name]);
}
}
}
if (!empty($modules)) {
// Check module dependencies.
$dependencies = drush_check_module_dependencies($modules, $extension_info);
$unmet_dependencies = array();
foreach ($dependencies as $module => $info) {
if (!empty($info['unmet-dependencies'])) {
foreach ($info['unmet-dependencies'] as $unmet_module) {
$unmet_project = drush_pm_find_project_from_extension($unmet_module);
if (!empty($unmet_project)) {
$unmet_dependencies[$module][$unmet_project] = $unmet_project;
}
}
}
}
if (!empty($unmet_dependencies)) {
$msgs = array();
$unmet_project_list = array();
foreach ($unmet_dependencies as $module => $unmet_projects) {
$unmet_project_list = array_merge($unmet_project_list, $unmet_projects);
$msgs[] = dt("!module requires !unmet-projects", array('!unmet-projects' => implode(', ', $unmet_projects), '!module' => $module));
}
if (drush_get_option('resolve-dependencies') || drush_confirm(dt("The following projects have unmet dependencies:\n!list\nWould you like to download them?", array('!list' => implode("\n", $msgs))))) {
// If we did not already print a log message via drush_confirm, then print one now.
if (drush_get_option('resolve-dependencies')) {
drush_log(dt("The following projects have unmet dependencies:\n@list\nThey are being downloaded.", array('@list' => implode("\n", $msgs))));
}
$result = drush_invoke_process_args('pm-download', $unmet_project_list, array('y' => TRUE));
// Refresh module cache after downloading the new modules.
$extension_info = drush_get_extensions();
$recheck = TRUE;
}
}
}
}
if (!empty($modules)) {
$all_dependencies = array();
$dependencies_ok = TRUE;
foreach ($dependencies as $key => $info) {
if (isset($info['error'])) {
unset($modules[$key]);
$dependencies_ok = drush_set_error($info['error']['code'], $info['error']['message']);
}
elseif (!empty($info['dependencies'])) {
// Make sure we have an assoc array.
$assoc = drupal_map_assoc($info['dependencies']);
$all_dependencies = array_merge($all_dependencies, $assoc);
}
}
if (!$dependencies_ok) {
return FALSE;
}
$modules = array_diff(array_merge($modules, $all_dependencies), pm_module_list());
// Discard modules which doesn't meet requirements.
require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc';
foreach ($modules as $key => $module) {
// Check to see if the module can be installed/enabled (hook_requirements).
// See @system_modules_submit
if (!drupal_check_module($module)) {
unset($modules[$key]);
drush_set_error('DRUSH_PM_ENABLE_MODULE_UNMEET_REQUIREMENTS', dt('Module !module doesn\'t meet the requirements to be enabled.', array('!module' => $module)));
_drush_log_drupal_messages();
return FALSE;
}
}
}
// Inform the user which extensions will finally be enabled.
$extensions = array_merge($modules, $themes);
if (empty($extensions)) {
return drush_log(dt('There were no extensions that could be enabled.'), 'ok');
}
else {
drush_print(dt('The following extensions will be enabled: !extensions', array('!extensions' => implode(', ', $extensions))));
if(!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
}
// Enable themes.
if (!empty($themes)) {
drush_theme_enable($themes);
}
// Enable modules and pass dependency validation in form submit.
if (!empty($modules)) {
drush_module_enable($modules);
drush_system_modules_form_submit(pm_module_list());
}
// Inform the user of final status.
$rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions));
$problem_extensions = array();
$searchpath = array();
while ($extension = drush_db_fetch_object($rsc)) {
if ($extension->status) {
drush_log(dt('!extension was enabled successfully.', array('!extension' => $extension->name)), 'ok');
$searchpath[] = dirname($extension_info[$extension->name]->filename);
}
else {
$problem_extensions[] = $extension->name;
}
}
// Add all modules that were enabled to the drush
// list of commandfiles (if they have any). This
// will allow these newly-enabled modules to participate
// in the post_pm_enable hook.
if (!empty($searchpath)) {
_drush_add_commandfiles($searchpath);
}
if (!empty($problem_extensions)) {
return drush_set_error('DRUSH_PM_ENABLE_EXTENSION_ISSUE', dt('There was a problem enabling !extension.', array('!extension' => implode(',', $problem_extensions))));
}
}
/**
* Command callback. Disable one or more extensions.
*/
function drush_pm_disable() {
$args = _convert_csv_to_array(func_get_args());
$extension_info = drush_get_extensions();
// classify $args in themes, modules or unknown.
$modules = array();
$themes = array();
drush_pm_classify_extensions($args, $modules, $themes, $extension_info);
$extensions = array_merge($modules, $themes);
$unknown = array_diff($args, $extensions);
// Discard and set an error for each unknown extension.
foreach ($unknown as $name) {
drush_log('DRUSH_PM_ENABLE_EXTENSION_NOT_FOUND', dt('!extension was not found and will not be disabled.', array('!extension' => $name)), 'warning');
}
// Discard already disabled extensions.
foreach ($extensions as $name) {
if (!$extension_info[$name]->status) {
if ($extension_info[$name]->type == 'module') {
unset($modules[$name]);
}
else {
unset($themes[$name]);
}
drush_log(dt('!extension is already disabled.', array('!extension' => $name)), 'ok');
}
}
// Discard default theme.
if (!empty($themes)) {
$default_theme = drush_theme_get_default();
if (in_array($default_theme, $themes)) {
unset($themes[$default_theme]);
drush_log(dt('!theme is the default theme and can\'t be disabled.', array('!theme' => $default_theme)), 'ok');
}
}
if (!empty($modules)) {
// Add enabled dependents to the list of modules to disable.
$dependents = drush_module_dependents($modules, $extension_info);
$dependents = array_intersect($dependents, pm_module_list());
$modules = array_merge($modules, $dependents);
// Discard required modules.
$required = drush_drupal_required_modules($extension_info);
foreach ($required as $module) {
if (isset($modules[$module])) {
unset($modules[$module]);
// No message for hidden modules.
if (!isset($extension_info[$module]->info['hidden'])) {
drush_log(dt('!module is a required module and can\'t be disabled.', array('!module' => $module)), 'ok');
}
}
}
}
// Inform the user which extensions will finally be disabled.
$extensions = array_merge($modules, $themes);
if (empty($extensions)) {
return drush_log(dt('There were no extensions that could be disabled.'), 'ok');
}
else {
drush_print(dt('The following extensions will be disabled: !extensions', array('!extensions' => implode(', ', $extensions))));
if(!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
}
// Disable themes.
if (!empty($themes)) {
drush_theme_disable($themes);
}
// Disable modules and pass dependency validation in form submit.
if (!empty($modules)) {
drush_module_disable($modules);
drush_system_modules_form_submit(pm_module_list());
}
// Inform the user of final status.
$rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions));
$problem_extensions = array();
while ($extension = drush_db_fetch_object($rsc)) {
if (!$extension->status) {
drush_log(dt('!extension was disabled successfully.', array('!extension' => $extension->name)), 'ok');
}
else {
$problem_extensions[] = $extension->name;
}
}
if (!empty($problem_extensions)) {
return drush_set_error('DRUSH_PM_DISABLE_EXTENSION_ISSUE', dt('There was a problem disabling !extension.', array('!extension' => implode(',', $problem_extensions))));
}
}
/**
* Command callback. Show detailed info for one or more extension.
*/
function drush_pm_info() {
$args = _convert_csv_to_array(func_get_args());
$extension_info = drush_pm_get_extensions();
_drush_pm_expand_extensions($args, $extension_info);
// If no extensions are provided, select all but the hidden ones.
if (count($args) == 0) {
foreach ($extension_info as $key => $extension) {
if (isset($extension->info['hidden'])) {
unset($extension_info[$key]);
}
}
$args = array_keys($extension_info);
}
foreach ($args as $project) {
if (isset($extension_info[$project])) {
$info = $extension_info[$project];
}
else {
drush_log(dt('!project was not found.', array('!project' => $project)), 'warning');
continue;
}
if ($info->type == 'module') {
$data = _drush_pm_info_module($info);
}
else {
$data = _drush_pm_info_theme($info);
}
drush_print_table(drush_key_value_to_array_table($data));
print "\n";
}
}
/**
* Return a string with general info of a extension.
*/
function _drush_pm_info_extension($info) {
$major_version = drush_drupal_major_version();
$data['Project'] = $info->name;
$data['Type'] = $info->type;
if (($info->type == 'module')||($major_version >= 6)) {
$data['Title'] = $info->info['name'];
$data['Description'] = $info->info['description'];
$data['Version'] = $info->info['version'];
}
$data['Package'] = $info->info['package'];
if ($major_version >= 6) {
$data['Core'] = $info->info['core'];
}
if ($major_version == 6) {
$data['PHP'] = $info->info['php'];
}
$data['Status'] = drush_get_extension_status($info);
$path = (($info->type == 'module')&&($major_version == 7))?$info->uri:$info->filename;
$path = substr($path, 0, strrpos($path, '/'));
$data['Path'] = $path;
return $data;
}
/**
* Return a string with info of a module.
*/
function _drush_pm_info_module($info) {
$major_version = drush_drupal_major_version();
$data = _drush_pm_info_extension($info);
if ($info->schema_version > 0) {
$schema_version = $info->schema_version;
}
elseif ($info->schema_version == -1) {
$schema_version = "no schema installed";
}
else {
$schema_version = "module has no schema";
}
$data['Schema version'] = $schema_version;
if ($major_version == 7) {
$data['Files'] = implode(', ', $info->info['files']);
}
if (count($info->info['dependencies']) > 0) {
$requires = implode(', ', $info->info['dependencies']);
}
else {
$requires = "none";
}
$data['Requires'] = $requires;
if ($major_version == 6) {
if (count($info->info['dependents']) > 0) {
$requiredby = implode(', ', $info->info['dependents']);
}
else {
$requiredby = "none";
}
$data['Required by'] = $requiredby;
}
return $data;
}
/**
* Return a string with info of a theme.
*/
function _drush_pm_info_theme($info) {
$major_version = drush_drupal_major_version();
$data = _drush_pm_info_extension($info);
if ($major_version == 5) {
$data['Engine'] = $info->description;
}
else {
$data['Core'] = $info->info['core'];
$data['PHP'] = $info->info['php'];
$data['Engine'] = $info->info['engine'];
$data['Base theme'] = isset($info->base_themes) ? implode($info->base_themes, ', ') : '';
$regions = implode(', ', $info->info['regions']);
$data['Regions'] = $regions;
$features = implode(', ', $info->info['features']);
$data['Features'] = $features;
if (count($info->info['stylesheets']) > 0) {
$data['Stylesheets'] = '';
foreach ($info->info['stylesheets'] as $media => $files) {
$files = implode(', ', array_keys($files));
$data['Media '.$media] = $files;
}
}
if (count($info->info['scripts']) > 0) {
$scripts = implode(', ', array_keys($info->info['scripts']));
$data['Scripts'] = $scripts;
}
}
return $data;
}
/**
* Add extensions that match extension_name*.
*
* A helper function for commands that take a space separated list of extension
* names. It will identify extensions that have been passed in with a
* trailing * and add all matching extensions to the array that is returned.
*
* @param $extensions
* An array of extensions, by reference.
* @param $extension_info
* Optional. An array of extension info as returned by drush_get_extensions().
*/
function _drush_pm_expand_extensions(&$extensions, $extension_info = array()) {
if (empty($extension_info)) {
$extension_info = drush_get_extensions();
}
foreach ($extensions as $key => $extension) {
if (($wildcard = rtrim($extension, '*')) !== $extension) {
foreach (array_keys($extension_info) as $extension_name) {
if (substr($extension_name, 0, strlen($wildcard)) == $wildcard) {
$extensions[] = $extension_name;
}
}
unset($extensions[$key]);
continue;
}
}
}
/**
* Command callback. Uninstall one or more modules.
* // TODO: Use drupal_execute on system_modules_uninstall_confirm_form so that input is validated.
*/
function drush_pm_uninstall() {
$modules = _convert_csv_to_array(func_get_args());
drush_include_engine('drupal', 'environment');
$module_info = drush_get_modules();
$required = drupal_required_modules();
// Discards modules which are enabled, not found or already uninstalled.
foreach ($modules as $key => $module) {
if (!isset($module_info[$module])) {
// The module does not exist in the system.
unset($modules[$key]);
drush_log(dt('Module !module was not found and will not be uninstalled.', array('!module' => $module)), 'warning');
}
else if ($module_info[$module]->status) {
// The module is enabled.
unset($modules[$key]);
drush_log(dt('!module is not disabled. Use `pm-disable` command before `pm-uninstall`.', array('!module' => $module)), 'warning');
}
else if ($module_info[$module]->schema_version == -1) { // SCHEMA_UNINSTALLED
// The module is uninstalled.
unset($modules[$key]);
drush_log(dt('!module is already uninstalled.', array('!module' => $module)), 'ok');
}
else {
$required_by = array();
foreach (array_keys($module_info[$module]->required_by) as $dependent) {
if (!in_array($dependent, $required) && ($module_info[$dependent]->schema_version != -1)) {
$required_by[] = $dependent;
}
}
if (count($required_by)) {
drush_log(dt('To uninstall !module, the following modules must be uninstalled first: !required', array('!module' => $module, '!required' => implode(', ', $required_by))), 'error');
unset($modules[$key]);
}
}
}
// Inform the user which modules will finally be uninstalled.
if (empty($modules)) {
return drush_log(dt('There were no modules that could be uninstalled.'), 'ok');
}
else {
drush_print(dt('The following modules will be uninstalled: !modules', array('!modules' => implode(', ', $modules))));
if(!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
}
// Disable the modules.
drush_module_uninstall($modules);
// Inform the user of final status.
foreach ($modules as $module) {
drush_log(dt('!module was successfully uninstalled.', array('!module' => $module)), 'ok');
}
}
/**
* Completes projects' update data with the path to install location on disk.
*
* Given an array of release info for available projects, find the path to the install location.
*/
function _pm_get_project_path($data, $lookup) {
foreach ($data as $name => $release) {
if ($name == 'drupal') {
continue;
}
// Array of extensions (modules/themes) within the project.
$extensions = array_keys($release[$lookup]);
$path = _pm_find_common_path($release['project_type'], $extensions);
$reserved = array('modules', 'sites', 'themes');
if ((in_array(basename($path), $reserved)) && (!in_array($name, $reserved))) {
drush_log(dt('Error while trying to find the common path for enabled extensions of project !project. Extensions are: !extensions.', array('!project' => $name, '!extensions' => implode(', ', $extensions))), 'error');
unset($data[$name]);
}
else {
$data[$name]['path'] = $path;
}
}
return $data;
}
/**
* Helper function to find the common path for a list of extensions in the aim to obtain the project name.
*
* @param $project_type
* Type of project we're trying to find. Valid values: module, theme.
* @param $extensions
* Array of extension names.
*/
function _pm_find_common_path($project_type, $extensions) {
// Select the first path as the candidate to be the common prefix.
$path = drupal_get_path($project_type, array_pop($extensions));
// If there's only one extension we are done. Otherwise, we need to find
// the common prefix for all of them.
if (count($extensions) > 0) {
// Iterate over the other projects.
while($project = array_pop($extensions)) {
$path2 = drupal_get_path($project_type, $project);
// Option 1: same path.
if ($path == $path2) {
continue;
}
// Option 2: $path is a prefix of $path2.
if (strpos($path2, $path) === 0) {
continue;
}
// Option 3: $path2 is a prefix of $path.
if (strpos($path, $path2) === 0) {
$path = $path2;
continue;
}
// Option 4: no one is a prefix of the other. Find the common
// prefix by iteratively strip the rigthtmost piece of $path.
// We will iterate until a prefix is found or path = '.', that on the
// other hand is a condition theorically impossible to reach.
do {
$path = dirname($path);
if (strpos($path2, $path) === 0) {
break;
}
} while ($path != '.');
}
}
return $path;
}
/**
* Command callback. Show available releases for given project(s).
*/
function drush_pm_releases() {
if (!$requests = _convert_csv_to_array(func_get_args())) {
$requests = array('drupal');
}
$info = _drush_pm_get_releases($requests);
if (!$info) {
return drush_log(dt('No valid projects given.'), 'ok');
}
foreach ($info as $name => $project) {
$header = dt('------- RELEASES FOR \'!name\' PROJECT -------', array('!name' => strtoupper($name)));
$rows = array();
$rows[] = array(dt('Release'), dt('Date'), dt('Status'));
$releases = _drush_pm_filter_releases($project['releases'], drush_get_option('all', FALSE), drush_get_option('dev', FALSE));
foreach ($releases as $release) {
$rows[] = array(
$release['version'],
gmdate('Y-M-d', $release['date']),
implode(', ', $release['release_status'])
);
}
drush_print($header);
drush_print_table($rows, TRUE, array(0 => 14));
}
return $info;
}
/**
* Obtain releases for a project's xml as returned by the update service.
*/
function _drush_pm_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;
}
/**
* Helper function for _drush_pm_filter_releases().
*/
function _drush_pm_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 $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 _drush_pm_filter_releases($releases, $all = FALSE, $dev = FALSE, $show_all_until_installed = TRUE) {
// Start off by sorting releases by release date.
uasort($releases, '_drush_pm_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();
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 = _drush_pm_filter_releases($releases, empty($options), $dev, FALSE);
}
return $options;
}
/**
* Return an array of available releases for given project(s).
*
* Helper function for pm-download.
*/
function _drush_pm_download_releases_choice($xml, $project, $all = FALSE, $dev = FALSE) {
$project_info = _drush_pm_get_releases_from_xml($xml, $project);
$releases = _drush_pm_filter_releases($project_info['releases'], $all, $dev);
$options = array();
foreach($releases as $version => $release) {
$options[$version] = array($version, '-', gmdate('Y-M-d', $release['date']), '-', implode(', ', $release['release_status']));
}
return $options;
}
/**
* Obtain releases info for given projects and fill in status information.
*
* It does connect directly to the update service and does not depend on
* a bootstraped site.
*
* @param $requests
* An array of drupal.org project names optionally with a version.
*
* @see drush_pm_releases()
* @see _drush_pm_releasenotes()
*/
function _drush_pm_get_releases($requests) {
$info = array();
// Parse out project name and version.
$requests = pm_parse_project_version($requests);
// Get release history for each request.
foreach ($requests as $name => $request) {
$xml = _drush_pm_get_release_history_xml($request);
if (!$xml) {
continue;
}
$project_info = _drush_pm_get_releases_from_xml($xml, $name);
$info[$name] = $project_info;
}
return $info;
}
/**
* Command callback. Show release notes for given project(s).
*/
function drush_pm_releasenotes() {
if (!$requests = _convert_csv_to_array(func_get_args())) {
$requests = array('drupal');
}
return _drush_pm_releasenotes($requests);
}
/**
* Internal function: prints release notes for given drupal projects.
*
* @param $requests
* An array of drupal.org project names optionally with a version.
* @param $print_status
* Boolean. Used by pm-download to not print a informative note.
* @param $tmpfile
* If provided, a file that contains contents to show before the
* release notes.
*
* @see drush_pm_releasenotes()
*/
function _drush_pm_releasenotes($requests, $print_status = TRUE, $tmpfile = NULL) {
if ($tmpfile == NULL) {
$tmpfile = drush_tempnam('rln-' . implode('-', $requests) . '.');
}
// Parse requests to strip versions.
$requests = pm_parse_project_version($requests);
// Get the releases.
$info = _drush_pm_get_releases(array_keys($requests));
if (!$info) {
return drush_log(dt('No valid projects given.'), 'ok');
}
foreach ($info as $key => $project) {
$selected_versions = array();
// If the request included version, only show its release notes.
if (isset($requests[$key]['version'])) {
$selected_versions[] = $requests[$key]['version'];
}
else {
// Special handling if the project is installed.
if (isset($project['recommended'], $project['installed'])) {
$releases = array_reverse($project['releases']);
foreach($releases as $version => $release) {
if ($release['date'] >= $project['releases'][$project['installed']]['date']) {
$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 so we will show the release notes
// for the recommended version, as the user did not specify one.
$selected_versions[] = $project['recommended'];
}
}
foreach ($selected_versions as $version) {
// Stage of parsing.
if (!isset($project['releases'][$version]['release_link'])) {
// We avoid the cases where the URL of the release notes does not exist.
drush_log(dt("Project !project does not have release notes for version !version.", array('!project' => $key, '!version' => $version)), 'warning');
continue;
}
else {
$release_page_url = $project['releases'][$version]['release_link'];
}
$release_page_url_parsed = parse_url($release_page_url);
$release_url_path = $release_page_url_parsed['path'];
if (!empty($release_url_path)) {
if ($release_page_url_parsed['host'] == 'drupal.org') {
$release_page_id = substr($release_url_path, strlen('/node/'));
drush_log(dt("Release link for !project (!version) project was found.", array('!project' => $key, '!version' => $version)), 'notice');
}
else {
drush_log(dt("Release notes' page for !project project is not hosted on drupal.org. See !url.", array('!project' => $key, '!url' => $release_page_url)), 'warning');
continue;
}
}
// We'll use drupal_http_request if available; it provides better error reporting.
if (function_exists('drupal_http_request')) {
$data = drupal_http_request($release_page_url);
if (isset($data->error)) {
drush_log(dt("Error (!error) while requesting the release notes page for !project project.", array('!error' => $data->error, '!project' => $key)), 'error');
continue;
}
@$dom = DOMDocument::loadHTML($data->data);
}
else {
$filename = _drush_download_file($release_page_url);
@$dom = DOMDocument::loadHTMLFile($filename);
@unlink($filename);
if ($dom === FALSE) {
drush_log(dt("Error while requesting the release notes page for !project project.", array('!project' => $key)), 'error');
continue;
}
}
if ($dom) {
drush_log(dt("Successfully parsed and loaded the HTML contained in the release notes' page for !project (!version) project.", array('!project' => $key, '!version' => $version)), 'notice');
}
$xml = simplexml_import_dom($dom);
$xpath_expression = '//*[@id="node-' . $release_page_id . '"]/div[@class="node-content"]';
$node_content = $xml->xpath($xpath_expression);
// We create the print format.
$notes_last_update = $node_content[0]->div[1]->div[0]->asXML();
unset($node_content[0]->div);
$project_notes = $node_content[0]->asXML();
// Build the status message from the info from _drush_pm_get_releases
$status_msg = '> ' . implode(', ', $project['releases'][$version]['release_status']);
$break = '
';
$notes_header = dt("