Updated to Drupal 8.5. Core Media not yet in use.
[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  * @internal
31  */
32 class ConfigSingleImportForm extends ConfirmFormBase {
33
34   /**
35    * The entity manager.
36    *
37    * @var \Drupal\Core\Entity\EntityManagerInterface
38    */
39   protected $entityManager;
40
41   /**
42    * The config storage.
43    *
44    * @var \Drupal\Core\Config\StorageInterface
45    */
46   protected $configStorage;
47
48   /**
49    * The renderer service.
50    *
51    * @var \Drupal\Core\Render\RendererInterface
52    */
53   protected $renderer;
54
55   /**
56    * The event dispatcher.
57    *
58    * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
59    */
60   protected $eventDispatcher;
61
62   /**
63    * The configuration manager.
64    *
65    * @var \Drupal\Core\Config\ConfigManagerInterface;
66    */
67   protected $configManager;
68
69   /**
70    * The database lock object.
71    *
72    * @var \Drupal\Core\Lock\LockBackendInterface
73    */
74   protected $lock;
75
76   /**
77    * The typed config manager.
78    *
79    * @var \Drupal\Core\Config\TypedConfigManagerInterface
80    */
81   protected $typedConfigManager;
82
83   /**
84    * The module handler.
85    *
86    * @var \Drupal\Core\Extension\ModuleHandlerInterface
87    */
88   protected $moduleHandler;
89
90   /**
91    * The theme handler.
92    *
93    * @var \Drupal\Core\Extension\ThemeHandlerInterface
94    */
95   protected $themeHandler;
96
97   /**
98    * The module installer.
99    *
100    * @var \Drupal\Core\Extension\ModuleInstallerInterface
101    */
102   protected $moduleInstaller;
103
104   /**
105    * If the config exists, this is that object. Otherwise, FALSE.
106    *
107    * @var \Drupal\Core\Config\Config|\Drupal\Core\Config\Entity\ConfigEntityInterface|bool
108    */
109   protected $configExists = FALSE;
110
111   /**
112    * The submitted data needing to be confirmed.
113    *
114    * @var array
115    */
116   protected $data = [];
117
118   /**
119    * Constructs a new ConfigSingleImportForm.
120    *
121    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
122    *   The entity manager.
123    * @param \Drupal\Core\Config\StorageInterface $config_storage
124    *   The config storage.
125    * @param \Drupal\Core\Render\RendererInterface $renderer
126    *   The renderer service.
127    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
128    *   The event dispatcher used to notify subscribers of config import events.
129    * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
130    *   The configuration manager.
131    * @param \Drupal\Core\Lock\LockBackendInterface $lock
132    *   The lock backend to ensure multiple imports do not occur at the same time.
133    * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
134    *   The typed configuration manager.
135    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
136    *   The module handler.
137    * @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
138    *   The module installer.
139    * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
140    *   The theme handler.
141    */
142   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) {
143     $this->entityManager = $entity_manager;
144     $this->configStorage = $config_storage;
145     $this->renderer = $renderer;
146
147     // Services necessary for \Drupal\Core\Config\ConfigImporter.
148     $this->eventDispatcher = $event_dispatcher;
149     $this->configManager = $config_manager;
150     $this->lock = $lock;
151     $this->typedConfigManager = $typed_config;
152     $this->moduleHandler = $module_handler;
153     $this->moduleInstaller = $module_installer;
154     $this->themeHandler = $theme_handler;
155   }
156
157   /**
158    * {@inheritdoc}
159    */
160   public static function create(ContainerInterface $container) {
161     return new static(
162       $container->get('entity.manager'),
163       $container->get('config.storage'),
164       $container->get('renderer'),
165       $container->get('event_dispatcher'),
166       $container->get('config.manager'),
167       $container->get('lock.persistent'),
168       $container->get('config.typed'),
169       $container->get('module_handler'),
170       $container->get('module_installer'),
171       $container->get('theme_handler')
172     );
173   }
174
175   /**
176    * {@inheritdoc}
177    */
178   public function getFormId() {
179     return 'config_single_import_form';
180   }
181
182   /**
183    * {@inheritdoc}
184    */
185   public function getCancelUrl() {
186     return new Url('config.import_single');
187   }
188
189   /**
190    * {@inheritdoc}
191    */
192   public function getQuestion() {
193     if ($this->data['config_type'] === 'system.simple') {
194       $name = $this->data['config_name'];
195       $type = $this->t('simple configuration');
196     }
197     else {
198       $definition = $this->entityManager->getDefinition($this->data['config_type']);
199       $name = $this->data['import'][$definition->getKey('id')];
200       $type = $definition->getLowercaseLabel();
201     }
202
203     $args = [
204       '%name' => $name,
205       '@type' => strtolower($type),
206     ];
207     if ($this->configExists) {
208       $question = $this->t('Are you sure you want to update the %name @type?', $args);
209     }
210     else {
211       $question = $this->t('Are you sure you want to create a new %name @type?', $args);
212     }
213     return $question;
214   }
215
216   /**
217    * {@inheritdoc}
218    */
219   public function buildForm(array $form, FormStateInterface $form_state) {
220     // When this is the confirmation step fall through to the confirmation form.
221     if ($this->data) {
222       return parent::buildForm($form, $form_state);
223     }
224
225     $entity_types = [];
226     foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
227       if ($definition->entityClassImplements(ConfigEntityInterface::class)) {
228         $entity_types[$entity_type] = $definition->getLabel();
229       }
230     }
231     // Sort the entity types by label, then add the simple config to the top.
232     uasort($entity_types, 'strnatcasecmp');
233     $config_types = [
234       'system.simple' => $this->t('Simple configuration'),
235     ] + $entity_types;
236     $form['config_type'] = [
237       '#title' => $this->t('Configuration type'),
238       '#type' => 'select',
239       '#options' => $config_types,
240       '#required' => TRUE,
241     ];
242     $form['config_name'] = [
243       '#title' => $this->t('Configuration name'),
244       '#description' => $this->t('Enter the name of the configuration file without the <em>.yml</em> extension. (e.g. <em>system.site</em>)'),
245       '#type' => 'textfield',
246       '#states' => [
247         'required' => [
248           ':input[name="config_type"]' => ['value' => 'system.simple'],
249         ],
250         'visible' => [
251           ':input[name="config_type"]' => ['value' => 'system.simple'],
252         ],
253       ],
254     ];
255     $form['import'] = [
256       '#title' => $this->t('Paste your configuration here'),
257       '#type' => 'textarea',
258       '#rows' => 24,
259       '#required' => TRUE,
260     ];
261     $form['advanced'] = [
262       '#type' => 'details',
263       '#title' => $this->t('Advanced'),
264     ];
265     $form['advanced']['custom_entity_id'] = [
266       '#title' => $this->t('Custom Entity ID'),
267       '#type' => 'textfield',
268       '#description' => $this->t('Specify a custom entity ID. This will override the entity ID in the configuration above.'),
269     ];
270     $form['actions'] = ['#type' => 'actions'];
271     $form['actions']['submit'] = [
272       '#type' => 'submit',
273       '#value' => $this->t('Import'),
274       '#button_type' => 'primary',
275     ];
276     return $form;
277   }
278
279   /**
280    * {@inheritdoc}
281    */
282   public function validateForm(array &$form, FormStateInterface $form_state) {
283     // The confirmation step needs no additional validation.
284     if ($this->data) {
285       return;
286     }
287
288     try {
289       // Decode the submitted import.
290       $data = Yaml::decode($form_state->getValue('import'));
291     }
292     catch (InvalidDataTypeException $e) {
293       $form_state->setErrorByName('import', $this->t('The import failed with the following message: %message', ['%message' => $e->getMessage()]));
294     }
295
296     // Validate for config entities.
297     if ($form_state->getValue('config_type') !== 'system.simple') {
298       $definition = $this->entityManager->getDefinition($form_state->getValue('config_type'));
299       $id_key = $definition->getKey('id');
300
301       // If a custom entity ID is specified, override the value in the
302       // configuration data being imported.
303       if (!$form_state->isValueEmpty('custom_entity_id')) {
304         $data[$id_key] = $form_state->getValue('custom_entity_id');
305       }
306
307       $entity_storage = $this->entityManager->getStorage($form_state->getValue('config_type'));
308       // If an entity ID was not specified, set an error.
309       if (!isset($data[$id_key])) {
310         $form_state->setErrorByName('import', $this->t('Missing ID key "@id_key" for this @entity_type import.', ['@id_key' => $id_key, '@entity_type' => $definition->getLabel()]));
311         return;
312       }
313
314       $config_name = $definition->getConfigPrefix() . '.' . $data[$id_key];
315       // If there is an existing entity, ensure matching ID and UUID.
316       if ($entity = $entity_storage->load($data[$id_key])) {
317         $this->configExists = $entity;
318         if (!isset($data['uuid'])) {
319           $form_state->setErrorByName('import', $this->t('An entity with this machine name already exists but the import did not specify a UUID.'));
320           return;
321         }
322         if ($data['uuid'] !== $entity->uuid()) {
323           $form_state->setErrorByName('import', $this->t('An entity with this machine name already exists but the UUID does not match.'));
324           return;
325         }
326       }
327       // If there is no entity with a matching ID, check for a UUID match.
328       elseif (isset($data['uuid']) && $entity_storage->loadByProperties(['uuid' => $data['uuid']])) {
329         $form_state->setErrorByName('import', $this->t('An entity with this UUID already exists but the machine name does not match.'));
330       }
331     }
332     else {
333       $config_name = $form_state->getValue('config_name');
334       $config = $this->config($config_name);
335       $this->configExists = !$config->isNew() ? $config : FALSE;
336     }
337
338     // Use ConfigImporter validation.
339     if (!$form_state->getErrors()) {
340       $source_storage = new StorageReplaceDataWrapper($this->configStorage);
341       $source_storage->replaceData($config_name, $data);
342       $storage_comparer = new StorageComparer(
343         $source_storage,
344         $this->configStorage,
345         $this->configManager
346       );
347
348       if (!$storage_comparer->createChangelist()->hasChanges()) {
349         $form_state->setErrorByName('import', $this->t('There are no changes to import.'));
350       }
351       else {
352         $config_importer = new ConfigImporter(
353           $storage_comparer,
354           $this->eventDispatcher,
355           $this->configManager,
356           $this->lock,
357           $this->typedConfigManager,
358           $this->moduleHandler,
359           $this->moduleInstaller,
360           $this->themeHandler,
361           $this->getStringTranslation()
362         );
363
364         try {
365           $config_importer->validate();
366           $form_state->set('config_importer', $config_importer);
367         }
368         catch (ConfigImporterException $e) {
369           // There are validation errors.
370           $item_list = [
371             '#theme' => 'item_list',
372             '#items' => $config_importer->getErrors(),
373             '#title' => $this->t('The configuration cannot be imported because it failed validation for the following reasons:'),
374           ];
375           $form_state->setErrorByName('import', $this->renderer->render($item_list));
376         }
377       }
378     }
379
380     // Store the decoded version of the submitted import.
381     $form_state->setValueForElement($form['import'], $data);
382   }
383
384   /**
385    * {@inheritdoc}
386    */
387   public function submitForm(array &$form, FormStateInterface $form_state) {
388     // If this form has not yet been confirmed, store the values and rebuild.
389     if (!$this->data) {
390       $form_state->setRebuild();
391       $this->data = $form_state->getValues();
392       return;
393     }
394
395     /** @var \Drupal\Core\Config\ConfigImporter $config_importer */
396     $config_importer = $form_state->get('config_importer');
397     if ($config_importer->alreadyImporting()) {
398       drupal_set_message($this->t('Another request may be importing configuration already.'), 'error');
399     }
400     else {
401       try {
402         $sync_steps = $config_importer->initialize();
403         $batch = [
404           'operations' => [],
405           'finished' => [ConfigSync::class, 'finishBatch'],
406           'title' => $this->t('Importing configuration'),
407           'init_message' => $this->t('Starting configuration import.'),
408           'progress_message' => $this->t('Completed @current step of @total.'),
409           'error_message' => $this->t('Configuration import has encountered an error.'),
410         ];
411         foreach ($sync_steps as $sync_step) {
412           $batch['operations'][] = [[ConfigSync::class, 'processBatch'], [$config_importer, $sync_step]];
413         }
414
415         batch_set($batch);
416       }
417       catch (ConfigImporterException $e) {
418         // There are validation errors.
419         drupal_set_message($this->t('The configuration import failed for the following reasons:'), 'error');
420         foreach ($config_importer->getErrors() as $message) {
421           drupal_set_message($message, 'error');
422         }
423       }
424     }
425   }
426
427 }