annotate core/modules/config/src/Form/ConfigSync.php @ 19:fa3358dc1485 tip

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