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 Project Manager

Terminology:

  • Request: a requested project (string or keyed array), with a name and (optionally) version.
  • Project: a drupal.org project (i.e drupal.org/project/*), such as cck or zen.
  • Extension: a drupal.org module, theme or profile.
  • 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).

Functions

Namesort descending Description
drush_find_empty_directories Return an array of empty directories.
drush_get_extension_status Calculate a extension status based on current status and schema version.
drush_get_projects Obtain an array of installed projects off the extensions available.
drush_pm_cache_project_extensions
drush_pm_classify_extensions Classify extensions as modules, themes or unknown.
drush_pm_disable Command callback. Disable one or more extensions.
drush_pm_download Command callback. Download Drupal core or any project.
drush_pm_download_validate Implementation of drush_COMMAND_validate().
drush_pm_enable Command callback. Enable one or more extensions from downloaded projects. Note that the modules and themes to be enabled were evaluated during the pm-enable validate hook, above.
drush_pm_enable_validate Validate callback. Determine the modules and themes that the user would like enabled.
drush_pm_extensions_in_project Print out all extensions (modules/themes/profiles) found in specified project.
drush_pm_find_project_from_extension
drush_pm_get_extensions Wrapper of drupal_get_extensions() 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 extension.
drush_pm_list Command callback. Show a list of extensions with type and status.
drush_pm_lookup_extension_in_cache
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.
drush_pm_put_extension_cache
drush_pm_refresh Command callback. Refresh update status information.
drush_pm_releasenotes Command callback. Show release notes for given project(s).
drush_pm_releases Command callback. Show available releases for given project(s).
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 pm-update.
drush_pm_updatecode_postupdate Command callback. Execute updatecode-postupdate.
drush_pm_updatecode_validate Validate callback for updatecode command. Abort if 'backup' directory exists.
drush_pm_update_lock Update the locked status of all of the candidate projects to be updated.
pm_dl_destination Returns the best destination for a particular download type we can find.
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_download_destination_alter Implementation of hook_drush_pm_download_destination_alter().
pm_module_list Returns a list of enabled modules.
pm_parse_project_version Parse out the project name and version and return as a structured array
pm_parse_release
pm_project_types
_drush_pm_compare_date Helper function for _drush_pm_filter_releases().
_drush_pm_download_releases_choice Return an array of available releases for given project(s).
_drush_pm_expand_extensions Add extensions that match extension_name*.
_drush_pm_extension_cache_file
_drush_pm_filter_releases Filter a list of releases.
_drush_pm_get_extension_cache
_drush_pm_get_releases Obtain releases info for given projects and fill in status information.
_drush_pm_get_releases_from_xml Obtain releases for a project's xml as returned by the update service.
_drush_pm_get_release_history_xml Download the release history xml for the specified request.
_drush_pm_info_extension Return a string with general info of a extension.
_drush_pm_info_module Return a string with info of a module.
_drush_pm_info_theme Return a string with info of a theme.
_drush_pm_releasenotes Internal function: prints release notes for given drupal projects.
_drush_pm_sort_extensions Sort callback function for sorting extensions.
_pm_find_common_path Helper function to find the common path for a list of extensions in the aim to obtain the project name.
_pm_get_project_path Completes projects' update data with the path to install location on disk.

Constants

Namesort descending Description
DRUSH_PM_REQUESTED_CURRENT User requested version already installed.
DRUSH_PM_REQUESTED_PROJECT_NOT_FOUND User requested project not found.
DRUSH_PM_REQUESTED_PROJECT_NOT_PACKAGED User requested project was not packaged by drupal.org.
DRUSH_PM_REQUESTED_PROJECT_NOT_UPDATEABLE User requested project not updateable.
DRUSH_PM_REQUESTED_UPDATE Project is a user requested version update.
DRUSH_PM_REQUESTED_VERSION_NOT_FOUND User requested version not found.

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 Project Manager
  5. *
  6. * Terminology:
  7. * - Request: a requested project (string or keyed array), with a name and (optionally) version.
  8. * - Project: a drupal.org project (i.e drupal.org/project/*), such as cck or zen.
  9. * - Extension: a drupal.org module, theme or profile.
  10. * - Version: a requested version, such as 1.0 or 1.x-dev.
  11. * - Release: a specific release of a project, with associated metadata (from the drupal.org update service).
  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 project was not packaged by drupal.org.
  23. */
  24. define('DRUSH_PM_REQUESTED_PROJECT_NOT_PACKAGED', 103);
  25. /**
  26. * User requested version not found.
  27. */
  28. define('DRUSH_PM_REQUESTED_VERSION_NOT_FOUND', 104);
  29. /**
  30. * User requested project not found.
  31. */
  32. define('DRUSH_PM_REQUESTED_PROJECT_NOT_FOUND', 105);
  33. /**
  34. * User requested project not updateable.
  35. */
  36. define('DRUSH_PM_REQUESTED_PROJECT_NOT_UPDATEABLE', 106);
  37. /**
  38. * Implementation of hook_drush_help().
  39. */
  40. function pm_drush_help($section) {
  41. switch ($section) {
  42. case 'meta:pm:title':
  43. return dt('Project manager commands');
  44. case 'meta:pm:summary':
  45. return dt('Download, enable, examine and update your modules and themes.');
  46. case 'drush:pm-enable':
  47. return dt('Enable one or more extensions (modules or themes). Enable dependant extensions as well.');
  48. case 'drush:pm-disable':
  49. return dt('Disable one or more extensions (modules or themes). Disable dependant extensions as well.');
  50. case 'drush:pm-updatecode':
  51. case 'drush:pm-update':
  52. $message = dt("Display available update information for Drupal core and all enabled projects and allow updating to latest recommended releases.");
  53. if ($section == 'drush:pm-update') {
  54. $message .= ' '.dt("Also apply any database updates required (same as pm-updatecode + updatedb).");
  55. }
  56. $message .= ' '.dt("Note: The user is asked to confirm before the actual update. Backups are performed unless directory is already under version control. Updated projects can potentially break your site. It is NOT recommended to update production sites without prior testing.");
  57. return $message;
  58. case 'drush:pm-updatecode-postupdate':
  59. return dt("This is a helper command needed by updatecode. It is used to check for db updates in a backend process after code updated have been performed. We need to run this task in a separate process to not conflict with old code already in memory.");
  60. case 'drush:pm-releases':
  61. return dt("View all releases for a given drupal.org project. Useful for deciding which version to install/update.");
  62. case 'drush:pm-download':
  63. return dt("Download Drupal core or projects from drupal.org (Drupal core, modules, themes or profiles) and other sources. It will automatically figure out which project version you want based on its recommended release, or you may specify a particular version.
  64. If no --destination is provided, then destination depends on the project type:
  65. - Profiles will be downloaded to profiles/ in your Drupal root.
  66. - Modules and themes will be downloaded to the site specific directory (sites/example.com/modules|themes) if available, or to sites/all/modules|themes.
  67. - If you're downloading drupal core or you are not running the command within a bootstrapped drupal site, the default location is the current directory.
  68. - Drush commands will be relocated to /usr/share/drush/commands (if available) or ~/.drush. Relocation is determined once the project is downloaded by examining its content. Note you can provide your own function in a commandfile to determine the relocation of any project.");
  69. }
  70. }
  71. /**
  72. * Implementation of hook_drush_command().
  73. */
  74. function pm_drush_command() {
  75. $update = 'update';
  76. if (drush_drupal_major_version() == 5) {
  77. $update = 'update_status';
  78. }
  79. $engines = array(
  80. 'engines' => array(
  81. 'version_control' => 'Integration with VCS in order to easily commit your changes to projects.',
  82. 'package_handler' => 'Determine how to download/checkout new projects and acquire updates to projects.',
  83. ),
  84. );
  85. $update_options = array(
  86. 'security-only' => 'Only update modules that have security updates available. However, if there were other releases of a module between the installed version the security update, other changes to features or functionality may occur.',
  87. 'lock' => 'Add a persistent lock to remove the specified projects from consideration during updates. Locks may be removed with the --unlock parameter, or overridden by specifically naming the project as a parameter to pm-update or pm-updatecode. The lock does not affect pm-download. See also the update-advanced project for similar and improved functionality.',
  88. );
  89. $update_suboptions = array(
  90. 'lock' => array(
  91. 'lock-message' => 'A brief message explaining why a project is being locked; displayed during pm-updatecode. Optional.',
  92. 'unlock' => 'Remove the persistent lock from the specified projects so that they may be updated again.',
  93. ),
  94. );
  95. $items['pm-enable'] = array(
  96. 'description' => 'Enable one or more extensions (modules or themes).',
  97. 'arguments' => array(
  98. 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to enable all matches.',
  99. ),
  100. 'aliases' => array('en'),
  101. 'deprecated-aliases' => array('enable'),
  102. );
  103. $items['pm-disable'] = array(
  104. 'description' => 'Disable one or more extensions (modules or themes).',
  105. 'arguments' => array(
  106. 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to disable multiple matches.',
  107. ),
  108. 'aliases' => array('dis'),
  109. 'deprecated-aliases' => array('disable'),
  110. );
  111. $items['pm-info'] = array(
  112. 'description' => 'Show detailed info for one or more extensions (modules or themes).',
  113. 'arguments' => array(
  114. 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to show info for multiple matches. If no argument is provided it will show info for all available extensions.',
  115. ),
  116. 'aliases' => array('pmi'),
  117. );
  118. // Install command is reserved for the download and enable of projects including dependencies.
  119. // @see http://drupal.org/node/112692 for more information.
  120. // $items['install'] = array(
  121. // 'description' => 'Download and enable one or more modules',
  122. // );
  123. $items['pm-uninstall'] = array(
  124. 'description' => 'Uninstall one or more modules.',
  125. 'arguments' => array(
  126. 'modules' => 'A list of modules.',
  127. ),
  128. 'deprecated-aliases' => array('uninstall'),
  129. );
  130. $items['pm-list'] = array(
  131. 'description' => 'Show a list of available extensions (modules and themes).',
  132. 'callback arguments' => array(array(), FALSE),
  133. 'options' => array(
  134. 'type' => 'Filter by extension type. Choices: module, theme.',
  135. 'status' => 'Filter by extension status. Choices: enabled, disable and/or \'not installed\'. You can use multiple comma separated values. (i.e. --status="disabled,not installed").',
  136. 'package' => 'Filter by project packages. You can use multiple comma separated values. (i.e. --package="Core - required,Other").',
  137. 'core' => 'Filter out extensions that are not in drupal core.',
  138. 'no-core' => 'Filter out extensions that are provided by drupal core.',
  139. 'pipe' => 'Returns a whitespace delimited list of the names of the resulting extensions.',
  140. ),
  141. 'aliases' => array('pml'),
  142. 'deprecated-aliases' => array('sm'),
  143. );
  144. $items['pm-refresh'] = array(
  145. 'description' => 'Refresh update status information.',
  146. 'drupal dependencies' => array($update),
  147. 'aliases' => array('rf'),
  148. 'deprecated-aliases' => array('refresh'),
  149. );
  150. $items['pm-updatecode'] = array(
  151. 'description' => 'Update Drupal core and contrib projects to latest recommended releases.',
  152. 'drupal dependencies' => array($update),
  153. 'arguments' => array(
  154. 'projects' => 'Optional. A list of installed projects to update.',
  155. ),
  156. 'options' => array(
  157. 'pipe' => 'Returns a whitespace delimited list of projects with any of its extensions enabled and their respective version and update information, one project per line. Order: project name, current version, recommended version, update status.',
  158. 'notes' => 'Show release notes for each project to be updated.',
  159. 'no-core' => 'Only update modules and skip the core update.',
  160. 'self-update' => 'Check for pending updates to drush itself. Set to 0 to avoid check.',
  161. ) + $update_options,
  162. 'sub-options' => $update_suboptions,
  163. 'aliases' => array('upc'),
  164. 'deprecated-aliases' => array('updatecode'),
  165. 'topics' => array('docs-policy'),
  166. ) + $engines;
  167. // Merge all items from above.
  168. $items['pm-update'] = array_merge($items['pm-updatecode'], array(
  169. 'description' => 'Update Drupal core and contrib projects and apply any pending database updates (Same as pm-updatecode + updatedb).',
  170. 'aliases' => array('up'),
  171. 'deprecated-aliases' => array('update'),
  172. ));
  173. $items['pm-updatecode-postupdate'] = array(
  174. 'description' => 'Notify of pending db updates.',
  175. 'hidden' => TRUE
  176. );
  177. $items['pm-releasenotes'] = array(
  178. 'description' => 'Print release notes for given projects.',
  179. 'arguments' => array(
  180. 'projects' => 'A list of drupal.org project names, with optional version. Defaults to \'drupal\'',
  181. ),
  182. 'options' => array(
  183. 'html' => dt('Display releasenotes in HTML rather than plain text.'),
  184. ),
  185. 'examples' => array(
  186. 'drush rln cck' => 'Prints the release notes for the recommended version of CCK project.',
  187. 'drush rln token-1.13' => 'View release notes of a specfic version of the Token project for my version of Drupal.',
  188. 'drush rln pathauto zen' => 'View release notes for the recommended version of Pathauto and Zen projects.',
  189. ),
  190. 'aliases' => array('rln'),
  191. 'bootstrap' => DRUSH_BOOTSTRAP_MAX,
  192. );
  193. $items['pm-releases'] = array(
  194. 'description' => 'Print release information for given projects.',
  195. 'arguments' => array(
  196. 'projects' => 'A list of drupal.org project names. Defaults to \'drupal\'',
  197. ),
  198. 'options' => array(
  199. 'dev' => "Show only development releases.",
  200. 'all' => "Shows all available releases instead of the default short list of recent releases.",
  201. ),
  202. 'examples' => array(
  203. 'drush pm-releases cck zen' => 'View releases for cck and Zen projects for your Drupal version.',
  204. ),
  205. 'aliases' => array('rl'),
  206. 'bootstrap' => DRUSH_BOOTSTRAP_MAX,
  207. );
  208. $items['pm-download'] = array(
  209. 'description' => 'Download projects from drupal.org or other sources.',
  210. 'examples' => array(
  211. 'drush dl' => 'Download latest recommended release of Drupal core.',
  212. 'drush dl drupal' => 'Same as `drush dl`.',
  213. 'drush dl drupal-7.x' => 'Download latest 7.x development version of Drupal core.',
  214. 'drush dl drupal-6' => 'Download latest recommended release of Drupal 6.x.',
  215. 'drush dl cck zen' => 'Download latest versions of CCK and Zen projects.',
  216. 'drush dl og-1.3' => 'Download a specfic version of Organic groups module for my version of Drupal.',
  217. 'drush dl diff-6.x-2.x' => 'Download a specific development branch of diff module for a specific Drupal version.',
  218. 'drush dl views --select' => 'Show a list of recent releases of the views project, prompt for which one to download.',
  219. 'drush dl webform --dev' => 'Download the latest dev release of webform.',
  220. ),
  221. 'arguments' => array(
  222. 'projects' => 'A comma delimited list of drupal.org project names, with optional version. Defaults to \'drupal\'',
  223. ),
  224. 'options' => array(
  225. 'destination' => 'Path to which the project will be copied. If you\'re providing a relative path, note it is relative to the drupal root (if bootstrapped).',
  226. 'use-site-dir' => 'Force to use the site specific directory. It will create the directory if it doesn\'t exist. If --destination is also present this option will be ignored.',
  227. 'source' => 'The base URL which provides project release history in XML. Defaults to http://updates.drupal.org/release-history.',
  228. 'notes' => 'Show release notes after each project is downloaded.',
  229. 'variant' => "Only useful for install profiles. Possible values: 'full', 'projects', 'profile-only'.",
  230. 'dev' => "Download a development release.",
  231. 'select' => "Select the version to download interactively from a list of available releases.",
  232. 'all' => "Useful only with --select; shows all available releases instead of a short list of recent releases.",
  233. 'drupal-project-rename' => 'Alternate name for "drupal-x.y" directory when downloading Drupal project. Defaults to "drupal".',
  234. 'default-major' => 'Specify the default major version of modules to download when there is no bootstrapped Drupal site. Defaults to "7".',
  235. 'pipe' => 'Returns a list of the names of the extensions (modules and themes) contained in the downloaded projects.',
  236. ),
  237. 'bootstrap' => DRUSH_BOOTSTRAP_MAX,
  238. 'aliases' => array('dl'),
  239. 'deprecated-aliases' => array('download'),
  240. ) + $engines;
  241. return $items;
  242. }
  243. /**
  244. * @defgroup extensions Extensions management.
  245. * @{
  246. * Functions to manage extensions.
  247. */
  248. /**
  249. * Sort callback function for sorting extensions.
  250. *
  251. * It will sort first by type, second by package and third by name.
  252. */
  253. function _drush_pm_sort_extensions($a, $b) {
  254. if ($a->type == 'module' && $b->type == 'theme') {
  255. return -1;
  256. }
  257. if ($a->type == 'theme' && $b->type == 'module') {
  258. return 1;
  259. }
  260. $cmp = strcasecmp($a->info['package'], $b->info['package']);
  261. if ($cmp == 0) {
  262. $cmp = strcasecmp($a->info['name'], $b->info['name']);
  263. }
  264. return $cmp;
  265. }
  266. /**
  267. * Calculate a extension status based on current status and schema version.
  268. *
  269. * @param $extension
  270. * Object of a single extension info.
  271. *
  272. * @return
  273. * String describing extension status. Values: enabled|disabled|not installed
  274. */
  275. function drush_get_extension_status($extension) {
  276. if (($extension->type == 'module')&&($extension->schema_version == -1)) {
  277. $status = "not installed";
  278. }
  279. else {
  280. $status = ($extension->status == 1)?'enabled':'disabled';
  281. }
  282. return $status;
  283. }
  284. /**
  285. * Wrapper of drupal_get_extensions() with additional information used by
  286. * pm- commands.
  287. *
  288. * @return
  289. * An array containing info for all available extensions w/additional info.
  290. */
  291. function drush_pm_get_extensions() {
  292. $extensions = drush_get_extensions();
  293. foreach ($extensions as $key => $extension) {
  294. if (empty($extension->info['package'])) {
  295. $extensions[$key]->info['package'] = dt('Other');
  296. }
  297. }
  298. return $extensions;
  299. }
  300. /**
  301. * Classify extensions as modules, themes or unknown.
  302. *
  303. * @param $extensions
  304. * Array of extension names, by reference.
  305. * @param $modules
  306. * Empty array to be filled with modules in the provided extension list.
  307. * @param $themes
  308. * Empty array to be filled with themes in the provided extension list.
  309. */
  310. function drush_pm_classify_extensions(&$extensions, &$modules, &$themes, $extension_info) {
  311. _drush_pm_expand_extensions($extensions, $extension_info);
  312. foreach ($extensions as $extension) {
  313. if (!isset($extension_info[$extension])) {
  314. continue;
  315. }
  316. if ($extension_info[$extension]->type == 'module') {
  317. $modules[$extension] = $extension;
  318. }
  319. else if ($extension_info[$extension]->type == 'theme') {
  320. $themes[$extension] = $extension;
  321. }
  322. }
  323. }
  324. /**
  325. * Obtain an array of installed projects off the extensions available.
  326. *
  327. * A project is considered to be 'enabled' when any of its extensions is
  328. * enabled.
  329. * If any extension lacks project information and it is found that the
  330. * extension was obtained from drupal.org's cvs or git repositories, a new
  331. * 'vcs' attribute will be set on the extension. Example:
  332. * $extensions[name]->vcs = 'cvs';
  333. *
  334. * @param array $extensions.
  335. * Array of extensions as returned by drush_get_extensions().
  336. * @return
  337. * Array of installed projects with info of version, status and provided
  338. * extensions.
  339. */
  340. function drush_get_projects(&$extensions = NULL) {
  341. if (is_null($extensions)) {
  342. $extensions = drush_get_extensions();
  343. }
  344. $projects = array('drupal' => array('version' => VERSION));
  345. foreach ($extensions as $extension) {
  346. // The project name is not available in this cases:
  347. // 1. the extension is part of drupal core.
  348. // 2. the project was checked out from CVS/git and cvs_deploy/git_deploy
  349. // is not installed.
  350. // 3. it is not a project hosted in drupal.org.
  351. if (empty($extension->info['project'])) {
  352. if (isset($extension->info['version']) && ($extension->info['version'] == VERSION)) {
  353. $project = 'drupal';
  354. }
  355. else {
  356. if (is_dir(dirname($extension->filename) . '/CVS') && (!module_exists('cvs_deploy'))) {
  357. $extension->vcs = 'cvs';
  358. drush_log(dt('Extension !extension is fetched from cvs. Ignoring.', array('!extension' => $extension->name)), 'debug');
  359. }
  360. elseif (is_dir(dirname($extension->filename) . '/.git') && (!module_exists('git_deploy'))) {
  361. $extension->vcs = 'git';
  362. drush_log(dt('Extension !extension is fetched from git. Ignoring.', array('!extension' => $extension->name)), 'debug');
  363. }
  364. continue;
  365. }
  366. }
  367. else {
  368. $project = $extension->info['project'];
  369. }
  370. // Create/update the project in $projects with the project data.
  371. if (!isset($projects[$project])) {
  372. $projects[$project] = array(
  373. 'type' => $extension->type,
  374. 'version' => $extension->info['version'],
  375. 'status' => $extension->status,
  376. 'extensions' => array(),
  377. );
  378. if (isset($extension->info['project status url'])) {
  379. $projects[$project]['status url'] = $extension->info['project status url'];
  380. }
  381. }
  382. elseif ($extension->status != 0) {
  383. $projects[$project]['status'] = $extension->status;
  384. }
  385. $projects[$project]['extensions'][] = $extension->name;
  386. }
  387. return $projects;
  388. }
  389. /**
  390. * Returns a list of enabled modules.
  391. *
  392. * This is a simplified version of module_list().
  393. */
  394. function pm_module_list() {
  395. $enabled = array();
  396. $rsc = drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1));
  397. while ($row = drush_db_result($rsc)) {
  398. $enabled[$row] = $row;
  399. }
  400. return $enabled;
  401. }
  402. /**
  403. * @} End of "defgroup extensions".
  404. */
  405. /**
  406. * Command callback. Show a list of extensions with type and status.
  407. */
  408. function drush_pm_list() {
  409. //--package
  410. $package_filter = array();
  411. $package = strtolower(drush_get_option('package'));
  412. if (!empty($package)) {
  413. $package_filter = explode(',', $package);
  414. }
  415. if (empty($package_filter) || count($package_filter) > 1) {
  416. $header[] = dt('Package');
  417. }
  418. $header[] = dt('Name');
  419. //--type
  420. $all_types = array('module', 'theme');
  421. $type_filter = strtolower(drush_get_option('type'));
  422. if (!empty($type_filter)) {
  423. $type_filter = explode(',', $type_filter);
  424. }
  425. else {
  426. $type_filter = $all_types;
  427. }
  428. if (count($type_filter) > 1) {
  429. $header[] = dt('Type');
  430. }
  431. foreach ($type_filter as $type) {
  432. if (!in_array($type, $all_types)) { //TODO: this kind of chck can be implemented drush-wide
  433. return drush_set_error('DRUSH_PM_INVALID_PROJECT_TYPE', dt('!type is not a valid project type.', array('!type' => $type)));
  434. }
  435. }
  436. //--status
  437. $all_status = array('enabled', 'disabled', 'not installed');
  438. $status_filter = strtolower(drush_get_option('status'));
  439. if (!empty($status_filter)) {
  440. $status_filter = explode(',', $status_filter);
  441. }
  442. else {
  443. $status_filter = $all_status;
  444. }
  445. if (count($status_filter) > 1) {
  446. $header[] = dt('Status');
  447. }
  448. foreach ($status_filter as $status) {
  449. if (!in_array($status, $status_filter)) { //TODO: this kind of chck can be implemented drush-wide
  450. return drush_set_error('DRUSH_PM_INVALID_PROJECT_TYPE', dt('!status is not a valid project status.', array('!status' => $status)));
  451. }
  452. }
  453. $header[] = dt('Version');
  454. $rows[] = $header;
  455. $extension_info = drush_pm_get_extensions();
  456. uasort($extension_info, '_drush_pm_sort_extensions');
  457. $major_version = drush_drupal_major_version();
  458. foreach ($extension_info as $key => $extension) {
  459. if (!in_array($extension->type, $type_filter)) {
  460. unset($extension_info[$key]);
  461. continue;
  462. }
  463. $status = drush_get_extension_status($extension);
  464. if (!in_array($status, $status_filter)) {
  465. unset($extension_info[$key]);
  466. continue;
  467. }
  468. if (($major_version >= 6) and (isset($extension->info['hidden']))) {
  469. unset($extension_info[$key]);
  470. continue;
  471. }
  472. // filter out core if --no-core specified
  473. if (drush_get_option('no-core', FALSE)) {
  474. if ($extension->info['version'] == VERSION) {
  475. unset($extension_info[$key]);
  476. continue;
  477. }
  478. }
  479. // filter out non-core if --core specified
  480. if (drush_get_option('core', FALSE)) {
  481. if ($extension->info['version'] != VERSION) {
  482. unset($extension_info[$key]);
  483. continue;
  484. }
  485. }
  486. // filter by package
  487. if (!empty($package_filter)) {
  488. if (!in_array(strtolower($extension->info['package']), $package_filter)) {
  489. unset($extension_info[$key]);
  490. continue;
  491. }
  492. }
  493. if (empty($package_filter) || count($package_filter) > 1) {
  494. $row[] = $extension->info['package'];
  495. }
  496. if (($major_version >= 6)||($extension->type == 'module')) {
  497. $row[] = $extension->info['name'].' ('.$extension->name.')';
  498. }
  499. else {
  500. $row[] = $extension->name;
  501. }
  502. if (count($type_filter) > 1) {
  503. $row[] = ucfirst($extension->type);
  504. }
  505. if (count($status_filter) > 1) {
  506. $row[] = ucfirst($status);
  507. }
  508. if (($major_version >= 6)||($extension->type == 'module')) {
  509. // Suppress notice when version is not present.
  510. $row[] = @$extension->info['version'];
  511. }
  512. $rows[] = $row;
  513. $pipe[] = $extension->name;
  514. unset($row);
  515. }
  516. drush_print_table($rows, TRUE);
  517. if (isset($pipe)) {
  518. // Newline-delimited list for use by other scripts. Set the --pipe option.
  519. drush_print_pipe($pipe);
  520. }
  521. // Set the result for backend invoke
  522. drush_backend_set_result($extension_info);
  523. }
  524. function drush_pm_find_project_from_extension($extension) {
  525. $result = drush_pm_lookup_extension_in_cache($extension);
  526. if (!isset($result)) {
  527. // If we can find info on a project that has the same name
  528. // as the requested extension, then we'll call that a match.
  529. $info = _drush_pm_get_releases(array($extension));
  530. if (!empty($info)) {
  531. $result = $extension;
  532. }
  533. }
  534. return $result;
  535. }
  536. /**
  537. * Validate callback. Determine the modules and themes that the user would like enabled.
  538. */
  539. function drush_pm_enable_validate() {
  540. $args = _convert_csv_to_array(func_get_args());
  541. $extension_info = drush_get_extensions();
  542. $recheck = TRUE;
  543. while ($recheck) {
  544. $recheck = FALSE;
  545. // Classify $args in themes, modules or unknown.
  546. $modules = array();
  547. $themes = array();
  548. drush_pm_classify_extensions($args, $modules, $themes, $extension_info);
  549. $extensions = array_merge($modules, $themes);
  550. $unknown = array_diff($args, $extensions);
  551. // Discard and set an error for each unknown extension.
  552. foreach ($unknown as $name) {
  553. drush_log(dt('!extension was not found and will not be enabled.', array('!extension' => $name)), 'warning');
  554. }
  555. // Discard already enabled and incompatible extensions.
  556. foreach ($extensions as $name) {
  557. if ($extension_info[$name]->status) {
  558. drush_log(dt('!extension is already enabled.', array('!extension' => $name)), 'ok');
  559. }
  560. // Check if the extension is compatible with Drupal core and php version.
  561. if (drush_extension_check_incompatibility($extension_info[$name])) {
  562. drush_set_error('DRUSH_PM_ENABLE_MODULE_INCOMPATIBLE', dt('!name is incompatible with the Drupal version.', array('!name' => $name)));
  563. if ($extension_info[$name]->type == 'module') {
  564. unset($modules[$name]);
  565. }
  566. else {
  567. unset($themes[$name]);
  568. }
  569. }
  570. }
  571. if (!empty($modules)) {
  572. // Check module dependencies.
  573. $dependencies = drush_check_module_dependencies($modules, $extension_info);
  574. $unmet_dependencies = array();
  575. foreach ($dependencies as $module => $info) {
  576. if (!empty($info['unmet-dependencies'])) {
  577. foreach ($info['unmet-dependencies'] as $unmet_module) {
  578. $unmet_project = drush_pm_find_project_from_extension($unmet_module);
  579. if (!empty($unmet_project)) {
  580. $unmet_dependencies[$module][$unmet_project] = $unmet_project;
  581. }
  582. }
  583. }
  584. }
  585. if (!empty($unmet_dependencies)) {
  586. $msgs = array();
  587. $unmet_project_list = array();
  588. foreach ($unmet_dependencies as $module => $unmet_projects) {
  589. $unmet_project_list = array_merge($unmet_project_list, $unmet_projects);
  590. $msgs[] = dt("!module requires !unmet-projects", array('!unmet-projects' => implode(', ', $unmet_projects), '!module' => $module));
  591. }
  592. if (drush_get_option('resolve-dependencies') || drush_confirm(dt("The following projects have unmet dependencies:\n!list\nWould you like to download them?", array('!list' => implode("\n", $msgs))))) {
  593. // If we did not already print a log message via drush_confirm, then print one now.
  594. if (drush_get_option('resolve-dependencies')) {
  595. drush_log(dt("The following projects have unmet dependencies:\n@list\nThey are being downloaded.", array('@list' => implode("\n", $msgs))));
  596. }
  597. // Disable DRUSH_AFFIRMATIVE context temporarily.
  598. $drush_affirmative = drush_get_context('DRUSH_AFFIRMATIVE');
  599. drush_set_context('DRUSH_AFFIRMATIVE', FALSE);
  600. // Invoke a new process to download dependencies.
  601. $result = drush_invoke_process('@self', 'pm-download', $unmet_project_list, array('cache' => TRUE), array('interactive' => TRUE));
  602. // Restore DRUSH_AFFIRMATIVE context.
  603. drush_set_context('DRUSH_AFFIRMATIVE', $drush_affirmative);
  604. // Refresh module cache after downloading the new modules.
  605. $extension_info = drush_get_extensions();
  606. $recheck = TRUE;
  607. }
  608. }
  609. }
  610. }
  611. if (!empty($modules)) {
  612. $all_dependencies = array();
  613. $dependencies_ok = TRUE;
  614. foreach ($dependencies as $key => $info) {
  615. if (isset($info['error'])) {
  616. unset($modules[$key]);
  617. $dependencies_ok = drush_set_error($info['error']['code'], $info['error']['message']);
  618. }
  619. elseif (!empty($info['dependencies'])) {
  620. // Make sure we have an assoc array.
  621. $assoc = drupal_map_assoc($info['dependencies']);
  622. $all_dependencies = array_merge($all_dependencies, $assoc);
  623. }
  624. }
  625. if (!$dependencies_ok) {
  626. return FALSE;
  627. }
  628. $modules = array_diff(array_merge($modules, $all_dependencies), pm_module_list());
  629. // Discard modules which doesn't meet requirements.
  630. require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc';
  631. foreach ($modules as $key => $module) {
  632. // Check to see if the module can be installed/enabled (hook_requirements).
  633. // See @system_modules_submit
  634. if (!drupal_check_module($module)) {
  635. unset($modules[$key]);
  636. drush_set_error('DRUSH_PM_ENABLE_MODULE_UNMEET_REQUIREMENTS', dt('Module !module doesn\'t meet the requirements to be enabled.', array('!module' => $module)));
  637. _drush_log_drupal_messages();
  638. return FALSE;
  639. }
  640. }
  641. }
  642. $searchpath = array();
  643. foreach (array_merge($modules, $themes) as $name) {
  644. $searchpath[] = dirname($extension_info[$name]->filename);
  645. }
  646. // Add all modules that passed validation to the drush
  647. // list of commandfiles (if they have any). This
  648. // will allow these newly-enabled modules to participate
  649. // in the pre-pm_enable and post-pm_enable hooks.
  650. if (!empty($searchpath)) {
  651. _drush_add_commandfiles($searchpath);
  652. }
  653. drush_set_context('PM_ENABLE_EXTENSION_INFO', $extension_info);
  654. drush_set_context('PM_ENABLE_MODULES', $modules);
  655. drush_set_context('PM_ENABLE_THEMES', $themes);
  656. return TRUE;
  657. }
  658. /**
  659. * Command callback. Enable one or more extensions from downloaded projects.
  660. * Note that the modules and themes to be enabled were evaluated during the
  661. * pm-enable validate hook, above.
  662. */
  663. function drush_pm_enable() {
  664. // Get the data built during the validate phase
  665. $extension_info = drush_get_context('PM_ENABLE_EXTENSION_INFO');
  666. $modules = drush_get_context('PM_ENABLE_MODULES');
  667. $themes = drush_get_context('PM_ENABLE_THEMES');
  668. // Inform the user which extensions will finally be enabled.
  669. $extensions = array_merge($modules, $themes);
  670. if (empty($extensions)) {
  671. return drush_log(dt('There were no extensions that could be enabled.'), 'ok');
  672. }
  673. else {
  674. drush_print(dt('The following extensions will be enabled: !extensions', array('!extensions' => implode(', ', $extensions))));
  675. if(!drush_confirm(dt('Do you really want to continue?'))) {
  676. return drush_user_abort();
  677. }
  678. }
  679. // Enable themes.
  680. if (!empty($themes)) {
  681. drush_theme_enable($themes);
  682. }
  683. // Enable modules and pass dependency validation in form submit.
  684. if (!empty($modules)) {
  685. drush_module_enable($modules);
  686. drush_system_modules_form_submit(pm_module_list());
  687. }
  688. // Inform the user of final status.
  689. $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions));
  690. $problem_extensions = array();
  691. while ($extension = drush_db_fetch_object($rsc)) {
  692. if ($extension->status) {
  693. drush_log(dt('!extension was enabled successfully.', array('!extension' => $extension->name)), 'ok');
  694. }
  695. else {
  696. $problem_extensions[] = $extension->name;
  697. }
  698. }
  699. if (!empty($problem_extensions)) {
  700. return drush_set_error('DRUSH_PM_ENABLE_EXTENSION_ISSUE', dt('There was a problem enabling !extension.', array('!extension' => implode(',', $problem_extensions))));
  701. }
  702. // Return the list of extensions enabled
  703. return $extensions;
  704. }
  705. /**
  706. * Command callback. Disable one or more extensions.
  707. */
  708. function drush_pm_disable() {
  709. $args = _convert_csv_to_array(func_get_args());
  710. $extension_info = drush_get_extensions();
  711. // classify $args in themes, modules or unknown.
  712. $modules = array();
  713. $themes = array();
  714. drush_pm_classify_extensions($args, $modules, $themes, $extension_info);
  715. $extensions = array_merge($modules, $themes);
  716. $unknown = array_diff($args, $extensions);
  717. // Discard and set an error for each unknown extension.
  718. foreach ($unknown as $name) {
  719. drush_log('DRUSH_PM_ENABLE_EXTENSION_NOT_FOUND', dt('!extension was not found and will not be disabled.', array('!extension' => $name)), 'warning');
  720. }
  721. // Discard already disabled extensions.
  722. foreach ($extensions as $name) {
  723. if (!$extension_info[$name]->status) {
  724. if ($extension_info[$name]->type == 'module') {
  725. unset($modules[$name]);
  726. }
  727. else {
  728. unset($themes[$name]);
  729. }
  730. drush_log(dt('!extension is already disabled.', array('!extension' => $name)), 'ok');
  731. }
  732. }
  733. // Discard default theme.
  734. if (!empty($themes)) {
  735. $default_theme = drush_theme_get_default();
  736. if (in_array($default_theme, $themes)) {
  737. unset($themes[$default_theme]);
  738. drush_log(dt('!theme is the default theme and can\'t be disabled.', array('!theme' => $default_theme)), 'ok');
  739. }
  740. }
  741. if (!empty($modules)) {
  742. // Add enabled dependents to the list of modules to disable.
  743. $dependents = drush_module_dependents($modules, $extension_info);
  744. $dependents = array_intersect($dependents, pm_module_list());
  745. $modules = array_merge($modules, $dependents);
  746. // Discard required modules.
  747. $required = drush_drupal_required_modules($extension_info);
  748. foreach ($required as $module) {
  749. if (isset($modules[$module])) {
  750. unset($modules[$module]);
  751. // No message for hidden modules.
  752. if (!isset($extension_info[$module]->info['hidden'])) {
  753. drush_log(dt('!module is a required module and can\'t be disabled.', array('!module' => $module)), 'ok');
  754. }
  755. }
  756. }
  757. }
  758. // Inform the user which extensions will finally be disabled.
  759. $extensions = array_merge($modules, $themes);
  760. if (empty($extensions)) {
  761. return drush_log(dt('There were no extensions that could be disabled.'), 'ok');
  762. }
  763. else {
  764. drush_print(dt('The following extensions will be disabled: !extensions', array('!extensions' => implode(', ', $extensions))));
  765. if(!drush_confirm(dt('Do you really want to continue?'))) {
  766. return drush_user_abort();
  767. }
  768. }
  769. // Disable themes.
  770. if (!empty($themes)) {
  771. drush_theme_disable($themes);
  772. }
  773. // Disable modules and pass dependency validation in form submit.
  774. if (!empty($modules)) {
  775. drush_module_disable($modules);
  776. drush_system_modules_form_submit(pm_module_list());
  777. }
  778. // Inform the user of final status.
  779. $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions));
  780. $problem_extensions = array();
  781. while ($extension = drush_db_fetch_object($rsc)) {
  782. if (!$extension->status) {
  783. drush_log(dt('!extension was disabled successfully.', array('!extension' => $extension->name)), 'ok');
  784. }
  785. else {
  786. $problem_extensions[] = $extension->name;
  787. }
  788. }
  789. if (!empty($problem_extensions)) {
  790. return drush_set_error('DRUSH_PM_DISABLE_EXTENSION_ISSUE', dt('There was a problem disabling !extension.', array('!extension' => implode(',', $problem_extensions))));
  791. }
  792. }
  793. /**
  794. * Command callback. Show detailed info for one or more extension.
  795. */
  796. function drush_pm_info() {
  797. $args = _convert_csv_to_array(func_get_args());
  798. $extension_info = drush_pm_get_extensions();
  799. _drush_pm_expand_extensions($args, $extension_info);
  800. // If no extensions are provided, select all but the hidden ones.
  801. if (count($args) == 0) {
  802. foreach ($extension_info as $key => $extension) {
  803. if (isset($extension->info['hidden'])) {
  804. unset($extension_info[$key]);
  805. }
  806. }
  807. $args = array_keys($extension_info);
  808. }
  809. foreach ($args as $project) {
  810. if (isset($extension_info[$project])) {
  811. $info = $extension_info[$project];
  812. }
  813. else {
  814. drush_log(dt('!project was not found.', array('!project' => $project)), 'warning');
  815. continue;
  816. }
  817. if ($info->type == 'module') {
  818. $data = _drush_pm_info_module($info);
  819. }
  820. else {
  821. $data = _drush_pm_info_theme($info);
  822. }
  823. drush_print_table(drush_key_value_to_array_table($data));
  824. print "\n";
  825. }
  826. }
  827. /**
  828. * Return a string with general info of a extension.
  829. */
  830. function _drush_pm_info_extension($info) {
  831. $major_version = drush_drupal_major_version();
  832. $data['Project'] = $info->name;
  833. $data['Type'] = $info->type;
  834. if (($info->type == 'module')||($major_version >= 6)) {
  835. $data['Title'] = $info->info['name'];
  836. $data['Description'] = $info->info['description'];
  837. $data['Version'] = $info->info['version'];
  838. }
  839. $data['Package'] = $info->info['package'];
  840. if ($major_version >= 6) {
  841. $data['Core'] = $info->info['core'];
  842. }
  843. if ($major_version == 6) {
  844. $data['PHP'] = $info->info['php'];
  845. }
  846. $data['Status'] = drush_get_extension_status($info);
  847. $path = (($info->type == 'module')&&($major_version == 7))?$info->uri:$info->filename;
  848. $path = substr($path, 0, strrpos($path, '/'));
  849. $data['Path'] = $path;
  850. return $data;
  851. }
  852. /**
  853. * Return a string with info of a module.
  854. */
  855. function _drush_pm_info_module($info) {
  856. $major_version = drush_drupal_major_version();
  857. $data = _drush_pm_info_extension($info);
  858. if ($info->schema_version > 0) {
  859. $schema_version = $info->schema_version;
  860. }
  861. elseif ($info->schema_version == -1) {
  862. $schema_version = "no schema installed";
  863. }
  864. else {
  865. $schema_version = "module has no schema";
  866. }
  867. $data['Schema version'] = $schema_version;
  868. if ($major_version == 7) {
  869. $data['Files'] = implode(', ', $info->info['files']);
  870. }
  871. if (count($info->info['dependencies']) > 0) {
  872. $requires = implode(', ', $info->info['dependencies']);
  873. }
  874. else {
  875. $requires = "none";
  876. }
  877. $data['Requires'] = $requires;
  878. if ($major_version == 6) {
  879. if (count($info->info['dependents']) > 0) {
  880. $requiredby = implode(', ', $info->info['dependents']);
  881. }
  882. else {
  883. $requiredby = "none";
  884. }
  885. $data['Required by'] = $requiredby;
  886. }
  887. return $data;
  888. }
  889. /**
  890. * Return a string with info of a theme.
  891. */
  892. function _drush_pm_info_theme($info) {
  893. $major_version = drush_drupal_major_version();
  894. $data = _drush_pm_info_extension($info);
  895. if ($major_version == 5) {
  896. $data['Engine'] = $info->description;
  897. }
  898. else {
  899. $data['Core'] = $info->info['core'];
  900. $data['PHP'] = $info->info['php'];
  901. $data['Engine'] = $info->info['engine'];
  902. $data['Base theme'] = isset($info->base_themes) ? implode($info->base_themes, ', ') : '';
  903. $regions = implode(', ', $info->info['regions']);
  904. $data['Regions'] = $regions;
  905. $features = implode(', ', $info->info['features']);
  906. $data['Features'] = $features;
  907. if (count($info->info['stylesheets']) > 0) {
  908. $data['Stylesheets'] = '';
  909. foreach ($info->info['stylesheets'] as $media => $files) {
  910. $files = implode(', ', array_keys($files));
  911. $data['Media '.$media] = $files;
  912. }
  913. }
  914. if (count($info->info['scripts']) > 0) {
  915. $scripts = implode(', ', array_keys($info->info['scripts']));
  916. $data['Scripts'] = $scripts;
  917. }
  918. }
  919. return $data;
  920. }
  921. /**
  922. * Add extensions that match extension_name*.
  923. *
  924. * A helper function for commands that take a space separated list of extension
  925. * names. It will identify extensions that have been passed in with a
  926. * trailing * and add all matching extensions to the array that is returned.
  927. *
  928. * @param $extensions
  929. * An array of extensions, by reference.
  930. * @param $extension_info
  931. * Optional. An array of extension info as returned by drush_get_extensions().
  932. */
  933. function _drush_pm_expand_extensions(&$extensions, $extension_info = array()) {
  934. if (empty($extension_info)) {
  935. $extension_info = drush_get_extensions();
  936. }
  937. foreach ($extensions as $key => $extension) {
  938. if (($wildcard = rtrim($extension, '*')) !== $extension) {
  939. foreach (array_keys($extension_info) as $extension_name) {
  940. if (substr($extension_name, 0, strlen($wildcard)) == $wildcard) {
  941. $extensions[] = $extension_name;
  942. }
  943. }
  944. unset($extensions[$key]);
  945. continue;
  946. }
  947. }
  948. }
  949. /**
  950. * Command callback. Uninstall one or more modules.
  951. * // TODO: Use drupal_execute on system_modules_uninstall_confirm_form so that input is validated.
  952. */
  953. function drush_pm_uninstall() {
  954. $modules = _convert_csv_to_array(func_get_args());
  955. drush_include_engine('drupal', 'environment');
  956. $module_info = drush_get_modules();
  957. $required = drupal_required_modules();
  958. // Discards modules which are enabled, not found or already uninstalled.
  959. foreach ($modules as $key => $module) {
  960. if (!isset($module_info[$module])) {
  961. // The module does not exist in the system.
  962. unset($modules[$key]);
  963. drush_log(dt('Module !module was not found and will not be uninstalled.', array('!module' => $module)), 'warning');
  964. }
  965. else if ($module_info[$module]->status) {
  966. // The module is enabled.
  967. unset($modules[$key]);
  968. drush_log(dt('!module is not disabled. Use `pm-disable` command before `pm-uninstall`.', array('!module' => $module)), 'warning');
  969. }
  970. else if ($module_info[$module]->schema_version == -1) { // SCHEMA_UNINSTALLED
  971. // The module is uninstalled.
  972. unset($modules[$key]);
  973. drush_log(dt('!module is already uninstalled.', array('!module' => $module)), 'ok');
  974. }
  975. else {
  976. $dependents = array();
  977. foreach (drush_module_dependents(array($module), $module_info) as $dependent) {
  978. if (!in_array($dependent, $required) && ($module_info[$dependent]->schema_version != -1)) {
  979. $dependents[] = $dependent;
  980. }
  981. }
  982. if (count($dependents)) {
  983. drush_log(dt('To uninstall !module, the following modules must be uninstalled first: !required', array('!module' => $module, '!required' => implode(', ', $dependents))), 'error');
  984. unset($modules[$key]);
  985. }
  986. }
  987. }
  988. // Inform the user which modules will finally be uninstalled.
  989. if (empty($modules)) {
  990. return drush_log(dt('There were no modules that could be uninstalled.'), 'ok');
  991. }
  992. else {
  993. drush_print(dt('The following modules will be uninstalled: !modules', array('!modules' => implode(', ', $modules))));
  994. if(!drush_confirm(dt('Do you really want to continue?'))) {
  995. return drush_user_abort();
  996. }
  997. }
  998. // Disable the modules.
  999. drush_module_uninstall($modules);
  1000. // Inform the user of final status.
  1001. foreach ($modules as $module) {
  1002. drush_log(dt('!module was successfully uninstalled.', array('!module' => $module)), 'ok');
  1003. }
  1004. }
  1005. /**
  1006. * Completes projects' update data with the path to install location on disk.
  1007. *
  1008. * Given an array of release info for available projects, find the path to the install location.
  1009. */
  1010. function _pm_get_project_path($data, $lookup) {
  1011. foreach ($data as $name => $release) {
  1012. if ($name == 'drupal') {
  1013. continue;
  1014. }
  1015. // Array of extensions (modules/themes) within the project.
  1016. $extensions = array_keys($release[$lookup]);
  1017. $path = _pm_find_common_path($release['project_type'], $extensions);
  1018. $reserved = array('modules', 'sites', 'themes');
  1019. if ((in_array(basename($path), $reserved)) && (!in_array($name, $reserved))) {
  1020. drush_log(dt('Error while trying to find the common path for enabled extensions of project !project. Extensions are: !extensions.', array('!project' => $name, '!extensions' => implode(', ', $extensions))), 'error');
  1021. unset($data[$name]);
  1022. }
  1023. else {
  1024. $data[$name]['path'] = $path;
  1025. }
  1026. }
  1027. return $data;
  1028. }
  1029. /**
  1030. * Helper function to find the common path for a list of extensions in the aim to obtain the project name.
  1031. *
  1032. * @param $project_type
  1033. * Type of project we're trying to find. Valid values: module, theme.
  1034. * @param $extensions
  1035. * Array of extension names.
  1036. */
  1037. function _pm_find_common_path($project_type, $extensions) {
  1038. // Select the first path as the candidate to be the common prefix.
  1039. $path = drupal_get_path($project_type, array_pop($extensions));
  1040. // If there's only one extension we are done. Otherwise, we need to find
  1041. // the common prefix for all of them.
  1042. if (count($extensions) > 0) {
  1043. // Iterate over the other projects.
  1044. while($project = array_pop($extensions)) {
  1045. $path2 = drupal_get_path($project_type, $project);
  1046. // Option 1: same path.
  1047. if ($path == $path2) {
  1048. continue;
  1049. }
  1050. // Option 2: $path is a prefix of $path2.
  1051. if (strpos($path2, $path) === 0) {
  1052. continue;
  1053. }
  1054. // Option 3: $path2 is a prefix of $path.
  1055. if (strpos($path, $path2) === 0) {
  1056. $path = $path2;
  1057. continue;
  1058. }
  1059. // Option 4: no one is a prefix of the other. Find the common
  1060. // prefix by iteratively strip the rigthtmost piece of $path.
  1061. // We will iterate until a prefix is found or path = '.', that on the
  1062. // other hand is a condition theorically impossible to reach.
  1063. do {
  1064. $path = dirname($path);
  1065. if (strpos($path2, $path) === 0) {
  1066. break;
  1067. }
  1068. } while ($path != '.');
  1069. }
  1070. }
  1071. return $path;
  1072. }
  1073. /**
  1074. * Command callback. Show available releases for given project(s).
  1075. */
  1076. function drush_pm_releases() {
  1077. if (!$requests = _convert_csv_to_array(func_get_args())) {
  1078. $requests = array('drupal');
  1079. }
  1080. $info = _drush_pm_get_releases($requests);
  1081. if (!$info) {
  1082. return drush_log(dt('No valid projects given.'), 'ok');
  1083. }
  1084. foreach ($info as $name => $project) {
  1085. $header = dt('------- RELEASES FOR \'!name\' PROJECT -------', array('!name' => strtoupper($name)));
  1086. $rows = array();
  1087. $rows[] = array(dt('Release'), dt('Date'), dt('Status'));
  1088. $releases = _drush_pm_filter_releases($project['releases'], drush_get_option('all', FALSE), drush_get_option('dev', FALSE));
  1089. foreach ($releases as $release) {
  1090. $rows[] = array(
  1091. $release['version'],
  1092. gmdate('Y-M-d', $release['date']),
  1093. implode(', ', $release['release_status'])
  1094. );
  1095. }
  1096. drush_print($header);
  1097. drush_print_table($rows, TRUE, array(0 => 14));
  1098. }
  1099. return $info;
  1100. }
  1101. /**
  1102. * Obtain releases for a project's xml as returned by the update service.
  1103. */
  1104. function _drush_pm_get_releases_from_xml($xml, $project) {
  1105. // If bootstraped, we can obtain which is the installed release of a project.
  1106. static $installed_projects = FALSE;
  1107. if (!$installed_projects) {
  1108. if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
  1109. $installed_projects = drush_get_projects();
  1110. }
  1111. else {
  1112. $installed_projects = array();
  1113. }
  1114. }
  1115. foreach (array('title', 'short_name', 'dc:creator', 'api_version', 'recommended_major', 'supported_majors', 'default_major', 'project_status', 'link') as $item) {
  1116. if (array_key_exists($item, $xml)) {
  1117. $value = $xml->xpath($item);
  1118. $project_info[$item] = (string)$value[0];
  1119. }
  1120. }
  1121. $recommended_major = @$xml->xpath("/project/recommended_major");
  1122. $recommended_major = empty($recommended_major)?"":(string)$recommended_major[0];
  1123. $supported_majors = @$xml->xpath("/project/supported_majors");
  1124. $supported_majors = empty($supported_majors)?array():array_flip(explode(',', (string)$supported_majors[0]));
  1125. $releases_xml = @$xml->xpath("/project/releases/release[status='published']");
  1126. $recommended_version = NULL;
  1127. $latest_version = NULL;
  1128. foreach ($releases_xml as $release) {
  1129. $release_info = array();
  1130. foreach (array('name', 'version', 'tag', 'version_major', 'version_extra', 'status', 'release_link', 'download_link', 'date', 'mdhash', 'filesize') as $item) {
  1131. if (array_key_exists($item, $release)) {
  1132. $value = $release->xpath($item);
  1133. $release_info[$item] = (string)$value[0];
  1134. }
  1135. }
  1136. $statuses = array();
  1137. if (array_key_exists($release_info['version_major'], $supported_majors)) {
  1138. $statuses[] = "Supported";
  1139. unset($supported_majors[$release_info['version_major']]);
  1140. }
  1141. if ($release_info['version_major'] == $recommended_major) {
  1142. if (!isset($latest_version)) {
  1143. $latest_version = $release_info['version'];
  1144. }
  1145. // The first stable version (no 'version extra') in the recommended major
  1146. // is the recommended release
  1147. if (empty($release_info['version_extra']) && (!isset($recommended_version))) {
  1148. $statuses[] = "Recommended";
  1149. $recommended_version = $release_info['version'];
  1150. }
  1151. }
  1152. if (!empty($release_info['version_extra']) && ($release_info['version_extra'] == "dev")) {
  1153. $statuses[] = "Development";
  1154. }
  1155. foreach ($release->xpath('terms/term/value') as $release_type) {
  1156. // There are three kinds of release types that we recognize:
  1157. // "Bug fixes", "New features" and "Security update".
  1158. // We will add "Security" for security updates, and nothing
  1159. // for the other kinds.
  1160. if (strpos($release_type, "Security") !== FALSE) {
  1161. $statuses[] = "Security";
  1162. }
  1163. }
  1164. // Add to status whether the project is installed.
  1165. if (isset($installed_projects[$project])) {
  1166. if ($installed_projects[$project]['version'] == $release_info['version']) {
  1167. $statuses[] = dt('Installed');
  1168. $project_info['installed'] = $release_info['version'];
  1169. }
  1170. }
  1171. $release_info['release_status'] = $statuses;
  1172. $releases[$release_info['version']] = $release_info;
  1173. }
  1174. // If there is no -stable- release in the recommended major,
  1175. // then take the latest verion in the recommended major to be
  1176. // the recommended release.
  1177. if (!isset($recommended_version) && isset($latest_version)) {
  1178. $recommended_version = $latest_version;
  1179. $releases[$recommended_version]['release_status'][] = "Recommended";
  1180. }
  1181. $project_info['releases'] = $releases;
  1182. $project_info['recommended'] = $recommended_version;
  1183. return $project_info;
  1184. }
  1185. /**
  1186. * Helper function for _drush_pm_filter_releases().
  1187. */
  1188. function _drush_pm_compare_date($a, $b) {
  1189. if ($a['date'] == $b['date']) {
  1190. return 0;
  1191. }
  1192. if ($a['version_major'] == $b['version_major']) {
  1193. return ($a['date'] > $b['date']) ? -1 : 1;
  1194. }
  1195. return ($a['version_major'] > $b['version_major']) ? -1 : 1;
  1196. }
  1197. /**
  1198. * Filter a list of releases.
  1199. *
  1200. * @param $releases
  1201. * Array of release information
  1202. * @param $all
  1203. * Show all releases. If FALSE, shows only the first release that is
  1204. * Recommended or Supported or Security or Installed.
  1205. * @param $dev
  1206. * Show only development release.
  1207. * @param $show_all_until_installed
  1208. * If TRUE, then all releases will be shown until the INSTALLED release is found,
  1209. * at which point the algorithm will stop.
  1210. */
  1211. function _drush_pm_filter_releases($releases, $all = FALSE, $dev = FALSE, $show_all_until_installed = TRUE) {
  1212. // Start off by sorting releases by release date.
  1213. uasort($releases, '_drush_pm_compare_date');
  1214. // Find version_major for the installed release
  1215. $installed_version_major = FALSE;
  1216. foreach ($releases as $version => $release_info) {
  1217. if (in_array("Installed", $release_info['release_status'])) {
  1218. $installed_version_major = $release_info['version_major'];
  1219. }
  1220. }
  1221. // Now iterate through and filter out the releases we're
  1222. // interested in.
  1223. $options = array();
  1224. $limits_list = array();
  1225. foreach ($releases as $version => $release_info) {
  1226. if (!$dev || ((array_key_exists('version_extra', $release_info)) && ($release_info['version_extra'] == 'dev'))) {
  1227. $saw_unique_status = FALSE;
  1228. foreach ($release_info['release_status'] as $one_status) {
  1229. // We will show the first release of a given kind;
  1230. // after we show the first security release, we show
  1231. // no other. We do this on a per-major-version basis,
  1232. // though, so if a project has three major versions, then
  1233. // we will show the first security release from each.
  1234. // This rule is overridden by $all and $show_all_until_installed.
  1235. $test_key = $release_info['version_major'] . $one_status;
  1236. if (!array_key_exists($test_key, $limits_list)) {
  1237. $limits_list[$test_key] = TRUE;
  1238. $saw_unique_status = TRUE;
  1239. // Once we see the "Installed" release we will stop
  1240. // showing all releases
  1241. if ($one_status == "Installed") {
  1242. $show_all_until_installed = FALSE;
  1243. $installed_release_date = $release_info['date'];
  1244. }
  1245. }
  1246. }
  1247. if ($all || ($show_all_until_installed && ($installed_version_major == $release_info['version_major'])) || $saw_unique_status) {
  1248. $options[$release_info['version']] = $release_info;
  1249. }
  1250. }
  1251. }
  1252. // If "show all until installed" is still true, that means that
  1253. // we never encountered the installed release anywhere in releases,
  1254. // and therefore we did not filter out any releases at all. If this
  1255. // is the case, then call ourselves again, this time with
  1256. // $show_all_until_installed set to FALSE from the beginning.
  1257. // The other situation we might encounter is when we do not encounter
  1258. // the installed release, and $options is still empty. This means
  1259. // that there were no supported or recommented or security or development
  1260. // releases found. If this is the case, then we will force ALL to TRUE
  1261. // and show everything on the second iteration.
  1262. if (($all === FALSE) && ($show_all_until_installed === TRUE)) {
  1263. $options = _drush_pm_filter_releases($releases, empty($options), $dev, FALSE);
  1264. }
  1265. return $options;
  1266. }
  1267. /**
  1268. * Return an array of available releases for given project(s).
  1269. *
  1270. * Helper function for pm-download.
  1271. */
  1272. function _drush_pm_download_releases_choice($xml, $project, $all = FALSE, $dev = FALSE) {
  1273. $project_info = _drush_pm_get_releases_from_xml($xml, $project);
  1274. $releases = _drush_pm_filter_releases($project_info['releases'], $all, $dev);
  1275. $options = array();
  1276. foreach($releases as $version => $release) {
  1277. $options[$version] = array($version, '-', gmdate('Y-M-d', $release['date']), '-', implode(', ', $release['release_status']));
  1278. }
  1279. return $options;
  1280. }
  1281. /**
  1282. * Obtain releases info for given projects and fill in status information.
  1283. *
  1284. * It does connect directly to the update service and does not depend on
  1285. * a bootstraped site.
  1286. *
  1287. * @param $requests
  1288. * An array of drupal.org project names optionally with a version.
  1289. *
  1290. * @see drush_pm_releases()
  1291. * @see _drush_pm_releasenotes()
  1292. */
  1293. function _drush_pm_get_releases($requests) {
  1294. $info = array();
  1295. // Parse out project name and version.
  1296. $requests = pm_parse_project_version($requests);
  1297. // Get release history for each request.
  1298. foreach ($requests as $name => $request) {
  1299. $xml = _drush_pm_get_release_history_xml($request);
  1300. if (!$xml) {
  1301. continue;
  1302. }
  1303. $project_info = _drush_pm_get_releases_from_xml($xml, $name);
  1304. $info[$name] = $project_info;
  1305. }
  1306. return $info;
  1307. }
  1308. /**
  1309. * Command callback. Show release notes for given project(s).
  1310. */
  1311. function drush_pm_releasenotes() {
  1312. if (!$requests = _convert_csv_to_array(func_get_args())) {
  1313. $requests = array('drupal');
  1314. }
  1315. return _drush_pm_releasenotes($requests);
  1316. }
  1317. /**
  1318. * Internal function: prints release notes for given drupal projects.
  1319. *
  1320. * @param $requests
  1321. * An array of drupal.org project names optionally with a version.
  1322. * @param $print_status
  1323. * Boolean. Used by pm-download to not print a informative note.
  1324. * @param $tmpfile
  1325. * If provided, a file that contains contents to show before the
  1326. * release notes.
  1327. *
  1328. * @see drush_pm_releasenotes()
  1329. */
  1330. function _drush_pm_releasenotes($requests, $print_status = TRUE, $tmpfile = NULL) {
  1331. if ($tmpfile == NULL) {
  1332. $tmpfile = drush_tempnam('rln-' . implode('-', $requests) . '.');
  1333. }
  1334. // Parse requests to strip versions.
  1335. $requests = pm_parse_project_version($requests);
  1336. // Get the releases.
  1337. $info = _drush_pm_get_releases(array_keys($requests));
  1338. if (!$info) {
  1339. return drush_log(dt('No valid projects given.'), 'ok');
  1340. }
  1341. foreach ($info as $key => $project) {
  1342. $selected_versions = array();
  1343. // If the request included version, only show its release notes.
  1344. if (isset($requests[$key]['version'])) {
  1345. $selected_versions[] = $requests[$key]['version'];
  1346. }
  1347. else {
  1348. // Special handling if the project is installed.
  1349. if (isset($project['recommended'], $project['installed'])) {
  1350. $releases = array_reverse($project['releases']);
  1351. foreach($releases as $version => $release) {
  1352. if ($release['date'] >= $project['releases'][$project['installed']]['date']) {
  1353. $release += array('version_extra' => '');
  1354. $project['releases'][$project['installed']] += array('version_extra' => '');
  1355. if ($release['version_extra'] == 'dev' && $project['releases'][$project['installed']]['version_extra'] != 'dev') {
  1356. continue;
  1357. }
  1358. $selected_versions[] = $version;
  1359. }
  1360. }
  1361. }
  1362. else {
  1363. // Project is not installed so we will show the release notes
  1364. // for the recommended version, as the user did not specify one.
  1365. $selected_versions[] = $project['recommended'];
  1366. }
  1367. }
  1368. foreach ($selected_versions as $version) {
  1369. // Stage of parsing.
  1370. if (!isset($project['releases'][$version]['release_link'])) {
  1371. // We avoid the cases where the URL of the release notes does not exist.
  1372. drush_log(dt("Project !project does not have release notes for version !version.", array('!project' => $key, '!version' => $version)), 'warning');
  1373. continue;
  1374. }
  1375. else {
  1376. $release_page_url = $project['releases'][$version]['release_link'];
  1377. }
  1378. $release_page_url_parsed = parse_url($release_page_url);
  1379. $release_url_path = $release_page_url_parsed['path'];
  1380. if (!empty($release_url_path)) {
  1381. if ($release_page_url_parsed['host'] == 'drupal.org') {
  1382. $release_page_id = substr($release_url_path, strlen('/node/'));
  1383. drush_log(dt("Release link for !project (!version) project was found.", array('!project' => $key, '!version' => $version)), 'notice');
  1384. }
  1385. else {
  1386. drush_log(dt("Release notes' page for !project project is not hosted on drupal.org. See !url.", array('!project' => $key, '!url' => $release_page_url)), 'warning');
  1387. continue;
  1388. }
  1389. }
  1390. // We'll use drupal_http_request if available; it provides better error reporting.
  1391. if (function_exists('drupal_http_request')) {
  1392. $data = drupal_http_request($release_page_url);
  1393. if (isset($data->error)) {
  1394. drush_log(dt("Error (!error) while requesting the release notes page for !project project.", array('!error' => $data->error, '!project' => $key)), 'error');
  1395. continue;
  1396. }
  1397. @$dom = DOMDocument::loadHTML($data->data);
  1398. }
  1399. else {
  1400. $filename = _drush_download_file($release_page_url);
  1401. @$dom = DOMDocument::loadHTMLFile($filename);
  1402. @unlink($filename);
  1403. if ($dom === FALSE) {
  1404. drush_log(dt("Error while requesting the release notes page for !project project.", array('!project' => $key)), 'error');
  1405. continue;
  1406. }
  1407. }
  1408. if ($dom) {
  1409. drush_log(dt("Successfully parsed and loaded the HTML contained in the release notes' page for !project (!version) project.", array('!project' => $key, '!version' => $version)), 'notice');
  1410. }
  1411. $xml = simplexml_import_dom($dom);
  1412. $xpath_expression = '//*[@id="node-' . $release_page_id . '"]/div[@class="node-content"]';
  1413. $node_content = $xml->xpath($xpath_expression);
  1414. // We create the print format.
  1415. $notes_last_update = $node_content[0]->div[1]->div[0]->asXML();
  1416. unset($node_content[0]->div);
  1417. $project_notes = $node_content[0]->asXML();
  1418. // Build the status message from the info from _drush_pm_get_releases
  1419. $status_msg = '> ' . implode(', ', $project['releases'][$version]['release_status']);
  1420. $break = '<br>';
  1421. $notes_header = dt("<hr>
  1422. > RELEASE NOTES FOR '!name' PROJECT, VERSION !version:!break
  1423. > !time.!break
  1424. !status
  1425. <hr>
  1426. ", array('!status' => $print_status ? $status_msg : '', '!name' => strtoupper($key), '!break' => $break, '!version' => $version, '!time' => $notes_last_update));
  1427. // Finally we print the release notes for the requested project.
  1428. if (drush_get_option('html', FALSE)) {
  1429. $print = $notes_header . $project_notes;
  1430. }
  1431. else {
  1432. $print = drush_html_to_text($notes_header . $project_notes . "\n", array('br', 'p', 'ul', 'ol', 'li', 'hr'));
  1433. if (drush_drupal_major_version() < 7) { $print .= "\n"; }
  1434. }
  1435. file_put_contents($tmpfile, $print, FILE_APPEND);
  1436. }
  1437. }
  1438. drush_print_file($tmpfile);
  1439. }
  1440. /**
  1441. * Command callback. Refresh update status information.
  1442. */
  1443. function drush_pm_refresh() {
  1444. // We don't provide for other options here, so we supply an explicit path.
  1445. drush_include_engine('update_info', 'drupal', NULL, DRUSH_BASE_PATH . '/commands/pm/update_info');
  1446. _pm_refresh();
  1447. }
  1448. /**
  1449. * Command callback. Execute pm-update.
  1450. */
  1451. function drush_pm_update() {
  1452. // Call pm-updatecode. updatedb will be called in the post-update process.
  1453. $args = _convert_csv_to_array(func_get_args());
  1454. array_unshift($args, 'pm-updatecode');
  1455. return call_user_func_array('drush_invoke', $args);
  1456. }
  1457. /**
  1458. * Post-command callback.
  1459. * Execute updatedb command after an updatecode - user requested `update`.
  1460. */
  1461. function drush_pm_post_pm_update() {
  1462. // Use drush_backend_invoke to start a subprocess. Cleaner that way.
  1463. if (drush_get_context('DRUSH_PM_UPDATED', FALSE) !== FALSE) {
  1464. drush_backend_invoke('updatedb');
  1465. }
  1466. }
  1467. /**
  1468. * Validate callback for updatecode command. Abort if 'backup' directory exists.
  1469. */
  1470. function drush_pm_updatecode_validate() {
  1471. $path = drush_get_context('DRUSH_DRUPAL_ROOT') . '/backup';
  1472. if (is_dir($path) && (realpath(drush_get_option('backup-dir', FALSE)) != $path)) {
  1473. return drush_set_error('', dt('Backup directory !path found. It\'s a security risk to store backups inside the Drupal tree. Drush now uses by default ~/drush-backups. You need to move !path out of the Drupal tree to proceed. Note: if you know what you\'re doing you can explicitly set --backup-dir to !path and continue.', array('!path' => $path)));
  1474. }
  1475. // Validate package-handler.
  1476. $package_handler = drush_get_option('package-handler', 'wget');
  1477. drush_include_engine('package_handler', $package_handler);
  1478. return package_handler_validate();
  1479. }
  1480. /**
  1481. * Post-command callback for updatecode.
  1482. *
  1483. * Execute pm-updatecode-postupdate in a backend process to not conflict with
  1484. * old code already in memory.
  1485. */
  1486. function drush_pm_post_pm_updatecode() {
  1487. // Skip if updatecode was invoked by pm-update.
  1488. // This way we avoid being noisy, as updatedb is to be executed.
  1489. $command = drush_get_command();
  1490. if ($command['command'] != 'pm-update') {
  1491. if (drush_get_context('DRUSH_PM_UPDATED', FALSE) !== FALSE) {
  1492. drush_backend_invoke('pm-updatecode-postupdate');
  1493. }
  1494. }
  1495. }
  1496. /**
  1497. * Command callback. Execute updatecode-postupdate.
  1498. */
  1499. function drush_pm_updatecode_postupdate() {
  1500. // Clear the cache, since some projects could have moved around.
  1501. drush_drupal_cache_clear_all();
  1502. // Notify of pending database updates.
  1503. // Make sure the installation API is available
  1504. require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc';
  1505. // Load all .install files.
  1506. drupal_load_updates();
  1507. // @see system_requirements().
  1508. foreach (pm_module_list() as $module) {
  1509. $updates = drupal_get_schema_versions($module);
  1510. if ($updates !== FALSE) {
  1511. $default = drupal_get_installed_schema_version($module);
  1512. if (max($updates) > $default) {
  1513. drush_log(dt("You have pending database updates. Run `drush updatedb` or visit update.php in your browser."), 'warning');
  1514. break;
  1515. }
  1516. }
  1517. }
  1518. }
  1519. /**
  1520. * Determine a candidate destination directory for a particular site path and
  1521. * return it if it exists, optionally attempting to create the directory.
  1522. */
  1523. function pm_dl_destination_lookup($type, $drupal_root, $sitepath, $create = FALSE) {
  1524. switch ($type) {
  1525. case 'module':
  1526. // Prefer sites/all/modules/contrib if it exists.
  1527. $destination = $sitepath . '/modules';
  1528. $contrib = $destination . '/contrib';
  1529. if (is_dir($contrib)) {
  1530. $destination = $contrib;
  1531. }
  1532. break;
  1533. case 'theme':
  1534. $destination = $sitepath . '/themes';
  1535. break;
  1536. case 'theme engine':
  1537. $destination = $sitepath . '/themes/engines';
  1538. break;
  1539. case 'profile':
  1540. $destination = $drupal_root . '/profiles';
  1541. break;
  1542. }
  1543. if ($create) {
  1544. drush_log(dt('Attempting to create destination directory at !dir', array('!dir' => $destination)));
  1545. drush_mkdir($destination);
  1546. }
  1547. if (is_dir($destination)) {
  1548. drush_log(dt('Using destination directory !dir', array('!dir' => $destination)));
  1549. return $destination;
  1550. }
  1551. drush_log(dt('Could not find destination directory at !dir', array('!dir' => $destination)));
  1552. return FALSE;
  1553. }
  1554. /**
  1555. * Returns the best destination for a particular download type we can find.
  1556. *
  1557. * It is based on the project type and drupal and site contexts.
  1558. */
  1559. function pm_dl_destination($type) {
  1560. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  1561. $site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT', FALSE);
  1562. $full_site_root = $drupal_root .'/'. $site_root;
  1563. $sites_all = $drupal_root . '/sites/all';
  1564. $in_site_directory = FALSE;
  1565. // Check if we are running within the site directory.
  1566. if ($full_site_root == substr(drush_cwd(), 0, strlen($full_site_root)) || (drush_get_option('use-site-dir', FALSE))) {
  1567. $in_site_directory = TRUE;
  1568. }
  1569. $destination = '';
  1570. if ($type != 'core') {
  1571. // Attempt 1: If we are in a specific site directory, and the destination
  1572. // directory already exists, then we use that.
  1573. if (empty($destination) && $site_root && $in_site_directory) {
  1574. $create_dir = drush_get_option('use-site-dir', FALSE);
  1575. $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root, $create_dir);
  1576. }
  1577. // Attempt 2: If the destination directory already exists for sites/all,
  1578. // then we use that.
  1579. if (empty($destination) && $drupal_root) {
  1580. $destination = pm_dl_destination_lookup($type, $drupal_root, $sites_all);
  1581. }
  1582. // Attempt 3: If a specific (non default) site directory exists and
  1583. // sites/all does not exist, then we create destination in the site
  1584. // specific directory.
  1585. if (empty($destination) && $site_root && $site_root !== 'sites/default' && is_dir($full_site_root) && !is_dir($sites_all)) {
  1586. $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root, TRUE);
  1587. }
  1588. // Attempt 4: If sites/all exists, then we create destination in the
  1589. // sites/all directory.
  1590. if (empty($destination) && is_dir($sites_all)) {
  1591. $destination = pm_dl_destination_lookup($type, $drupal_root, $sites_all, TRUE);
  1592. }
  1593. // Attempt 5: If site directory exists (even default), then we create
  1594. // destination in the this directory.
  1595. if (empty($destination) && $site_root && is_dir($full_site_root)) {
  1596. $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root, TRUE);
  1597. }
  1598. }
  1599. // Attempt 6: If we didn't find a valid directory yet (or we somehow found
  1600. // one that doesn't exist) we always fall back to the current directory.
  1601. if (empty($destination) || !is_dir($destination)) {
  1602. $destination = drush_cwd();
  1603. }
  1604. return $destination;
  1605. }
  1606. /*
  1607. * Pick most appropriate release from XML list.
  1608. *
  1609. * @param array $request
  1610. * An array of version specifications as returned by pm_parse_project_version().
  1611. * @param resource $xml
  1612. * A handle to the XML document.
  1613. */
  1614. function pm_parse_release($request, $xml) {
  1615. if (!empty($request['version'])) {
  1616. $releases = $xml->xpath("/project/releases/release[status='published'][version='" . $request['version'] . "']");
  1617. if (empty($releases)) {
  1618. drush_log(dt("Could not locate specified project version, downloading latest stable version"), 'warning');
  1619. }
  1620. }
  1621. // If that did not work, we will get the first published release for the
  1622. // recommended major version.
  1623. if (empty($releases)) {
  1624. if ($recommended_major = $xml->xpath("/project/recommended_major")) {
  1625. $xpath_releases = "/project/releases/release[status='published'][version_major=" . (string)$recommended_major[0] . "]";
  1626. $releases = @$xml->xpath($xpath_releases);
  1627. }
  1628. }
  1629. // If there are recommended releases (no 'version_extra' elements), then use
  1630. // only recommended releases. Otherwise, use all; in this case, the
  1631. // recommended release defaults to the latest published release with the
  1632. // right recommended major version number.
  1633. $recommended_releases = array();
  1634. if (!empty($releases)) {
  1635. foreach ($releases as $one_release) {
  1636. if (!array_key_exists('version_extra', $one_release)) {
  1637. $recommended_releases[] = $one_release;
  1638. }
  1639. }
  1640. }
  1641. if (!empty($recommended_releases)) {
  1642. $releases = $recommended_releases;
  1643. }
  1644. $release_type = 'recommended';
  1645. if (drush_get_option('dev', FALSE)) {
  1646. $releases = @$xml->xpath("/project/releases/release[status='published'][version_extra='dev']");
  1647. $release_type = 'development';
  1648. }
  1649. if (drush_get_option('select', FALSE) || empty($releases)) {
  1650. if (empty($releases)) {
  1651. drush_print(dt('There is no !type release for project !project.', array('!type' => $release_type, '!project' => $request['name'])));
  1652. }
  1653. $options = _drush_pm_download_releases_choice($xml, $request['name'], drush_get_option('all', FALSE), drush_get_option('dev', FALSE));
  1654. $choice = drush_choice($options, dt('Choose one of the available releases for project !project:', array('!project' => $request['name'])));
  1655. if ($choice) {
  1656. $releases = $xml->xpath("/project/releases/release[status='published'][version='" . $choice . "']");
  1657. }
  1658. else {
  1659. return FALSE;
  1660. }
  1661. }
  1662. // First published release for the recommended major version is just the
  1663. // first value in $releases.
  1664. return (array)$releases[0];
  1665. }
  1666. /**
  1667. * Parse out the project name and version and return as a structured array
  1668. *
  1669. * @param $requests an array of project names
  1670. */
  1671. function pm_parse_project_version($requests) {
  1672. $requestdata = array();
  1673. $drupal_version_default = drush_get_context('DRUSH_DRUPAL_MAJOR_VERSION', drush_get_option('default-major',7)) . '.x';
  1674. $drupal_bootstrap = drush_get_context('DRUSH_BOOTSTRAP_PHASE') > 0;
  1675. foreach($requests as $request) {
  1676. $drupal_version = $drupal_version_default;
  1677. $project_version = NULL;
  1678. $version = NULL;
  1679. $project = $request;
  1680. // project-HEAD or project-5.x-1.0-beta
  1681. // '5.x-' is optional, as is '-beta'
  1682. preg_match('/-+(HEAD|(?:(\d+\.x)-+)?(\d+\.[\dx]+.*))$/', $request, $matches);
  1683. if (isset($matches[1])) {
  1684. // The project is whatever we have prior to the version part of the request.
  1685. $project = trim(substr($request, 0, strlen($request) - strlen($matches[0])), ' -');
  1686. if ($matches[1] == 'HEAD' || $matches[2] == 'HEAD') {
  1687. drush_log('DRUSH_PM_HEAD', 'Can\'t download HEAD releases because Drupal.org project information only provides for numbered release nodes.', 'warning');
  1688. continue;
  1689. }
  1690. if (!empty($matches[2])) {
  1691. // We have a specified Drupal core version.
  1692. $drupal_version = trim($matches[2], '-.');
  1693. }
  1694. if (!empty($matches[3])) {
  1695. if (!$drupal_bootstrap && empty($matches[2]) && $project != 'drupal') {
  1696. // We are not working on a bootstrapped site, and the project is not Drupal itself,
  1697. // so we assume this value is the Drupal core version and we want the stable project.
  1698. $drupal_version = trim($matches[3], '-.');
  1699. }
  1700. else {
  1701. // We are working on a bootstrapped site, or the user specified a Drupal version,
  1702. // so this value must be a specified project version.
  1703. $project_version = trim($matches[3], '-.');
  1704. if (substr($project_version, -1, 1) == 'x') {
  1705. // If a dev branch was requested, we add a -dev suffix.
  1706. $project_version .= '-dev';
  1707. }
  1708. }
  1709. }
  1710. }
  1711. // special checking for 'drupal-VERSION', == drupal latest stable release
  1712. elseif ((substr($request,0,7) == 'drupal-') && (is_numeric(substr($request,7)))) {
  1713. $project = 'drupal';
  1714. $drupal_version = substr($request,7) . '.x';
  1715. $project_version = $version;
  1716. }
  1717. if ($project_version) {
  1718. if ($project == 'drupal') {
  1719. // For project Drupal, ensure the major version branch is correct, so
  1720. // we can locate the requested or stable release for that branch.
  1721. $project_version_array = explode('.', $project_version);
  1722. $drupal_version = $project_version_array[0] . '.x';
  1723. // We use the project version only, since it is core.
  1724. $version = $project_version;
  1725. }
  1726. else {
  1727. // For regular projects the version string includes the Drupal core version.
  1728. $version = $drupal_version . '-' . $project_version;
  1729. }
  1730. }
  1731. $requestdata[$project] = array(
  1732. 'name' => $project,
  1733. 'version' => $version,
  1734. 'drupal_version' => $drupal_version,
  1735. 'project_version' => $project_version,
  1736. );
  1737. }
  1738. return $requestdata;
  1739. }
  1740. function pm_project_types() {
  1741. // Lookup the 'Project type' vocabulary to some standard strings.
  1742. $types = array(
  1743. 'core' => 'Drupal core',
  1744. 'profile' => 'Installation profiles',
  1745. 'module' => 'Modules',
  1746. 'theme' => 'Themes',
  1747. 'theme engine' => 'Theme engines',
  1748. 'translation' => 'Translations'
  1749. );
  1750. return $types;
  1751. }
  1752. /**
  1753. * Used by dl and updatecode commands to determine how to download/checkout new projects and acquire updates to projects.
  1754. */
  1755. function pm_drush_engine_package_handler() {
  1756. return array(
  1757. 'wget' => array(),
  1758. 'cvs' => array(
  1759. 'options' => array(
  1760. 'package-handler=cvs' => 'Use CVS to checkout and update projects.',
  1761. ),
  1762. 'sub-options' => array(
  1763. 'package-handler=cvs' => array(
  1764. 'cvsparams' => 'Add options to the `cvs` program',
  1765. 'cvsmethod' => 'Force cvs updates or checkouts (checkout is default unless the directory is managed by a supported version control system).',
  1766. 'cvscredentials' => 'A username and password that is sent for cvs checkout command. Defaults to anonymous:anonymous',
  1767. ),
  1768. ),
  1769. 'examples' => array(
  1770. 'drush [command] cck --cvscredentials=\"name:password\"' => 'Checkout should use these credentials.',
  1771. 'drush [command] cck --cvsparams=\"-C\"' => 'Overwrite all local changes (Quotes are required).',
  1772. '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.',
  1773. ),
  1774. ),
  1775. 'git_drupalorg' => array(
  1776. 'options' => array(
  1777. 'package-handler=git_drupalorg' => 'Use git.drupal.org to checkout and update projects.',
  1778. ),
  1779. 'sub-options' => array(
  1780. 'package-handler=git_drupalorg' => array(
  1781. 'gitusername' => 'Your git username as shown on user/[uid]/edit/git. Typically, this is set this in drushrc.php. Omitting this prevents users from pushing changes back to git.drupal.org.',
  1782. 'gitsubmodule' => 'Use git submodules for checking out new projects. Existing git checkouts are unaffected, and will continue to (not) use submodules regardless of this setting.',
  1783. 'gitcheckoutparams' => 'Add options to the `git checkout` command.',
  1784. 'gitcloneparams' => 'Add options to the `git clone` command.',
  1785. 'gitfetchparams' => 'Add options to the `git fetch` command.',
  1786. 'gitpullparams' => 'Add options to the `git pull` command.',
  1787. 'gitsubmoduleaddparams' => 'Add options to the `git submodule add` command.',
  1788. 'cache' => 'Use a local git cache and clone projects using --reference.',
  1789. ),
  1790. ),
  1791. ),
  1792. );
  1793. }
  1794. /**
  1795. * Integration with VCS in order to easily commit your changes to projects.
  1796. */
  1797. function pm_drush_engine_version_control() {
  1798. return array(
  1799. 'backup' => array(
  1800. 'options' => array(
  1801. 'version-control=backup' => 'Default engine. Backup all project files before updates.',
  1802. ),
  1803. 'sub-options' => array(
  1804. 'version-control=backup' => array(
  1805. 'no-backup' => 'Do not perform backups.',
  1806. 'backup-dir' => 'Specify a directory to backup projects into. Defaults to drush-backups within the home directory of the user running the command. It is forbidden to specify a directory inside your drupal root.',
  1807. ),
  1808. ),
  1809. ),
  1810. 'bzr' => array(
  1811. 'signature' => 'bzr root %s',
  1812. 'options' => array(
  1813. 'version-control=bzr' => 'Quickly add/remove/commit your project changes to Bazaar.',
  1814. ),
  1815. 'sub-options' => array(
  1816. 'version-control=bzr' => array(
  1817. 'bzrsync' => 'Automatically add new files to the Bazaar repository and remove deleted files. Caution.',
  1818. 'bzrcommit' => 'Automatically commit changes to Bazaar repository. You must also usw the --bzrsync option.',
  1819. ),
  1820. 'bzrcommit' => array(
  1821. 'bzrmessage' => 'Override default commit message which is: Drush automatic commit. Project <name> <type> Command: <the drush command line used>',
  1822. )
  1823. ),
  1824. 'examples' => array(
  1825. 'drush dl cck --version-control=bzr --bzrsync --bzrcommit' => 'Download the cck project and then add it and commit it to Bazaar.'
  1826. ),
  1827. ),
  1828. 'svn' => array(
  1829. 'signature' => 'svn info %s',
  1830. 'options' => array(
  1831. 'version-control=svn' => 'Quickly add/remove/commit your project changes to Subversion.',
  1832. ),
  1833. 'sub-options' => array(
  1834. 'version-control=svn' => array(
  1835. 'svnsync' => 'Automatically add new files to the SVN repository and remove deleted files. Caution.',
  1836. 'svncommit' => 'Automatically commit changes to SVN repository. You must also using the --svnsync option.',
  1837. 'svnstatusparams' => "Add options to the 'svn status' command",
  1838. 'svnaddparams' => 'Add options to the `svn add` command',
  1839. 'svnremoveparams' => 'Add options to the `svn remove` command',
  1840. 'svnrevertparams' => 'Add options to the `svn revert` command',
  1841. 'svncommitparams' => 'Add options to the `svn commit` command',
  1842. ),
  1843. 'svncommit' => array(
  1844. 'svnmessage' => 'Override default commit message which is: Drush automatic commit: <the drush command line used>',
  1845. )
  1846. ),
  1847. 'examples' => array(
  1848. 'drush [command] cck --svncommitparams=\"--username joe\"' => 'Commit changes as the user \'joe\' (Quotes are required).'
  1849. ),
  1850. ),
  1851. );
  1852. }
  1853. /**
  1854. * Interface for version control systems.
  1855. * We use a simple object layer because we conceivably need more than one
  1856. * loaded at a time.
  1857. */
  1858. interface drush_pm_version_control {
  1859. function pre_update(&$project);
  1860. function rollback($project);
  1861. function post_update($project);
  1862. function post_download($project);
  1863. static function reserved_files();
  1864. }
  1865. /**
  1866. * A simple factory function that tests for version control systems, in a user
  1867. * specified order, and return the one that appears to be appropriate for a
  1868. * specific directory.
  1869. */
  1870. function drush_pm_include_version_control($directory = '.') {
  1871. $version_control_engines = drush_get_engines('version_control');
  1872. $version_controls = drush_get_option('version-control', FALSE);
  1873. // If no version control was given, use a list of defaults.
  1874. if (!$version_controls) {
  1875. // Backup engine is the last option.
  1876. $version_controls = array_reverse(array_keys($version_control_engines));
  1877. }
  1878. else {
  1879. $version_controls = array($version_controls);
  1880. }
  1881. // Find the first valid engine in the list, checking signatures if needed.
  1882. $engine = FALSE;
  1883. while (!$engine && count($version_controls)) {
  1884. $version_control = array_shift($version_controls);
  1885. if (isset($version_control_engines[$version_control])) {
  1886. if (!empty($version_control_engines[$version_control]['signature'])) {
  1887. drush_log(dt('Verifying signature for !vcs version control engine.', array('!vcs' => $version_control)), 'debug');
  1888. if (drush_shell_exec($version_control_engines[$version_control]['signature'], $directory)) {
  1889. $engine = $version_control;
  1890. }
  1891. }
  1892. else {
  1893. $engine = $version_control;
  1894. }
  1895. }
  1896. }
  1897. if (!$engine) {
  1898. 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)));
  1899. }
  1900. drush_include_engine('version_control', $version_control);
  1901. $class = 'drush_pm_version_control_' . $engine;
  1902. $instance = new $class();
  1903. $instance->engine = $engine;
  1904. return $instance;
  1905. }
  1906. /**
  1907. * Implementation of drush_COMMAND_validate().
  1908. */
  1909. function drush_pm_download_validate() {
  1910. // Validate the user specified destination directory.
  1911. $destination = drush_get_option('destination');
  1912. if (!empty($destination)) {
  1913. $destination = rtrim($destination, DIRECTORY_SEPARATOR);
  1914. if (!is_dir($destination)) {
  1915. drush_print(dt("The directory !destination does not exist.", array('!destination' => $destination)));
  1916. if (!drush_get_context('DRUSH_SIMULATE')) {
  1917. if (drush_confirm(dt('Would you like to create it?'))) {
  1918. drush_mkdir($destination);
  1919. }
  1920. if (!is_dir($destination)) {
  1921. return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Unable to create destination directory !destination.', array('!destination' => $destination)));
  1922. }
  1923. }
  1924. }
  1925. if (!is_writable($destination)) {
  1926. return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Destination directory !destination is not writable.', array('!destination' => $destination)));
  1927. }
  1928. // Ignore --use-site-dir, if given.
  1929. if (drush_get_option('use-site-dir', FALSE)) {
  1930. drush_set_option('use-site-dir', FALSE);
  1931. }
  1932. }
  1933. // Validate --variant or enforce a sane default.
  1934. $variant = drush_get_option('variant', FALSE);
  1935. if ($variant) {
  1936. if (!in_array($variant, array('full', 'projects', 'profile-only'))) {
  1937. drush_log(dt('Unknown variant !variant. Valid values: !variations', array('!variant' => $variant, '!variations' => implode(', ', $variations))), 'error');
  1938. }
  1939. }
  1940. // 'full' and 'projects' variants are only valid for wget package handler.
  1941. $package_handler = drush_get_option('package-handler', 'wget');
  1942. if (($package_handler != 'wget') && ($variant != 'profile-only')) {
  1943. $new_variant = 'profile-only';
  1944. if ($variant) {
  1945. drush_log(dt('Variant !variant is incompatible with !ph package-handler.', array('!variant' => $variant, '!ph' => $package_handler)), 'warning');
  1946. }
  1947. }
  1948. // If we are working on a drupal root, full variant is not an option.
  1949. else if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) {
  1950. if ((!$variant) || (($variant == 'full') && (!isset($new_variant)))) {
  1951. $new_variant = 'projects';
  1952. }
  1953. if ($variant == 'full') {
  1954. drush_log(dt('Variant full is not a valid option within a Drupal root.'), 'warning');
  1955. }
  1956. }
  1957. if (isset($new_variant)) {
  1958. drush_set_option('variant', $new_variant);
  1959. if ($variant) {
  1960. drush_log(dt('Switching to --variant=!variant.', array('!variant' => $new_variant)), 'ok');
  1961. }
  1962. }
  1963. // Validate package-handler.
  1964. $package_handler = drush_get_option('package-handler', 'wget');
  1965. drush_include_engine('package_handler', $package_handler);
  1966. // Return value not currently used.
  1967. return package_handler_validate();
  1968. }
  1969. /**
  1970. * Download the release history xml for the specified request.
  1971. */
  1972. function _drush_pm_get_release_history_xml($request) {
  1973. // Don't rely on UPDATE_DEFAULT_URL since perhaps we are not fully
  1974. // bootstrapped.
  1975. $url = drush_get_option('source', 'http://updates.drupal.org/release-history') . '/' . $request['name'] . '/' . $request['drupal_version'];
  1976. drush_log('Downloading release history from ' . $url);
  1977. if ($path = drush_download_file($url, drush_tempnam($request['name']), drush_get_option('cache-duration-releasexml', 24*3600))) {
  1978. $xml = simplexml_load_file($path);
  1979. }
  1980. if (!$xml) {
  1981. // We are not getting here since drupal.org always serves an XML response.
  1982. return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Could not download project status information from !url', array('!url' => $url)));
  1983. }
  1984. if ($error = $xml->xpath('/error')) {
  1985. // Don't set an error here since it stops processing during site-upgrade.
  1986. drush_log($error[0], 'warning'); // 'DRUSH_PM_COULD_NOT_LOAD_UPDATE_FILE',
  1987. return FALSE;
  1988. }
  1989. // Unpublished project?
  1990. $project_status = $xml->xpath('/project/project_status');
  1991. if ($project_status[0][0] == 'unpublished') {
  1992. return drush_set_error('DRUSH_PM_PROJECT_UNPUBLISHED', dt("Project !project is unpublished and has no releases available.", array('!project' => $request['name'])), 'warning');
  1993. }
  1994. return $xml;
  1995. }
  1996. /**
  1997. * Command callback. Download Drupal core or any project.
  1998. */
  1999. function drush_pm_download() {
  2000. $package_handler = drush_get_option('package-handler', 'wget');
  2001. drush_include_engine('package_handler', $package_handler);
  2002. if (!$requests = _convert_csv_to_array(func_get_args())) {
  2003. $requests = array('drupal');
  2004. }
  2005. // Parse out project name and version.
  2006. $requests = pm_parse_project_version($requests);
  2007. // Get release history for each request and download the project.
  2008. $project_types = pm_project_types();
  2009. $project_types_xpath = '(value="' . implode('" or value="', $project_types) . '")';
  2010. foreach ($requests as $name => $request) {
  2011. $xml = _drush_pm_get_release_history_xml($request);
  2012. if (!$xml) {
  2013. continue;
  2014. }
  2015. // Identify the most appropriate release.
  2016. $release = pm_parse_release($request, $xml);
  2017. if (!$release) {
  2018. continue;
  2019. }
  2020. // Determine what type of project we are to download.
  2021. $request['project_type'] = 'module';
  2022. if ($types = $xml->xpath('/project/terms/term[name="Projects" and ' . $project_types_xpath . ']')) {
  2023. $request['project_type'] = array_search($types[0]->value, $project_types);
  2024. }
  2025. if ($request['project_type'] == 'translation') {
  2026. drush_set_error('DRUSH_PM_DOWNLOAD_TRANSLATIONS_FORBIDDEN', dt('Drush has dropped support for downloading translation projects. See l10n_update and l10n_install projects.'));
  2027. continue;
  2028. }
  2029. // Determine the name of the directory that will contain the project.
  2030. // We face here all the asymetries to make it smooth for package handlers.
  2031. // For Drupal core: --drupal-project-rename or drupal-x.y
  2032. if ($request['project_type'] == 'core') {
  2033. // Avoid downloading core into existing core.
  2034. if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) {
  2035. if (strpos(realpath(drush_get_option('destination')), DRUPAL_ROOT) !== FALSE) {
  2036. drush_log(dt('It\'s forbidden to download !project core into an existing core.', array('!project' => $request['name'])), 'error');
  2037. continue;
  2038. }
  2039. }
  2040. if ($rename = drush_get_option('drupal-project-rename', FALSE)) {
  2041. if ($rename === TRUE) {
  2042. $request['project_dir'] = 'drupal';
  2043. }
  2044. else {
  2045. $request['project_dir'] = $rename;
  2046. }
  2047. }
  2048. else {
  2049. // Set to drupal-x.y, the expected name for .tar.gz contents.
  2050. // Explicitly needed for cvs package handler.
  2051. $request['project_dir'] = strtolower(strtr($release['name'], ' ', '-'));
  2052. }
  2053. }
  2054. // For the other project types we want the project name. Including core
  2055. // variant for profiles. Note those come with drupal-x.y in the .tar.gz.
  2056. else {
  2057. $request['project_dir'] = $request['name'];
  2058. }
  2059. // Download the project to a temporary location.
  2060. $request['base_project_path'] = drush_tempdir();
  2061. $request['full_project_path'] = $request['base_project_path'] .'/'. $request['project_dir'];
  2062. drush_log(dt('Downloading project !name to !dir ...', array('!name' => $request['name'], '!dir' => $request['base_project_path'])));
  2063. if (!package_handler_download_project($request, $release)) {
  2064. drush_log('Error downloading '.$request['name']);
  2065. continue;
  2066. }
  2067. // Determine the install location for the project. User provided
  2068. // --destination has preference.
  2069. $destination = drush_get_option('destination');
  2070. if (!empty($destination)) {
  2071. $request['project_install_location'] = drush_get_context('DRUSH_DRUPAL_ROOT') . '/' .$destination;
  2072. $request['project_install_location'] = realpath($destination);
  2073. }
  2074. else {
  2075. $request['project_install_location'] = pm_dl_destination($request['project_type']);
  2076. }
  2077. // If user did not provide --destination, then call the
  2078. // download-destination-alter hook to give the chance to any commandfiles
  2079. // to adjust the install location or abort it.
  2080. if (empty($destination)) {
  2081. $result = drush_command_invoke_all_ref('drush_pm_download_destination_alter', $request, $release);
  2082. if (array_search(FALSE, $result, TRUE) !== FALSE) {
  2083. return FALSE;
  2084. }
  2085. }
  2086. // Load version control engine and detect if (the parent directory of) the
  2087. // project install location is under a vcs.
  2088. if (!$version_control = drush_pm_include_version_control($request['project_install_location'])) {
  2089. continue;
  2090. }
  2091. // Check for drush self update
  2092. if ($request['project_install_location'] == DRUSH_BASE_PATH && $request['name'] == 'drush') {
  2093. if (($backup_dir = drush_prepare_backup_dir()) === FALSE) {
  2094. return FALSE;
  2095. }
  2096. // Move the running drush out of the way
  2097. $drush_backup = $backup_dir . "/drush";
  2098. if (drush_move_dir(DRUSH_BASE_PATH, $drush_backup, TRUE) == FALSE) {
  2099. return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Failed to move drush directory !drush to !backup_target', array('!drush' => DRUSH_BASE_PATH, '!backup_target' => $drush_backup)));
  2100. }
  2101. else {
  2102. drush_log(dt("drush backed up to !targetdir", array('!targetdir' => $drush_backup)), "ok");
  2103. }
  2104. }
  2105. else {
  2106. // For all other projects, the final project install location will go in the project_dir.
  2107. $request['project_install_location'] .= '/' . $request['project_dir'];
  2108. }
  2109. // Check if install location already exists.
  2110. if (is_dir($request['project_install_location'])) {
  2111. if (!drush_confirm(dt('Install location !location already exists. Do you want to overwrite it?', array('!location' => $request['project_install_location'])))) {
  2112. drush_log(dt("Skip installation of !project to !dest.", array('!project' => $request['name'], '!dest' => $request['project_install_location'])), 'warning');
  2113. continue;
  2114. }
  2115. }
  2116. // Move the project to the install location.
  2117. if (drush_move_dir($request['full_project_path'], $request['project_install_location'], TRUE)) {
  2118. drush_log(dt("Project !project (!version) downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])), 'success');
  2119. $request['base_project_path'] = basename($request['project_install_location']);
  2120. $request['full_project_path'] = $request['project_install_location'];
  2121. if ($request['project_install_location'] == DRUSH_BASE_PATH) {
  2122. drush_log(dt("Drush successfully updated to version !version.", array('!version' => $release['version'])), 'success');
  2123. }
  2124. }
  2125. else {
  2126. drush_log(dt("Project !project (!version) could not be downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])), 'error');
  2127. continue;
  2128. }
  2129. // Post download actions.
  2130. package_handler_post_download($request);
  2131. drush_command_invoke_all('drush_pm_post_download', $request, $release);
  2132. $version_control->post_download($request);
  2133. // Print release notes if --notes option is set.
  2134. if (drush_get_option('notes') && !drush_get_context('DRUSH_PIPE')) {
  2135. _drush_pm_releasenotes(array($name . '-' . $release['version']), FALSE);
  2136. }
  2137. // Inform the user about available modules a/o themes in the downloaded project.
  2138. drush_pm_extensions_in_project($request);
  2139. }
  2140. }
  2141. /**
  2142. * Implementation of hook_drush_pm_download_destination_alter().
  2143. *
  2144. * Built-in download-destination-alter hook. This particular version of
  2145. * the hook will move modules that contain only drush commands to
  2146. * /usr/share/drush/commands if it exists, or $HOME/.drush if the
  2147. * site-wide location does not exist.
  2148. */
  2149. function pm_drush_pm_download_destination_alter(&$project, $release) {
  2150. // A module is a pure drush command if it has no .module and contain
  2151. // .drush.inc files.
  2152. if ($project['project_type'] == 'module') {
  2153. $module_files = drush_scan_directory($project['full_project_path'], '/.*\.module/');
  2154. if (empty($module_files)) {
  2155. if ($project['name'] == 'drush') {
  2156. // $project['version'] is empty here, so compose the version from the $release structure.
  2157. $drush_release_version = $release['version_major'] . "." . $release['version_patch'] . (empty($release['version_extra']) ? '' : ('-' . $release['version_extra']));
  2158. if(($project['project_install_location'] != DRUSH_BASE_PATH) && ($release['version_major'] >= '4')) {
  2159. $backup_dir = drush_preflight_backup_dir();
  2160. if (drush_confirm(dt('Would you like to back up your current drush version !currentversion to !backup and replace it with drush !version?', array('!version' => $drush_release_version, '!backup' => $backup_dir, '!currentversion' => DRUSH_VERSION)))) {
  2161. $project['project_install_location'] = DRUSH_BASE_PATH;
  2162. }
  2163. else {
  2164. // If we are called via 'drush self-update', then "no" means "do nothing".
  2165. // If we are called via 'drush dl drush', then "no" means "download to cwd".
  2166. if (drush_get_option('self-update', FALSE)) {
  2167. return drush_user_cancel();
  2168. }
  2169. }
  2170. }
  2171. }
  2172. else {
  2173. $drush_command_files = drush_scan_directory($project['full_project_path'], '/.*\.drush.inc/');
  2174. if (!empty($drush_command_files)) {
  2175. $install_dir = drush_get_context('SHARE_PREFIX', '/usr') . '/share/drush/commands';
  2176. if (!is_dir($install_dir) || !is_writable($install_dir)) {
  2177. $install_dir = drush_server_home() . '/.drush';
  2178. }
  2179. // Make the .drush dir if it does not already exist.
  2180. if (!is_dir($install_dir)) {
  2181. drush_mkdir($install_dir);
  2182. }
  2183. // Change the location if the mkdir worked.
  2184. if (is_dir($install_dir)) {
  2185. $project['project_install_location'] = $install_dir;
  2186. }
  2187. }
  2188. }
  2189. }
  2190. }
  2191. }
  2192. /**
  2193. * Update the locked status of all of the candidate projects
  2194. * to be updated.
  2195. *
  2196. * @param array &$projects
  2197. * The projects array from pm_updatecode. $project['locked'] will
  2198. * be set for every file where a persistent lockfile can be found.
  2199. * The 'lock' and 'unlock' operations are processed first.
  2200. * @param array $projects_to_lock
  2201. * A list of projects to create peristent lock files for
  2202. * @param array $projects_to_unlock
  2203. * A list of projects to clear the persistent lock on
  2204. * @param string $lock_message
  2205. * The reason the project is being locked; stored in the lockfile.
  2206. *
  2207. * @return array
  2208. * A list of projects that are locked.
  2209. */
  2210. function drush_pm_update_lock(&$projects, $projects_to_lock, $projects_to_unlock, $lock_message = NULL) {
  2211. $locked_result = array();
  2212. // Warn about ambiguous lock / unlock values
  2213. if ($projects_to_lock == array('1')) {
  2214. $projects_to_lock = array();
  2215. drush_log(dt('Ignoring --lock with no value.'), 'warning');
  2216. }
  2217. if ($projects_to_unlock == array('1')) {
  2218. $projects_to_unlock = array();
  2219. drush_log(dt('Ignoring --unlock with no value.'), 'warning');
  2220. }
  2221. // Log if we are going to lock or unlock anything
  2222. if (!empty($projects_to_unlock)) {
  2223. drush_log(dt('Unlocking !projects', array('!projects' => implode(',', $projects_to_unlock))), 'ok');
  2224. }
  2225. if (!empty($projects_to_lock)) {
  2226. drush_log(dt('Locking !projects', array('!projects' => implode(',', $projects_to_lock))), 'ok');
  2227. }
  2228. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  2229. foreach ($projects as $name => $project) {
  2230. if ($name == 'drupal') {
  2231. continue;
  2232. }
  2233. $message = NULL;
  2234. $lockfile = $drupal_root . '/' . $project['path'] . '/.drush-lock-update';
  2235. // Remove the lock file if the --unlock option was specified
  2236. if (((in_array($name, $projects_to_unlock)) || (in_array('all', $projects_to_unlock))) && (file_exists($lockfile))) {
  2237. drush_op('unlink', $lockfile);
  2238. }
  2239. // Create the lock file if the --lock option was specified
  2240. if ((in_array($name, $projects_to_lock)) || (in_array('all', $projects_to_lock))) {
  2241. drush_op('file_put_contents', $lockfile, $lock_message != NULL ? $lock_message : "Locked via drush.");
  2242. // Note that the project is locked. This will work even if we are simulated,
  2243. // or if we get permission denied from the file_put_contents.
  2244. // If the lock is -not- simulated or transient, then the lock message will be
  2245. // read from the lock file below.
  2246. $message = drush_get_context('DRUSH_SIMULATE') ? 'Simulated lock.' : 'Transient lock.';
  2247. }
  2248. // If the persistent lock file exists, then mark the project as locked.
  2249. if (file_exists($lockfile)) {
  2250. $message = trim(file_get_contents($lockfile));
  2251. }
  2252. // If there is a message set, then mark the project as locked.
  2253. if (isset($message)) {
  2254. $projects[$name]['locked'] = !empty($message) ? $message : "Locked.";
  2255. $locked_result[$name] = $project;
  2256. }
  2257. }
  2258. return $locked_result;
  2259. }
  2260. function _drush_pm_extension_cache_file() {
  2261. return drush_server_home() . "/.drush/drush-extension-cache.inc";
  2262. }
  2263. function _drush_pm_get_extension_cache() {
  2264. $extension_cache = array();
  2265. $cache_file = _drush_pm_extension_cache_file();
  2266. if (file_exists($cache_file)) {
  2267. include $cache_file;
  2268. }
  2269. if (!array_key_exists('extension-map', $extension_cache)) {
  2270. $extension_cache['extension-map'] = array();
  2271. }
  2272. return $extension_cache;
  2273. }
  2274. function drush_pm_lookup_extension_in_cache($extension) {
  2275. $result = NULL;
  2276. $extension_cache = _drush_pm_get_extension_cache();
  2277. if (!empty($extension_cache) && array_key_exists($extension, $extension_cache)) {
  2278. $result = $extension_cache[$extension];
  2279. }
  2280. return $result;
  2281. }
  2282. function drush_pm_put_extension_cache($extension_cache) {
  2283. }
  2284. function drush_pm_cache_project_extensions($project, $found) {
  2285. $extension_cache = _drush_pm_get_extension_cache();
  2286. foreach($found as $extension) {
  2287. // Simple cache does not handle conflicts
  2288. // We could keep an array of projects, and count
  2289. // how many times each one has been seen...
  2290. $extension_cache[$extension] = $project['name'];
  2291. }
  2292. drush_pm_put_extension_cache($extension_cache);
  2293. }
  2294. /**
  2295. * Print out all extensions (modules/themes/profiles) found in specified project.
  2296. *
  2297. * Find .info files in the project path and identify modules, themes and
  2298. * profiles. It handles two kind of projects: drupal core/profiles and
  2299. * modules/themes.
  2300. * It does nothing with theme engine projects.
  2301. */
  2302. function drush_pm_extensions_in_project($project) {
  2303. // Mask for drush_scan_directory, to avoid tests directories.
  2304. $nomask = array('.', '..', 'CVS', 'tests');
  2305. // Drupal core and profiles can contain modules, themes and profiles.
  2306. if (in_array($project['project_type'], array('core', 'profile'))) {
  2307. $found = array('profile' => array(), 'theme' => array(), 'module' => array());
  2308. // Find all of the .info files
  2309. foreach (drush_scan_directory($project['project_install_location'], "/.*\.info$/", $nomask) as $filename => $info) {
  2310. // Find the project type corresponding the .info file.
  2311. // (Only drupal >=7.x has .info for .profile)
  2312. $base = dirname($filename) . '/' . $info->name;
  2313. if (is_file($base . '.module')) {
  2314. $found['module'][] = $info->name;
  2315. }
  2316. else if (is_file($base . '.profile')) {
  2317. $found['profile'][] = $info->name;
  2318. }
  2319. else {
  2320. $found['theme'][] = $info->name;
  2321. }
  2322. }
  2323. // Special case: find profiles for drupal < 7.x (no .info)
  2324. if ($project['drupal_version'][0] < 7) {
  2325. foreach (drush_scan_directory($project['project_install_location'], "/.*\.profile$/", $nomask) as $filename => $info) {
  2326. $found['profile'][] = $info->name;
  2327. }
  2328. }
  2329. // Log results.
  2330. $msg = "Project !project contains:\n";
  2331. $args = array('!project' => $project['name']);
  2332. foreach (array_keys($found) as $type) {
  2333. if ($count = count($found[$type])) {
  2334. $msg .= " - !count_$type !type_$type: !found_$type\n";
  2335. $args += array("!count_$type" => $count, "!type_$type" => $type, "!found_$type" => implode(', ', $found[$type]));
  2336. if ($count > 1) {
  2337. $args["!type_$type"] = $type.'s';
  2338. }
  2339. }
  2340. }
  2341. drush_log(dt($msg, $args), 'success');
  2342. drush_print_pipe(call_user_func_array('array_merge', array_values($found)));
  2343. }
  2344. // Modules and themes can only contain other extensions of the same type.
  2345. elseif (in_array($project['project_type'], array('module', 'theme'))) {
  2346. // Find all of the .info files
  2347. $found = array();
  2348. foreach (drush_scan_directory($project['project_install_location'], "/.*\.info$/", $nomask) as $filename => $info) {
  2349. $found[] = $info->name;
  2350. }
  2351. // Log results.
  2352. // If there is only one module / theme in the project, only print out
  2353. // the message if is different than the project name.
  2354. if (count($found) == 1) {
  2355. if ($found[0] != $project['name']) {
  2356. $msg = "Project !project contains a !type named !found.";
  2357. }
  2358. }
  2359. // If there are multiple modules or themes in the project, list them all.
  2360. else {
  2361. $msg = "Project !project contains !count !types: !found.";
  2362. }
  2363. if (isset($msg)) {
  2364. drush_print(dt($msg, array('!project' => $project['name'], '!count' => count($found), '!type' => $project['project_type'], '!found' => implode(', ', $found))));
  2365. }
  2366. drush_print_pipe($found);
  2367. // Cache results.
  2368. drush_pm_cache_project_extensions($project, $found);
  2369. }
  2370. }
  2371. /**
  2372. * Return an array of empty directories.
  2373. *
  2374. * Walk a directory and return an array of subdirectories that are empty. Will
  2375. * return the given directory if it's empty.
  2376. * If a list of items to exclude is provided, subdirectories will be condidered
  2377. * empty even if they include any of the items in the list.
  2378. *
  2379. * @param string $dir
  2380. * Path to the directory to work in.
  2381. * @param array $exclude
  2382. * Array of files or directory to exclude in the check.
  2383. */
  2384. function drush_find_empty_directories($dir, $exclude = array()) {
  2385. $to_exclude = array_merge(array('.', '..'), $exclude);
  2386. $empty = array();
  2387. $dir_is_empty = TRUE;
  2388. if ($dh = opendir($dir)) {
  2389. while (($file = readdir($dh)) !== FALSE) {
  2390. if (in_array($file, $to_exclude)) {
  2391. continue;
  2392. }
  2393. if (is_dir($dir .'/'. $file)) {
  2394. $subdir = $dir .'/'. $file;
  2395. $subdir_is_empty = TRUE;
  2396. if ($dh2 = opendir($subdir)) {
  2397. while (($file2 = readdir($dh2)) !== FALSE) {
  2398. if (in_array($file2, $to_exclude)) {
  2399. continue;
  2400. }
  2401. $subdir_is_empty = FALSE;
  2402. if (is_dir($subdir . '/'. $file2)) {
  2403. $empty2 = drush_find_empty_directories($subdir . '/'. $file2, $exclude);
  2404. $empty = array_merge($empty, $empty2);
  2405. }
  2406. }
  2407. if ($subdir_is_empty) {
  2408. $empty[] = $subdir . '/'. $file2;
  2409. }
  2410. }
  2411. closedir($dh2);
  2412. }
  2413. $dir_is_empty = FALSE;
  2414. }
  2415. }
  2416. closedir($dh);
  2417. if ($dir_is_empty) {
  2418. return array($dir);
  2419. }
  2420. return $empty;
  2421. }