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