3 namespace Drupal\config\Form;
5 use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
6 use Drupal\config\StorageReplaceDataWrapper;
7 use Drupal\Core\Config\ConfigImporter;
8 use Drupal\Core\Config\ConfigImporterException;
9 use Drupal\Core\Config\ConfigManagerInterface;
10 use Drupal\Core\Config\Entity\ConfigEntityInterface;
11 use Drupal\Core\Config\StorageComparer;
12 use Drupal\Core\Config\StorageInterface;
13 use Drupal\Core\Config\TypedConfigManagerInterface;
14 use Drupal\Core\Entity\EntityManagerInterface;
15 use Drupal\Core\Extension\ModuleHandlerInterface;
16 use Drupal\Core\Extension\ModuleInstallerInterface;
17 use Drupal\Core\Extension\ThemeHandlerInterface;
18 use Drupal\Core\Form\ConfirmFormBase;
19 use Drupal\Core\Form\FormStateInterface;
20 use Drupal\Core\Lock\LockBackendInterface;
21 use Drupal\Core\Render\RendererInterface;
22 use Drupal\Core\Serialization\Yaml;
24 use Symfony\Component\DependencyInjection\ContainerInterface;
25 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
28 * Provides a form for importing a single configuration file.
30 class ConfigSingleImportForm extends ConfirmFormBase {
35 * @var \Drupal\Core\Entity\EntityManagerInterface
37 protected $entityManager;
42 * @var \Drupal\Core\Config\StorageInterface
44 protected $configStorage;
47 * The renderer service.
49 * @var \Drupal\Core\Render\RendererInterface
54 * The event dispatcher.
56 * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
58 protected $eventDispatcher;
61 * The configuration manager.
63 * @var \Drupal\Core\Config\ConfigManagerInterface;
65 protected $configManager;
68 * The database lock object.
70 * @var \Drupal\Core\Lock\LockBackendInterface
75 * The typed config manager.
77 * @var \Drupal\Core\Config\TypedConfigManagerInterface
79 protected $typedConfigManager;
84 * @var \Drupal\Core\Extension\ModuleHandlerInterface
86 protected $moduleHandler;
91 * @var \Drupal\Core\Extension\ThemeHandlerInterface
93 protected $themeHandler;
96 * The module installer.
98 * @var \Drupal\Core\Extension\ModuleInstallerInterface
100 protected $moduleInstaller;
103 * If the config exists, this is that object. Otherwise, FALSE.
105 * @var \Drupal\Core\Config\Config|\Drupal\Core\Config\Entity\ConfigEntityInterface|bool
107 protected $configExists = FALSE;
110 * The submitted data needing to be confirmed.
114 protected $data = [];
117 * Constructs a new ConfigSingleImportForm.
119 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
120 * The entity manager.
121 * @param \Drupal\Core\Config\StorageInterface $config_storage
122 * The config storage.
123 * @param \Drupal\Core\Render\RendererInterface $renderer
124 * The renderer service.
125 * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
126 * The event dispatcher used to notify subscribers of config import events.
127 * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
128 * The configuration manager.
129 * @param \Drupal\Core\Lock\LockBackendInterface $lock
130 * The lock backend to ensure multiple imports do not occur at the same time.
131 * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
132 * The typed configuration manager.
133 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
134 * The module handler.
135 * @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
136 * The module installer.
137 * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
140 public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage, RendererInterface $renderer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler) {
141 $this->entityManager = $entity_manager;
142 $this->configStorage = $config_storage;
143 $this->renderer = $renderer;
145 // Services necessary for \Drupal\Core\Config\ConfigImporter.
146 $this->eventDispatcher = $event_dispatcher;
147 $this->configManager = $config_manager;
149 $this->typedConfigManager = $typed_config;
150 $this->moduleHandler = $module_handler;
151 $this->moduleInstaller = $module_installer;
152 $this->themeHandler = $theme_handler;
158 public static function create(ContainerInterface $container) {
160 $container->get('entity.manager'),
161 $container->get('config.storage'),
162 $container->get('renderer'),
163 $container->get('event_dispatcher'),
164 $container->get('config.manager'),
165 $container->get('lock.persistent'),
166 $container->get('config.typed'),
167 $container->get('module_handler'),
168 $container->get('module_installer'),
169 $container->get('theme_handler')
176 public function getFormId() {
177 return 'config_single_import_form';
183 public function getCancelUrl() {
184 return new Url('config.import_single');
190 public function getQuestion() {
191 if ($this->data['config_type'] === 'system.simple') {
192 $name = $this->data['config_name'];
193 $type = $this->t('simple configuration');
196 $definition = $this->entityManager->getDefinition($this->data['config_type']);
197 $name = $this->data['import'][$definition->getKey('id')];
198 $type = $definition->getLowercaseLabel();
203 '@type' => strtolower($type),
205 if ($this->configExists) {
206 $question = $this->t('Are you sure you want to update the %name @type?', $args);
209 $question = $this->t('Are you sure you want to create a new %name @type?', $args);
217 public function buildForm(array $form, FormStateInterface $form_state) {
218 // When this is the confirmation step fall through to the confirmation form.
220 return parent::buildForm($form, $form_state);
224 foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
225 if ($definition->entityClassImplements(ConfigEntityInterface::class)) {
226 $entity_types[$entity_type] = $definition->getLabel();
229 // Sort the entity types by label, then add the simple config to the top.
230 uasort($entity_types, 'strnatcasecmp');
232 'system.simple' => $this->t('Simple configuration'),
234 $form['config_type'] = [
235 '#title' => $this->t('Configuration type'),
237 '#options' => $config_types,
240 $form['config_name'] = [
241 '#title' => $this->t('Configuration name'),
242 '#description' => $this->t('Enter the name of the configuration file without the <em>.yml</em> extension. (e.g. <em>system.site</em>)'),
243 '#type' => 'textfield',
246 ':input[name="config_type"]' => ['value' => 'system.simple'],
249 ':input[name="config_type"]' => ['value' => 'system.simple'],
254 '#title' => $this->t('Paste your configuration here'),
255 '#type' => 'textarea',
259 $form['advanced'] = [
260 '#type' => 'details',
261 '#title' => $this->t('Advanced'),
263 $form['advanced']['custom_entity_id'] = [
264 '#title' => $this->t('Custom Entity ID'),
265 '#type' => 'textfield',
266 '#description' => $this->t('Specify a custom entity ID. This will override the entity ID in the configuration above.'),
268 $form['actions'] = ['#type' => 'actions'];
269 $form['actions']['submit'] = [
271 '#value' => $this->t('Import'),
272 '#button_type' => 'primary',
280 public function validateForm(array &$form, FormStateInterface $form_state) {
281 // The confirmation step needs no additional validation.
287 // Decode the submitted import.
288 $data = Yaml::decode($form_state->getValue('import'));
290 catch (InvalidDataTypeException $e) {
291 $form_state->setErrorByName('import', $this->t('The import failed with the following message: %message', ['%message' => $e->getMessage()]));
294 // Validate for config entities.
295 if ($form_state->getValue('config_type') !== 'system.simple') {
296 $definition = $this->entityManager->getDefinition($form_state->getValue('config_type'));
297 $id_key = $definition->getKey('id');
299 // If a custom entity ID is specified, override the value in the
300 // configuration data being imported.
301 if (!$form_state->isValueEmpty('custom_entity_id')) {
302 $data[$id_key] = $form_state->getValue('custom_entity_id');
305 $entity_storage = $this->entityManager->getStorage($form_state->getValue('config_type'));
306 // If an entity ID was not specified, set an error.
307 if (!isset($data[$id_key])) {
308 $form_state->setErrorByName('import', $this->t('Missing ID key "@id_key" for this @entity_type import.', ['@id_key' => $id_key, '@entity_type' => $definition->getLabel()]));
312 $config_name = $definition->getConfigPrefix() . '.' . $data[$id_key];
313 // If there is an existing entity, ensure matching ID and UUID.
314 if ($entity = $entity_storage->load($data[$id_key])) {
315 $this->configExists = $entity;
316 if (!isset($data['uuid'])) {
317 $form_state->setErrorByName('import', $this->t('An entity with this machine name already exists but the import did not specify a UUID.'));
320 if ($data['uuid'] !== $entity->uuid()) {
321 $form_state->setErrorByName('import', $this->t('An entity with this machine name already exists but the UUID does not match.'));
325 // If there is no entity with a matching ID, check for a UUID match.
326 elseif (isset($data['uuid']) && $entity_storage->loadByProperties(['uuid' => $data['uuid']])) {
327 $form_state->setErrorByName('import', $this->t('An entity with this UUID already exists but the machine name does not match.'));
331 $config_name = $form_state->getValue('config_name');
332 $config = $this->config($config_name);
333 $this->configExists = !$config->isNew() ? $config : FALSE;
336 // Use ConfigImporter validation.
337 if (!$form_state->getErrors()) {
338 $source_storage = new StorageReplaceDataWrapper($this->configStorage);
339 $source_storage->replaceData($config_name, $data);
340 $storage_comparer = new StorageComparer(
342 $this->configStorage,
346 if (!$storage_comparer->createChangelist()->hasChanges()) {
347 $form_state->setErrorByName('import', $this->t('There are no changes to import.'));
350 $config_importer = new ConfigImporter(
352 $this->eventDispatcher,
353 $this->configManager,
355 $this->typedConfigManager,
356 $this->moduleHandler,
357 $this->moduleInstaller,
359 $this->getStringTranslation()
363 $config_importer->validate();
364 $form_state->set('config_importer', $config_importer);
366 catch (ConfigImporterException $e) {
367 // There are validation errors.
369 '#theme' => 'item_list',
370 '#items' => $config_importer->getErrors(),
371 '#title' => $this->t('The configuration cannot be imported because it failed validation for the following reasons:'),
373 $form_state->setErrorByName('import', $this->renderer->render($item_list));
378 // Store the decoded version of the submitted import.
379 $form_state->setValueForElement($form['import'], $data);
385 public function submitForm(array &$form, FormStateInterface $form_state) {
386 // If this form has not yet been confirmed, store the values and rebuild.
388 $form_state->setRebuild();
389 $this->data = $form_state->getValues();
393 /** @var \Drupal\Core\Config\ConfigImporter $config_importer */
394 $config_importer = $form_state->get('config_importer');
395 if ($config_importer->alreadyImporting()) {
396 drupal_set_message($this->t('Another request may be importing configuration already.'), 'error');
400 $sync_steps = $config_importer->initialize();
403 'finished' => [ConfigSync::class, 'finishBatch'],
404 'title' => $this->t('Importing configuration'),
405 'init_message' => $this->t('Starting configuration import.'),
406 'progress_message' => $this->t('Completed @current step of @total.'),
407 'error_message' => $this->t('Configuration import has encountered an error.'),
409 foreach ($sync_steps as $sync_step) {
410 $batch['operations'][] = [[ConfigSync::class, 'processBatch'], [$config_importer, $sync_step]];
415 catch (ConfigImporterException $e) {
416 // There are validation errors.
417 drupal_set_message($this->t('The configuration import failed for the following reasons:'), 'error');
418 foreach ($config_importer->getErrors() as $message) {
419 drupal_set_message($message, 'error');