download.pm.inc

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

pm-download command implementation.

Functions

Namesort descending Description
drush_pm_download Command callback. Download Drupal core or any project.
drush_pm_download_validate Implements drush_hook_COMMAND_validate().
pm_drush_pm_download_destination_alter Implementation of hook_drush_pm_download_destination_alter().
_pm_download_destination Returns the best destination for a particular download type we can find.
_pm_download_destination_lookup Determines a candidate destination directory for a particular site path.

File

commands/pm/download.pm.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * pm-download command implementation.
  5. */
  6. /**
  7. * Implements drush_hook_COMMAND_validate().
  8. */
  9. function drush_pm_download_validate() {
  10. // Accomodate --select to the values accepted by release_info_fetch().
  11. $select = drush_get_option('select', 'auto');
  12. if ($select === TRUE) {
  13. drush_set_option('select', 'always');
  14. }
  15. else if ($select === FALSE) {
  16. drush_set_option('select', 'never');
  17. }
  18. // Validate the user specified destination directory.
  19. $destination = drush_get_option('destination');
  20. if (!empty($destination)) {
  21. $destination = rtrim($destination, DIRECTORY_SEPARATOR);
  22. if (!is_dir($destination)) {
  23. drush_print(dt("The directory !destination does not exist.", array('!destination' => $destination)));
  24. if (!drush_get_context('DRUSH_SIMULATE')) {
  25. if (drush_confirm(dt('Would you like to create it?'))) {
  26. drush_mkdir($destination, TRUE);
  27. }
  28. if (!is_dir($destination)) {
  29. return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Unable to create destination directory !destination.', array('!destination' => $destination)));
  30. }
  31. }
  32. }
  33. if (!is_writable($destination)) {
  34. return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Destination directory !destination is not writable.', array('!destination' => $destination)));
  35. }
  36. // Ignore --use-site-dir, if given.
  37. if (drush_get_option('use-site-dir', FALSE)) {
  38. drush_set_option('use-site-dir', FALSE);
  39. }
  40. }
  41. // Validate --variant or enforce a sane default.
  42. $variant = drush_get_option('variant', FALSE);
  43. if ($variant) {
  44. $variants = array('full', 'projects', 'profile-only');
  45. if (!in_array($variant, $variants)) {
  46. return drush_set_error('DRUSH_PM_PROFILE_INVALID_VARIANT', dt('Invalid variant !variant. Valid values: !variants.', array('!variant' => $variant, '!variants' => implode(', ', $variants))));
  47. }
  48. }
  49. // 'full' and 'projects' variants are only valid for wget package handler.
  50. $package_handler = drush_get_option('package-handler', 'wget');
  51. if (($package_handler != 'wget') && ($variant != 'profile-only')) {
  52. $new_variant = 'profile-only';
  53. if ($variant) {
  54. drush_log(dt('Variant !variant is incompatible with !ph package-handler.', array('!variant' => $variant, '!ph' => $package_handler)), 'warning');
  55. }
  56. }
  57. // If we are working on a drupal root, full variant is not an option.
  58. else if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) {
  59. if ((!$variant) || (($variant == 'full') && (!isset($new_variant)))) {
  60. $new_variant = 'projects';
  61. }
  62. if ($variant == 'full') {
  63. drush_log(dt('Variant full is not a valid option within a Drupal root.'), 'warning');
  64. }
  65. }
  66. if (isset($new_variant)) {
  67. drush_set_option('variant', $new_variant);
  68. if ($variant) {
  69. drush_log(dt('Switching to --variant=!variant.', array('!variant' => $new_variant)), 'ok');
  70. }
  71. }
  72. }
  73. /**
  74. * Command callback. Download Drupal core or any project.
  75. */
  76. function drush_pm_download() {
  77. if (!$requests = pm_parse_arguments(func_get_args(), FALSE)) {
  78. $requests = array('drupal');
  79. }
  80. // Parse out project name and version.
  81. $requests = pm_parse_project_version($requests);
  82. // Get release history for each request and download the project.
  83. $source = drush_get_option('source', RELEASE_INFO_DEFAULT_URL);
  84. $restrict_to = drush_get_option('dev', FALSE) ? 'dev' : '';
  85. $select = drush_get_option('select', 'auto');
  86. $all = drush_get_option('all', FALSE);
  87. foreach ($requests as $name => $request) {
  88. $request['status url'] = $source;
  89. $release = release_info_fetch($request, $restrict_to, $select, $all);
  90. if ($release == FALSE) {
  91. // Stop working on the first failure. Return silently on user abort.
  92. if (drush_get_context('DRUSH_USER_ABORT', FALSE)) {
  93. return FALSE;
  94. }
  95. // Signal that the command failed for all other problems.
  96. return drush_set_error('DRUSH_DOWNLOAD_FAILED', dt("Could not download requested project(s)."));
  97. }
  98. // Determine the name of the directory that will contain the project.
  99. // We face here all the asymetries to make it smooth for package handlers.
  100. // For Drupal core: --drupal-project-rename or drupal-x.y
  101. if (($request['project_type'] == 'core') ||
  102. (($request['project_type'] == 'profile') && (drush_get_option('variant', 'full') == 'full'))) {
  103. // Avoid downloading core into existing core.
  104. if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) {
  105. if (strpos(realpath(drush_get_option('destination')), DRUPAL_ROOT) !== FALSE) {
  106. return drush_set_error('DRUSH_PM_DOWNLOAD_TRANSLATIONS_FORBIDDEN', dt('It\'s forbidden to download !project core into an existing core.', array('!project' => $request['name'])));
  107. }
  108. }
  109. if ($rename = drush_get_option('drupal-project-rename', FALSE)) {
  110. if ($rename === TRUE) {
  111. $request['project_dir'] = $request['name'];
  112. }
  113. else {
  114. $request['project_dir'] = $rename;
  115. }
  116. }
  117. else {
  118. // Set to drupal-x.y, the expected name for .tar.gz contents.
  119. // Explicitly needed for cvs package handler.
  120. $request['project_dir'] = strtolower(strtr($release['name'], ' ', '-'));
  121. }
  122. }
  123. // For the other project types we want the project name. Including core
  124. // variant for profiles. Note those come with drupal-x.y in the .tar.gz.
  125. else {
  126. $request['project_dir'] = $request['name'];
  127. }
  128. // Download the project to a temporary location.
  129. $request['base_project_path'] = drush_tempdir();
  130. $request['full_project_path'] = $request['base_project_path'] .'/'. $request['project_dir'];
  131. drush_log(dt('Downloading project !name to !dir ...', array('!name' => $request['name'], '!dir' => $request['base_project_path'])));
  132. if (!package_handler_download_project($request, $release)) {
  133. // Delete the cached updatexml since it may be invalid.
  134. drush_delete_dir(drush_download_file_name(updatexml_get_url($request)), TRUE);
  135. drush_log(dt('Error downloading !name', array('!name' => $request['name']), 'error'));
  136. continue;
  137. }
  138. // Determine the install location for the project. User provided
  139. // --destination has preference.
  140. $destination = drush_get_option('destination');
  141. if (!empty($destination)) {
  142. if (!file_exists($destination)) {
  143. drush_mkdir($destination);
  144. }
  145. $request['project_install_location'] = realpath($destination);
  146. }
  147. else {
  148. $request['project_install_location'] = _pm_download_destination($request['project_type']);
  149. }
  150. // If user did not provide --destination, then call the
  151. // download-destination-alter hook to give the chance to any commandfiles
  152. // to adjust the install location or abort it.
  153. if (empty($destination)) {
  154. $result = drush_command_invoke_all_ref('drush_pm_download_destination_alter', $request, $release);
  155. if (array_search(FALSE, $result, TRUE) !== FALSE) {
  156. return FALSE;
  157. }
  158. }
  159. // Load version control engine and detect if (the parent directory of) the
  160. // project install location is under a vcs.
  161. if (!$version_control = drush_pm_include_version_control($request['project_install_location'])) {
  162. continue;
  163. }
  164. $request['project_install_location'] .= '/' . $request['project_dir'];
  165. if ($version_control->engine == 'backup') {
  166. // Check if install location already exists.
  167. if (is_dir($request['project_install_location'])) {
  168. if (drush_confirm(dt('Install location !location already exists. Do you want to overwrite it?', array('!location' => $request['project_install_location'])))) {
  169. drush_delete_dir($request['project_install_location'], TRUE);
  170. }
  171. else {
  172. drush_log(dt("Skip installation of !project to !dest.", array('!project' => $request['name'], '!dest' => $request['project_install_location'])), 'warning');
  173. continue;
  174. }
  175. }
  176. }
  177. else {
  178. // Find and unlink all files but the ones in the vcs control directories.
  179. $skip_list = array('.', '..');
  180. $skip_list = array_merge($skip_list, drush_version_control_reserved_files());
  181. drush_scan_directory($request['project_install_location'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE);
  182. }
  183. // Copy the project to the install location.
  184. if (drush_op('_drush_recursive_copy', $request['full_project_path'], $request['project_install_location'])) {
  185. drush_log(dt("Project !project (!version) downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])), 'success');
  186. $request['base_project_path'] = basename($request['project_install_location']);
  187. $request['full_project_path'] = $request['project_install_location'];
  188. // If the version control engine is a proper vcs we also need to remove
  189. // orphan directories.
  190. if ($version_control->engine != 'backup') {
  191. $empty_dirs = drush_find_empty_directories($request['full_project_path'], $version_control->reserved_files());
  192. foreach ($empty_dirs as $empty_dir) {
  193. // Some VCS files are read-only on Windows (e.g., .svn/entries).
  194. drush_delete_dir($empty_dir, TRUE);
  195. }
  196. }
  197. // Post download actions.
  198. package_handler_post_download($request, $release);
  199. drush_command_invoke_all('drush_pm_post_download', $request, $release);
  200. $version_control->post_download($request);
  201. // Print release notes if --notes option is set.
  202. if (drush_get_option('notes') && !drush_get_context('DRUSH_PIPE')) {
  203. release_info_print_releasenotes(array($name . '-' . $release['version']), FALSE);
  204. }
  205. // Inform the user about available modules a/o themes in the downloaded project.
  206. drush_pm_extensions_in_project($request);
  207. }
  208. else {
  209. // We don't `return` here in order to proceed with downloading additional projects.
  210. drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt("Project !project (!version) could not be downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])));
  211. }
  212. // Notify about this project.
  213. if (drush_notify_allowed('pm-download')) {
  214. $msg = dt('Project !project (!version) downloaded to !install.', array(
  215. '!project' => $name,
  216. '!version' => $release['version'],
  217. '!install' => $request['project_install_location'],
  218. ));
  219. drush_notify_send(drush_notify_command_message('pm-download', $msg));
  220. }
  221. }
  222. }
  223. /**
  224. * Implementation of hook_drush_pm_download_destination_alter().
  225. *
  226. * Built-in download-destination-alter hook. This particular version of
  227. * the hook will move modules that contain only drush commands to
  228. * /usr/share/drush/commands if it exists, or $HOME/.drush if the
  229. * site-wide location does not exist.
  230. */
  231. function pm_drush_pm_download_destination_alter(&$project, $release) {
  232. // A module is a pure drush command if it has no .module and contain
  233. // .drush.inc files. Skip this test for drush itself, though; we do
  234. // not want to download drush to the ~/.drush folder.
  235. if (($project['project_type'] == 'module') && ($project['name'] != 'drush')) {
  236. $drush_command_files = drush_scan_directory($project['full_project_path'], '/.*\.drush.inc/');
  237. if (!empty($drush_command_files)) {
  238. $module_files = drush_scan_directory($project['full_project_path'], '/.*\.module/');
  239. if (empty($module_files)) {
  240. $install_dir = drush_get_context('DRUSH_SITE_WIDE_COMMANDFILES');
  241. if (!is_dir($install_dir) || !is_writable($install_dir)) {
  242. $install_dir = drush_get_context('DRUSH_PER_USER_CONFIGURATION');
  243. }
  244. // Make the .drush dir if it does not already exist.
  245. if (!is_dir($install_dir)) {
  246. drush_mkdir($install_dir, FALSE);
  247. }
  248. // Change the location if the mkdir worked.
  249. if (is_dir($install_dir)) {
  250. $project['project_install_location'] = $install_dir;
  251. }
  252. }
  253. // We need to clear the drush commandfile cache so that
  254. // our newly-downloaded drush extension commandfiles can be found.
  255. drush_cache_clear_all();
  256. }
  257. }
  258. }
  259. /**
  260. * Determines a candidate destination directory for a particular site path.
  261. *
  262. * Optionally attempts to create the directory.
  263. *
  264. * @return String the candidate destination if it exists.
  265. */
  266. function _pm_download_destination_lookup($type, $drupal_root, $sitepath, $create = FALSE) {
  267. if ($type == 'theme engine') {
  268. $destination = 'themes/engines';
  269. }
  270. // Profiles in Drupal < 8
  271. elseif (($type == 'profile') && (drush_drupal_major_version() < 8)) {
  272. $destination = 'profiles';
  273. }
  274. // Type: module, theme or profile.
  275. else {
  276. $destination = $type . 's';
  277. // Prefer /contrib if it exists.
  278. if ($sitepath) {
  279. $destination = $sitepath . '/' . $destination;
  280. }
  281. $contrib = $destination . '/contrib';
  282. if (is_dir($contrib)) {
  283. $destination = $contrib;
  284. }
  285. }
  286. if ($create) {
  287. drush_log(dt('Attempting to create destination directory at !dir', array('!dir' => $destination)));
  288. drush_mkdir($destination, TRUE);
  289. }
  290. if (is_dir($destination)) {
  291. drush_log(dt('Using destination directory !dir', array('!dir' => $destination)));
  292. return $destination;
  293. }
  294. drush_log(dt('Could not find destination directory at !dir', array('!dir' => $destination)));
  295. return FALSE;
  296. }
  297. /**
  298. * Returns the best destination for a particular download type we can find.
  299. *
  300. * It is based on the project type and drupal and site contexts.
  301. */
  302. function _pm_download_destination($type) {
  303. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  304. $site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT', FALSE);
  305. $full_site_root = $drupal_root .'/'. $site_root;
  306. $sitewide = drush_drupal_sitewide_directory();
  307. $in_site_directory = FALSE;
  308. // Check if we are running within the site directory.
  309. if (strpos(realpath(drush_cwd()), realpath($full_site_root)) !== FALSE || (drush_get_option('use-site-dir', FALSE))) {
  310. $in_site_directory = TRUE;
  311. }
  312. $destination = '';
  313. if ($type != 'core') {
  314. // Attempt 1: If we are in a specific site directory, and the destination
  315. // directory already exists, then we use that.
  316. if (empty($destination) && $site_root && $in_site_directory) {
  317. $create_dir = drush_get_option('use-site-dir', FALSE);
  318. $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, $create_dir);
  319. }
  320. // Attempt 2: If the destination directory already exists for
  321. // the sitewide directory, use that.
  322. if (empty($destination) && $drupal_root) {
  323. $destination = _pm_download_destination_lookup($type, $drupal_root, $sitewide);
  324. }
  325. // Attempt 3: If a specific (non default) site directory exists and
  326. // the sitewide directory does not exist, then create destination
  327. // in the site specific directory.
  328. if (empty($destination) && $site_root && $site_root !== 'sites/default' && is_dir($full_site_root) && !is_dir($sitewide)) {
  329. $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, TRUE);
  330. }
  331. // Attempt 4: If sitewide directory exists, then create destination there.
  332. if (empty($destination) && is_dir($sitewide)) {
  333. $destination = _pm_download_destination_lookup($type, $drupal_root, $sitewide, TRUE);
  334. }
  335. // Attempt 5: If site directory exists (even default), then create
  336. // destination in that directory.
  337. if (empty($destination) && $site_root && is_dir($full_site_root)) {
  338. $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, TRUE);
  339. }
  340. }
  341. // Attempt 6: If we didn't find a valid directory yet (or we somehow found
  342. // one that doesn't exist) we always fall back to the current directory.
  343. if (empty($destination) || !is_dir($destination)) {
  344. $destination = drush_cwd();
  345. }
  346. return $destination;
  347. }