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