2 namespace Drush\Drupal\Commands\config;
4 use Consolidation\AnnotatedCommand\CommandError;
5 use Consolidation\AnnotatedCommand\CommandData;
6 use Drupal\config\StorageReplaceDataWrapper;
7 use Drupal\Core\Config\ConfigManagerInterface;
8 use Drupal\Core\Config\StorageComparer;
9 use Drupal\Core\Config\ConfigImporter;
10 use Drupal\Core\Config\ConfigException;
11 use Drupal\Core\Config\FileStorage;
12 use Drupal\Core\Config\StorageInterface;
13 use Drupal\Core\Config\TypedConfigManagerInterface;
14 use Drupal\Core\Extension\ModuleHandlerInterface;
15 use Drupal\Core\Extension\ModuleInstallerInterface;
16 use Drupal\Core\Extension\ThemeHandlerInterface;
17 use Drupal\Core\Lock\LockBackendInterface;
18 use Drupal\Core\StringTranslation\TranslationInterface;
19 use Drush\Commands\DrushCommands;
20 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
21 use Webmozart\PathUtil\Path;
23 class ConfigImportCommands extends DrushCommands
27 * @var ConfigManagerInterface
29 protected $configManager;
31 protected $configStorage;
33 protected $configStorageSync;
35 protected $eventDispatcher;
39 protected $configTyped;
41 protected $moduleInstaller;
43 protected $themeHandler;
45 protected $stringTranslation;
48 * @var \Drupal\Core\Extension\ModuleHandlerInterface
50 protected $moduleHandler;
53 * @return ConfigManagerInterface
55 public function getConfigManager()
57 return $this->configManager;
61 * @return StorageInterface
63 public function getConfigStorage()
65 return $this->configStorage;
69 * @return StorageInterface
71 public function getConfigStorageSync()
73 return $this->configStorageSync;
76 public function getModuleHandler()
78 return $this->moduleHandler;
82 * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
84 public function getEventDispatcher()
86 return $this->eventDispatcher;
90 * @return \Drupal\Core\Lock\LockBackendInterface
92 public function getLock()
98 * @return \Drupal\Core\Config\TypedConfigManagerInterface
100 public function getConfigTyped()
102 return $this->configTyped;
106 * @return \Drupal\Core\Extension\ModuleInstallerInterface
108 public function getModuleInstaller()
110 return $this->moduleInstaller;
114 * @return \Drupal\Core\Extension\ThemeHandlerInterface
116 public function getThemeHandler()
118 return $this->themeHandler;
122 * @return \Drupal\Core\StringTranslation\TranslationInterface
124 public function getStringTranslation()
126 return $this->stringTranslation;
130 * @param ConfigManagerInterface $configManager
131 * @param StorageInterface $configStorage
132 * @param StorageInterface $configStorageSync
134 public function __construct(ConfigManagerInterface $configManager, StorageInterface $configStorage, StorageInterface $configStorageSync, ModuleHandlerInterface $moduleHandler, EventDispatcherInterface $eventDispatcher, LockBackendInterface $lock, TypedConfigManagerInterface $configTyped, ModuleInstallerInterface $moduleInstaller, ThemeHandlerInterface $themeHandler, TranslationInterface $stringTranslation)
136 parent::__construct();
137 $this->configManager = $configManager;
138 $this->configStorage = $configStorage;
139 $this->configStorageSync = $configStorageSync;
140 $this->moduleHandler = $moduleHandler;
141 $this->eventDispatcher = $eventDispatcher;
143 $this->configTyped = $configTyped;
144 $this->moduleInstaller = $moduleInstaller;
145 $this->themeHandler = $themeHandler;
146 $this->stringTranslation = $stringTranslation;
150 * Import config from a config directory.
152 * @command config:import
153 * @param $label A config directory label (i.e. a key in \$config_directories array in settings.php).
154 * @interact-config-label
155 * @option diff Show preview as a diff.
156 * @option preview Deprecated. Format for displaying proposed changes. Recognized values: list, diff.
157 * @option source An arbitrary directory that holds the configuration files. An alternative to label argument
158 * @option partial 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).
159 * @aliases cim,config-import
161 public function import($label = null, $options = ['preview' => 'list', 'source' => self::REQ, 'partial' => false, 'diff' => false])
163 // Determine source directory.
165 $source_storage_dir = ConfigCommands::getDirectory($label, $options['source']);
167 // Prepare the configuration storage for the import.
168 if ($source_storage_dir == Path::canonicalize(\config_get_config_directory(CONFIG_SYNC_DIRECTORY))) {
169 $source_storage = $this->getConfigStorageSync();
171 $source_storage = new FileStorage($source_storage_dir);
174 // Determine $source_storage in partial case.
175 $active_storage = $this->getConfigStorage();
176 if ($options['partial']) {
177 $replacement_storage = new StorageReplaceDataWrapper($active_storage);
178 foreach ($source_storage->listAll() as $name) {
179 $data = $source_storage->read($name);
180 $replacement_storage->replaceData($name, $data);
182 $source_storage = $replacement_storage;
185 $config_manager = $this->getConfigManager();
186 $storage_comparer = new StorageComparer($source_storage, $active_storage, $config_manager);
189 if (!$storage_comparer->createChangelist()->hasChanges()) {
190 $this->logger()->notice(('There are no changes to import.'));
194 if ($options['preview'] == 'list' && !$options['diff']) {
196 foreach ($storage_comparer->getAllCollectionNames() as $collection) {
197 $change_list[$collection] = $storage_comparer->getChangelist(null, $collection);
199 $table = ConfigCommands::configChangesTable($change_list, $this->output());
202 $output = ConfigCommands::getDiff($active_storage, $source_storage, $this->output());
204 $this->output()->writeln(implode("\n", $output));
207 if ($this->io()->confirm(dt('Import the listed configuration changes?'))) {
208 return drush_op([$this, 'doImport'], $storage_comparer);
212 // Copied from submitForm() at /core/modules/config/src/Form/ConfigSync.php
213 public function doImport($storage_comparer)
215 $config_importer = new ConfigImporter(
217 $this->getEventDispatcher(),
218 $this->getConfigManager(),
220 $this->getConfigTyped(),
221 $this->getModuleHandler(),
222 $this->getModuleInstaller(),
223 $this->getThemeHandler(),
224 $this->getStringTranslation()
226 if ($config_importer->alreadyImporting()) {
227 $this->logger()->warning('Another request may be synchronizing configuration already.');
230 // This is the contents of \Drupal\Core\Config\ConfigImporter::import.
231 // Copied here so we can log progress.
232 if ($config_importer->hasUnprocessedConfigurationChanges()) {
233 $sync_steps = $config_importer->initialize();
234 foreach ($sync_steps as $step) {
237 $config_importer->doSyncStep($step, $context);
238 if (isset($context['message'])) {
239 $this->logger()->notice(str_replace('Synchronizing', 'Synchronized', (string)$context['message']));
241 } while ($context['finished'] < 1);
244 if ($config_importer->getErrors()) {
245 throw new ConfigException('Errors occurred during import');
247 $this->logger()->success('The configuration was imported successfully.');
249 } catch (ConfigException $e) {
250 // Return a negative result for UI purposes. We do not differentiate
251 // between an actual synchronization error and a failed lock, because
252 // concurrent synchronizations are an edge-case happening only when
253 // multiple developers or site builders attempt to do it without
255 $message = 'The import failed due to the following reasons:' . "\n";
256 $message .= implode("\n", $config_importer->getErrors());
258 watchdog_exception('config_import', $e);
259 throw new \Exception($message);
265 * @hook validate config-import
266 * @param \Consolidation\AnnotatedCommand\CommandData $commandData
267 * @return \Consolidation\AnnotatedCommand\CommandError|null
269 public function validate(CommandData $commandData)
272 if ($commandData->input()->getOption('partial') && !$this->getModuleHandler()->moduleExists('config')) {
273 $msgs[] = 'Enable the config module in order to use the --partial option.';
276 if ($source = $commandData->input()->getOption('source')) {
277 if (!file_exists($source)) {
278 $msgs[] = 'The source directory does not exist.';
280 if (!is_dir($source)) {
281 $msgs[] = 'The source is not a directory.';
286 return new CommandError(implode(' ', $msgs));