fcb388305e9dc3823b2724b4bf33dc1f5e951acf
[yaffs-website] / web / core / modules / config / src / Form / ConfigSingleImportForm.php
1 <?php
2
3 namespace Drupal\config\Form;
4
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;
23 use Drupal\Core\Url;
24 use Symfony\Component\DependencyInjection\ContainerInterface;
25 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
26
27 /**
28  * Provides a form for importing a single configuration file.
29  */
30 class ConfigSingleImportForm extends ConfirmFormBase {
31
32   /**
33    * The entity manager.
34    *
35    * @var \Drupal\Core\Entity\EntityManagerInterface
36    */
37   protected $entityManager;
38
39   /**
40    * The config storage.
41    *
42    * @var \Drupal\Core\Config\StorageInterface
43    */
44   protected $configStorage;
45
46   /**
47    * The renderer service.
48    *
49    * @var \Drupal\Core\Render\RendererInterface
50    */
51   protected $renderer;
52
53   /**
54    * The event dispatcher.
55    *
56    * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
57    */
58   protected $eventDispatcher;
59
60   /**
61    * The configuration manager.
62    *
63    * @var \Drupal\Core\Config\ConfigManagerInterface;
64    */
65   protected $configManager;
66
67   /**
68    * The database lock object.
69    *
70    * @var \Drupal\Core\Lock\LockBackendInterface
71    */
72   protected $lock;
73
74   /**
75    * The typed config manager.
76    *
77    * @var \Drupal\Core\Config\TypedConfigManagerInterface
78    */
79   protected $typedConfigManager;
80
81   /**
82    * The module handler.
83    *
84    * @var \Drupal\Core\Extension\ModuleHandlerInterface
85    */
86   protected $moduleHandler;
87
88   /**
89    * The theme handler.
90    *
91    * @var \Drupal\Core\Extension\ThemeHandlerInterface
92    */
93   protected $themeHandler;
94
95   /**
96    * The module installer.
97    *
98    * @var \Drupal\Core\Extension\ModuleInstallerInterface
99    */
100   protected $moduleInstaller;
101
102   /**
103    * If the config exists, this is that object. Otherwise, FALSE.
104    *
105    * @var \Drupal\Core\Config\Config|\Drupal\Core\Config\Entity\ConfigEntityInterface|bool
106    */
107   protected $configExists = FALSE;
108
109   /**
110    * The submitted data needing to be confirmed.
111    *
112    * @var array
113    */
114   protected $data = [];
115
116   /**
117    * Constructs a new ConfigSingleImportForm.
118    *
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
138    *   The theme handler.
139    */
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;
144
145     // Services necessary for \Drupal\Core\Config\ConfigImporter.
146     $this->eventDispatcher = $event_dispatcher;
147     $this->configManager = $config_manager;
148     $this->lock = $lock;
149     $this->typedConfigManager = $typed_config;
150     $this->moduleHandler = $module_handler;
151     $this->moduleInstaller = $module_installer;
152     $this->themeHandler = $theme_handler;
153   }
154
155   /**
156    * {@inheritdoc}
157    */
158   public static function create(ContainerInterface $container) {
159     return new static(
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')
170     );
171   }
172
173   /**
174    * {@inheritdoc}
175    */
176   public function getFormId() {
177     return 'config_single_import_form';
178   }
179
180   /**
181    * {@inheritdoc}
182    */
183   public function getCancelUrl() {
184     return new Url('config.import_single');
185   }
186
187   /**
188    * {@inheritdoc}
189    */
190   public function getQuestion() {
191     if ($this->data['config_type'] === 'system.simple') {
192       $name = $this->data['config_name'];
193       $type = $this->t('simple configuration');
194     }
195     else {
196       $definition = $this->entityManager->getDefinition($this->data['config_type']);
197       $name = $this->data['import'][$definition->getKey('id')];
198       $type = $definition->getLowercaseLabel();
199     }
200
201     $args = [
202       '%name' => $name,
203       '@type' => strtolower($type),
204     ];
205     if ($this->configExists) {
206       $question = $this->t('Are you sure you want to update the %name @type?', $args);
207     }
208     else {
209       $question = $this->t('Are you sure you want to create a new %name @type?', $args);
210     }
211     return $question;
212   }
213
214   /**
215    * {@inheritdoc}
216    */
217   public function buildForm(array $form, FormStateInterface $form_state) {
218     // When this is the confirmation step fall through to the confirmation form.
219     if ($this->data) {
220       return parent::buildForm($form, $form_state);
221     }
222
223     $entity_types = [];
224     foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
225       if ($definition->entityClassImplements(ConfigEntityInterface::class)) {
226         $entity_types[$entity_type] = $definition->getLabel();
227       }
228     }
229     // Sort the entity types by label, then add the simple config to the top.
230     uasort($entity_types, 'strnatcasecmp');
231     $config_types = [
232       'system.simple' => $this->t('Simple configuration'),
233     ] + $entity_types;
234     $form['config_type'] = [
235       '#title' => $this->t('Configuration type'),
236       '#type' => 'select',
237       '#options' => $config_types,
238       '#required' => TRUE,
239     ];
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',
244       '#states' => [
245         'required' => [
246           ':input[name="config_type"]' => ['value' => 'system.simple'],
247         ],
248         'visible' => [
249           ':input[name="config_type"]' => ['value' => 'system.simple'],
250         ],
251       ],
252     ];
253     $form['import'] = [
254       '#title' => $this->t('Paste your configuration here'),
255       '#type' => 'textarea',
256       '#rows' => 24,
257       '#required' => TRUE,
258     ];
259     $form['advanced'] = [
260       '#type' => 'details',
261       '#title' => $this->t('Advanced'),
262     ];
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.'),
267     ];
268     $form['actions'] = ['#type' => 'actions'];
269     $form['actions']['submit'] = [
270       '#type' => 'submit',
271       '#value' => $this->t('Import'),
272       '#button_type' => 'primary',
273     ];
274     return $form;
275   }
276
277   /**
278    * {@inheritdoc}
279    */
280   public function validateForm(array &$form, FormStateInterface $form_state) {
281     // The confirmation step needs no additional validation.
282     if ($this->data) {
283       return;
284     }
285
286     try {
287       // Decode the submitted import.
288       $data = Yaml::decode($form_state->getValue('import'));
289     }
290     catch (InvalidDataTypeException $e) {
291       $form_state->setErrorByName('import', $this->t('The import failed with the following message: %message', ['%message' => $e->getMessage()]));
292     }
293
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');
298
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');
303       }
304
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()]));
309         return;
310       }
311
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.'));
318           return;
319         }
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.'));
322           return;
323         }
324       }
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.'));
328       }
329     }
330     else {
331       $config_name = $form_state->getValue('config_name');
332       $config = $this->config($config_name);
333       $this->configExists = !$config->isNew() ? $config : FALSE;
334     }
335
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(
341         $source_storage,
342         $this->configStorage,
343         $this->configManager
344       );
345
346       if (!$storage_comparer->createChangelist()->hasChanges()) {
347         $form_state->setErrorByName('import', $this->t('There are no changes to import.'));
348       }
349       else {
350         $config_importer = new ConfigImporter(
351           $storage_comparer,
352           $this->eventDispatcher,
353           $this->configManager,
354           $this->lock,
355           $this->typedConfigManager,
356           $this->moduleHandler,
357           $this->moduleInstaller,
358           $this->themeHandler,
359           $this->getStringTranslation()
360         );
361
362         try {
363           $config_importer->validate();
364           $form_state->set('config_importer', $config_importer);
365         }
366         catch (ConfigImporterException $e) {
367           // There are validation errors.
368           $item_list = [
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:'),
372           ];
373           $form_state->setErrorByName('import', $this->renderer->render($item_list));
374         }
375       }
376     }
377
378     // Store the decoded version of the submitted import.
379     $form_state->setValueForElement($form['import'], $data);
380   }
381
382   /**
383    * {@inheritdoc}
384    */
385   public function submitForm(array &$form, FormStateInterface $form_state) {
386     // If this form has not yet been confirmed, store the values and rebuild.
387     if (!$this->data) {
388       $form_state->setRebuild();
389       $this->data = $form_state->getValues();
390       return;
391     }
392
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');
397     }
398     else {
399       try {
400         $sync_steps = $config_importer->initialize();
401         $batch = [
402           'operations' => [],
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.'),
408         ];
409         foreach ($sync_steps as $sync_step) {
410           $batch['operations'][] = [[ConfigSync::class, 'processBatch'], [$config_importer, $sync_step]];
411         }
412
413         batch_set($batch);
414       }
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');
420         }
421       }
422     }
423   }
424
425 }