... is the short name of a project hosted on drupal.org, or the short name and the version number (Drupal major version is optional). e.g. project, project-5.x-1.0, project-1.0, project-1.x-dev, project-1.1-rc1 So far, only modules are supported.\n The modules will be installed into a site specific modules directory if one exists, otherwise sites/all/modules is used. After installing, you still have to activate on the normal module administration page\n\n". $handler); case 'drush:pm update': return t("Usage: drush [options] pm update\n Displays update status information and allows to update all installed packages to the latest recommended version (so far, only modules are supported). If you only want to update certain projects, pass those as additional arguments (e.g. cck devel views ...). Note: The user is asked to confirm before the actual update is started. Use the -y option to answer all questions with yes automatically.\n\n". $handler); case 'drush:pm info': return t("Usage: drush [options] pm info ...\n View all releases for a given project. Useful for deciding which version to install/update. "); } } /** * Implementation of hook_drush_command(). */ function drush_pm_drush_command() { $items['pm install'] = array( 'callback' => 'drush_pm_install', 'description' => 'Install one or more modules' ); $items['pm refresh'] = array( 'callback' => 'drush_pm_refresh', 'description' => 'Refresh update status information' ); $items['pm update'] = array( 'callback' => 'drush_pm_update', 'description' => 'Update your modules' ); $items['pm info'] = array( 'callback' => 'drush_pm_info', 'description' => 'Release information for a module' ); return $items; } function drush_pm_requirements($phase) { switch ($phase) { case 'runtime': $package_handlers = module_invoke_all('drush_pm_package_handler'); $requirements['handlers'] = array( 'title' => t('Drush: Package handler'), 'description' => t('In order to use the Drush package manager module to install/update projects, you must install one of its package handler modules.'), 'severity' => empty($package_handlers) ? REQUIREMENT_ERROR : REQUIREMENT_OK, ); break; } return is_array($requirements) ? $requirements : array(); } /** * Parse out the project name and version and return as a structured array * * @param $requests an array of project names */ function drush_pm_parse_project_version($requests) { $requestdata = array(); foreach($requests as $request) { // project-HEAD or project-5.x-1.0-beta // '5.x-' is optional, as is '-beta' preg_match('/-(HEAD|(\d+)\.([\dx]+)(-.+)?)$/', $request, $matches); if ($matches[0]) { // Specific version requested $version = $matches[0]; $project = substr($request, 0, strlen($request) - strlen($version)); } else { // Recommended stable version requested $project = $request; } if (empty($project)) { drush_die(t("Project name not found.\n\nRun drush help pm install for more information.")); } $requestdata[$project] = array( 'name' => $project, 'version' => trim($version, ' -'), ); } return $requestdata; } /** * Command callback. Installs one or more packages (so far only modules). */ function drush_pm_install() { $requests = func_get_args(); if (empty($requests)) { drush_die(t("No project specified.\n\nRun drush help pm install for more information.")); } // Parse out project name and version $requests = drush_pm_parse_project_version($requests); // If a URI is provided then we install to that specific site, otherwise we install to sites/all/modules if (DRUSH_URI) { $path = conf_path(); $modulepath = DRUSH_DRUPAL_ROOT .'/'. $path .'/modules/'; } if (!isset($modulepath) || !file_exists($modulepath)) { $modulepath = DRUSH_DRUPAL_ROOT .'/sites/all/modules/'; } // Get the module info from drupal.org via xml-rpc $releases = drush_pm_get_project_info($requests); if (!$releases) { drush_die(t("None of the given projects exists or has releases that are compatible with your Drupal version.")); } $startdir = getcwd(); $package_handler = drush_pm_get_package_handler() .'_install_project'; if (!function_exists($package_handler)) { drush_die(t("The $package_handler package handler does not handle installs.")); } // Download and install each module foreach($requests as $project => $request) { if (isset($releases[$project]) && $release = drush_pm_get_release($request, $releases[$project])) { if (is_dir($modulepath . $project)) { drush_error(t('Project !project is already installed. Skipping.', array('!project' => $project))); } elseif ($package_handler($project, $release, $modulepath)) { drush_print(t("Project !project successfully installed (version !version).", array('!project' => $project, '!version' => $release['version']))); module_invoke_all('drush_pm_post_install', $project, $release, $modulepath); } } else { drush_error(t('Project !project doesn\' exist or has no releases that are compatible with your Drupal version. Skipping.', array('!project' => $project))); } } drush_op('chdir', $startdir); } /** * Command callback. Displays update status info and allows to update installed modules. * Pass specific projects as arguments, otherwise we update all that have candidate releases. * * This command prompts for confirmation before updating, so it is safe to run just to check on * In this case, say at the confirmation prompt. */ function drush_pm_update() { // Get update status information. $releases = _drush_pm_get_update_info(); // Get specific requests $requests = func_get_args(); // Parse out project name and version $requests = drush_pm_parse_project_version($requests); // Preprocess releases if (!empty($requests)) { // Force update projects where a specific version is reqested foreach ($requests as $project => $request) { if (!empty($request['version'])) { // Match the requested release $release = drush_pm_get_release($request, $releases[$project]); if ($release['version'] == $releases[$project]['existing_version']) { $releases[$project]['status'] = DRUSH_PM_REQUESTED_CURRENT; } else { $releases[$project]['status'] = DRUSH_PM_REQUESTED_UPDATE; } // Set the candidate version to the requested release $releases[$project]['candidate_version'] = $release['version']; } } } // Table headers. $rows[] = array(t('Name'), t('Installed version'), t('Recommended version'), t('Status')); // Process releases, notifying user of status and building a list of proposed updates $updateable = array(); foreach ($releases as $release) { if (!$release['title']) { continue; } switch($release['status']) { case UPDATE_CURRENT: $status = t('Up to date'); $release['candidate_version'] = $release['recommended']; break; case UPDATE_NOT_CURRENT: $status = t('Update available'); $release['candidate_version'] = $release['recommended']; $updateable[$release['name']] = $release; break; case UPDATE_NOT_SECURE: $status = t('SECURITY UPDATE available'); $release['candidate_version'] = $release['recommended']; $updateable[$release['name']] = $release; break; case UPDATE_REVOKED: $status = t('Intalled version REVOKED'); $release['candidate_version'] = $release['recommended']; $updateable[$project['name']] = $release; break; case DRUSH_PM_REQUESTED_UPDATE: $status = t('Specified version available'); $updateable[$release['name']] = $release; break; case DRUSH_PM_REQUESTED_CURRENT: $status = t('Specified version already installed'); break; case UPDATE_NOT_SUPPORTED: $status = t('Installed version not supported'); $release['candidate_version'] = $release['recommended']; $updateable[$project['name']] = $release; break; case UPDATE_NOT_CHECKED: $status = t('Unable to check status'); break; case UPDATE_UNKNOWN: default: $status = t('Unknown'); break; } $rows[] = array($release['title'], $release['existing_version'], $release['candidate_version'], $status); } $last = variable_get('update_last_check', 0); drush_print(t('Update information last refreshed: ') . ($last ? format_date($last) : t('Never'))); drush_print(); drush_print(t("Update status information on all installed and enabled Drupal modules:")); drush_print_table($rows, 2, TRUE); drush_print(); // If specific project updates were requested then remove releases for all others if (!empty($requests)) { foreach ($updateable as $project => $release) { if (!isset($requests[$project])) { unset($updateable[$project]); } } } if (empty($updateable)) { drush_die(t('No updates available.')); } // Offer to update to the identified releases drush_pm_update_packages($updateable); } /** * Update packages according to an array of releases, following interactive * confirmation from the user. * * @param $releases * An array of releases from the drupal.org update service, with an additional * array key candidate_version that specifies the version to be installed. */ function drush_pm_update_packages($releases) { if (isset($releases['drupal'])) { drush_print("NOTE: An update for the Drupal core is available. \nDrupal itself can't yet be updated by this tool. Please update Drupal manually.\n"); unset($releases['drupal']); } drush_print(t('Updates will be made to the following projects:')); foreach($releases as $release) { $print .= $release['title'] . " [" . $release['name'] . '-' . $release['candidate_version'] . "], "; } drush_print(substr($print, 0, strlen($print)-2)); drush_print(); drush_print(t("Note: Updated modules can potentially break your site. It's not recommended to update production sites without prior testing.")); if (module_exists('drush_pm_svn')) { drush_print(t("Note: A backup of your package will be stored to backups directory if no .svn directory is found.")); } drush_print(t('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(t('Do you really want to continue?'))) { drush_die('Aborting.'); } $package_handler = drush_pm_get_package_handler() .'_update_project'; if (!function_exists($package_handler)) { drush_die(t("The $package_handler package handler does not handle updates.")); } // Save the date to be used in the backup directory's path name. $date = date('YmdHis'); // Now we start the actual updating. foreach($releases as $release) { drush_verbose(t('Starting to update !project ...', array('!project' => $release['title']))); $source = DRUSH_DRUPAL_ROOT .'/' . $release['path']; $skip_backup = module_invoke_all('drush_pm_skip_backup', $source); if (!count($skip_backup)) { $backup_dir = DRUSH_DRUPAL_ROOT. '/backup'; drush_op('mkdir', $backup_dir, 0777); $backup_dir .= '/modules'; drush_op('mkdir', $backup_dir, 0777); $backup_dir .= "/$date"; drush_op('mkdir', $backup_dir, 0777); $backup_target = $backup_dir . '/'. $release['name']; if (!drush_op('rename', $source, $backup_target)) { drush_die(t('Failed to backup project directory !source to !backup_target', array('!source' => $source, '!backup_target' => $backup_target))); } } // Install the new version. // $basepath is the dir where the current module is installed. It's one dir up from the // place of the project's info files. $basepath = explode('/', $release['path']); // move a directory up, so we can copy updated dir to parent array_pop($basepath); $project_parent_path = DRUSH_DRUPAL_ROOT. '/'. implode('/', $basepath). '/'; if (!$package_handler($release['name'], $release['releases'][$release['candidate_version']], $project_parent_path)) { if (!count($skip_backup)) { drush_error(t('Updating project !project failed. Restoring previously installed version.', array('!project' => $release['name']))); drush_op('rename', $backup_target, $source); } else { drush_error(t('Updating project !project failed. Please revert to the previously installed version.', array('!project' => $release['name']))); } } else { drush_print(t('Project !project was updated successfully. Installed version is now !version.', array('!project' => $release['name'], '!version' => $release['candidate_version']))); module_invoke_all('drush_pm_post_update', $release['name'], $release['releases'][$release['candidate_version']], $project_parent_path); } } if ($backup_dir) { drush_print(t("Backups were saved into the directory !backup_dir.", array('!backup_dir' => $backup_dir))); } drush_print(t("You should now run update.php through your browser.")); } /** * Find a module handler */ function drush_pm_get_package_handler() { $package_handlers = module_invoke_all('drush_pm_package_handler'); if (empty($package_handlers)) { drush_die(t("No package handlers found.")); } $handler = drush_get_option('handler'); // See if we have the full handler provided if (array_search($handler, $package_handlers)) { return $handler; } // Allow a shortcut for any functions named drush_pm_* if (array_search('drush_pm_'. $handler, $package_handlers)) { return 'drush_pm_'. $handler; } // Fallback on the first provided handler (from the lightest module) return $package_handlers[0]; } /** * Get update information for all installed projects. * * @return An array containing remote and local versions for all installed projects */ function _drush_pm_get_update_info($projects = NULL) { $info = update_get_available(); $data = update_calculate_project_data($info); $data = drush_pm_get_project_path($data); return $data; } /** * Command callback. Refresh update status information. */ function drush_pm_refresh() { drush_print(t("Refreshing update status information ...")); update_refresh(); drush_print(t("Done.")); } /** * Get project information from drupal.org. * * @param $projects An array of project names */ function drush_pm_get_project_info($projects) { $info = array(); $data = array(); foreach ($projects as $project_name => $project) { $url = UPDATE_DEFAULT_URL. "/$project_name/". DRUPAL_CORE_COMPATIBILITY; $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; } /** * Get the recommended release for a certain so far uninstalled project. * * @param $project A project information array for the requested project * @param $info A project information array for this project, as returned by an update service from drush_pm_get_project_info() */ function drush_pm_get_release($project, $info) { $minor = ''; $version_patch_changed = ''; if ($project['version']) { // The user specified a specific version - try to find that exact version foreach($info['releases'] as $version => $release) { // Ignore unpublished releases. if ($release['status'] != 'published') { continue; } // Straight match if (!isset($recommended_version) && $release['version'] == $project['version']) { $recommended_version = $version; } // Shortcut match with ommitted Drupal version if (!isset($recommended_version) && $release['version'] == DRUPAL_CORE_COMPATIBILITY .'-'. $project['version']) { $recommended_version = $version; } } } else { // No version specified - try to find the best version we can foreach($info['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'] == $info['default_major']) { $latest_version = $version; } if (!isset($recommended_version) && $release['version_major'] == $info['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 $info['releases'][$recommended_version]; } else if (isset($latest_version)) { return $info['releases'][$latest_version]; } else { return false; } } /** * We need to set the project path by looking at the module location. Ideally, update.module would do this for us. */ function drush_pm_get_project_path($projects) { foreach ($projects as $project => $info) { if (!isset($info['path']) && $project != 'drupal') { // looks for an enabled module. foreach ($info['includes'] as $module => $name) { if ($path = drupal_get_path('module', $module)) { continue; } } // As some modules are not located in their project's root directory // but in a subdirectory (e.g. all the ecommerce modules), we take the module's // info file's path, and then move up until we are at a directory with the // project's name. $parts = explode('/', $path); $i = count($parts) - 1; $stop = array_search($project, $parts); while ($i > $stop) { unset($parts[$i]); $i--; } $projects[$project]['path'] = implode('/', $parts); } } return $projects; } /** * A drush command callback. Show release info for given project(s). * **/ function drush_pm_info() { $projects = func_get_args(); $projects = drupal_map_assoc($projects); $info = drush_pm_get_project_info($projects); $rows[] = array(t('Project'), t('Release'), t('Date')); foreach ($info as $key => $project) { foreach ($project['releases'] as $release) { $rows[] = array( $key, $release['version'], format_date($release['date'], 'custom', 'Y-M-d'), ); } } return drush_print_table($rows, FALSE, TRUE); } /** * Deletes a directory, all files in it and all subdirectories in it (recursively). * Use with care! * Written by Andreas Kalsch */ function delete_dir($dir) { if (substr($dir, strlen($dir)-1, 1) != '/') $dir .= '/'; if ($handle = opendir($dir)) { while ($obj = readdir($handle)) { if ($obj != '.' && $obj != '..') { if (is_dir($dir.$obj)) { if (!delete_dir($dir.$obj)) { return false; } } elseif (is_file($dir.$obj)) { if (!unlink($dir.$obj)) { return false; } } } } closedir($handle); if (!@rmdir($dir)) { return false; } return true; } return false; } ?>