1a75c97db3c7a24627e4839ef436f245db7d6894
[yaffs-website] / vendor / drush / drush / src / Drupal / Commands / config / ConfigExportCommands.php
1 <?php
2 namespace Drush\Drupal\Commands\config;
3
4 use Consolidation\AnnotatedCommand\CommandData;
5 use Drupal\Core\Config\ConfigManagerInterface;
6 use Drupal\Core\Config\StorageComparer;
7 use Drupal\Core\Config\FileStorage;
8 use Drupal\Core\Config\StorageInterface;
9 use Drush\Commands\DrushCommands;
10 use Drush\Exceptions\UserAbortException;
11 use Symfony\Component\Console\Output\BufferedOutput;
12 use Webmozart\PathUtil\Path;
13
14 class ConfigExportCommands extends DrushCommands
15 {
16
17     /**
18      * @var ConfigManagerInterface
19      */
20     protected $configManager;
21
22     /**
23      * @var StorageInterface
24      */
25     protected $configStorage;
26
27     /**
28      * @var StorageInterface
29      */
30     protected $configStorageSync;
31
32     /**
33      * @return ConfigManagerInterface
34      */
35     public function getConfigManager()
36     {
37         return $this->configManager;
38     }
39
40     /**
41      * @return StorageInterface
42      */
43     public function getConfigStorage()
44     {
45         return $this->configStorage;
46     }
47
48     /**
49      * @return StorageInterface
50      */
51     public function getConfigStorageSync()
52     {
53         return $this->configStorageSync;
54     }
55
56
57     /**
58      * @param ConfigManagerInterface $configManager
59      * @param StorageInterface $configStorage
60      * @param StorageInterface $configStorageSync
61      */
62     public function __construct(ConfigManagerInterface $configManager, StorageInterface $configStorage, StorageInterface $configStorageSync)
63     {
64         parent::__construct();
65         $this->configManager = $configManager;
66         $this->configStorage = $configStorage;
67         $this->configStorageSync = $configStorageSync;
68     }
69
70     /**
71      * Export Drupal configuration to a directory.
72      *
73      * @command config:export
74      * @interact-config-label
75      * @param string $label A config directory label (i.e. a key in $config_directories array in settings.php).
76      * @option add Run `git add -p` after exporting. This lets you choose which config changes to sync for commit.
77      * @option commit Run `git add -A` and `git commit` after exporting.  This commits everything that was exported without prompting.
78      * @option message Commit comment for the exported configuration.  Optional; may only be used with --commit.
79      * @option destination An arbitrary directory that should receive the exported files. A backup directory is used when no value is provided.
80      * @option diff Show preview as a diff, instead of a change list.
81      * @usage drush config:export --destination
82      *   Export configuration; Save files in a backup directory named config-export.
83      * @aliases cex,config-export
84      */
85     public function export($label = null, $options = ['add' => false, 'commit' => false, 'message' => self::REQ, 'destination' => self::OPT, 'diff' => false])
86     {
87         // Get destination directory.
88         $destination_dir = ConfigCommands::getDirectory($label, $options['destination']);
89
90         // Do the actual config export operation.
91         $preview = $this->doExport($options, $destination_dir);
92
93         // Do the VCS operations.
94         $this->doAddCommit($options, $destination_dir, $preview);
95     }
96
97     public function doExport($options, $destination_dir)
98     {
99         // Prepare the configuration storage for the export.
100         if ($destination_dir ==  Path::canonicalize(\config_get_config_directory(CONFIG_SYNC_DIRECTORY))) {
101             $target_storage = $this->getConfigStorageSync();
102         } else {
103             $target_storage = new FileStorage($destination_dir);
104         }
105
106         if (count(glob($destination_dir . '/*')) > 0) {
107             // Retrieve a list of differences between the active and target configuration (if any).
108             $config_comparer = new StorageComparer($this->getConfigStorage(), $target_storage, $this->getConfigManager());
109             if (!$config_comparer->createChangelist()->hasChanges()) {
110                 $this->logger()->notice(dt('The active configuration is identical to the configuration in the export directory (!target).', ['!target' => $destination_dir]));
111                 return;
112             }
113             $this->output()->writeln("Differences of the active config to the export directory:\n");
114
115             if ($options['diff']) {
116                 $diff = ConfigCommands::getDiff($target_storage, $this->getConfigStorage(), $this->output());
117                 $this->output()->writeln($diff);
118             } else {
119                 $change_list = [];
120                 foreach ($config_comparer->getAllCollectionNames() as $collection) {
121                     $change_list[$collection] = $config_comparer->getChangelist(null, $collection);
122                 }
123                 // Print a table with changes in color, then re-generate again without
124                 // color to place in the commit comment.
125                 $bufferedOutput = new BufferedOutput();
126                 $table = ConfigCommands::configChangesTable($change_list, $bufferedOutput, false);
127                 $table->render();
128                 $preview = $bufferedOutput->fetch();
129                 $table = ConfigCommands::configChangesTable($change_list, $this->output(), true);
130                 $table->render();
131             }
132
133             if (!$this->io()->confirm(dt('The .yml files in your export directory (!target) will be deleted and replaced with the active config.', ['!target' => $destination_dir]))) {
134                 throw new UserAbortException();
135             }
136             // Only delete .yml files, and not .htaccess or .git.
137             $target_storage->deleteAll();
138         }
139
140         // Write all .yml files.
141         ConfigCommands::copyConfig($this->getConfigStorage(), $target_storage);
142
143         $this->logger()->success(dt('Configuration successfully exported to !target.', ['!target' => $destination_dir]));
144         drush_backend_set_result($destination_dir);
145         return isset($preview) ? $preview : 'No existing configuration to diff against.';
146     }
147
148     public function doAddCommit($options, $destination_dir, $preview)
149     {
150         // Commit or add exported configuration if requested.
151         if ($options['commit']) {
152             // There must be changed files at the destination dir; if there are not, then
153             // we will skip the commit step.
154             $result = drush_shell_cd_and_exec($destination_dir, 'git status --porcelain .');
155             if (!$result) {
156                 throw new \Exception(dt("`git status` failed."));
157             }
158             $uncommitted_changes = drush_shell_exec_output();
159             if (!empty($uncommitted_changes)) {
160                 $result = drush_shell_cd_and_exec($destination_dir, 'git add -A .');
161                 if (!$result) {
162                     throw new \Exception(dt("`git add -A` failed."));
163                 }
164                 $comment_file = drush_save_data_to_temp_file($options['message'] ?: 'Exported configuration.'. $preview);
165                 $result = drush_shell_cd_and_exec($destination_dir, 'git commit --file=%s', $comment_file);
166                 if (!$result) {
167                     throw new \Exception(dt("`git commit` failed.  Output:\n\n!output", ['!output' => implode("\n", drush_shell_exec_output())]));
168                 }
169             }
170         } elseif ($options['add']) {
171             drush_shell_exec_interactive('git add -p %s', $destination_dir);
172         }
173     }
174
175     /**
176      * @hook validate config-export
177      * @param \Consolidation\AnnotatedCommand\CommandData $commandData
178      */
179     public function validate(CommandData $commandData)
180     {
181         $destination = $commandData->input()->getOption('destination');
182
183         if ($destination === true) {
184             // We create a dir in command callback. No need to validate.
185             return;
186         }
187
188         if (!empty($destination)) {
189             // TODO: evaluate %files et. al. in destination
190             // $commandData->input()->setOption('destination', $destination);
191             if (!file_exists($destination)) {
192                 $parent = dirname($destination);
193                 if (!is_dir($parent)) {
194                     throw new \Exception('The destination parent directory does not exist.');
195                 }
196                 if (!is_writable($parent)) {
197                     throw new \Exception('The destination parent directory is not writable.');
198                 }
199             } else {
200                 if (!is_dir($destination)) {
201                     throw new \Exception('The destination is not a directory.');
202                 }
203                 if (!is_writable($destination)) {
204                     throw new \Exception('The destination directory is not writable.');
205                 }
206             }
207         }
208     }
209 }