comparison core/modules/config/src/Form/ConfigSync.php @ 0:c75dbcec494b

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