29bf6b97072a46a0544907b91206a387fbbc50bc
[yaffs-website] / vendor / drush / drush / commands / make / make.download.inc
1 <?php
2 /**
3  * @file
4  * Download-specific functions for Drush Make.
5  */
6
7 use Drush\Log\LogLevel;
8
9 /**
10  * Downloads the given package to the destination directory.
11  *
12  * @return mixed
13  *   The destination path on success, FALSE on failure.
14  */
15 function make_download_factory($name, $type, $download, $download_location) {
16   $function = 'make_download_' . $download['type'];
17   if (function_exists($function)) {
18     return $function($name, $type, $download, $download_location);
19   }
20   else {
21     return FALSE;
22   }
23 }
24
25 /**
26  * Download project using drush's pm-download command.
27  */
28 function make_download_pm($name, $type, $download, $download_location) {
29   $full_project_version = $name . '-' . $download['full_version'];
30
31   $options = array(
32     'destination' => dirname($download_location),
33     'yes' => TRUE,
34     'package-handler' => 'wget',
35     'source' => $download['status url'],
36     // This is only relevant for profiles, but we generally want the variant to
37     // be 'profile-only' so we don't end up with extra copies of core.
38     'variant' => $type == 'core' ? 'full' : $download['variant'],
39     'cache' => TRUE,
40   );
41   if ($type == 'core') {
42     $options['drupal-project-rename'] = basename($download_location);
43   }
44   if (drush_get_option('no-cache', FALSE)) {
45     unset($options['cache']);
46   }
47
48   $backend_options = array();
49   if (!drush_get_option(array('verbose', 'debug'), FALSE)) {
50     $backend_options['integrate'] = TRUE;
51     $backend_options['log'] = FALSE;
52   }
53
54   // Perform actual download with `drush pm-download`.
55   $return = drush_invoke_process('@none', 'pm-download', array($full_project_version), $options, $backend_options);
56   if (empty($return['error_log'])) {
57     // @todo Report the URL we used for download. See
58     // http://drupal.org/node/1452672.
59     drush_log(dt('@project downloaded.', array('@project' => $full_project_version)), LogLevel::OK);
60   }
61 }
62
63 /**
64  * Downloads a file to the specified location.
65  *
66  * @return mixed
67  *   The destination directory on success, FALSE on failure.
68  */
69 function make_download_file($name, $type, $download, $download_location, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) {
70   if ($filename = _make_download_file($download['url'], $cache_duration)) {
71     if (!drush_get_option('ignore-checksums') && !_make_verify_checksums($download, $filename)) {
72       return FALSE;
73     }
74     drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK);
75     $download_filename = isset($download['filename']) ? $download['filename'] : '';
76     $subtree = isset($download['subtree']) ? $download['subtree'] : NULL;
77     return make_download_file_unpack($filename, $download_location, $download_filename, $subtree);
78   }
79   make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
80   return FALSE;
81 }
82
83 /**
84  * Wrapper to drush_download_file().
85  *
86  * @param string $download
87  *   The url of the file to download.
88  * @param int $cache_duration
89  *   The time in seconds to cache the resultant download.
90  *
91  * @return string
92  *   The location of the downloaded file, or FALSE on failure.
93  */
94 function _make_download_file($download, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) {
95   if (drush_get_option('no-cache', FALSE)) {
96     $cache_duration = 0;
97   }
98
99   $tmp_path = make_tmp();
100   // Ensure that we aren't including the querystring when generating a filename
101   // to save our download to.
102   $file = basename(current(explode('?', $download, 2)));
103   return drush_download_file($download, $tmp_path . '/' . $file, $cache_duration);
104 }
105
106 /**
107  * Unpacks a file to the specified download location.
108  *
109  * @return mixed
110  *   The download location on success, FALSE on failure.
111  */
112 function make_download_file_unpack($filename, $download_location, $name, $subtree = NULL) {
113   $success = FALSE;
114
115   if (drush_file_is_tarball($filename)) {
116     $tmp_location = drush_tempdir();
117
118     if (!drush_tarball_extract($filename, $tmp_location)) {
119       return FALSE;
120     }
121
122     if ($subtree) {
123       $tmp_location .= '/' . $subtree;
124       if (!file_exists($tmp_location)) {
125         return drush_set_error('DRUSH_MAKE_SUBTREE_NOT_FOUND', dt('Directory !subtree not found within !file', array('!subtree' => $subtree, '!file' => $filename)));
126       }
127     }
128     else {
129       $files = scandir($tmp_location);
130       unset($files[0]); // . directory
131       unset($files[1]); // .. directory
132       if ((count($files) == 1) && is_dir($tmp_location . '/' . current($files))) {
133         $tmp_location .= '/' . current($files);
134       }
135     }
136
137     $success = drush_move_dir($tmp_location, $download_location, TRUE);
138
139     // Remove the tarball.
140     if (file_exists($filename)) {
141       drush_delete_dir($filename, TRUE);
142     }
143   }
144   else {
145     // If this is an individual file, and no filename has been specified,
146     // assume the original name.
147     if (is_file($filename) && !$name) {
148       $name = basename($filename);
149     }
150
151     // The destination directory has already been created by
152     // findDownloadLocation().
153     $destination = $download_location . ($name ? '/' . $name : '');
154     $success = drush_move_dir($filename, $destination, TRUE);
155   }
156   return $success ? $download_location : FALSE;
157 }
158
159 /**
160  * Move a downloaded and unpacked file or directory into place.
161  */
162 function _make_download_file_move($tmp_path, $filename, $download_location, $subtree = NULL) {
163   $lines = drush_scan_directory($tmp_path, '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE);
164   $main_directory = basename($download_location);
165   if (count($lines) == 1) {
166     $directory = array_shift($lines);
167     if ($directory->basename != $main_directory) {
168       drush_move_dir($directory->filename, $tmp_path . DIRECTORY_SEPARATOR . $main_directory, TRUE);
169     }
170     drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . $main_directory . DIRECTORY_SEPARATOR . $subtree, $download_location, FILE_EXISTS_OVERWRITE);
171     drush_delete_dir($tmp_path, TRUE);
172   }
173   elseif (count($lines) > 1) {
174     drush_delete_dir($download_location, TRUE);
175     drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $subtree, $download_location, TRUE);
176   }
177
178   // Remove the tarball.
179   if (file_exists($filename)) {
180     drush_delete_dir($filename, TRUE);
181   }
182
183   if (file_exists($tmp_path)) {
184     drush_delete_dir($tmp_path, TRUE);
185   }
186   return TRUE;
187 }
188
189
190 /**
191  * For backwards compatibility.
192  */
193 function make_download_get($name, $type, $download, $download_location) {
194   return make_download_file($name, $type, $download, $download_location);
195 }
196
197 /**
198  * Copies a folder the specified location.
199  *
200  * @return mixed
201  *   The TRUE on success, FALSE on failure.
202  */
203 function make_download_copy($name, $type, $download, $download_location) {
204     if ($folder = _make_download_copy($download['url'])) {
205         drush_log(dt('@project copied from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK);
206         return drush_copy_dir($folder, $download_location, FILE_EXISTS_OVERWRITE);
207     }
208     make_error('COPY_ERROR', dt('Unable to copy @project from @url.', array('@project' => $name, '@url' => $download['url'])));
209     return FALSE;
210 }
211
212 /**
213  * Wrapper to drush_download_copy().
214  *
215  * @param string $folder
216  *   The location of the folder to copy.
217  *
218  * @return string
219  *   The location of the folder, or FALSE on failure.
220  */
221 function _make_download_copy($folder) {
222     if (substr($folder, 0, 7) == 'file://') {
223         $folder = substr($folder, 7);
224     }
225
226     if (is_dir($folder)) {
227         return $folder;
228     }
229     return FALSE;
230 }
231
232 /**
233  * Checks out a git repository to the specified download location.
234  *
235  * Allowed parameters in $download, in order of precedence:
236  *   - 'tag'
237  *   - 'revision'
238  *   - 'branch'
239  *
240  * This will also attempt to write out release information to the
241  * .info file if the 'no-gitinfofile' option is FALSE. If
242  * $download['full_version'] is present, this will be used, otherwise,
243  * version will be set in this order of precedence:
244  *   - 'tag'
245  *   - 'branch'
246  *   - 'revision'
247  *
248  * @return mixed
249  *   The download location on success, FALSE otherwise.
250  */
251 function make_download_git($name, $type, $download, $download_location) {
252   $tmp_path = make_tmp();
253   $wc = _get_working_copy_option($download);
254   $checkout_after_clone = TRUE;
255   // If no download URL specified, assume anonymous clone from git.drupal.org.
256   $download['url'] = isset($download['url']) ? $download['url'] : "http://git.drupal.org/project/$name.git";
257   // If no working-copy download URL specified, assume it is the same.
258   $download['wc_url'] = isset($download['wc_url']) ? $download['wc_url'] : $download['url'];
259
260   // If not a working copy, and if --no-cache has not been explicitly
261   // declared, create a new git reference cache of the remote repository,
262   // or update the existing cache to fetch recent changes.
263   // @see package_handler_download_project()
264   $cache = !$wc && !drush_get_option('no-cache', FALSE);
265   if ($cache && ($git_cache = drush_directory_cache('git'))) {
266     $project_cache = $git_cache . '/' . $name . '-' . md5($download['url']);
267     // Set up a new cache, if it doesn't exist.
268     if (!file_exists($project_cache)) {
269       $command = 'git clone --mirror';
270       if (drush_get_context('DRUSH_VERBOSE')) {
271         $command .= ' --verbose --progress';
272       }
273       $command .= ' %s %s';
274       drush_shell_cd_and_exec($git_cache, $command, $download['url'], $project_cache);
275     }
276     else {
277       // Update the --mirror clone.
278       drush_shell_cd_and_exec($project_cache, 'git remote update');
279     }
280     $git_cache = $project_cache;
281   }
282
283   // Use working-copy download URL if --working-copy specified.
284   $url = $wc ? $download['wc_url'] : $download['url'];
285
286   $tmp_location = drush_tempdir() . '/' . basename($download_location);
287
288   $command = 'git clone %s %s';
289   if (drush_get_context('DRUSH_VERBOSE')) {
290     $command .= ' --verbose --progress';
291   }
292   if ($cache) {
293     $command .= ' --reference ' . drush_escapeshellarg($git_cache);
294   }
295
296   // the shallow clone option is only applicable to git entries which reference a tag or a branch
297   if (drush_get_option('shallow-clone', FALSE) &&
298      (!empty($download['tag']) || !empty($download['branch']))) {
299
300     $branch = (!empty($download['branch']) ? $download['branch'] : $download['tag']);
301     $command .= " --depth=1 --branch=${branch}";
302
303     // since the shallow copy option automatically "checks out" the requested branch, no further
304     // actions are needed after the clone command
305     $checkout_after_clone = FALSE;
306   }
307
308   // Before we can checkout anything, we need to clone the repository.
309   if (!drush_shell_exec($command, $url, $tmp_location)) {
310     make_error('DOWNLOAD_ERROR', dt('Unable to clone @project from @url.', array('@project' => $name, '@url' => $url)));
311     return FALSE;
312   }
313
314   drush_log(dt('@project cloned from @url.', array('@project' => $name, '@url' => $url)), LogLevel::OK);
315
316   if ($checkout_after_clone) {
317     // Get the current directory (so we can move back later).
318     $cwd = getcwd();
319     // Change into the working copy of the cloned repo.
320     chdir($tmp_location);
321
322     // We want to use the most specific target possible, so first try a refspec.
323     if (!empty($download['refspec'])) {
324       if (drush_shell_exec("git fetch %s %s", $url, $download['refspec'])) {
325         drush_log(dt("Fetched refspec !refspec.", array('!refspec' => $download['refspec'])), LogLevel::OK);
326
327         if (drush_shell_exec("git checkout FETCH_HEAD")) {
328           drush_log(dt("Checked out FETCH_HEAD."), LogLevel::INFO);
329         }
330       }
331       else {
332         make_error('DOWNLOAD_ERROR', dt("Unable to fetch the refspec @refspec from @project.", array('@refspec' => $download['refspec'], '@project' => $name)));
333       }
334     }
335
336     // If there wasn't a refspec, try a tag.
337     elseif (!empty($download['tag'])) {
338       // @TODO: change checkout to refs path.
339       if (drush_shell_exec("git checkout %s", 'refs/tags/' . $download['tag'])) {
340         drush_log(dt("Checked out tag @tag.", array('@tag' => $download['tag'])), LogLevel::OK);
341       }
342       else {
343         make_error('DOWNLOAD_ERROR', dt("Unable to check out tag @tag.", array('@tag' => $download['tag'])));
344       }
345     }
346
347     // If there wasn't a tag, try a specific revision hash.
348     elseif (!empty($download['revision'])) {
349       if (drush_shell_exec("git checkout %s", $download['revision'])) {
350         drush_log(dt("Checked out revision @revision.", array('@revision' => $download['revision'])), LogLevel::OK);
351       }
352       else {
353         make_error('DOWNLOAD_ERROR', dt("Unable to checkout revision @revision", array('@revision' => $download['revision'])));
354       }
355     }
356
357     // If not, see if we at least have a branch.
358     elseif (!empty($download['branch'])) {
359       if (drush_shell_exec("git checkout %s", $download['branch']) && (trim(implode(drush_shell_exec_output())) != '')) {
360         drush_log(dt("Checked out branch @branch.", array('@branch' => $download['branch'])), LogLevel::OK);
361       }
362       elseif (drush_shell_exec("git checkout -b %s %s", $download['branch'], 'origin/' . $download['branch'])) {
363         drush_log(dt('Checked out branch origin/@branch.', array('@branch' => $download['branch'])), LogLevel::OK);
364       }
365       else {
366         make_error('DOWNLOAD_ERROR', dt('Unable to check out branch @branch.', array('@branch' => $download['branch'])));
367       }
368     }
369
370     if (!empty($download['submodule'])) {
371       $command = 'git submodule update';
372       foreach ($download['submodule'] as $option) {
373         $command .= ' --%s';
374       }
375       if (call_user_func_array('drush_shell_exec', array_merge(array($command), $download['submodule']))) {
376         drush_log(dt('Initialized registered submodules.'), LogLevel::OK);
377       }
378       else {
379         make_error('DOWNLOAD_ERROR', dt('Unable to initialize submodules.'));
380       }
381     }
382
383     // Move back to last current directory (first line).
384     chdir($cwd);
385   }
386
387   // Move the directory into the final resting location.
388   drush_copy_dir($tmp_location, $download_location, FILE_EXISTS_OVERWRITE);
389
390   return dirname($tmp_location);
391 }
392
393 /**
394  * Checks out a Bazaar repository to the specified download location.
395  *
396  * @return mixed
397  *   The download location on success, FALSE otherwise.
398  */
399 function make_download_bzr($name, $type, $download, $download_location) {
400   $tmp_path = make_tmp();
401   $tmp_location = drush_tempdir() . '/' . basename($download_location);
402   $wc = _get_working_copy_option($download);
403   if (!empty($download['url'])) {
404     $args = array();
405     $command = 'bzr';
406     if ($wc) {
407       $command .= ' branch  --use-existing-dir';
408     }
409     else {
410       $command .= ' export';
411     }
412     if (isset($download['revision'])) {
413       $command .= ' -r %s';
414       $args[] = $download['revision'];
415     }
416     $command .= ' %s %s';
417     if ($wc) {
418       $args[] = $download['url'];
419       $args[] = $tmp_location;
420     }
421     else {
422       $args[] = $tmp_location;
423       $args[] = $download['url'];
424     }
425     array_unshift($args, $command);
426     if (call_user_func_array('drush_shell_exec', $args)) {
427       drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK);
428       drush_copy_dir($tmp_location, $download_location, FILE_EXISTS_OVERWRITE);
429       return dirname($download_location);
430     }
431   }
432   else {
433     $download['url'] = dt("unspecified location");
434   }
435   make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
436   drush_delete_dir(dirname($tmp_location), TRUE);
437   return FALSE;
438 }
439
440 /**
441  * Checks out an SVN repository to the specified download location.
442  *
443  * @return mixed
444  *   The download location on success, FALSE otherwise.
445  */
446 function make_download_svn($name, $type, $download, $download_location) {
447   $wc = _get_working_copy_option($download);
448   if (!empty($download['url'])) {
449     if (!empty($download['interactive'])) {
450       $function = 'drush_shell_exec_interactive';
451     }
452     else {
453       $options = ' --non-interactive';
454       $function = 'drush_shell_exec';
455     }
456     if (!isset($download['force']) || $download['force']) {
457       $options = ' --force';
458     }
459     if ($wc) {
460       $command = 'svn' . $options . ' checkout';
461     }
462     else {
463       $command = 'svn' . $options . ' export';
464     }
465
466     $args = array();
467
468     if (isset($download['revision'])) {
469       $command .= ' -r%s';
470       $args[] = $download['revision'];
471     }
472
473     $command .= ' %s %s';
474     $args[] = $download['url'];
475     $args[] = $download_location;
476
477     if (!empty($download['username'])) {
478       $command .= ' --username %s';
479       $args[] = $download['username'];
480       if (!empty($download['password'])) {
481         $command .= ' --password %s';
482         $args[] = $download['password'];
483       }
484     }
485     array_unshift($args, $command);
486     $result = call_user_func_array($function, $args);
487     if ($result) {
488       $args = array(
489         '@project' => $name,
490         '@command' => $command,
491         '@url' => $download['url'],
492       );
493       drush_log(dt('@project @command from @url.', $args), LogLevel::OK);
494       return $download_location;
495     }
496     else {
497       $download['url'] = dt("unspecified location");
498     }
499   }
500   else {
501     make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
502     return FALSE;
503   }
504 }
505
506 /**
507  * Test that any supplied hash values match the hash of the file content.
508  *
509  * Unsupported hash algorithms are reported as failure.
510  */
511 function _make_verify_checksums($info, $filename) {
512   $hash_algos = array('md5', 'sha1', 'sha256', 'sha512');
513   // We only have something to do if a key is an
514   // available function.
515   if (array_intersect(array_keys($info), $hash_algos)) {
516     $content = file_get_contents($filename);
517     foreach ($hash_algos as $algo) {
518       if (!empty($info[$algo])) {
519         $hash = _make_hash($algo, $content);
520         if ($hash !== $info[$algo]) {
521           $args = array(
522             '@algo' => $algo,
523             '@file' => basename($filename),
524             '@expected' => $info[$algo],
525             '@hash' => $hash,
526           );
527           make_error('DOWNLOAD_ERROR', dt('Checksum @algo verification failed for @file. Expected @expected, received @hash.', $args));
528           return FALSE;
529         }
530       }
531     }
532   }
533   return TRUE;
534 }
535
536 /**
537  * Calculate the hash of a string for a given algorithm.
538  */
539 function _make_hash($algo, $string) {
540   switch ($algo) {
541     case 'md5':
542       return md5($string);
543     case 'sha1':
544       return sha1($string);
545     default:
546       return function_exists('hash') ? hash($algo, $string) : '';
547   }
548 }