Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / modules / config / src / Form / ConfigSync.php
1 <?php
2
3 namespace Drupal\config\Form;
4
5 use Drupal\Core\Config\ConfigImporterException;
6 use Drupal\Core\Config\ConfigImporter;
7 use Drupal\Core\Config\Importer\ConfigImporterBatch;
8 use Drupal\Core\Config\TypedConfigManagerInterface;
9 use Drupal\Core\Extension\ModuleHandlerInterface;
10 use Drupal\Core\Extension\ModuleInstallerInterface;
11 use Drupal\Core\Extension\ThemeHandlerInterface;
12 use Drupal\Core\Config\ConfigManagerInterface;
13 use Drupal\Core\Form\FormBase;
14 use Drupal\Core\Config\StorageInterface;
15 use Drupal\Core\Form\FormStateInterface;
16 use Drupal\Core\Lock\LockBackendInterface;
17 use Drupal\Core\Config\StorageComparer;
18 use Drupal\Core\Render\RendererInterface;
19 use Drupal\Core\Url;
20 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
21 use Symfony\Component\DependencyInjection\ContainerInterface;
22
23 /**
24  * Construct the storage changes in a configuration synchronization form.
25  *
26  * @internal
27  */
28 class ConfigSync extends FormBase {
29
30   /**
31    * The database lock object.
32    *
33    * @var \Drupal\Core\Lock\LockBackendInterface
34    */
35   protected $lock;
36
37   /**
38    * The sync configuration object.
39    *
40    * @var \Drupal\Core\Config\StorageInterface
41    */
42   protected $syncStorage;
43
44   /**
45    * The active configuration object.
46    *
47    * @var \Drupal\Core\Config\StorageInterface
48    */
49   protected $activeStorage;
50
51   /**
52    * The snapshot configuration object.
53    *
54    * @var \Drupal\Core\Config\StorageInterface
55    */
56   protected $snapshotStorage;
57
58   /**
59    * Event dispatcher.
60    *
61    * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
62    */
63   protected $eventDispatcher;
64
65   /**
66    * The configuration manager.
67    *
68    * @var \Drupal\Core\Config\ConfigManagerInterface
69    */
70   protected $configManager;
71
72   /**
73    * The typed config manager.
74    *
75    * @var \Drupal\Core\Config\TypedConfigManagerInterface
76    */
77   protected $typedConfigManager;
78
79   /**
80    * The module handler.
81    *
82    * @var \Drupal\Core\Extension\ModuleHandlerInterface
83    */
84   protected $moduleHandler;
85
86   /**
87    * The theme handler.
88    *
89    * @var \Drupal\Core\Extension\ThemeHandlerInterface
90    */
91   protected $themeHandler;
92
93   /**
94    * The module installer.
95    *
96    * @var \Drupal\Core\Extension\ModuleInstallerInterface
97    */
98   protected $moduleInstaller;
99
100   /**
101    * The renderer.
102    *
103    * @var \Drupal\Core\Render\RendererInterface
104    */
105   protected $renderer;
106
107   /**
108    * Constructs the object.
109    *
110    * @param \Drupal\Core\Config\StorageInterface $sync_storage
111    *   The source storage.
112    * @param \Drupal\Core\Config\StorageInterface $active_storage
113    *   The target storage.
114    * @param \Drupal\Core\Config\StorageInterface $snapshot_storage
115    *   The snapshot storage.
116    * @param \Drupal\Core\Lock\LockBackendInterface $lock
117    *   The lock object.
118    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
119    *   Event dispatcher.
120    * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
121    *   Configuration manager.
122    * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
123    *   The typed configuration manager.
124    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
125    *   The module handler.
126    * @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
127    *   The module installer.
128    * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
129    *   The theme handler.
130    * @param \Drupal\Core\Render\RendererInterface $renderer
131    *   The renderer.
132    */
133   public function __construct(StorageInterface $sync_storage, StorageInterface $active_storage, StorageInterface $snapshot_storage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, RendererInterface $renderer) {
134     $this->syncStorage = $sync_storage;
135     $this->activeStorage = $active_storage;
136     $this->snapshotStorage = $snapshot_storage;
137     $this->lock = $lock;
138     $this->eventDispatcher = $event_dispatcher;
139     $this->configManager = $config_manager;
140     $this->typedConfigManager = $typed_config;
141     $this->moduleHandler = $module_handler;
142     $this->moduleInstaller = $module_installer;
143     $this->themeHandler = $theme_handler;
144     $this->renderer = $renderer;
145   }
146
147   /**
148    * {@inheritdoc}
149    */
150   public static function create(ContainerInterface $container) {
151     return new static(
152       $container->get('config.storage.sync'),
153       $container->get('config.storage'),
154       $container->get('config.storage.snapshot'),
155       $container->get('lock.persistent'),
156       $container->get('event_dispatcher'),
157       $container->get('config.manager'),
158       $container->get('config.typed'),
159       $container->get('module_handler'),
160       $container->get('module_installer'),
161       $container->get('theme_handler'),
162       $container->get('renderer')
163     );
164   }
165
166   /**
167    * {@inheritdoc}
168    */
169   public function getFormId() {
170     return 'config_admin_import_form';
171   }
172
173   /**
174    * {@inheritdoc}
175    */
176   public function buildForm(array $form, FormStateInterface $form_state) {
177     $form['actions'] = ['#type' => 'actions'];
178     $form['actions']['submit'] = [
179       '#type' => 'submit',
180       '#value' => $this->t('Import all'),
181     ];
182     $source_list = $this->syncStorage->listAll();
183     $storage_comparer = new StorageComparer($this->syncStorage, $this->activeStorage, $this->configManager);
184     if (empty($source_list) || !$storage_comparer->createChangelist()->hasChanges()) {
185       $form['no_changes'] = [
186         '#type' => 'table',
187         '#header' => [$this->t('Name'), $this->t('Operations')],
188         '#rows' => [],
189         '#empty' => $this->t('There are no configuration changes to import.'),
190       ];
191       $form['actions']['#access'] = FALSE;
192       return $form;
193     }
194     elseif (!$storage_comparer->validateSiteUuid()) {
195       $this->messenger()->addError($this->t('The staged configuration cannot be imported, because it originates from a different site than this site. You can only synchronize configuration between cloned instances of this site.'));
196       $form['actions']['#access'] = FALSE;
197       return $form;
198     }
199     // A list of changes will be displayed, so check if the user should be
200     // warned of potential losses to configuration.
201     if ($this->snapshotStorage->exists('core.extension')) {
202       $snapshot_comparer = new StorageComparer($this->activeStorage, $this->snapshotStorage, $this->configManager);
203       if (!$form_state->getUserInput() && $snapshot_comparer->createChangelist()->hasChanges()) {
204         $change_list = [];
205         foreach ($snapshot_comparer->getAllCollectionNames() as $collection) {
206           foreach ($snapshot_comparer->getChangelist(NULL, $collection) as $config_names) {
207             if (empty($config_names)) {
208               continue;
209             }
210             foreach ($config_names as $config_name) {
211               $change_list[] = $config_name;
212             }
213           }
214         }
215         sort($change_list);
216         $message = [
217           [
218             '#markup' => $this->t('The following items in your active configuration have changes since the last import that may be lost on the next import.'),
219           ],
220           [
221             '#theme' => 'item_list',
222             '#items' => $change_list,
223           ],
224         ];
225         $this->messenger()->addWarning($this->renderer->renderPlain($message));
226       }
227     }
228
229     // Store the comparer for use in the submit.
230     $form_state->set('storage_comparer', $storage_comparer);
231
232     // Add the AJAX library to the form for dialog support.
233     $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
234
235     foreach ($storage_comparer->getAllCollectionNames() as $collection) {
236       if ($collection != StorageInterface::DEFAULT_COLLECTION) {
237         $form[$collection]['collection_heading'] = [
238           '#type' => 'html_tag',
239           '#tag' => 'h2',
240           '#value' => $this->t('@collection configuration collection', ['@collection' => $collection]),
241         ];
242       }
243       foreach ($storage_comparer->getChangelist(NULL, $collection) as $config_change_type => $config_names) {
244         if (empty($config_names)) {
245           continue;
246         }
247
248         // @todo A table caption would be more appropriate, but does not have the
249         //   visual importance of a heading.
250         $form[$collection][$config_change_type]['heading'] = [
251           '#type' => 'html_tag',
252           '#tag' => 'h3',
253         ];
254         switch ($config_change_type) {
255           case 'create':
256             $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count new', '@count new');
257             break;
258
259           case 'update':
260             $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count changed', '@count changed');
261             break;
262
263           case 'delete':
264             $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count removed', '@count removed');
265             break;
266
267           case 'rename':
268             $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count renamed', '@count renamed');
269             break;
270         }
271         $form[$collection][$config_change_type]['list'] = [
272           '#type' => 'table',
273           '#header' => [$this->t('Name'), $this->t('Operations')],
274         ];
275
276         foreach ($config_names as $config_name) {
277           if ($config_change_type == 'rename') {
278             $names = $storage_comparer->extractRenameNames($config_name);
279             $route_options = ['source_name' => $names['old_name'], 'target_name' => $names['new_name']];
280             $config_name = $this->t('@source_name to @target_name', ['@source_name' => $names['old_name'], '@target_name' => $names['new_name']]);
281           }
282           else {
283             $route_options = ['source_name' => $config_name];
284           }
285           if ($collection != StorageInterface::DEFAULT_COLLECTION) {
286             $route_name = 'config.diff_collection';
287             $route_options['collection'] = $collection;
288           }
289           else {
290             $route_name = 'config.diff';
291           }
292           $links['view_diff'] = [
293             'title' => $this->t('View differences'),
294             'url' => Url::fromRoute($route_name, $route_options),
295             'attributes' => [
296               'class' => ['use-ajax'],
297               'data-dialog-type' => 'modal',
298               'data-dialog-options' => json_encode([
299                 'width' => 700,
300               ]),
301             ],
302           ];
303           $form[$collection][$config_change_type]['list']['#rows'][] = [
304             'name' => $config_name,
305             'operations' => [
306               'data' => [
307                 '#type' => 'operations',
308                 '#links' => $links,
309               ],
310             ],
311           ];
312         }
313       }
314     }
315     return $form;
316   }
317
318   /**
319    * {@inheritdoc}
320    */
321   public function submitForm(array &$form, FormStateInterface $form_state) {
322     $config_importer = new ConfigImporter(
323       $form_state->get('storage_comparer'),
324       $this->eventDispatcher,
325       $this->configManager,
326       $this->lock,
327       $this->typedConfigManager,
328       $this->moduleHandler,
329       $this->moduleInstaller,
330       $this->themeHandler,
331       $this->getStringTranslation()
332     );
333     if ($config_importer->alreadyImporting()) {
334       $this->messenger()->addStatus($this->t('Another request may be synchronizing configuration already.'));
335     }
336     else {
337       try {
338         $sync_steps = $config_importer->initialize();
339         $batch = [
340           'operations' => [],
341           'finished' => [ConfigImporterBatch::class, 'finish'],
342           'title' => t('Synchronizing configuration'),
343           'init_message' => t('Starting configuration synchronization.'),
344           'progress_message' => t('Completed step @current of @total.'),
345           'error_message' => t('Configuration synchronization has encountered an error.'),
346         ];
347         foreach ($sync_steps as $sync_step) {
348           $batch['operations'][] = [[ConfigImporterBatch::class, 'process'], [$config_importer, $sync_step]];
349         }
350
351         batch_set($batch);
352       }
353       catch (ConfigImporterException $e) {
354         // There are validation errors.
355         $this->messenger()->addError($this->t('The configuration cannot be imported because it failed validation for the following reasons:'));
356         foreach ($config_importer->getErrors() as $message) {
357           $this->messenger()->addError($message);
358         }
359       }
360     }
361   }
362
363   /**
364    * Processes the config import batch and persists the importer.
365    *
366    * @param \Drupal\Core\Config\ConfigImporter $config_importer
367    *   The batch config importer object to persist.
368    * @param string $sync_step
369    *   The synchronization step to do.
370    * @param array $context
371    *   The batch context.
372    *
373    * @deprecated in Drupal 8.6.0 and will be removed before 9.0.0. Use
374    *   \Drupal\Core\Config\Importer\ConfigImporterBatch::process() instead.
375    *
376    * @see https://www.drupal.org/node/2897299
377    */
378   public static function processBatch(ConfigImporter $config_importer, $sync_step, &$context) {
379     @trigger_error('\Drupal\config\Form\ConfigSync::processBatch() deprecated in Drupal 8.6.0 and will be removed before 9.0.0. Use \Drupal\Core\Config\Importer\ConfigImporterBatch::process() instead. See https://www.drupal.org/node/2897299');
380     ConfigImporterBatch::process($config_importer, $sync_step, $context);
381   }
382
383   /**
384    * Finish batch.
385    *
386    * This function is a static function to avoid serializing the ConfigSync
387    * object unnecessarily.
388    *
389    * @deprecated in Drupal 8.6.0 and will be removed before 9.0.0. Use
390    *   \Drupal\Core\Config\Importer\ConfigImporterBatch::finish() instead.
391    *
392    * @see https://www.drupal.org/node/2897299
393    */
394   public static function finishBatch($success, $results, $operations) {
395     @trigger_error('\Drupal\config\Form\ConfigSync::finishBatch() deprecated in Drupal 8.6.0 and will be removed before 9.0.0. Use \Drupal\Core\Config\Importer\ConfigImporterBatch::finish() instead. See https://www.drupal.org/node/2897299');
396     ConfigImporterBatch::finish($success, $results, $operations);
397   }
398
399 }