Minor dependency updates
[yaffs-website] / vendor / drush / drush / commands / core / config.drush.inc
1 <?php
2
3 /**
4  * @file
5  *   Provides Configuration Management commands.
6  */
7
8 use Drupal\config\StorageReplaceDataWrapper;
9 use Drush\Log\LogLevel;
10 use Drupal\Core\Config\StorageComparer;
11 use Drupal\Core\Config\ConfigImporter;
12 use Drupal\Core\Config\ConfigException;
13 use Drupal\Core\Config\FileStorage;
14 use Drupal\Component\Utility\NestedArray;
15 use Drush\Config\StorageWrapper;
16 use Drush\Config\CoreExtensionFilter;
17 use Symfony\Component\Yaml\Parser;
18
19 /**
20  * Implementation of hook_drush_help().
21  */
22 function config_drush_help($section) {
23   switch ($section) {
24     case 'meta:config:title':
25       return dt('Config commands');
26     case 'meta:config:summary':
27       return dt('Interact with the configuration system.');
28   }
29 }
30
31 /**
32  * Implementation of hook_drush_command().
33  */
34 function config_drush_command() {
35   $deps = array('drupal dependencies' => array('config'));
36   $items['config-get'] = array(
37     'description' => 'Display a config value, or a whole configuration object.',
38     'arguments' => array(
39       'config-name' => 'The config object name, for example "system.site".',
40       'key' => 'The config key, for example "page.front". Optional.',
41     ),
42     'required-arguments' => 1,
43     'options' => array(
44       'source' => array(
45         'description' => 'The config storage source to read. Additional labels may be defined in settings.php',
46         'example-value' => 'sync',
47         'value' => 'required',
48       ),
49       'include-overridden' => array(
50         'description' => 'Include overridden values.',
51       )
52     ),
53     'examples' => array(
54       'drush config-get system.site' => 'Displays the system.site config.',
55       'drush config-get system.site page.front' => 'gets system.site:page.front value.',
56     ),
57     'outputformat' => array(
58       'default' => 'yaml',
59       'pipe-format' => 'var_export',
60     ),
61     'aliases' => array('cget'),
62     'core' => array('8+'),
63   );
64
65   $items['config-set'] = array(
66     'description' => 'Set config value directly. Does not perform a config import.',
67     'arguments' => array(
68       'config-name' => 'The config object name, for example "system.site".',
69       'key' => 'The config key, for example "page.front".',
70       'value' => 'The value to assign to the config key. Use \'-\' to read from STDIN.',
71     ),
72     'options' => array(
73       'format' => array(
74         'description' => 'Format to parse the object. Use "string" for string (default), and "yaml" for YAML.',
75         'example-value' => 'yaml',
76         'value' => 'required',
77       ),
78       // A convenient way to pass a multiline value within a backend request.
79       'value' => array(
80         'description' => 'The value to assign to the config key (if any).',
81         'hidden' => TRUE,
82       ),
83     ),
84     'examples' => array(
85       'drush config-set system.site page.front node' => 'Sets system.site:page.front to "node".',
86     ),
87     'aliases' => array('cset'),
88     'core' => array('8+'),
89   );
90
91   $items['config-export'] = array(
92     'description' => 'Export configuration to a directory.',
93     'core' => array('8+'),
94     'aliases' => array('cex'),
95     'arguments' => array(
96       'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'",
97     ),
98     'options' => array(
99       'add' => 'Run `git add -p` after exporting. This lets you choose which config changes to sync for commit.',
100       'commit' => 'Run `git add -A` and `git commit` after exporting.  This commits everything that was exported without prompting.',
101       'message' => 'Commit comment for the exported configuration.  Optional; may only be used with --commit or --push.',
102       'push' => 'Run `git push` after committing.  Implies --commit.',
103       'remote' => array(
104         'description' => 'The remote git branch to use to push changes.  Defaults to "origin".',
105         'example-value' => 'origin',
106       ),
107       'branch' => array(
108         'description' => 'Make commit on provided working branch. Ignored if used without --commit or --push.',
109         'example-value' => 'branchname',
110       ),
111       'destination' => 'An arbitrary directory that should receive the exported files. An alternative to label argument.',
112     ),
113     'examples' => array(
114       'drush config-export --destination' => 'Export configuration; Save files in a backup directory named config-export.',
115     ),
116   );
117
118   $items['config-import'] = array(
119     'description' => 'Import config from a config directory.',
120     'arguments' => array(
121       'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'",
122     ),
123     'options' => array(
124       'preview' => array(
125         'description' => 'Format for displaying proposed changes. Recognized values: list, diff. Defaults to list.',
126         'example-value' => 'list',
127       ),
128       'source' => array(
129         'description' => 'An arbitrary directory that holds the configuration files. An alternative to label argument',
130       ),
131       'partial' => array(
132         'description' => 'Allows for partial config imports from the source directory. Only updates and new configs will be processed with this flag (missing configs will not be deleted).',
133       ),
134     ),
135     'core' => array('8+'),
136     'examples' => array(
137       'drush config-import --partial' => 'Import configuration; do not remove missing configuration.',
138     ),
139     'aliases' => array('cim'),
140   );
141
142   $items['config-list'] = array(
143     'description' => 'List config names by prefix.',
144     'core' => array('8+'),
145     'aliases' => array('cli'),
146     'arguments' => array(
147       'prefix' => 'The config prefix. For example, "system". No prefix will return all names in the system.',
148     ),
149     'examples' => array(
150       'drush config-list system' => 'Return a list of all system config names.',
151       'drush config-list "image.style"' => 'Return a list of all image styles.',
152       'drush config-list --format="json"' => 'Return all config names as json.',
153     ),
154     'outputformat' => array(
155       'default' => 'list',
156       'pipe-format' => 'var_export',
157       'output-data-type' => 'format-list',
158     ),
159   );
160
161   $items['config-edit'] = $deps + array(
162     'description' => 'Open a config file in a text editor. Edits are imported into active configuration after closing editor.',
163     'core' => array('8+'),
164     'aliases' => array('cedit'),
165     'arguments' => array(
166       'config-name' => 'The config object name, for example "system.site".',
167     ),
168     'global-options' => array('editor', 'bg'),
169     'allow-additional-options' => array('config-import'),
170     'examples' => array(
171       'drush config-edit image.style.large' => 'Edit the image style configurations.',
172       'drush config-edit' => 'Choose a config file to edit.',
173       'drush config-edit --choice=2' => 'Edit the second file in the choice list.',
174       'drush --bg config-edit image.style.large' => 'Return to shell prompt as soon as the editor window opens.',
175     ),
176   );
177
178   $items['config-delete'] = array(
179     'description' => 'Delete a configuration object.',
180     'core' => array('8+'),
181     'aliases' => array('cdel'),
182     'arguments' => array(
183         'config-name' => 'The config object name, for example "system.site".',
184     ),
185     'required arguments'
186   );
187
188   $items['config-pull'] = array(
189     'description' => 'Export and transfer config from one environment to another.',
190     // 'core' => array('8+'), Operates on remote sites so not possible to declare this locally.
191     'drush dependencies' => array('config', 'core'), // core-rsync, core-execute.
192     'bootstrap' => DRUSH_BOOTSTRAP_NONE,
193     'aliases' => array('cpull'),
194     'arguments' => array(
195       'source' => 'A site-alias or the name of a subdirectory within /sites whose config you want to copy from.',
196       'target' => 'A site-alias or the name of a subdirectory within /sites whose config you want to replace.',
197     ),
198     'required-arguments' => TRUE,
199     'allow-additional-options' => array(), // Most options from config-export and core-rsync unusable.
200     'examples' => array(
201       'drush config-pull @prod @stage' => "Export config from @prod and transfer to @stage.",
202       'drush config-pull @prod @self --label=vcs' => "Export config from @prod and transfer to the 'vcs' config directory of current site.",
203     ),
204     'options' => array(
205       'safe' => 'Validate that there are no git uncommitted changes before proceeding',
206       'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'",
207       'runner' => 'Where to run the rsync command; defaults to the local site. Can also be "source" or "destination".',
208     ),
209     'topics' => array('docs-aliases', 'docs-config-exporting'),
210   );
211
212   return $items;
213 }
214
215 /**
216  * Implements hook_drush_help_alter().
217  */
218 function config_drush_help_alter(&$command) {
219   // Hide additional-options which are for internal use only.
220   if ($command['command'] == 'config-edit') {
221     $command['options']['source']['hidden'] = TRUE;
222     $command['options']['partial']['hidden'] = TRUE;
223   }
224 }
225
226 /**
227  * Config list command callback
228  *
229  * @param string $prefix
230  *   The config prefix to retrieve, or empty to return all.
231  */
232 function drush_config_list($prefix = '') {
233   $names = \Drupal::configFactory()->listAll($prefix);
234
235   if (empty($names)) {
236     // Just in case there is no config.
237     if (!$prefix) {
238       return drush_set_error(dt('No config storage names found.'));
239     }
240     else {
241       return drush_set_error(dt('No config storage names found matching @prefix', array('@prefix' => $prefix)));
242     }
243   }
244
245   return $names;
246 }
247
248 /**
249  * Config get command callback.
250  *
251  * @param $config_name
252  *   The config name.
253  * @param $key
254  *   The config key.
255  */
256 function drush_config_get($config_name, $key = NULL) {
257   if (!isset($key)) {
258     return drush_config_get_object($config_name);
259   }
260   else {
261     return drush_config_get_value($config_name, $key);
262   }
263 }
264
265 /**
266  * Config delete command callback.
267  *
268  * @param $config_name
269  *   The config name.
270  */
271 function drush_config_delete($config_name) {
272   $config =\Drupal::service('config.factory')->getEditable($config_name);
273   if ($config->isNew()) {
274     return drush_set_error('DRUSH_CONFIG_ERROR', 'Configuration name not recognized. Use config-list to see all names.');
275   }
276   else {
277     $config->delete();
278   }
279 }
280
281 /**
282  * Config set command callback.
283  *
284  * @param $config_name
285  *   The config name.
286  * @param $key
287  *   The config key.
288  * @param $data
289  *    The data to save to config.
290  */
291 function drush_config_set($config_name, $key = NULL, $data = NULL) {
292   // This hidden option is a convenient way to pass a value without passing a key.
293   $data = drush_get_option('value', $data);
294
295   if (!isset($data)) {
296     return drush_set_error('DRUSH_CONFIG_ERROR', dt('No config value specified.'));
297   }
298
299   $config = \Drupal::configFactory()->getEditable($config_name);
300   // Check to see if config key already exists.
301   if ($config->get($key) === NULL) {
302     $new_key = TRUE;
303   }
304   else {
305     $new_key = FALSE;
306   }
307
308   // Special flag indicating that the value has been passed via STDIN.
309   if ($data === '-') {
310     $data = stream_get_contents(STDIN);
311   }
312
313   // Now, we parse the value.
314   switch (drush_get_option('format', 'string')) {
315     case 'yaml':
316       $parser = new Parser();
317       $data = $parser->parse($data, TRUE);
318   }
319
320   if (is_array($data) && drush_confirm(dt('Do you want to update or set multiple keys on !name config.', array('!name' => $config_name)))) {
321     foreach ($data as $key => $value) {
322       $config->set($key, $value);
323     }
324     return $config->save();
325   }
326   else {
327     $confirmed = FALSE;
328     if ($config->isNew() && drush_confirm(dt('!name config does not exist. Do you want to create a new config object?', array('!name' => $config_name)))) {
329       $confirmed = TRUE;
330     }
331     elseif ($new_key && drush_confirm(dt('!key key does not exist in !name config. Do you want to create a new config key?', array('!key' => $key, '!name' => $config_name)))) {
332       $confirmed = TRUE;
333     }
334     elseif (drush_confirm(dt('Do you want to update !key key in !name config?', array('!key' => $key, '!name' => $config_name)))) {
335       $confirmed = TRUE;
336     }
337     if ($confirmed && !drush_get_context('DRUSH_SIMULATE')) {
338       return $config->set($key, $data)->save();
339     }
340   }
341 }
342
343 /*
344  * If provided $destination is not TRUE and not empty, make sure it is writable.
345  */
346 function drush_config_export_validate() {
347   $destination = drush_get_option('destination');
348   if ($destination === TRUE) {
349     // We create a dir in command callback. No need to validate.
350     return;
351   }
352
353   if (!empty($destination)) {
354     $additional = array();
355     $values = drush_sitealias_evaluate_path($destination, $additional, TRUE);
356     if (!isset($values['path'])) {
357       return drush_set_error('config_export_target', 'The destination directory could not be evaluated.');
358     }
359     $destination = $values['path'];
360     drush_set_option('destination', $destination);
361     if (!file_exists($destination)) {
362       $parent = dirname($destination);
363       if (!is_dir($parent)) {
364         return drush_set_error('config_export_target', 'The destination parent directory does not exist.');
365       }
366       if (!is_writable($parent)) {
367         return drush_set_error('config_export_target', 'The destination parent directory is not writable.');
368       }
369     }
370     else {
371       if (!is_dir($destination)) {
372         return drush_set_error('config_export_target', 'The destination is not a directory.');
373       }
374       if (!is_writable($destination)) {
375         return drush_set_error('config_export_target', 'The destination directory is not writable.');
376       }
377     }
378   }
379 }
380
381 /**
382  * Command callback: Export config to specified directory (usually sync).
383  */
384 function drush_config_export($destination = NULL) {
385   global $config_directories;
386
387   // Determine which target directory to use.
388   if ($target = drush_get_option('destination')) {
389     if ($target === TRUE) {
390       // User did not pass a specific value for --destination. Make one.
391       /** @var drush_version_control_backup $backup */
392       $backup = drush_include_engine('version_control', 'backup');
393       $destination_dir = $backup->prepare_backup_dir('config-export');
394     }
395     else {
396       $destination_dir = $target;
397       // It is important to be able to specify a destination directory that
398       // does not exist yet, for exporting on remote systems
399       drush_mkdir($destination_dir);
400     }
401   }
402   else {
403     $choices = drush_map_assoc(array_keys($config_directories));
404     unset($choices[CONFIG_ACTIVE_DIRECTORY]);
405     if (!isset($destination) && count($choices) >= 2) {
406       $destination = drush_choice($choices, 'Choose a destination.');
407       if (empty($destination)) {
408         return drush_user_abort();
409       }
410     }
411     elseif (!isset($destination)) {
412       $destination = CONFIG_SYNC_DIRECTORY;
413     }
414     $destination_dir = config_get_config_directory($destination);
415   }
416
417   // Prepare a new branch, if applicable
418   $remote = drush_get_option('push', FALSE);
419   $original_branch = FALSE;
420   $branch = FALSE;
421   if ($remote) {
422     // Get the branch that we're on at the moment
423     $result = drush_shell_cd_and_exec($destination_dir, 'git rev-parse --abbrev-ref HEAD');
424     if (!$result) {
425       return drush_set_error('DRUSH_CONFIG_EXPORT_NO_GIT', dt("The drush config-export command requires that the selected configuration directory !dir be under git revision control when using --commit or --push options.", array('!dir' => $destination_dir)));
426     }
427     $output = drush_shell_exec_output();
428     $original_branch = $output[0];
429     $branch = drush_get_option('branch', FALSE);
430     if (!$branch) {
431       $branch = $original_branch;
432     }
433     if ($branch != $original_branch) {
434       // Switch to the working branch; create it if it does not exist.
435       // We do NOT want to use -B here, as we do NOT want to reset the
436       // branch if it already exists.
437       $result = drush_shell_cd_and_exec($destination_dir, 'git checkout %s', $branch);
438       if (!$result) {
439         $result = drush_shell_cd_and_exec($destination_dir, 'git checkout -b %s', $branch);
440       }
441     }
442   }
443
444   // Do the actual config export operation
445   $result = _drush_config_export($destination, $destination_dir, $branch);
446
447   // Regardless of the result of the export, reset to our original branch.
448   if ($branch != $original_branch) {
449     drush_shell_cd_and_exec($destination_dir, 'git checkout %s', $original_branch);
450   }
451
452   return $result;
453 }
454
455 function _drush_config_export($destination, $destination_dir, $branch) {
456   $commit = drush_get_option('commit');
457   $comment = drush_get_option('message', 'Exported configuration.');
458   if (count(glob($destination_dir . '/*')) > 0) {
459     // Retrieve a list of differences between the active and target configuration (if any).
460     if ($destination == CONFIG_SYNC_DIRECTORY) {
461       $target_storage = \Drupal::service('config.storage.sync');
462     }
463     else {
464       $target_storage = new FileStorage($destination_dir);
465     }
466     /** @var \Drupal\Core\Config\StorageInterface $active_storage */
467     $active_storage = \Drupal::service('config.storage');
468     $comparison_source = $active_storage;
469
470     $config_comparer = new StorageComparer($comparison_source, $target_storage, \Drupal::service('config.manager'));
471     if (!$config_comparer->createChangelist()->hasChanges()) {
472       return drush_log(dt('The active configuration is identical to the configuration in the export directory (!target).', array('!target' => $destination_dir)), LogLevel::OK);
473     }
474
475     drush_print("Differences of the active config to the export directory:\n");
476     $change_list = array();
477     foreach ($config_comparer->getAllCollectionNames() as $collection) {
478       $change_list[$collection] = $config_comparer->getChangelist(NULL, $collection);
479     }
480     // Print a table with changes in color, then re-generate again without
481     // color to place in the commit comment.
482     _drush_print_config_changes_table($change_list);
483     $tbl = _drush_format_config_changes_table($change_list);
484     $output = $tbl->getTable();
485     if (!stristr(PHP_OS, 'WIN')) {
486       $output = str_replace("\r\n", PHP_EOL, $output);
487     }
488     $comment .= "\n\n$output";
489
490     if (!$commit && !drush_confirm(dt('The .yml files in your export directory (!target) will be deleted and replaced with the active config.', array('!target' => $destination_dir)))) {
491       return drush_user_abort();
492     }
493     // Only delete .yml files, and not .htaccess or .git.
494     $target_storage->deleteAll();
495   }
496
497   // Write all .yml files.
498   $source_storage = \Drupal::service('config.storage');
499   if ($destination == CONFIG_SYNC_DIRECTORY) {
500     $destination_storage = \Drupal::service('config.storage.sync');
501   }
502   else {
503     $destination_storage = new FileStorage($destination_dir);
504   }
505
506   foreach ($source_storage->listAll() as $name) {
507     $destination_storage->write($name, $source_storage->read($name));
508   }
509
510   // Export configuration collections.
511   foreach (\Drupal::service('config.storage')->getAllCollectionNames() as $collection) {
512     $source_storage = $source_storage->createCollection($collection);
513     $destination_storage = $destination_storage->createCollection($collection);
514     foreach ($source_storage->listAll() as $name) {
515       $destination_storage->write($name, $source_storage->read($name));
516     }
517   }
518
519   drush_log(dt('Configuration successfully exported to !target.', array('!target' => $destination_dir)), LogLevel::SUCCESS);
520   drush_backend_set_result($destination_dir);
521
522   // Commit and push, or add exported configuration if requested.
523   $remote = drush_get_option('push', FALSE);
524   if ($commit || $remote) {
525     // There must be changed files at the destination dir; if there are not, then
526     // we will skip the commit-and-push step
527     $result = drush_shell_cd_and_exec($destination_dir, 'git status --porcelain .');
528     if (!$result) {
529       return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git status` failed."));
530     }
531     $uncommitted_changes = drush_shell_exec_output();
532     if (!empty($uncommitted_changes)) {
533       $result = drush_shell_cd_and_exec($destination_dir, 'git add -A .');
534       if (!$result) {
535         return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git add -A` failed."));
536       }
537       $comment_file = drush_save_data_to_temp_file($comment);
538       $result = drush_shell_cd_and_exec($destination_dir, 'git commit --file=%s', $comment_file);
539       if (!$result) {
540         return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git commit` failed.  Output:\n\n!output", array('!output' => implode("\n", drush_shell_exec_output()))));
541       }
542       if ($remote) {
543         // Remote might be FALSE, if --push was not specified, or
544         // it might be TRUE if --push was not given a value.
545         if (!is_string($remote)) {
546           $remote = 'origin';
547         }
548         $result = drush_shell_cd_and_exec($destination_dir, 'git push --set-upstream %s %s', $remote, $branch);
549         if (!$result) {
550           return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git push` failed."));
551         }
552       }
553     }
554   }
555   elseif (drush_get_option('add')) {
556     drush_shell_exec_interactive('git add -p %s', $destination_dir);
557   }
558
559   $values = array(
560     'destination' => $destination_dir,
561   );
562   return $values;
563 }
564
565 function drush_config_import_validate() {
566   drush_include_engine('drupal', 'environment');
567   if (drush_get_option('partial') && !drush_module_exists('config')) {
568     return drush_set_error('config_import_partial', 'Enable the config module in order to use the --partial option.');
569   }
570   if ($source = drush_get_option('source')) {
571     if (!file_exists($source)) {
572       return drush_set_error('config_import_target', 'The source directory does not exist.');
573     }
574     if (!is_dir($source)) {
575       return drush_set_error('config_import_target', 'The source is not a directory.');
576     }
577   }
578 }
579
580 /**
581  * Command callback. Import from specified config directory (defaults to sync).
582  */
583 function drush_config_import($source = NULL) {
584   global $config_directories;
585
586   // Determine source directory.
587   if ($target = drush_get_option('source')) {
588     $source_dir = $target;
589   }
590   else {
591     $choices = drush_map_assoc(array_keys($config_directories));
592     unset($choices[CONFIG_ACTIVE_DIRECTORY]);
593     if (!isset($source) && count($choices) >= 2) {
594       $source= drush_choice($choices, 'Choose a source.');
595       if (empty($source)) {
596         return drush_user_abort();
597       }
598     }
599     elseif (!isset($source)) {
600       $source = CONFIG_SYNC_DIRECTORY;
601     }
602     $source_dir = config_get_config_directory($source);
603   }
604
605   if ($source == CONFIG_SYNC_DIRECTORY) {
606     $source_storage = \Drupal::service('config.storage.sync');
607   }
608   else {
609     $source_storage = new FileStorage($source_dir);
610   }
611
612   // Determine $source_storage in partial and non-partial cases.
613   /** @var \Drupal\Core\Config\StorageInterface $active_storage */
614   $active_storage = \Drupal::service('config.storage');
615   if (drush_get_option('partial')) {
616     $replacement_storage = new StorageReplaceDataWrapper($active_storage);
617     foreach ($source_storage->listAll() as $name) {
618       $data = $source_storage->read($name);
619       $replacement_storage->replaceData($name, $data);
620     }
621     $source_storage = $replacement_storage;
622   }
623
624   /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
625   $config_manager = \Drupal::service('config.manager');
626   $storage_comparer = new StorageComparer($source_storage, $active_storage, $config_manager);
627
628   if (!$storage_comparer->createChangelist()->hasChanges()) {
629     return drush_log(dt('There are no changes to import.'), LogLevel::OK);
630   }
631
632   if (drush_get_option('preview', 'list') == 'list') {
633     $change_list = array();
634     foreach ($storage_comparer->getAllCollectionNames() as $collection) {
635       $change_list[$collection] = $storage_comparer->getChangelist(NULL, $collection);
636     }
637     _drush_print_config_changes_table($change_list);
638   }
639   else {
640     // Copy active storage to the temporary directory.
641     $temp_dir = drush_tempdir();
642     $temp_storage = new FileStorage($temp_dir);
643     $source_dir_storage = new FileStorage($source_dir);
644     foreach ($source_dir_storage->listAll() as $name) {
645       if ($data = $active_storage->read($name)) {
646         $temp_storage->write($name, $data);
647       }
648     }
649     drush_shell_exec('diff -x %s -u %s %s', '*.git', $temp_dir, $source_dir);
650     $output = drush_shell_exec_output();
651     drush_print(implode("\n", $output));
652   }
653
654   if (drush_confirm(dt('Import the listed configuration changes?'))) {
655     return drush_op('_drush_config_import', $storage_comparer);
656   }
657 }
658
659 // Copied from submitForm() at /core/modules/config/src/Form/ConfigSync.php
660 function _drush_config_import(StorageComparer $storage_comparer) {
661   $config_importer = new ConfigImporter(
662     $storage_comparer,
663     \Drupal::service('event_dispatcher'),
664     \Drupal::service('config.manager'),
665     \Drupal::lock(),
666     \Drupal::service('config.typed'),
667     \Drupal::moduleHandler(),
668     \Drupal::service('module_installer'),
669     \Drupal::service('theme_handler'),
670     \Drupal::service('string_translation')
671   );
672   if ($config_importer->alreadyImporting()) {
673     drush_log('Another request may be synchronizing configuration already.', LogLevel::WARNING);
674   }
675   else{
676     try {
677       // This is the contents of \Drupal\Core\Config\ConfigImporter::import.
678       // Copied here so we can log progress.
679       if ($config_importer->hasUnprocessedConfigurationChanges()) {
680         $sync_steps = $config_importer->initialize();
681         foreach ($sync_steps as $step) {
682           $context = array();
683           do {
684             $config_importer->doSyncStep($step, $context);
685             if (isset($context['message'])) {
686               drush_log(str_replace('Synchronizing', 'Synchronized', (string)$context['message']), LogLevel::OK);
687             }
688           } while ($context['finished'] < 1);
689         }
690       }
691       if ($config_importer->getErrors()) {
692         throw new \Drupal\Core\Config\ConfigException('Errors occurred during import');
693       }
694       else {
695         drush_log('The configuration was imported successfully.', LogLevel::SUCCESS);
696       }
697     }
698     catch (ConfigException $e) {
699       // Return a negative result for UI purposes. We do not differentiate
700       // between an actual synchronization error and a failed lock, because
701       // concurrent synchronizations are an edge-case happening only when
702       // multiple developers or site builders attempt to do it without
703       // coordinating.
704       $message = 'The import failed due for the following reasons:' . "\n";
705       $message .= implode("\n", $config_importer->getErrors());
706
707       watchdog_exception('config_import', $e);
708       return drush_set_error('config_import_fail', $message);
709     }
710   }
711 }
712
713 /**
714  * Edit command callback.
715  */
716 function drush_config_edit($config_name = '') {
717   // Identify and validate input.
718   if ($config_name) {
719     $config = \Drupal::configFactory()->get($config_name);
720     if ($config->isNew()) {
721       return drush_set_error(dt('Config !name does not exist', array('!name' => $config_name)));
722     }
723   }
724   else {
725     $config_names = \Drupal::configFactory()->listAll();
726     $choice = drush_choice($config_names, 'Choose a configuration.');
727     if (empty($choice)) {
728       return drush_user_abort();
729     }
730     else {
731       $config_name = $config_names[$choice];
732       $config = \Drupal::configFactory()->get($config_name);
733     }
734   }
735
736   $active_storage = $config->getStorage();
737   $contents = $active_storage->read($config_name);
738
739   // Write tmp YAML file for editing
740   $temp_dir = drush_tempdir();
741   $temp_storage = new FileStorage($temp_dir);
742   $temp_storage->write($config_name, $contents);
743
744   $exec = drush_get_editor();
745   drush_shell_exec_interactive($exec, $temp_storage->getFilePath($config_name));
746
747   // Perform import operation if user did not immediately exit editor.
748   if (!drush_get_option('bg', FALSE)) {
749     $options = drush_redispatch_get_options() + array('partial' => TRUE, 'source' => $temp_dir);
750     $backend_options = array('interactive' => TRUE);
751     return (bool) drush_invoke_process('@self', 'config-import', array(), $options, $backend_options);
752   }
753 }
754
755 /**
756  * Config pull validate callback
757  *
758  */
759 function drush_config_pull_validate($source, $destination) {
760   if (drush_get_option('safe')) {
761     $return = drush_invoke_process($destination, 'core-execute', array('git diff --quiet'), array('escape' => 0));
762     if ($return['error_status']) {
763       return drush_set_error('DRUSH_GIT_DIRTY', 'There are uncommitted changes in your git working copy.');
764     }
765   }
766 }
767
768 /**
769  * Config pull command callback
770  *
771  * @param string $label
772  *   The config label which receives the transferred files.
773  */
774 function drush_config_pull($source, $destination) {
775   // @todo drush_redispatch_get_options() assumes you will execute same command. Not good.
776   $global_options = drush_redispatch_get_options() + array(
777     'strict' => 0,
778   );
779
780   // @todo If either call is made interactive, we don't get an $return['object'] back.
781   $backend_options = array('interactive' => FALSE);
782   if (drush_get_context('DRUSH_SIMULATE')) {
783     $backend_options['backend-simulate'] = TRUE;
784   }
785
786   $export_options = array(
787     // Use the standard backup directory on Destination.
788     'destination' => TRUE,
789   );
790   drush_log(dt('Starting to export configuration on Target.'), LogLevel::OK);
791   $return = drush_invoke_process($source, 'config-export', array(), $global_options + $export_options, $backend_options);
792   if ($return['error_status']) {
793     return drush_set_error('DRUSH_CONFIG_PULL_EXPORT_FAILED', dt('Config-export failed.'));
794   }
795   else {
796     // Trailing slash assures that transfer files and not the containing dir.
797     $export_path = $return['object'] . '/';
798   }
799
800   $rsync_options = array(
801     'remove-source-files' => TRUE,
802     'delete' => TRUE,
803     'exclude-paths' => '.htaccess',
804     'yes' => TRUE,  // No need to prompt as destination is always the target config directory.
805   );
806   $label = drush_get_option('label', 'sync');
807   $runner = drush_get_runner($source, $destination, drush_get_option('runner', FALSE));
808   drush_log(dt('Starting to rsync configuration files from !source to !dest.', array('!source' => $source, '!dest' => $destination)), LogLevel::OK);
809   // This comment applies similarly to sql-sync's use of core-rsync.
810   // Since core-rsync is a strict-handling command and drush_invoke_process() puts options at end, we can't send along cli options to rsync.
811   // Alternatively, add options like --ssh-options to a site alias (usually on the machine that initiates the sql-sync).
812   $return = drush_invoke_process($runner, 'core-rsync', array("$source:$export_path", "$destination:%config-$label"), $rsync_options);
813   if ($return['error_status']) {
814     return drush_set_error('DRUSH_CONFIG_PULL_RSYNC_FAILED', dt('Config-pull rsync failed.'));
815   }
816
817   drush_backend_set_result($return['object']);
818 }
819
820 /**
821  * Show and return a config object
822  *
823  * @param $config_name
824  *   The config object name.
825  */
826 function drush_config_get_object($config_name) {
827   $source = drush_get_option('source', 'active');
828   $include_overridden = drush_get_option('include-overridden', FALSE);
829
830   if ($include_overridden) {
831     // Displaying overrides only applies to active storage.
832     $config = \Drupal::config($config_name);
833     $data = $config->get();
834   }
835   elseif ($source == 'active') {
836     $config = \Drupal::service('config.storage');
837     $data = $config->read($config_name);
838   }
839   elseif ($source == 'sync') {
840     $config = \Drupal::service('config.storage.sync');
841     $data = $config->read($config_name);
842   }
843   else {
844     return drush_set_error(dt('Unknown value !value for config source.', array('!value' => $source)));
845   }
846
847   if ($data === FALSE) {
848     return drush_set_error(dt('Config !name does not exist in !source configuration.', array('!name' => $config_name, '!source' => $source)));
849   }
850   if (empty($data)) {
851     drush_log(dt('Config !name exists but has no data.', array('!name' => $config_name)), LogLevel::NOTICE);
852     return;
853   }
854   return $data;
855 }
856
857 /**
858  * Show and return a value from config system.
859  *
860  * @param $config_name
861  *   The config name.
862  * @param $key
863  *   The config key.
864  */
865 function drush_config_get_value($config_name, $key) {
866   $data = drush_config_get_object($config_name);
867   $parts = explode('.', $key);
868   if (count($parts) == 1) {
869     $value =  isset($data[$key]) ? $data[$key] : NULL;
870   }
871   else {
872     $value = NestedArray::getValue($data, $parts, $key_exists);
873     $value = $key_exists ? $value : NULL;
874   }
875
876   $returns[$config_name . ':' . $key] = $value;
877
878   if ($value === NULL) {
879     return drush_set_error('DRUSH_CONFIG_ERROR', dt('No matching key found in !name config.', array('!name' => $config_name)));
880   }
881   else {
882     return $returns;
883   }
884 }
885
886 /**
887  * Print a table of config changes.
888  *
889  * @param array $config_changes
890  *   An array of changes keyed by collection.
891  */
892 function _drush_format_config_changes_table(array $config_changes, $use_color = FALSE) {
893   if (!$use_color) {
894     $red = "%s";
895     $yellow = "%s";
896     $green = "%s";
897   }
898   else {
899     $red = "\033[31;40m\033[1m%s\033[0m";
900     $yellow = "\033[1;33;40m\033[1m%s\033[0m";
901     $green = "\033[1;32;40m\033[1m%s\033[0m";
902   }
903
904   $rows = array();
905   $rows[] = array('Collection', 'Config', 'Operation');
906   foreach ($config_changes as $collection => $changes) {
907     foreach ($changes as $change => $configs) {
908       switch ($change) {
909         case 'delete':
910           $colour = $red;
911           break;
912         case 'update':
913           $colour = $yellow;
914           break;
915         case 'create':
916           $colour = $green;
917           break;
918         default:
919           $colour = "%s";
920           break;
921       }
922       foreach($configs as $config) {
923         $rows[] = array(
924           $collection,
925           $config,
926           sprintf($colour, $change)
927         );
928       }
929     }
930   }
931   $tbl = _drush_format_table($rows);
932   return $tbl;
933 }
934
935 /**
936  * Print a table of config changes.
937  *
938  * @param array $config_changes
939  *   An array of changes keyed by collection.
940  */
941 function _drush_print_config_changes_table(array $config_changes) {
942   $tbl =  _drush_format_config_changes_table($config_changes, !drush_get_context('DRUSH_NOCOLOR'));
943
944   $output = $tbl->getTable();
945   if (!stristr(PHP_OS, 'WIN')) {
946     $output = str_replace("\r\n", PHP_EOL, $output);
947   }
948
949   drush_print(rtrim($output));
950   return $tbl;
951 }
952
953 /**
954  * Command argument complete callback.
955  */
956 function config_config_get_complete() {
957   return _drush_config_names_complete();
958 }
959
960 /**
961  * Command argument complete callback.
962  */
963 function config_config_set_complete() {
964   return _drush_config_names_complete();
965 }
966
967 /**
968  * Command argument complete callback.
969  */
970 function config_config_view_complete() {
971   return _drush_config_names_complete();
972 }
973
974 /**
975  * Command argument complete callback.
976  */
977 function config_config_edit_complete() {
978   return _drush_config_names_complete();
979 }
980
981 /**
982  * Command argument complete callback.
983  */
984 function config_config_import_complete() {
985   return _drush_config_directories_complete();
986 }
987
988 /**
989  * Command argument complete callback.
990  */
991 function config_config_export_complete() {
992   return _drush_config_directories_complete();
993 }
994
995 /**
996  * Command argument complete callback.
997  */
998 function config_config_pull_complete() {
999   return array('values' => array_keys(_drush_sitealias_all_list()));
1000 }
1001
1002 /**
1003  * Helper function for command argument complete callback.
1004  *
1005  * @return
1006  *   Array of available config directories.
1007  */
1008 function _drush_config_directories_complete() {
1009   drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
1010   global $config_directories;
1011   return array('values' => array_keys($config_directories));
1012 }
1013
1014 /**
1015  * Helper function for command argument complete callback.
1016  *
1017  * @return
1018  *   Array of available config names.
1019  */
1020 function _drush_config_names_complete() {
1021   drush_bootstrap_max();
1022   return array('values' => $storage = \Drupal::service('config.storage')->listAll());
1023 }