pm.drush.inc

  1. 8.0.x commands/pm/pm.drush.inc
  2. 6.x commands/pm/pm.drush.inc
  3. 7.x commands/pm/pm.drush.inc
  4. 3.x commands/pm/pm.drush.inc
  5. 4.x commands/pm/pm.drush.inc
  6. 5.x commands/pm/pm.drush.inc
  7. master commands/pm/pm.drush.inc

The drush Package Manager

Terminology:

  • Request: a requested package (string or keyed array), with a project name and (optionally) version.
  • Project: a drupal.org project, such as cck or zen.
  • Version: a requested version, such as 1.0 or 1.x-dev.
  • Release: a specific release of a project, with associated metadata (from the drupal.org update service).
  • Package: the collection of files that make up a release.

Functions

Namesort descending Description
delete_dir Deletes a directory, all files in it and all subdirectories in it (recursively). Use with care! Written by Andreas Kalsch
drush_pm_classify_projects Classify projects in modules, themes or unknown ones.
drush_pm_disable Command callback. Disable one or more projects.
drush_pm_download Command callback. Download Drupal core or any project.
drush_pm_enable Command callback. Enable one or more projects.
drush_pm_get_projects Wrapper of drupal_get_projects() with additional information used by pm- commands.
drush_pm_include_version_control 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.
drush_pm_info Command callback. Show detailed info for one or more projects.
drush_pm_list Command callback. Show a list of modules and status.
drush_pm_post_pm_update Post-command callback. Execute updatedb command after an updatecode - user requested `update`.
drush_pm_post_pm_updatecode Post-command callback for updatecode. Notify about any pending DB updates.
drush_pm_refresh Command callback. Refresh update status information.
drush_pm_releases A drush command callback. Show release info for given project(s).
drush_pm_relocate_project drush_pm_relocate_project moves projects that should be relocated to a different installation directory to the location they belong in. For example, modules that are only collections of drush commands will be installed to $HOME/.drush.
drush_pm_uninstall Command callback. Uninstall one or more modules. // TODO: Use drupal_execute on system_modules_uninstall_confirm_form so that input is validated.
drush_pm_update Command callback. Execute updatecode.
pm_dl_destination Return the best destination for a particular download type we can find, given the drupal and site contexts.
pm_dl_destination_lookup Determine a candidate destination directory for a particular site path and return it if it exists, optionally attempting to create the directory.
pm_drush_command Implementation of hook_drush_command().
pm_drush_engine_package_handler Used by dl and updatecode commands to determine how to download/checkout new projects and acquire updates to projects.
pm_drush_engine_version_control Integration with VCS in order to easily commit your changes to projects.
pm_drush_help Implementation of hook_drush_help().
pm_drush_pm_adjust_download_destination Built-in adjust-download-destination hook. This particular version of the hook will move modules that contain only drush commands to /usr/share/drush/commands if it exists, or $HOME/.drush if the site-wide location does not exist.
pm_get_project_path We need to set the project path by looking at the module location. Ideally, update.module would do this for us.
pm_is_enabled Array filter callback to return enabled modules.
pm_parse_project_version Parse out the project name and version and return as a structured array
pm_project_types
pm_true Callback helper.
_drush_pm_expand_projects Add sub projects that match project_name*.
_drush_pm_info_module Return a string with info of a module.
_drush_pm_info_project Return a string with general info of a project (module or theme).
_drush_pm_info_theme Return a string with info of a theme.
_drush_pm_sort_projects Sort callback function for sorting projects First by type, second by package and third by name

Constants

Namesort descending Description
DRUSH_PM_NO_VERSION User requested version already installed.
DRUSH_PM_REQUESTED_CURRENT User requested version already installed.
DRUSH_PM_REQUESTED_NOT_FOUND User requested version not found.
DRUSH_PM_REQUESTED_UPDATE Project is a user requested version update.

Interfaces

Namesort descending Description
drush_pm_version_control Interface for version control systems. We use a simple object layer because we conceivably need more than one loaded at a time.

File

commands/pm/pm.drush.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * The drush Package Manager
  5. *
  6. * Terminology:
  7. * - Request: a requested package (string or keyed array), with a project name and (optionally) version.
  8. * - Project: a drupal.org project, such as cck or zen.
  9. * - Version: a requested version, such as 1.0 or 1.x-dev.
  10. * - Release: a specific release of a project, with associated metadata (from the drupal.org update service).
  11. * - Package: the collection of files that make up a release.
  12. */
  13. /**
  14. * Project is a user requested version update.
  15. */
  16. define('DRUSH_PM_REQUESTED_UPDATE', 101);
  17. /**
  18. * User requested version already installed.
  19. */
  20. define('DRUSH_PM_REQUESTED_CURRENT', 102);
  21. /**
  22. * User requested version already installed.
  23. */
  24. define('DRUSH_PM_NO_VERSION', 103);
  25. /**
  26. * User requested version not found.
  27. */
  28. define('DRUSH_PM_REQUESTED_NOT_FOUND', 104);
  29. /**
  30. * Sort callback function for sorting projects
  31. * First by type, second by package and third by name
  32. *
  33. * Special handling for 'Package' on modules and themes.
  34. */
  35. function _drush_pm_sort_projects($a, $b) {
  36. if ($a->type == 'module' && $b->type == 'theme') {
  37. return -1;
  38. }
  39. if ($a->type == 'theme' && $b->type == 'module') {
  40. return 1;
  41. }
  42. $cmp = strcasecmp($a->info['package'], $b->info['package']);
  43. if ($cmp == 0) {
  44. $cmp = strcasecmp($a->info['name'], $b->info['name']);
  45. }
  46. return $cmp;
  47. }
  48. /**
  49. * Implementation of hook_drush_help().
  50. */
  51. function pm_drush_help($section) {
  52. switch ($section) {
  53. case 'drush:pm-enable':
  54. return dt('Enable one or more modules or themes. Enable dependant modules as well.');
  55. case 'drush:pm-disable':
  56. return dt('Disable one or more modules or themes. Disable dependant modules as well.');
  57. case 'drush:pm-info':
  58. return dt('Show detailed info for one or more projects.');
  59. case 'drush:pm-uninstall':
  60. return dt('Uninstall one or more modules. Modules must be disabled first.');
  61. case 'drush:pm-list':
  62. return dt('Show a list of available modules and themes.');
  63. case 'drush:pm-refresh':
  64. return dt('Refresh update status information. Run this before running update or updatecode commands.');
  65. case 'drush:pm-updatecode':
  66. return dt("Display available update information and allow updating of all installed project code to the specified version (or latest by default). Note: The user is asked to confirm before the actual update. Use the --yes option to answer yes automatically.");
  67. case 'drush:pm-update':
  68. return dt("Display available update information and allow updating of all installed projects to the specified version (or latest by default), followed by applying any database updates required (as with running update.php). Note: The user is asked to confirm before the actual update. Use the --yes option to answer yes automatically.");
  69. case 'drush:pm-releases':
  70. return dt("View all releases for a given project (modules, themes, profiles, translations). Useful for deciding which version to install/update.");
  71. case 'drush:pm-download':
  72. return dt("Quickly download projects (modules, themes, profiles, translations) from drupal.org. Automatically figures out which module version you want based on its latest release, or you may specify a particular version. Downloads drupal core as well. If no destination is provided, defaults to a site specific modules directory if available, then to sites/all/modules if available, then to the current working directory.");
  73. }
  74. }
  75. /**
  76. * Implementation of hook_drush_command().
  77. */
  78. function pm_drush_command() {
  79. $update = 'update';
  80. if (drush_drupal_major_version() == 5) {
  81. $update = 'update_status';
  82. }
  83. $engines = array(
  84. 'engines' => array(
  85. 'version_control' => 'Integration with VCS in order to easily commit your changes to projects.',
  86. 'package_handler' => 'Determine how to download/checkout new projects and acquire updates to projects.',
  87. ),
  88. );
  89. $items['pm-enable'] = array(
  90. 'description' => 'Enable one or more modules or themes.',
  91. 'arguments' => array(
  92. 'modules' => 'A space delimited list of modules or themes. You can use the * wildcard at the end of module and theme names to to enable all matches.',
  93. ),
  94. 'aliases' => array('en'),
  95. 'deprecated-aliases' => array('enable'),
  96. );
  97. $items['pm-disable'] = array(
  98. 'description' => 'Disable one or more modules or themes.',
  99. 'arguments' => array(
  100. 'modules' => 'A space delimited list of modules or themes. You can use the * wildcard at the end of module and theme names to disable multiple matches.',
  101. ),
  102. 'aliases' => array('dis'),
  103. 'deprecated-aliases' => array('disable'),
  104. );
  105. $items['pm-info'] = array(
  106. 'description' => 'Show info for one or more projects.',
  107. 'arguments' => array(
  108. 'projects' => 'A space delimited list of projects. You can use the * wildcard at the end of module names to get info for the project and all its sub projects.',
  109. ),
  110. );
  111. // Install command is reserved for the download and enable of projects including dependencies.
  112. // @see http://drupal.org/node/112692 for more information.
  113. // $items['install'] = array(
  114. // 'description' => 'Download and enable one or more modules',
  115. // );
  116. $items['pm-uninstall'] = array(
  117. 'description' => 'Uninstall one or more modules.',
  118. 'arguments' => array(
  119. 'modules' => 'A space delimited list of modules.',
  120. ),
  121. 'deprecated-aliases' => array('uninstall'),
  122. );
  123. $items['pm-list'] = array(
  124. 'description' => 'Show a list of available modules and themes',
  125. 'callback arguments' => array(array(), FALSE),
  126. 'options' => array(
  127. '--type' => 'Filter by project type. Choices: module, theme.',
  128. '--status' => 'Filter by project status. Choices: enabled,disable and/or \'not installed\'. You can use multiple comma separated values. (i.e. --status="disabled,not installed").',
  129. '--package' => 'Filter by project packages. You can use multiple comma separated values. (i.e. --package="Core - required,Other").',
  130. '--pipe' => 'Returns a space delimited list of the names of the resulting projects.',
  131. ),
  132. 'aliases' => array('sm'),
  133. 'deprecated-aliases' => array('statusmodules'),
  134. );
  135. $items['pm-refresh'] = array(
  136. 'description' => 'Refresh update status information',
  137. 'drupal dependencies' => array($update),
  138. 'aliases' => array('rf'),
  139. 'deprecated-aliases' => array('refresh'),
  140. );
  141. $items['pm-updatecode'] = array(
  142. 'description' => 'Update your project code',
  143. 'drupal dependencies' => array($update),
  144. 'arguments' => array(
  145. 'projects' => 'Optional. A space delimited list of installed projects (modules or themes) to update.',
  146. ),
  147. 'options' => array(
  148. '--backup-dir' => 'Specify a directory to backup packages into, defaults to a backup directory within your Drupal root.',
  149. '--pipe' => 'Returns a space delimited list of enabled modules and their respective version and update information, one module per line. Order: module name, current version, recommended version, update status.',
  150. ),
  151. 'aliases' => array('upc'),
  152. 'deprecated-aliases' => array('updatecode'),
  153. ) + $engines;
  154. // Merge help from above.
  155. $items['pm-update'] = array_merge($items['pm-updatecode'], array(
  156. 'description' => 'Update your project code and apply any database updates required (update.php)',
  157. 'aliases' => array('up'),
  158. 'deprecated-aliases' => array('update'),
  159. ));
  160. $items['pm-releases'] = array(
  161. 'description' => 'Release information for a project',
  162. 'drupal dependencies' => array($update),
  163. 'arguments' => array(
  164. 'projects' => 'A space separated list of drupal.org project names.',
  165. ),
  166. 'examples' => array(
  167. 'drush pm-releases cck zen' => 'View releases for cck and Zen projects.',
  168. ),
  169. 'deprecated-aliases' => array('info'),
  170. );
  171. $items['pm-download'] = array(
  172. 'description' => 'Download core Drupal and projects like CCK, Zen, etc.',
  173. 'examples' => array(
  174. 'drush dl' => 'Download latest version of Drupal core.',
  175. 'drush dl drupal' => 'Download latest stable version of Drupal core',
  176. 'drush dl drupal-7.x' => 'Download latest 7.x development version of Drupal core',
  177. 'drush dl cck zen es' => 'Download latest versions of CCK, Zen and Spanish translations for my version of Drupal.',
  178. 'drush dl og-1.3' => 'Download a specfic version of Organic groups module for my version of Drupal.',
  179. 'drush dl diff-6.x-2.x' => 'Download a specific development branch of diff module for a specific Drupal version.',
  180. ),
  181. 'arguments' => array(
  182. 'projects' => 'A space separated list of project names, with optional version. Defaults to \'drupal\'',
  183. ),
  184. 'options' => array(
  185. '--destination' => 'Path to which the project will be copied.',
  186. '--source' => 'The base URL which provides project release history in XML. Defaults to http://updates.drupal.org/release-history.',
  187. '--variant' => "Only useful for install profiles. Possible values: 'core', 'no-core', 'make'.",
  188. '--drupal-project-rename' => 'Alternate name for "drupal" directory when downloading drupal project.',
  189. ),
  190. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all.
  191. 'aliases' => array('dl'),
  192. 'deprecated-aliases' => array('download'),
  193. ) + $engines;
  194. return $items;
  195. }
  196. /**
  197. * Command callback. Show a list of modules and status.
  198. *
  199. */
  200. function drush_pm_list() {
  201. //--package
  202. $package_filter = array();
  203. $package = strtolower(drush_get_option('package'));
  204. if (!empty($package)) {
  205. $package_filter = explode(',', $package);
  206. }
  207. if (empty($package_filter) || count($package_filter) > 1) {
  208. $header[] = dt('Package');
  209. }
  210. $header[] = dt('Name');
  211. //--type
  212. $all_types = array('module', 'theme');
  213. $type_filter = strtolower(drush_get_option('type'));
  214. if (!empty($type_filter)) {
  215. $type_filter = explode(',', $type_filter);
  216. }
  217. else {
  218. $type_filter = $all_types;
  219. }
  220. if (count($type_filter) > 1) {
  221. $header[] = dt('Type');
  222. }
  223. foreach ($type_filter as $type) {
  224. if (!in_array($type, $all_types)) { //TODO: this kind of chck can be implemented drush-wide
  225. return drush_set_error('DRUSH_PM_INVALID_PROJECT_TYPE', dt('!type is not a valid project type.', array('!type' => $type)));
  226. }
  227. }
  228. //--status
  229. $all_status = array('enabled', 'disabled', 'not installed');
  230. $status_filter = strtolower(drush_get_option('status'));
  231. if (!empty($status_filter)) {
  232. $status_filter = explode(',', $status_filter);
  233. }
  234. else {
  235. $status_filter = $all_status;
  236. }
  237. if (count($status_filter) > 1) {
  238. $header[] = dt('Status');
  239. }
  240. foreach ($status_filter as $status) {
  241. if (!in_array($status, $status_filter)) { //TODO: this kind of chck can be implemented drush-wide
  242. return drush_set_error('DRUSH_PM_INVALID_PROJECT_TYPE', dt('!status is not a valid project status.', array('!status' => $status)));
  243. }
  244. }
  245. $header[] = dt('Version');
  246. $rows[] = $header;
  247. $projects = drush_pm_get_projects();
  248. uasort($projects, '_drush_pm_sort_projects');
  249. $major_version = drush_drupal_major_version();
  250. foreach ($projects as $project) {
  251. if (!in_array($project->type, $type_filter)) {
  252. continue;
  253. }
  254. $status = drush_get_project_status($project);
  255. if (!in_array($status, $status_filter)) {
  256. continue;
  257. }
  258. if (($major_version >= 7) and (isset($project->info['hidden']))) {
  259. continue;
  260. }
  261. // filter by package
  262. if (!empty($package_filter)) {
  263. if (!in_array(strtolower($project->info['package']), $package_filter)) {
  264. continue;
  265. }
  266. }
  267. if (empty($package_filter) || count($package_filter) > 1) {
  268. $row[] = $project->info['package'];
  269. }
  270. if (($major_version >= 6)||($project->type == 'module')) {
  271. $row[] = $project->info['name'].' ('.$project->name.')';
  272. }
  273. else {
  274. $row[] = $project->name;
  275. }
  276. if (count($type_filter) > 1) {
  277. $row[] = ucfirst($project->type);
  278. }
  279. if (count($status_filter) > 1) {
  280. $row[] = ucfirst($status);
  281. }
  282. if (($major_version >= 6)||($project->type == 'module')) {
  283. $row[] = $project->info['version'];
  284. }
  285. $rows[] = $row;
  286. $pipe[] = $project->name;
  287. unset($row);
  288. }
  289. drush_print_table($rows, TRUE);
  290. if (isset($pipe)) {
  291. // Newline-delimited list for use by other scripts. Set the --pipe option.
  292. drush_print_pipe($pipe);
  293. }
  294. }
  295. /**
  296. * Command callback. Enable one or more projects.
  297. */
  298. function drush_pm_enable() {
  299. $args = func_get_args();
  300. $project_info = drush_get_projects();
  301. // Classify $args in themes, modules or unknown.
  302. $modules = array();
  303. $themes = array();
  304. drush_pm_classify_projects($args, $modules, $themes, $project_info);
  305. $projects = array_merge($modules, $themes);
  306. $unknown = array_diff($args, $projects);
  307. // Discard and set an error for each unknown project.
  308. foreach ($unknown as $project) {
  309. drush_set_error('DRUSH_PM_ENABLE_PROJECT_NOT_FOUND', dt('!project was not found and will not be enabled.', array('!project' => $project)));
  310. }
  311. // Discard already enabled projects.
  312. foreach ($projects as $project) {
  313. if ($project_info[$project]->status) {
  314. if ($project_info[$project]->type == 'module') {
  315. unset($modules[$project]);
  316. }
  317. else {
  318. unset($themes[$project]);
  319. }
  320. drush_log(dt('!project is already enabled.', array('!project' => $project)), 'ok');
  321. }
  322. }
  323. if (!empty($modules)) {
  324. // Check module dependencies.
  325. $dependencies = drush_check_module_dependencies($modules, $project_info);
  326. $all_dependencies = array();
  327. foreach ($dependencies as $key => $info) {
  328. if (isset($info['error'])) {
  329. unset($modules[$key]);
  330. drush_set_error($info['error']['code'], $info['error']['message']);
  331. }
  332. elseif (!empty($info['dependencies'])) {
  333. // Make sure we have an assoc array.
  334. $assoc = drupal_map_assoc($info['dependencies']);
  335. $all_dependencies = array_merge($all_dependencies, $assoc);
  336. }
  337. }
  338. $all_dependencies = array_unique($all_dependencies);
  339. $enabled = array_keys(array_filter(drush_get_modules(), 'pm_is_enabled'));
  340. $needed = array_diff($all_dependencies, $enabled);
  341. $modules = $needed + $modules;
  342. // Discard modules which don't meet requirements.
  343. require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc';
  344. foreach ($modules as $key => $module) {
  345. // Check to see if the module can be installed/enabled (hook_requirements).
  346. // See @system_modules_submit
  347. if (!drupal_check_module($module)) {
  348. unset($modules[$key]);
  349. drush_set_error('DRUSH_PM_ENABLE_MODULE_UNMEET_REQUIREMENTS', dt('Module !module don\'t meet the requirements to be enabled.', array('!module' => $module)));
  350. }
  351. }
  352. }
  353. // Inform the user which projects will finally be enabled.
  354. $projects = array_merge($modules, $themes);
  355. if (empty($projects)) {
  356. return drush_log(dt('There were no projects that could be enabled.'), 'ok');
  357. }
  358. else {
  359. drush_print(dt('The following projects will be enabled: !projects', array('!projects' => implode(', ', $projects))));
  360. if(!drush_confirm(dt('Do you really want to continue?'))) {
  361. return drush_log(dt('Aborting.'));
  362. }
  363. }
  364. // Enable themes.
  365. if (!empty($themes)) {
  366. drush_theme_enable($themes);
  367. }
  368. // Enable modules and pass dependency validation in form submit.
  369. if (!empty($modules)) {
  370. drush_module_enable($modules);
  371. $current = drupal_map_assoc($enabled, 'pm_true');
  372. $processed = drupal_map_assoc($modules, 'pm_true');
  373. $active_modules = array_merge($current, $processed);
  374. drush_system_modules_form_submit($active_modules);
  375. }
  376. // Inform the user of final status.
  377. $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:projects)', array(':projects' => $projects));
  378. while ($project = drush_db_fetch_object($rsc)) {
  379. if ($project->status) {
  380. drush_log(dt('!project was enabled successfully.', array('!project' => $project->name)), 'ok');
  381. }
  382. else {
  383. drush_set_error('DRUSH_PM_ENABLE_PROJECT_ISSUE', dt('There was a problem enabling !project.', array('!project' => $project->name)));
  384. }
  385. }
  386. }
  387. /**
  388. * Command callback. Disable one or more projects.
  389. */
  390. function drush_pm_disable() {
  391. $args = func_get_args();
  392. $project_info = drush_get_projects();
  393. // classify $args in themes, modules or unknown
  394. $modules = array();
  395. $themes = array();
  396. drush_pm_classify_projects($args, $modules, $themes, $project_info);
  397. $projects = array_merge($modules, $themes);
  398. $unknown = array_diff($args, $projects);
  399. // Discard and set an error for each unknown project.
  400. foreach ($unknown as $project) {
  401. drush_set_error('DRUSH_PM_ENABLE_PROJECT_NOT_FOUND', dt('!project was not found and will not be disabled.', array('!project' => $project)));
  402. }
  403. // Discard already disabled projects.
  404. foreach ($projects as $project) {
  405. if (!$project_info[$project]->status) {
  406. if ($project_info[$project]->type == 'module') {
  407. unset($modules[$project]);
  408. }
  409. else {
  410. unset($themes[$project]);
  411. }
  412. drush_log(dt('!project is already disabled.', array('!project' => $project)), 'ok');
  413. }
  414. }
  415. // Discard default theme.
  416. if (!empty($themes)) {
  417. $default_theme = drush_theme_get_default();
  418. if (in_array($default_theme, $themes)) {
  419. unset($themes[$default_theme]);
  420. drush_log(dt('!theme is the default theme and can\'t be disabled.', array('!theme' => $default_theme)), 'ok');
  421. }
  422. }
  423. // Add enabled dependents to list of modules to disable.
  424. if (!empty($modules)) {
  425. $enabled = array_keys(array_filter(drush_get_modules(), 'pm_is_enabled'));
  426. $dependents = drush_module_dependents($modules, $project_info);
  427. $dependents = array_unique($dependents);
  428. $dependents = array_intersect($dependents, $enabled);
  429. $modules = array_merge($modules, $dependents);
  430. }
  431. // Inform the user which projects will finally be disabled.
  432. $projects = array_merge($modules, $themes);
  433. if (empty($projects)) {
  434. return drush_log(dt('There were no projects that could be disabled.'), 'ok');
  435. }
  436. else {
  437. drush_print(dt('The following projects will be disabled: !projects', array('!projects' => implode(', ', $projects))));
  438. if(!drush_confirm(dt('Do you really want to continue?'))) {
  439. return drush_log(dt('Aborting.'));
  440. }
  441. }
  442. // Disable themes.
  443. if (!empty($themes)) {
  444. drush_theme_disable($themes);
  445. }
  446. // Disable modules and pass dependency validation in form submit.
  447. if (!empty($modules)) {
  448. drush_module_disable($modules);
  449. $active_modules = array_diff($enabled, $modules);
  450. $active_modules = drupal_map_assoc($active_modules, 'pm_true');
  451. drush_system_modules_form_submit($active_modules);
  452. }
  453. // Inform the user of final status.
  454. $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:projects)', array(':projects' => $projects));
  455. while ($project = drush_db_fetch_object($rsc)) {
  456. if (!$project->status) {
  457. drush_log(dt('!project was disabled successfully.', array('!project' => $project->name)), 'ok');
  458. }
  459. else {
  460. drush_set_error('DRUSH_PM_DISABLE_PROJECT_ISSUE', dt('There was a problem disabling !project.', array('!project' => $project->name)));
  461. }
  462. }
  463. }
  464. /**
  465. * Wrapper of drupal_get_projects() with additional information used by
  466. * pm- commands.
  467. *
  468. * @return
  469. * An array containing info for all available modules and themes w/additional
  470. * info.
  471. */
  472. function drush_pm_get_projects() {
  473. $projects = drush_get_projects();
  474. foreach ($projects as $key => $project) {
  475. if (empty($project->info['package'])) {
  476. $projects[$key]->info['package'] = dt('Other');
  477. }
  478. }
  479. return $projects;
  480. }
  481. /**
  482. * Classify projects in modules, themes or unknown ones.
  483. *
  484. * @param $projects
  485. * Array of project names, by reference.
  486. * @param $modules
  487. * Empty array to be filled with modules in $projects.
  488. * @param $themes
  489. * Empty array to be filled with themes in $projects.
  490. */
  491. function drush_pm_classify_projects(&$projects, &$modules, &$themes, $project_info) {
  492. _drush_pm_expand_projects($projects, $project_info);
  493. foreach ($projects as $project) {
  494. if (!isset($project_info[$project])) {
  495. continue;
  496. }
  497. if ($project_info[$project]->type == 'module') {
  498. $modules[$project] = $project;
  499. }
  500. else if ($project_info[$project]->type == 'theme') {
  501. $themes[$project] = $project;
  502. }
  503. }
  504. }
  505. /**
  506. * Command callback. Show detailed info for one or more projects.
  507. */
  508. function drush_pm_info() {
  509. $args = func_get_args();
  510. $project_info = drush_pm_get_projects();
  511. _drush_pm_expand_projects($args, $project_info);
  512. foreach ($args as $project) {
  513. if (isset($project_info[$project])) {
  514. $info = $project_info[$project];
  515. }
  516. else {
  517. drush_set_error('DRUSH_PM_INFO_PROJECT_NOT_FOUND', dt('!project was not found.', array('!project' => $project)));
  518. continue;
  519. }
  520. if ($info->type == 'module') {
  521. $data = _drush_pm_info_module($info);
  522. }
  523. else {
  524. $data = _drush_pm_info_theme($info);
  525. }
  526. drush_print_table(drush_key_value_to_array_table($data));
  527. print "\n";
  528. }
  529. }
  530. /**
  531. * Return a string with general info of a project (module or theme).
  532. */
  533. function _drush_pm_info_project($info) {
  534. $major_version = drush_drupal_major_version();
  535. $data['Project'] = $info->name;
  536. $data['Type'] = $info->type;
  537. if (($info->type == 'module')||($major_version >= 6)) {
  538. $data['Title'] = $info->info['name'];
  539. $data['Description'] = $info->info['description'];
  540. $data['Version'] = $info->info['version'];
  541. }
  542. $data['Package'] = $info->info['package'];
  543. if ($major_version >= 6) {
  544. $data['Core'] = $info->info['core'];
  545. }
  546. if ($major_version == 6) {
  547. $data['PHP'] = $info->info['php'];
  548. }
  549. $data['Status'] = drush_get_project_status($info);
  550. $path = (($info->type == 'module')&&($major_version == 7))?$info->uri:$info->filename;
  551. $path = substr($path, 0, strrpos($path, '/'));
  552. $data['Path'] = $path;
  553. return $data;
  554. }
  555. /**
  556. * Return a string with info of a module.
  557. */
  558. function _drush_pm_info_module($info) {
  559. $major_version = drush_drupal_major_version();
  560. $data = _drush_pm_info_project($info);
  561. if ($info->schema_version > 0) {
  562. $schema_version = $info->schema_version;
  563. }
  564. elseif ($info->schema_version == -1) {
  565. $schema_version = "no schema installed";
  566. }
  567. else {
  568. $schema_version = "module has no schema";
  569. }
  570. $data['Schema version'] = $schema_version;
  571. if ($major_version == 7) {
  572. $data['Files'] = implode(', ', $info->info['files']);
  573. }
  574. if (count($info->info['dependencies']) > 0) {
  575. $requires = implode(', ', $info->info['dependencies']);
  576. }
  577. else {
  578. $requires = "none";
  579. }
  580. $data['Requires'] = $requires;
  581. if ($major_version == 6) {
  582. if (count($info->info['dependents']) > 0) {
  583. $requiredby = implode(', ', $info->info['dependents']);
  584. }
  585. else {
  586. $requiredby = "none";
  587. }
  588. $data['Required by'] = $requiredby;
  589. }
  590. return $data;
  591. }
  592. /**
  593. * Return a string with info of a theme.
  594. */
  595. function _drush_pm_info_theme($info) {
  596. $major_version = drush_drupal_major_version();
  597. $data = _drush_pm_info_project($info);
  598. if ($major_version == 5) {
  599. $data['Engine'] = $info->description;
  600. }
  601. else {
  602. $data['Core'] = $info->info['core'];
  603. $data['PHP'] = $info->info['php'];
  604. $data['Engine'] = $info->info['engine'];
  605. $data['Base theme'] = isset($info->base_themes) ? implode($info->base_themes, ', ') : '';
  606. $regions = implode(', ', $info->info['regions']);
  607. $data['Regions'] = $regions;
  608. $features = implode(', ', $info->info['features']);
  609. $data['Features'] = $features;
  610. if (count($info->info['stylesheets']) > 0) {
  611. $data['Stylesheets'] = '';
  612. foreach ($info->info['stylesheets'] as $media => $files) {
  613. $files = implode(', ', array_keys($files));
  614. $data['Media '.$media] = $files;
  615. }
  616. }
  617. if (count($info->info['scripts']) > 0) {
  618. $scripts = implode(', ', array_keys($info->info['scripts']));
  619. $data['Scripts'] = $scripts;
  620. }
  621. }
  622. return $data;
  623. }
  624. /**
  625. * Add sub projects that match project_name*.
  626. *
  627. * A helper function for commands that take a space separated list of project
  628. * names. It will identify project names that have been passed in with a
  629. * trailing * and add all matching projects to the array that is returned.
  630. *
  631. * @param $projects
  632. * An array of projects, by reference.
  633. * @param $project_info
  634. * Optional. An array of project info as returned by drush_get_projects().
  635. */
  636. function _drush_pm_expand_projects(&$projects, $project_info = array()) {
  637. if (empty($project_info)) {
  638. $project_info = drush_get_projects();
  639. }
  640. foreach ($projects as $key => $project) {
  641. if (($wildcard = rtrim($project, '*')) !== $project) {
  642. foreach (array_keys($project_info) as $project_name) {
  643. if (strpos($project_name, $wildcard) !== FALSE) {
  644. $projects[] = $project_name;
  645. }
  646. }
  647. unset($projects[$key]);
  648. continue;
  649. }
  650. }
  651. }
  652. /**
  653. * Command callback. Uninstall one or more modules.
  654. * // TODO: Use drupal_execute on system_modules_uninstall_confirm_form so that input is validated.
  655. */
  656. function drush_pm_uninstall() {
  657. $modules = func_get_args();
  658. drush_include_engine('drupal', 'environment');
  659. $module_info = drush_get_modules();
  660. // Discards modules which are enabled, not found or already uninstalled.
  661. foreach ($modules as $key => $module) {
  662. if (!isset($module_info[$module])) {
  663. // The module does not exist in the system.
  664. unset($modules[$key]);
  665. drush_set_error('DRUSH_PM_ENABLE_MODULE_NOT_FOUND', dt('Module !module was not found and will not be uninstalled.', array('!module' => $module)));
  666. }
  667. else if ($module_info[$module]->status) {
  668. // The module is enabled.
  669. unset($modules[$key]);
  670. drush_set_error('DRUSH_PM_UNINSTALL_ACTIVE_MODULE', dt('!module is not disabled. Use `pm-disable` command before `pm-uninstall`.', array('!module' => $module)));
  671. }
  672. else if ($module_info[$module]->schema_version == -1) { // SCHEMA_UNINSTALLED
  673. // The module is uninstalled.
  674. unset($modules[$key]);
  675. drush_log(dt('!module is already uninstalled.', array('!module' => $module)), 'ok');
  676. }
  677. }
  678. // Inform the user which modules will finally be uninstalled.
  679. if (empty($modules)) {
  680. return drush_log(dt('There were no modules that could be uninstalled.'), 'ok');
  681. }
  682. else {
  683. drush_print(dt('The following modules will be uninstalled: !modules', array('!modules' => implode(', ', $modules))));
  684. if(!drush_confirm(dt('Do you really want to continue?'))) {
  685. return drush_log(dt('Aborting.'));
  686. }
  687. }
  688. // Disable the modules.
  689. drush_module_uninstall($modules);
  690. // Inform the user of final status.
  691. foreach ($modules as $module) {
  692. drush_log(dt('!module was successfully uninstalled.', array('!module' => $module)), 'ok');
  693. }
  694. }
  695. /**
  696. * Array filter callback to return enabled modules.
  697. *
  698. * @param $module
  699. * A module object as returned by drush_get_modules().
  700. */
  701. function pm_is_enabled($module) {
  702. return $module->status;
  703. }
  704. /**
  705. * Callback helper.
  706. */
  707. function pm_true() {
  708. return TRUE;
  709. }
  710. /**
  711. * We need to set the project path by looking at the module location. Ideally, update.module would do this for us.
  712. *
  713. * TODO: Improve logic so this works even if your project directory is not
  714. * named the same as the project name.
  715. */
  716. function pm_get_project_path($projects, $lookup) {
  717. foreach ($projects as $name => $project) {
  718. if (!isset($project['path']) && $name != 'drupal') {
  719. // looks for an enabled module.
  720. foreach ($project[$lookup] as $filename => $title) {
  721. if ($path = drupal_get_path($project['project_type'], $filename)) {
  722. continue;
  723. }
  724. }
  725. // As some modules are not located in their project's root directory
  726. // but in a subdirectory (e.g. all the ecommerce modules), we take the module's
  727. // info file's path, and then move up until we are at a directory with the
  728. // project's name.
  729. $parts = explode('/', $path);
  730. $i = count($parts) - 1;
  731. $stop = array_search($name, $parts);
  732. while ($i > $stop) {
  733. unset($parts[$i]);
  734. $i--;
  735. }
  736. $projects[$name]['path'] = implode('/', $parts);
  737. }
  738. }
  739. return $projects;
  740. }
  741. /**
  742. * A drush command callback. Show release info for given project(s).
  743. *
  744. **/
  745. function drush_pm_releases() {
  746. // We don't provide for other options here, so we supply an explicit path.
  747. drush_include_engine('update_info', 'drupal', NULL, DRUSH_BASE_PATH . '/commands/pm/update_info');
  748. $projects = func_get_args();
  749. $projects = drupal_map_assoc($projects);
  750. $info = pm_get_project_info($projects);
  751. $project_info = drush_get_projects();
  752. $rows[] = array(dt('Project'), dt('Release'), dt('Date'), dt('Status'));
  753. foreach ($info as $key => $project) {
  754. $recommended = isset($project['recommended_major'])?$project['recommended_major']:NULL;
  755. $supported = isset($project['supported_majors'])?explode(',', $project['supported_majors']):array();
  756. $default = $project['default_major'];
  757. $recommended_version = NULL;
  758. $latest_version = NULL;
  759. foreach ($project['releases'] as $release) {
  760. if ($release['version_major'] == $recommended) {
  761. if (!isset($latest_version)) {
  762. $latest_version = $release['version'];
  763. }
  764. if (empty($release['version_extra'])) {
  765. if (!isset($recommended_version)) {
  766. $recommended_version = $release['version'];
  767. }
  768. }
  769. }
  770. }
  771. if (!isset($recommended_version)) {
  772. $recommended_version = $latest_version;
  773. }
  774. foreach ($project['releases'] as $release) {
  775. $status = array();
  776. $type = array();
  777. if (($k = array_search($release['version_major'], $supported)) !== FALSE) {
  778. $status[] = dt('Supported');
  779. unset($supported[$k]);
  780. }
  781. if ((isset($recommended_version)) && ($release['version'] == $recommended_version)) {
  782. $status[] = dt('Recommended');
  783. }
  784. if ($release['version_extra'] == 'dev') {
  785. $status[] = dt('Development');
  786. }
  787. if (isset($project_info[$key])) {
  788. if ($project_info[$key]->info['version'] == $release['version']) {
  789. $status[] = dt('Installed');
  790. }
  791. }
  792. if (isset($release['terms']) && array_key_exists('Release type', $release['terms'])) {
  793. foreach ($release['terms']['Release type'] as $one_type) {
  794. if ($one_type == 'Security update') {
  795. $status[] = dt('Security');
  796. }
  797. }
  798. }
  799. $rows[] = array(
  800. $key,
  801. $release['version'],
  802. format_date($release['date'], 'custom', 'Y-M-d'),
  803. implode(', ', $status)
  804. );
  805. }
  806. }
  807. if (count($rows) == 1) {
  808. return drush_set_error('DRUSH_PM_PROJECT_NOT_FOUND', dt('No information available.'));
  809. }
  810. else {
  811. return drush_print_table($rows, TRUE);
  812. }
  813. }
  814. /**
  815. * Command callback. Refresh update status information.
  816. */
  817. function drush_pm_refresh() {
  818. // We don't provide for other options here, so we supply an explicit path.
  819. drush_include_engine('update_info', 'drupal', NULL, DRUSH_BASE_PATH . '/commands/pm/update_info');
  820. _pm_refresh();
  821. }
  822. /**
  823. * Command callback. Execute updatecode.
  824. */
  825. function drush_pm_update() {
  826. // Signal that we will update drush core after the drush modules
  827. // are updated, if an update to core is available.
  828. drush_set_context('DRUSH_PM_UPDATE_ALL', TRUE);
  829. // Call pm-updatecode. updatedb will be called in the post-update process.
  830. $args = func_get_args();
  831. array_unshift($args, 'pm-updatecode');
  832. call_user_func_array('drush_invoke', $args);
  833. // pm-updatecode will not do a core update of Drupal
  834. // on the same invocation where non-core modules are
  835. // updated. If there is a core update available, then
  836. // call pm-updatecode a second time to update core
  837. // (but only if the first run finished successfully).
  838. if (drush_get_context('DRUSH_PM_CORE_UPDATE_AVAILABLE', FALSE) && (drush_get_error() == DRUSH_SUCCESS)) {
  839. call_user_func_array('drush_invoke', array('pm-updatecode', 'drupal'));
  840. }
  841. }
  842. /**
  843. * Post-command callback.
  844. * Execute updatedb command after an updatecode - user requested `update`.
  845. */
  846. function drush_pm_post_pm_update() {
  847. // Use drush_backend_invoke to start a subprocess. Cleaner that way.
  848. drush_backend_invoke('updatedb');
  849. }
  850. /**
  851. * Post-command callback for updatecode. Notify about any pending DB updates.
  852. */
  853. function drush_pm_post_pm_updatecode() {
  854. // Make sure the installation API is available
  855. require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc';
  856. // Load all .install files.
  857. drupal_load_updates();
  858. // @see system_requirements().
  859. foreach (module_list() as $module) {
  860. $updates = drupal_get_schema_versions($module);
  861. if ($updates !== FALSE) {
  862. $default = drupal_get_installed_schema_version($module);
  863. if (max($updates) > $default) {
  864. drush_log(dt("You have pending database updates. Please run `drush updatedb` or visit update.php in your browser."), 'warning');
  865. break;
  866. }
  867. }
  868. }
  869. }
  870. /**
  871. * Deletes a directory, all files in it and all subdirectories in it (recursively).
  872. * Use with care!
  873. * Written by Andreas Kalsch
  874. */
  875. function delete_dir($dir) {
  876. if (substr($dir, strlen($dir)-1, 1) != '/')
  877. $dir .= '/';
  878. if ($handle = opendir($dir)) {
  879. while ($obj = readdir($handle)) {
  880. if ($obj != '.' && $obj != '..') {
  881. if (is_dir($dir.$obj)) {
  882. if (!delete_dir($dir.$obj)) {
  883. return false;
  884. }
  885. }
  886. elseif (is_file($dir.$obj)) {
  887. if (!unlink($dir.$obj)) {
  888. return false;
  889. }
  890. }
  891. }
  892. }
  893. closedir($handle);
  894. if (!@rmdir($dir)) {
  895. }
  896. return true;
  897. }
  898. return false;
  899. }
  900. /**
  901. * Determine a candidate destination directory for a particular site path and
  902. * return it if it exists, optionally attempting to create the directory.
  903. */
  904. function pm_dl_destination_lookup($type, $drupal_root, $sitepath, $create = FALSE) {
  905. switch ($type) {
  906. case 'module':
  907. // Prefer sites/all/modules/contrib if it exists.
  908. $destination = $sitepath . 'modules/';
  909. $contrib = $destination . 'contrib/';
  910. if (is_dir($contrib)) {
  911. $destination = $contrib;
  912. }
  913. break;
  914. case 'theme':
  915. $destination = $sitepath . 'themes/';
  916. break;
  917. case 'theme engine':
  918. $destination = $sitepath . 'themes/engines/';
  919. break;
  920. case 'translation':
  921. $destination = $drupal_root . '/';
  922. break;
  923. case 'profile':
  924. $destination = $drupal_root . '/profiles/';
  925. break;
  926. }
  927. if ($create) {
  928. drush_log(dt('Attempting to create destination directory at !dir', array('!dir' => $destination)));
  929. @drush_op('mkdir', $destination, 0777, TRUE);
  930. }
  931. if (is_dir($destination)) {
  932. drush_log(dt('Using destination directory !dir', array('!dir' => $destination)));
  933. return $destination;
  934. }
  935. drush_log(dt('Could not find destination directory at !dir', array('!dir' => $destination)));
  936. return FALSE;
  937. }
  938. /**
  939. * Return the best destination for a particular download type we can find,
  940. * given the drupal and site contexts.
  941. */
  942. function pm_dl_destination($type) {
  943. // Attempt 0: Use the user specified destination directory, if it exists.
  944. $destination = drush_get_option('destination');
  945. if (!empty($destination)) {
  946. $destination = rtrim($destination, '/') . '/';
  947. if (!is_dir($destination)) {
  948. drush_print(dt("The directory !destination does not exist.", array('!destination' => $destination)));
  949. if (!drush_get_context('DRUSH_SIMULATE')) {
  950. if (drush_confirm(dt('Would you like to create it?'))) {
  951. @drush_op('mkdir', $destination, 0777, TRUE);
  952. }
  953. }
  954. }
  955. if (is_dir($destination)) {
  956. return $destination;
  957. }
  958. else {
  959. return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('The destination directory !destination does not appear to exist.', array('!destination' => $destination)));
  960. }
  961. }
  962. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  963. $site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT', FALSE);
  964. $full_site_root = $drupal_root .'/'. $site_root . '/';
  965. $sites_all = $drupal_root . '/sites/all/';
  966. $in_site_directory = FALSE;
  967. // Check if we are running within the site directory.
  968. if ($full_site_root == substr(drush_cwd() . '/', 0, strlen($full_site_root))) {
  969. $in_site_directory = TRUE;
  970. }
  971. // Attempt 1: If we are in a specific site directory, and the destination directory already exists, then we use that.
  972. if (empty($destination) && $site_root && $in_site_directory) {
  973. $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root);
  974. }
  975. // Attempt 2: If the destination directory already exists for sites/all, then we use that.
  976. if (empty($destination) && $drupal_root) {
  977. $destination = pm_dl_destination_lookup($type, $drupal_root, $sites_all);
  978. }
  979. // Attempt 3: If a specific (non default) site directory exists and sites/all does not exist, then we create destination in the site specific directory.
  980. if (empty($destination) && $site_root && $site_root !== 'sites/default' && is_dir($full_site_root) && !is_dir($sites_all)) {
  981. $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root, TRUE);
  982. }
  983. // Attempt 4: If sites/all exists, then we create destination in the sites/all directory.
  984. if (empty($destination) && is_dir($sites_all)) {
  985. $destination = pm_dl_destination_lookup($type, $drupal_root, $sites_all, TRUE);
  986. }
  987. // Attempt 5: If site directory exists (even default), then we create destination in the this directory.
  988. if (empty($destination) && $site_root && is_dir($full_site_root)) {
  989. $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root, TRUE);
  990. }
  991. // Attempt 6: If we didn't find a valid directory yet (or we somehow found one that doesn't exist) we always fall back to the current directory.
  992. if (empty($destination) || !is_dir($destination)) {
  993. $destination = drush_cwd() . '/';
  994. }
  995. return $destination;
  996. }
  997. /**
  998. * Parse out the project name and version and return as a structured array
  999. *
  1000. * @param $requests an array of project names
  1001. */
  1002. function pm_parse_project_version($requests) {
  1003. $requestdata = array();
  1004. $drupal_version_default = drush_get_context('DRUSH_DRUPAL_MAJOR_VERSION', 6) . '.x';
  1005. // ignore-bootstrap is a temporary hack. You can't currently download a 7.x
  1006. // module after bootstrapping a 6.x site. This is needed by core-upgrade.
  1007. // Related to http://drupal.org/node/463110.
  1008. $drupal_bootstrap = drush_get_option('bootstrap_cancel') ? !drush_get_option('bootstrap_cancel') : drush_get_context('DRUSH_BOOTSTRAP_PHASE') > 0;
  1009. foreach($requests as $request) {
  1010. $drupal_version = $drupal_version_default;
  1011. $project_version = NULL;
  1012. $version = NULL;
  1013. $project = $request;
  1014. // project-HEAD or project-5.x-1.0-beta
  1015. // '5.x-' is optional, as is '-beta'
  1016. preg_match('/-+(HEAD|(?:(\d+\.x)-+)?(\d+\.[\dx]+.*))$/', $request, $matches);
  1017. if (isset($matches[1])) {
  1018. // The project is whatever we have prior to the version part of the request.
  1019. $project = trim(substr($request, 0, strlen($request) - strlen($matches[0])), ' -');
  1020. if ($matches[1] == 'HEAD' || $matches[2] == 'HEAD') {
  1021. drush_set_error('DRUSH_PM_HEAD', 'Can\'t download HEAD releases because Drupal.org project information only provides for numbered release nodes.');
  1022. continue;
  1023. }
  1024. if (!empty($matches[2])) {
  1025. // We have a specified Drupal core version.
  1026. $drupal_version = trim($matches[2], '-.');
  1027. }
  1028. if (!empty($matches[3])) {
  1029. if (!$drupal_bootstrap && empty($matches[2]) && $project != 'drupal') {
  1030. // We are not working on a bootstrapped site, and the project is not Drupal itself,
  1031. // so we assume this value is the Drupal core version and we want the stable project.
  1032. $drupal_version = trim($matches[3], '-.');
  1033. }
  1034. else {
  1035. // We are working on a bootstrapped site, or the user specified a Drupal version,
  1036. // so this value must be a specified project version.
  1037. $project_version = trim($matches[3], '-.');
  1038. if (substr($project_version, -1, 1) == 'x') {
  1039. // If a dev branch was requested, we add a -dev suffix.
  1040. $project_version .= '-dev';
  1041. }
  1042. }
  1043. }
  1044. }
  1045. if ($project_version) {
  1046. if ($project == 'drupal') {
  1047. // For project Drupal, ensure the major version branch is correct, so
  1048. // we can locate the requested or stable release for that branch.
  1049. $project_version_array = explode('.', $project_version);
  1050. $drupal_version = $project_version_array[0] . '.x';
  1051. // We use the project version only, since it is core.
  1052. $version = $project_version;
  1053. }
  1054. else {
  1055. // For regular projects the version string includes the Drupal core version.
  1056. $version = $drupal_version . '-' . $project_version;
  1057. }
  1058. }
  1059. $requestdata[$project] = array(
  1060. 'name' => $project,
  1061. 'version' => $version,
  1062. 'drupal_version' => $drupal_version,
  1063. 'project_version' => $project_version,
  1064. );
  1065. }
  1066. return $requestdata;
  1067. }
  1068. function pm_project_types() {
  1069. // Lookup the 'Project type' vocabulary to some standard strings.
  1070. $types = array(
  1071. 'core' => 'Drupal project',
  1072. 'profile' => 'Installation profiles',
  1073. 'module' => 'Modules',
  1074. 'theme' => 'Themes',
  1075. 'theme engine' => 'Theme engines',
  1076. 'translation' => 'Translations',
  1077. );
  1078. return $types;
  1079. }
  1080. /**
  1081. * Used by dl and updatecode commands to determine how to download/checkout new projects and acquire updates to projects.
  1082. */
  1083. function pm_drush_engine_package_handler() {
  1084. return array(
  1085. 'wget' => array(),
  1086. 'cvs' => array(
  1087. 'options' => array(
  1088. '--package-handler=cvs' => 'Use CVS to checkout and update projects.',
  1089. ' --cvsparams' => 'Add options to the `cvs` program',
  1090. ' --cvsmethod' => 'Force cvs updates or checkouts (checkout is default unless the directory is managed by a supported version control system).',
  1091. ' --cvscredentials' => 'A username and password that is sent for cvs checkout command. Defaults to anonymous:anonymous',
  1092. ),
  1093. 'examples' => array(
  1094. 'drush [command] cck --cvscredentials=\"name:password\"' => 'Checkout should use these credentials.',
  1095. 'drush [command] cck --cvsparams=\"-C\"' => 'Overwrite all local changes (Quotes are required).',
  1096. 'drush [command] cck --cvsmethod=update' => 'Will update the project, and try to merge changes, rather than overwriting them. Any conflicts will need to be resolved manually.',
  1097. ),
  1098. ),
  1099. );
  1100. }
  1101. /**
  1102. * Integration with VCS in order to easily commit your changes to projects.
  1103. */
  1104. function pm_drush_engine_version_control() {
  1105. return array(
  1106. 'svn' => array(
  1107. 'signature' => 'svn info %s',
  1108. 'options' => array(
  1109. '--version-control=svn' => 'Quickly add/remove/commit your project changes to Subversion.',
  1110. ' --svnsync' => 'Automatically add new files to the SVN repository and remove deleted files. Caution.',
  1111. ' --svncommit' => 'Automatically commit changes to SVN repository. You must also using the --svnsync option.',
  1112. ' --svnmessage' => 'Override default commit message which is: Drush automatic commit: <the drush command line used>',
  1113. ' --svnstatusparams' => "Add options to the 'svn status' command",
  1114. ' --svnaddparams' => 'Add options to the `svn add` command',
  1115. ' --svnremoveparams' => 'Add options to the `svn remove` command',
  1116. ' --svnrevertparams' => 'Add options to the `svn revert` command',
  1117. ' --svncommitparams' => 'Add options to the `svn commit` command',
  1118. ),
  1119. 'examples' => array(
  1120. 'drush [command] cck --svncommitparams=\"--username joe\"' => 'Commit changes as the user \'joe\' (Quotes are required).'
  1121. ),
  1122. ),
  1123. 'backup' => array(
  1124. 'options' => array(
  1125. '--version-control=backup' => 'Backup all project files before updates.',
  1126. ' --backup-dir' => 'Backup destination directory. Defaults to a "/backup" subdirectory inside your Drupal root.',
  1127. ),
  1128. ),
  1129. 'bzr' => array(
  1130. 'signature' => 'bzr root %s',
  1131. 'options' => array(
  1132. '--version-control=bzr' => 'Quickly add/remove/commit your project changes to Bazaar.',
  1133. ' --bzrsync' => 'Automatically add new files to the Bazaar repository and remove deleted files. Caution.',
  1134. ' --bzrcommit' => 'Automatically commit changes to Bazaar repository. You must also using the --bzrsync option.',
  1135. ' --bzrmessage' => 'Override default commit message which is: Drush automatic commit: <the drush command line used>',
  1136. ),
  1137. ),
  1138. );
  1139. }
  1140. /**
  1141. * Interface for version control systems.
  1142. * We use a simple object layer because we conceivably need more than one
  1143. * loaded at a time.
  1144. */
  1145. interface drush_pm_version_control {
  1146. function pre_update(&$project);
  1147. function rollback($project);
  1148. function post_update($project);
  1149. function post_download($project);
  1150. }
  1151. /**
  1152. * A simple factory function that tests for version control systems, in a user
  1153. * specified order, and return the one that appears to be appropriate for a
  1154. * specific directory.
  1155. */
  1156. function drush_pm_include_version_control($directory = '.') {
  1157. $version_controls = explode(',', drush_get_option('version-control', 'svn,backup'));
  1158. $version_control_engines = drush_get_engines('version_control');
  1159. // Find the first valid engine in the list, checking signatures if needed.
  1160. $engine = FALSE;
  1161. while (!$engine && count($version_controls)) {
  1162. $version_control = array_shift($version_controls);
  1163. if (isset($version_control_engines[$version_control])) {
  1164. if (!empty($version_control_engines[$version_control]['signature'])) {
  1165. if (drush_shell_exec($version_control_engines[$version_control]['signature'], $directory)) {
  1166. $engine = $version_control;
  1167. }
  1168. }
  1169. else {
  1170. $engine = $version_control;
  1171. }
  1172. }
  1173. }
  1174. if (!$engine) {
  1175. 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)));
  1176. }
  1177. if (!drush_include_engine('version_control', $engine)) {
  1178. return FALSE;
  1179. }
  1180. $engine = 'drush_pm_version_control_' . $engine;
  1181. return new $engine();
  1182. }
  1183. /**
  1184. * Command callback. Download Drupal core or any project.
  1185. */
  1186. function drush_pm_download() {
  1187. // Bootstrap to the highest level possible.
  1188. drush_bootstrap_max();
  1189. drush_include_engine('package_handler', drush_get_option('package-handler', 'wget'));
  1190. if (!$requests = func_get_args()) {
  1191. $requests = array('drupal');
  1192. }
  1193. // Parse out project name and version
  1194. $requests = pm_parse_project_version($requests);
  1195. $project_types = pm_project_types();
  1196. $project_types_xpath = '(value="' . implode('" or value="', $project_types) . '")';
  1197. foreach ($requests as $name => $request) {
  1198. // Don't rely on UPDATE_DEFAULT_URL since we are not fully bootstrapped.
  1199. $url = drush_get_option('source', 'http://updates.drupal.org/release-history') . '/' . $request['name'] . '/' . $request['drupal_version'];
  1200. drush_log('Downloading release history from ' . $url);
  1201. // A simple download, which is never available via CVS.
  1202. // Some hosts have allow_url_fopen disabled.
  1203. if (!$xml = @simplexml_load_file($url)) {
  1204. if (!drush_shell_exec("wget $url")) {
  1205. drush_shell_exec("curl -O $url");
  1206. }
  1207. // Get the filename...
  1208. $filename = explode('/', $url);
  1209. $filename = array_pop($filename);
  1210. $xml = simplexml_load_file($filename);
  1211. drush_op('unlink', $filename);
  1212. }
  1213. if ($xml) {
  1214. if ($error = $xml->xpath('/error')) {
  1215. drush_set_error('DRUSH_PM_COULD_NOT_LOAD_UPDATE_FILE', $error[0]);
  1216. }
  1217. else {
  1218. // Try to get the specified release.
  1219. if (!empty($request['version'])) {
  1220. $releases = $xml->xpath("/project/releases/release[status='published'][version='" . $request['version'] . "']");
  1221. if (empty($releases)) {
  1222. drush_log(dt("Could not locate specified project version, downloading latest stable version"), 'notice');
  1223. }
  1224. }
  1225. // If that did not work, get the first published release for the recommended major version.
  1226. if (empty($releases)) {
  1227. if ($recommended_major = $xml->xpath("/project/recommended_major")) {
  1228. $xpath_releases = "/project/releases/release[status='published'][version_major=" . (string)$recommended_major[0] . "]";
  1229. $releases = @$xml->xpath($xpath_releases);
  1230. }
  1231. }
  1232. // If there are recommended releases (no 'version_extra' elements),
  1233. // then use only recommended releases. Otherwise, use all; in
  1234. // this case, the recommended release defaults to the latest published
  1235. // release with the right recommended major version number.
  1236. $recommended_releases = array();
  1237. if (!empty($releases)) {
  1238. foreach ($releases as $one_release) {
  1239. if (!array_key_exists('version_extra', $one_release)) {
  1240. $recommended_releases[] = $one_release;
  1241. }
  1242. }
  1243. }
  1244. if (!empty($recommended_releases)) {
  1245. $releases = $recommended_releases;
  1246. }
  1247. if (empty($releases)) {
  1248. drush_log(dt('There is no *recommended* release for project !project on Drupal !drupal_version. Ask the maintainer to review http://drupal.org/node/197584 and create/recommend a release in order to be compatible with drush and the drupal.org security broadcast system. A recommended development snapshot release is sufficient. Alternatively, run pm-releases command and explicity pm-download any non-recommended release that might be available.', array('!drupal_version' => $request['drupal_version'], '!project' => $request['name'])), 'ok');
  1249. continue;
  1250. }
  1251. // Profiles have 3 variants for a given real relase. Admin can specify using option.
  1252. // Depends on a fixed order of variations in releases list.
  1253. // Usually, the first release is chosen.
  1254. $variations = array('core', 'no-core', 'make');
  1255. $variant = drush_get_option('variant', 'core');
  1256. $release = (array)$releases[array_search($variant, $variations)];
  1257. // Determine what type of project we have, so we know where to put it.
  1258. $release['type'] = 'module';
  1259. if ($types = $xml->xpath('/project/terms/term[name="Projects" and ' . $project_types_xpath . ']')) {
  1260. $release['type'] = array_search($types[0]->value, $project_types);
  1261. }
  1262. // Create basic project array.
  1263. $project = $request;
  1264. if ($project['base_project_path'] = pm_dl_destination($release['type'])) {
  1265. $project['full_project_path'] = $project['base_project_path'] . $request['name'];
  1266. $project['project_type'] = $release['type'];
  1267. if (!$version_control = drush_pm_include_version_control($project['base_project_path'])) {
  1268. return FALSE;
  1269. }
  1270. if (package_handler_install_project($project, $release)) {
  1271. // If the --destination option was not specified, then
  1272. // allow commandfiles that implement the adjust-download-destination
  1273. // hook to pick a new default location for the project.
  1274. if (drush_get_option('destination', FALSE) === FALSE) {
  1275. drush_pm_relocate_project($project, $release);
  1276. }
  1277. drush_log(dt("Project !project (!version) downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $project['full_project_path'])), 'success');
  1278. drush_command_invoke_all('drush_pm_post_download', $project, $release);
  1279. $version_control->post_download($project);
  1280. }
  1281. }
  1282. }
  1283. }
  1284. else {
  1285. // We are not getting here since drupal.org always serves an XML response.
  1286. drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Could not download project status information from !url', array('!url' => $url)));
  1287. }
  1288. unset($error, $release, $releases, $types);
  1289. }
  1290. }
  1291. /**
  1292. * drush_pm_relocate_project moves projects that should be relocated to a different
  1293. * installation directory to the location they belong in. For example,
  1294. * modules that are only collections of drush commands will be installed
  1295. * to $HOME/.drush.
  1296. *
  1297. * This function is called after the project is downloaded so that its
  1298. * contents can be examined to determine its optimal installation location.
  1299. * Every drush commandfile is given a chance to examine the project contents
  1300. * and decide where the project should be located.
  1301. */
  1302. function drush_pm_relocate_project(&$project, $release) {
  1303. // Call the get-install-location hook to see if any drush commandfiles
  1304. // would like to adjust the install location for this project.
  1305. // drush_command_invoke_all() allows the first parameter to be passed by reference.
  1306. drush_command_invoke_all_ref('drush_pm_adjust_download_destination', $project, $release);
  1307. if (isset($project['project_install_location']) && ($project['full_project_path'] != $project['project_install_location'])) {
  1308. if (drush_move_dir($project['full_project_path'], $project['project_install_location'], TRUE)) {
  1309. $project['full_project_path'] = $project['project_install_location'];
  1310. }
  1311. else {
  1312. drush_log(dt("Project !project (!version) could not be relocated to !dest.", array('!project' => $project['name'], '!version' => $release['version'], '!dest' => $project['project_install_location'])), 'warning');
  1313. }
  1314. }
  1315. return TRUE;
  1316. }
  1317. /**
  1318. * Built-in adjust-download-destination hook. This particular version of
  1319. * the hook will move modules that contain only drush commands to
  1320. * /usr/share/drush/commands if it exists, or $HOME/.drush if the
  1321. * site-wide location does not exist.
  1322. */
  1323. function pm_drush_pm_adjust_download_destination(&$project, $release) {
  1324. // If this project is a module, but it has no .module file, then
  1325. // check to see if it contains drush commands. If that is all
  1326. // that it contains, then install it to $HOME/.drush.
  1327. if ($release['type'] == 'module') {
  1328. $module_files = drush_scan_directory($project['full_project_path'], '/.*\.module/');
  1329. if (empty($module_files)) {
  1330. $drush_command_files = drush_scan_directory($project['full_project_path'], '/.*\.drush.inc/');
  1331. if (!empty($drush_command_files)) {
  1332. $install_dir = drush_get_context('SHARE_PREFIX', '/usr') . '/share/drush/commands/';
  1333. if (!is_dir($install_dir)) {
  1334. $install_dir = drush_server_home() . '/.drush/';
  1335. }
  1336. // Make the .drush dir if it does not already exist
  1337. if (!is_dir($install_dir)) {
  1338. mkdir($install_dir);
  1339. }
  1340. // Change the location if the mkdir worked
  1341. if (is_dir($install_dir)) {
  1342. $project['project_install_location'] = $install_dir . basename($project['full_project_path']);
  1343. }
  1344. }
  1345. }
  1346. }
  1347. }