Yaffs site version 1.1
[yaffs-website] / vendor / drush / drush / commands / core / archive.drush.inc
1 <?php
2
3 /**
4  * @file
5  *   An early implementation of Site Archive dump/restore. See
6  *   http://groups.drupal.org/site-archive-format.
7  */
8
9 use Drush\Log\LogLevel;
10
11 function archive_drush_command() {
12   $items['archive-dump'] = array(
13     'description' => 'Backup your code, files, and database into a single file.',
14     'arguments' => array(
15       'sites' => 'Optional. Site specifications, delimited by commas. Typically, list subdirectory name(s) under /sites.',
16     ),
17     // Most options on sql-dump should not be shown, so just offer a subset.
18     'options' => drush_sql_get_table_selection_options() + array(
19       'description' => 'Describe the archive contents.',
20       'tags' => 'Add tags to the archive manifest. Delimit multiple by commas.',
21       'destination' => 'The full path and filename in which the archive should be stored. If omitted, it will be saved to the drush-backups directory and a filename will be generated.',
22       'overwrite' => 'Do not fail if the destination file exists; overwrite it instead. Default is --no-overwrite.',
23       'generator' => 'The generator name to store in the MANIFEST file. The default is "Drush archive-dump".',
24       'generatorversion' => 'The generator version number to store in the MANIFEST file. The default is ' . DRUSH_VERSION . '.',
25       'pipe' => 'Only print the destination of the archive. Useful for scripts that don\'t pass --destination.',
26       'preserve-symlinks' => 'Preserve symbolic links.',
27       'no-core' => 'Exclude Drupal core, so the backup only contains the site specific stuff.',
28       'tar-options' => 'Options passed thru to the tar command.',
29     ),
30     'examples' => array(
31       'drush archive-dump default,example.com,foo.com' => 'Write an archive containing 3 sites in it.',
32       'drush archive-dump @sites' => 'Save archive containing all sites in a multi-site.',
33       'drush archive-dump default --destination=/backups/mysite.tar' => 'Save archive to custom location.',
34       'drush archive-dump --tar-options="--exclude=.git --exclude=sites/default/files"' => 'Omits any .git directories found in the tree as well as sites/default/files.',
35       'drush archive-dump --tar-options="--exclude=%files"' => 'Placeholder %files is replaced with the real path for the current site, and that path is excluded.',
36     ),
37     'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
38     'aliases' => array('ard', 'archive-backup', 'arb'),
39   );
40   $items['archive-restore'] = array(
41     'description' => 'Expand a site archive into a Drupal web site.',
42     'arguments' => array(
43       'file' => 'The site archive file that should be expanded.',
44       'site name' => 'Optional. Which site within the archive you want to restore. Defaults to all.',
45     ),
46     'required-arguments' => 1,
47     'options' => array(
48       'destination' => 'Specify where the Drupal site should be expanded, including the docroot. Defaults to the current working directory.',
49       'db-prefix' => 'An optional table prefix to use during restore.',
50       'db-url' => array(
51         'description' => 'A Drupal 6 style database URL indicating the target for database restore. If not provided, the archived settings.php is used.',
52         'example-value' => 'mysql://root:pass@host/db',
53       ),
54       'db-su' => 'Account to use when creating the new database. Optional.',
55       'db-su-pw' => 'Password for the "db-su" account. Optional.',
56       'overwrite' => 'Allow drush to overwrite any files in the destination. Default is --no-overwrite.',
57       'tar-options' => 'Options passed thru to the tar command.',
58     ),
59     'examples' => array(
60       'drush archive-restore ./example.tar.gz' => 'Restore the files and databases for all sites in the archive.',
61       'drush archive-restore ./example.tar.gz example.com' => 'Restore the files and database for example.com site.',
62       'drush archive-restore ./example.tar.gz --destination=/var/www/example.com/docroot' => 'Restore archive to a custom location.',
63       'drush archive-restore ./example.tar.gz --db-url=mysql://root:pass@127.0.0.1/dbname' => 'Restore archive to a new database (and customize settings.php to point there.).',
64     ),
65     'bootstrap' => DRUSH_BOOTSTRAP_NONE,
66     'aliases' => array('arr'),
67   );
68   return $items;
69 }
70
71 /**
72  * Command callback. Generate site archive file.
73  */
74 function drush_archive_dump($sites_subdirs = '@self') {
75   $include_platform = !drush_get_option('no-core', FALSE);
76   $tar = drush_get_tar_executable();
77
78   $sites = array();
79   list($aliases, $not_found) = drush_sitealias_resolve_sitespecs(explode(',', $sites_subdirs));
80   if (!empty($not_found)) {
81     drush_log(dt("Some site subdirectories are not valid Drupal sites: @list", array("@list" => implode(', ', $not_found))), LogLevel::WARNING);
82   }
83   foreach ($aliases as $key => $alias) {
84     $sites[$key] = $alias;
85
86     if (($db_record = sitealias_get_databases_from_record($alias))) {
87       $sites[$key]['databases'] = $db_record;
88     }
89     else {
90       $sites[$key]['databases'] = array();
91       drush_log(dt('DB definition not found for !alias', array('!alias' => $key)), LogLevel::NOTICE);
92     }
93   }
94
95   // The user can specify a destination filepath or not. That filepath might
96   // end with .gz, .tgz, or something else. At the end of this command we will
97   // gzip a file, and we want it to end up with the user-specified name (if
98   // any), but gzip renames files and refuses to compress files ending with
99   // .gz and .tgz, making our lives difficult. Solution:
100   //
101   // 1. Create a unique temporary base name to which gzip WILL append .gz.
102   // 2. If no destination is provided, set $dest_dir to a backup directory and
103   // $final_destination to be the unique name in that dir.
104   // 3. If a destination is provided, set $dest_dir to that directory and
105   // $final_destination to the exact name given.
106   // 4. Set $destination, the actual working file we will build up, to the
107   // unqiue name in $dest_dir.
108   // 5. After gzip'ing $destination, rename $destination.gz to
109   // $final_destination.
110   //
111   // Sheesh.
112
113   // Create the unique temporary name.
114   $prefix = 'none';
115   if (!empty($sites)) {
116     $first = current($sites);
117     if ( !empty($first['databases']['default']['default']['database']) ) {
118       $prefix = count($sites) > 1 ? 'multiple_sites' : str_replace('/', '-', $first['databases']['default']['default']['database']);
119     }
120   }
121   $date = gmdate('Ymd_His');
122   $temp_dest_name = "$prefix.$date.tar";
123
124   $final_destination = drush_get_option('destination');
125   if (!$final_destination) {
126     // No destination provided.
127     $backup = drush_include_engine('version_control', 'backup');
128     // TODO: this standard Drush pattern leads to a slightly obtuse directory structure.
129     $dest_dir = $backup->prepare_backup_dir('archive-dump');
130     if (empty($dest_dir)) {
131       $dest_dir = drush_tempdir();
132     }
133     $final_destination = "$dest_dir/$temp_dest_name.gz";
134   }
135   else {
136     // Use the supplied --destination. If it is relative, resolve it
137     // relative to the directory in which drush was invoked.
138     $command_cwd = getcwd();
139     drush_op('chdir', drush_get_context('DRUSH_OLDCWD', getcwd()));
140     // This doesn't perform realpath on the basename, but that's okay. This is
141     // not path-based security. We just use it for checking for perms later.
142     drush_mkdir(dirname($final_destination));
143     $dest_dir = realpath(dirname($final_destination));
144     $final_destination = $dest_dir . '/' . basename($final_destination);
145     drush_op('chdir', $command_cwd);
146   }
147
148   // $dest_dir is either the backup directory or specified directory. Set our
149   // working file.
150   $destination = "$dest_dir/$temp_dest_name";
151
152   // Validate the FINAL destination. It should be a file that does not exist
153   // (unless --overwrite) in a writable directory (and a writable file if
154   // it exists). We check all this up front to avoid failing after a long
155   // dump process.
156   $overwrite = drush_get_option('overwrite');
157   $dest_dir = dirname($final_destination);
158   $dt_args = array('!file' => $final_destination, '!dir' => $dest_dir);
159   if (is_dir($final_destination)) {
160     drush_set_error('DRUSH_ARCHIVE_DEST_IS_DIR', dt('Destination !file must be a file, not a directory.', $dt_args));
161     return;
162   }
163   else if (file_exists($final_destination)) {
164     if (!$overwrite) {
165       drush_set_error('DRUSH_ARCHIVE_DEST_EXISTS', dt('Destination !file exists; specify --overwrite to overwrite.', $dt_args));
166       return;
167     }
168     else if (!is_writable($final_destination)) {
169       drush_set_error('DRUSH_ARCHIVE_DEST_FILE_NOT_WRITEABLE', dt('Destination !file is not writable.', $dt_args));
170       return;
171     }
172   }
173   else if (!is_writable($dest_dir)) {
174     drush_set_error('DRUSH_ARCHIVE_DEST_DIR_NOT_WRITEABLE', dt('Destination directory !dir is not writable.', $dt_args));
175     return;
176   }
177
178   // Get the extra options for tar, if any
179   $tar_extra_options = drush_sitealias_evaluate_paths_in_options(drush_get_option('tar-options', ''));
180
181   // Start adding codebase to the archive.
182   $docroot_path = realpath(drush_get_context('DRUSH_DRUPAL_ROOT'));
183   $docroot = basename($docroot_path);
184   $workdir = dirname($docroot_path);
185
186   if ($include_platform) {
187     $dereference = (drush_get_option('preserve-symlinks', FALSE)) ? '' : '--dereference ';
188     // Convert destination path to Unix style for tar on MinGW - see http://drupal.org/node/1844224
189     if (drush_is_mingw()) {
190       $destination_orig = $destination;
191       $destination = str_replace('\\', '/', $destination);
192       $destination = preg_replace('$^([a-zA-Z]):$', '/$1', $destination);
193     }
194     // Archive Drupal core, excluding sites dir.
195     drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} --exclude \"{$docroot}/sites\" {$dereference}-cf %s %s", $destination, $docroot);
196     // Add sites/all to the same archive.
197     drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} {$dereference}-rf %s %s", $destination, "{$docroot}/sites/all");
198     // Add special files in sites/ to the archive. Last 2 items are new in Drupal8.
199     $files_to_add = array('sites/README.txt', 'sites/sites.php', 'sites/example.sites.php', 'sites/development.services.yml', 'sites/example.settings.local.php');
200     foreach ($files_to_add as $file_to_add) {
201       if (file_exists($file_to_add)) {
202         drush_shell_cd_and_exec($workdir, "$tar {$dereference}-rf %s %s", $destination, $docroot . '/' . $file_to_add);
203       }
204     }
205   }
206
207   $tmp = drush_tempdir();
208   $all_dbs = array();
209   // Dump the default database for each site and add to the archive.
210   foreach ($sites as $key => $alias) {
211     if (isset($alias['databases']['default']['default'])) {
212       $db_spec = $alias['databases']['default']['default'];
213       // Use a subdirectory name matching the docroot name.
214       drush_mkdir("{$tmp}/{$docroot}");
215
216       // Ensure uniqueness by prefixing key if needed. Remove path delimiters.
217       $dbname = str_replace(DIRECTORY_SEPARATOR, '-', $db_spec['database']);
218       $result_file = count($sites) == 1 ? "$tmp/$dbname.sql" : str_replace('@', '', "$tmp/$key-$dbname.sql");
219
220       $all_dbs[$key] = array(
221         'file' => basename($result_file),
222         'driver' => $db_spec['driver'],
223       );
224       $sql = drush_sql_get_class($db_spec);
225       $sql->dump($result_file);
226       drush_shell_cd_and_exec($tmp, "$tar {$tar_extra_options} --dereference -rf %s %s", $destination, basename($result_file));
227     }
228   }
229
230   // Build a manifest file AND add sites/$subdir to archive as we go.
231   $platform = array(
232     'datestamp' => time(),
233     'formatversion' => '1.0',
234     'generator' => drush_get_option('generator', 'Drush archive-dump'),
235     'generatorversion' => drush_get_option('generatorversion', DRUSH_VERSION),
236     'description' => drush_get_option('description', ''),
237     'tags' => drush_get_option('tags', ''),
238     'archiveformat' => ($include_platform ? 'platform' : 'site'),
239   );
240   $contents = drush_export_ini(array('Global' => $platform));
241
242   $i=0;
243   foreach ($sites as $key => $alias) {
244     $status = drush_invoke_process($alias, 'core-status', array(), array(), array('integrate' => FALSE));
245     if (!$status || $status['error_log']) {
246       drush_log(dt('Unable to determine sites directory for !alias', array('!alias' => $key)), LogLevel::WARNING);
247     }
248
249     // Add the site specific directory to archive.
250     if (!empty($status['object']['%paths']['%site'])) {
251       drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} --dereference -rf %s %s", $destination,  "{$docroot}/sites/" . basename($status['object']['%paths']['%site']));
252     }
253
254     $site = array(
255       'docroot' => DRUPAL_ROOT,
256       'sitedir' => @$status['object']['%paths']['%site'],
257       'files-public' => @$status['object']['%paths']['%files'],
258       'files-private' => @$status['object']['%paths']['%private'],
259     );
260     $site["database-default-file"] = $all_dbs[$key]['file'];
261     $site["database-default-driver"] = $all_dbs[$key]['driver'];
262     // The section title is the sites subdirectory name.
263     $info[basename($site['sitedir'])] = $site;
264     $contents .= "\n" . drush_export_ini($info);
265     unset($info);
266     $i++;
267   }
268   file_put_contents("{$tmp}/MANIFEST.ini", $contents);
269
270   // Add manifest to archive.
271   drush_shell_cd_and_exec($tmp, "$tar --dereference -rf %s %s", $destination, 'MANIFEST.ini');
272
273   // Ensure that default/default.settings.php is in the archive. This is needed
274   // by site-install when restoring a site without any DB.
275   // NOTE: Windows tar file replace operation is broken so we have to check if file already exists.
276   // Otherwise it will corrupt the archive.
277   $res = drush_shell_cd_and_exec($workdir, "$tar -tf %s %s", $destination, $docroot . '/sites/default/default.settings.php');
278   $output = drush_shell_exec_output();
279   if (!$res || !isset($output[0]) || empty($output[0])) {
280     drush_shell_cd_and_exec($workdir, "$tar --dereference -vrf %s %s", $destination, $docroot . '/sites/default/default.settings.php');
281   }
282
283   // Switch back to original destination in case it was modified for tar on MinGW.
284   if (!empty($destination_orig)) {
285     $destination = $destination_orig;
286   }
287
288   // Compress the archive
289   if (!drush_shell_exec("gzip --no-name -f %s", $destination)) {
290     // Clean up the tar file
291     drush_register_file_for_deletion($destination);
292     return drush_set_error(DRUSH_APPLICATION_ERROR, dt('Failed to gzip !destination', ['!destination' => $destination]));
293   }
294
295   // gzip appends .gz unless the name already ends in .gz, .tgz, or .taz.
296   if ("{$destination}.gz" != $final_destination) {
297     drush_move_dir("{$destination}.gz", $final_destination, $overwrite);
298   }
299
300   drush_log(dt('Archive saved to !dest', array('!dest' => $final_destination)), LogLevel::OK);
301   return $final_destination;
302 }
303
304 /**
305  * Command argument complete callback.
306  *
307  * @return
308  *   List of site names/aliases for archival.
309  */
310 function archive_archive_dump_complete() {
311   return array('values' => array_keys(_drush_sitealias_all_list()));
312 }
313
314 /**
315  * Command callback. Restore web site(s) from a site archive file.
316  */
317 function drush_archive_restore($file, $site_id = NULL) {
318   $tmp = drush_tempdir();
319
320   // Get the extra options for tar, if any
321   $tar_extra_options = drush_sitealias_evaluate_paths_in_options(drush_get_option('tar-options', ''));
322
323   if (!$files = drush_tarball_extract($file, $tmp, FALSE, $tar_extra_options)) {
324     return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_EXTRACT', dt('Unable to extract site archive tarball to !tmp.', array('!tmp' => $tmp)));
325   }
326
327   $manifest = $tmp . '/MANIFEST.ini';
328   if (file_exists($manifest)) {
329     if (!$ini = parse_ini_file($manifest, TRUE)) {
330       return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_PARSE_MANIFEST', dt('Unable to parse MANIFEST.ini in the archive.'));
331     }
332   }
333   else {
334     $ini = drush_archive_guess_manifest($tmp);
335   }
336
337   // Backward compatibility: 'archiveformat' did not exist
338   // in older versions of archive-dump.
339   if (!isset( $ini['Global']['archiveformat'])) {
340     $ini['Global']['archiveformat'] = 'platform';
341   }
342
343   // Grab the first site in the Manifest and move docroot to destination.
344   $ini_tmp = $ini;
345   unset($ini_tmp['Global']);
346   $first = array_shift($ini_tmp);
347   $docroot = basename($first['docroot']);
348   $destination = drush_get_option('destination', realpath('.') . "/$docroot");
349
350   if ($ini['Global']['archiveformat'] == 'platform') {
351     // Move the whole platform in-place at once.
352     if (!drush_move_dir("$tmp/$docroot", $destination, drush_get_option('overwrite'))) {
353       return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_RESTORE_FILES', dt('Unable to restore platform to !dest', array('!dest' => $destination)));
354     }
355   }
356   else {
357     // When no platform is included we do this on a per-site basis.
358   }
359
360   // Loop over sites and restore databases and append to settings.php.
361   foreach ($ini as $section => $site) {
362     if ($section != 'Global' && (!isset($site_id) || $section == $site_id) && !empty($site['database-default-file'])) {
363       $site_destination = $destination . '/' . $site['sitedir'];
364       // Restore site, in case not already done above.
365       if ($ini['Global']['archiveformat'] == 'site') {
366         if (!drush_move_dir("$tmp/$docroot/" . $site['sitedir'], $site_destination, drush_get_option('overwrite'))) {
367           return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_RESTORE_FILES', dt('Unable to restore site to !dest', array('!dest' => $site_destination)));
368         }
369       }
370
371       // Restore database.
372       $sql_file = $tmp . '/' . $site['database-default-file'];
373       if ($db_url = drush_get_option('db-url')) {
374         if (empty($site_id) && count($ini) >= 3) {
375           // TODO: Use drushrc to provide multiple db-urls for multi-restore?
376           return drush_set_error('DRUSH_ARCHIVE_UNABLE_MULTI_RESTORE', dt('You must specify a site to restore when the archive contains multiple sites and a db-url is provided.'));
377         }
378         $db_spec = drush_convert_db_from_db_url($db_url);
379       }
380       else {
381         $site_specification = $destination . '#' . $section;
382         if ($return = drush_invoke_process($site_specification, 'sql-conf', array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE))) {
383           $databases = $return['object'];
384           $db_spec = $databases['default']['default'];
385         }
386         else {
387           return drush_set_error('DRUSH_ARCHIVE_UNABLE_DISCOVER_DB', dt('Unable to get database details from db-url option or settings.php', array('!key' => $key)));
388         }
389       }
390       $sql = drush_sql_get_class($db_spec);
391       $sql->drop_or_create();
392       $sql->query(NULL, $sql_file);
393
394       // Append new DB info to settings.php.
395       if ($db_url) {
396         $settingsfile = $destination . '/' . $site['sitedir'] . '/settings.php';
397         //If settings.php doesn't exist in the archive, create it from default.settings.php.
398         if (!file_exists($settingsfile)) {
399           drush_op('copy', $destination . '/sites/default/default.settings.php', $settingsfile);
400         }
401         // Need to do something here or else we can't write.
402         chmod($settingsfile, 0664);
403         file_put_contents($settingsfile, "\n// Appended by drush archive-restore command.\n", FILE_APPEND);
404         if (drush_drupal_major_version($destination) >= 7) {
405           file_put_contents($settingsfile, "\n" . '$databases = ' . var_export(drush_sitealias_convert_db_from_db_url($db_url), TRUE) . ";\n", FILE_APPEND);
406         }
407         else {
408           file_put_contents($settingsfile, "\n" . '$db_url = \'' . $db_url . "';\n", FILE_APPEND);
409         }
410         drush_log(dt('Drush appended the new database configuration at settings.php. Optionally remove the old configuration manually.'), LogLevel::OK);
411       }
412     }
413   }
414   drush_log(dt('Archive restored to !dest', array('!dest' => $destination)), LogLevel::OK);
415
416   return $destination;
417 }
418
419
420 /**
421  * Command argument complete callback.
422  *
423  * @return
424  *   Strong glob of files to complete on.
425  */
426 function archive_archive_restore_complete() {
427   return array(
428     'files' => array(
429       'directories' => array(
430         'pattern' => '*',
431         'flags' => GLOB_ONLYDIR,
432       ),
433       'tar' => array(
434         'pattern' => '*.tar.gz',
435       ),
436     ),
437   );
438 }
439
440 /**
441  * Try to find docroot and DB dump file in an extracted archive.
442  *
443  * @param string $path The location of the extracted archive.
444  * @return array The manifest data.
445  */
446 function drush_archive_guess_manifest($path) {
447   $db_file = drush_scan_directory($path, '/\.sql$/',  array('.', '..', 'CVS'), 0, 0);
448
449   if (file_exists($path . '/index.php')) {
450     $docroot = './';
451   }
452   else {
453     $directories = glob($path . '/*' , GLOB_ONLYDIR);
454     $docroot = reset($directories);
455   }
456
457   $ini = array(
458     'Global' => array(
459         // Very crude detection of a platform...
460         'archiveformat' => (drush_drupal_version($docroot) ? 'platform' : 'site'),
461     ),
462     'default' => array(
463       'docroot' => $docroot,
464       'sitedir' => 'sites/default',
465       'database-default-file' => key($db_file),
466     ),
467   );
468
469   return $ini;
470 }