Calling all Drupal developers!
Help us get this on the first page of Digg. DIGG NOW!
Help us get this on the first page of Digg. DIGG NOW!
<?php
// $Id: drush_pm.module,v 1.22 2008/04/14 02:34:16 weitzman Exp $
/**
* @file
* The drush Package Manager
*/
/**
* Implementation of hook_help().
*/
function drush_pm_help($section) {
$handlers = str_replace('drush_pm_', '', module_invoke_all('drush_pm_package_handler'));
if ($handlers) {
$handlers[0] = $handlers[0] .' (default)';
$handler = '--handler - specify which package handler you would like to use.
Available options: '. implode(', ', $handlers) . "\n";
}
else {
$handler = "ERROR: You must activate a drush Package Manager handler module in order
to install or update packages.";
}
switch ($section) {
case 'drush:pm install':
return t("Usage: drush [options] pm install <package_1> <package_2> ...
<package_n> 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 <package_1> <package_2> ...\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_package',
'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 $projects an array of project names
*/
function drush_pm_parse_package_name($projects) {
$projectdata = array();
foreach($projects as $project) {
// project-HEAD or project-5.x-1.0-beta
// '5.x-' is optional, as is '-beta'
preg_match('/-(HEAD|(\d+)\.([\dx]+)(-.+)?)$/', $project, $matches);
if ($matches[0]) {
// Specific version requested
$version = $matches[0];
$name = substr($project, 0, strlen($project) - strlen($version));
}
else {
// Recommended stable version requested
$name = $project;
}
if (empty($name)) {
drush_die(t("Project name not found.\n\nRun drush help pm install for more information."));
}
$projectdata[$name] = array(
'name' => $name,
'version' => trim($version, ' -'),
);
}
return $projectdata;
}
/**
* Command callback. Installs one or more packages (so far only modules).
*/
function drush_pm_install_package() {
$projects = func_get_args();
if (empty($projects)) {
drush_die(t("No project specified.\n\nRun drush help pm install for more information."));
}
// Parse out project name and version
$projects = drush_pm_parse_package_name($projects);
// 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
$info = drush_pm_get_project_info($projects);
if (!$info) {
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($projects as $project_name => $project) {
if (isset($info[$project_name]) && $release = drush_pm_get_release($project, $info[$project_name])) {
if (is_dir($modulepath . $project_name)) {
drush_error(t('Project !project is already installed. Skipping.', array('!project' => $project_name)));
}
elseif ($package_handler($project_name, $release, $modulepath)) {
drush_print(t("Project !project successfully installed (version !version).",
array('!project' => $project_name, '!version' => $release['version'])));
}
}
else {
drush_error(t('Project !project doesn\' exist or has no releases that are compatible with your Drupal version. Skipping.', array('!project' => $project_name)));
}
}
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.
$data = _drush_pm_get_update_info();
// Remove info for projects we don't care about (if specified)
if ($specified_projects = func_get_args()) {
foreach ($data as $key => $value) {
if (!in_array($key, $specified_projects)) {
unset($data[$key]);
}
}
}
$last = variable_get('update_last_check', 0);
drush_print(t('Update information last refreshed: ') . ($last ? format_date($last) : t('Never')));
drush_print();
// table headers
$rows[] = array(t('Name'), t('Installed version'), t('Recommended version'), t('Status'));
foreach ($data as $project) {
if (!$project['title']) {
continue;
}
$project['candidate_version'] = $project['recommended'];
switch($project['status']) {
case UPDATE_CURRENT:
$status = t('OK');
break;
case UPDATE_NOT_CURRENT:
$status = t('Update available');
$updateable[$project['name']] = $project;
break;
case UPDATE_NOT_SECURE:
$status = t('SECURITY UPDATE available');
$updateable[$project['name']] = $project;
break;
case UPDATE_REVOKED:
$status = t('Intalled version REVOKED');
$updateable[$project['name']] = $project;
break;
default:
$status = t('ignored: !reason', array('!reason' => $project['reason']));
$project['title'] = $project['name'];
$project['candidate_version'] = t('unknown');
break;
}
$rows[] = array($project['title'], $project['existing_version'], $project['candidate_version'], $status);
}
drush_print(t("Update status information on all installed and enabled Drupal modules:"));
drush_print_table($rows, 2, TRUE);
drush_print();
if (isset($updateable['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($updateable['drupal']);
}
if (empty($updateable)) {
drush_die(t('No updates available.'));
}
drush_print(t('Updates are available for the following projects:'));
foreach($updateable as $project) {
$print .= $project['title'] . " [" . $project['name'] . "], ";
}
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."));
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."));
}
// Now we start the actual updating.
foreach($updateable as $project) {
drush_verbose(t('Starting to update !project ...', array('!project' => $project['title'])));
$svn = FALSE;
$source = DRUSH_DRUPAL_ROOT .'/' . $project['path'];
if (file_exists($source. '/.svn')) {
// Skip backup since we need all .svn directories. Assuming admin can use svn revert if new project is bad.
drush_verbose(t('Backup skipped because .svn directory was detected. Use Subversion to revert if needed.'));
$svn = TRUE;
}
else {
$date = date('Ymd');
$backup_dir = DRUSH_DRUPAL_ROOT. "/backup/modules/$date";
drush_op('mkdir', $backup_dir, 0777, TRUE);
$backup_target = $backup_dir . '/'. $project['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('/', $project['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($project['name'], $project['releases'][$project['candidate_version']], $project_parent_path)) {
drush_error(t('Updating project !project failed. Restoring previously installed version.', array('!project' => $project['name'])));
drush_op('rename', $backup_target, $source);
}
else {
drush_print(t('Project !project was updated successfully. Installed version is now !version.', array('!project' => $project['name'], '!version' => $project['candidate_version'])));
}
if ($svn) {
drush_print(t('You should consider committing the new code to your Subversion repository. If this version becomes undesireable, use \'svn revert\' to roll back.'));
}
else {
drush_print(t("Backups were saved into 'backup' directory."));
}
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();
if (empty($projects)) {
drush_die(t("No project specified.\n\nRun drush help pm info for more information."));
}
// Get update status information.
$data = _drush_pm_get_update_info();
$rows[] = array(t('Release'), t('Date'), t('Rating'));
foreach ($projects as $project) {
foreach ($data[$project]['releases'] as $release) {
if ($release['version'] == $data[$project]['recommended']) {
$rating = t('Recommended');
}
else {
$rating = '';
}
$rows[] = array(
$release['version'],
format_date($release['date'], 'custom', 'Y-M-d'),
$rating,
);
}
}
return drush_print_table($rows, 0, 1);
}
/**
* 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;
}
?>