diff --git a/core/modules/config/config.routing.yml b/core/modules/config/config.routing.yml index bd0d5c5..9349f97 100644 --- a/core/modules/config/config.routing.yml +++ b/core/modules/config/config.routing.yml @@ -53,3 +53,10 @@ config.export_single: config_name: NULL requirements: _permission: 'export configuration' + +config.snapshot_diff: + path: '/admin/config/development/configuration/sync/snapshot-diff' + defaults: + _content: '\Drupal\config\Controller\ConfigController::snapshotDiff' + requirements: + _permission: 'import configuration' diff --git a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php index 18f6287..98a3ca4 100644 --- a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php +++ b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php @@ -14,6 +14,7 @@ use Drupal\system\FileDownloadController; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; +use Drupal\Core\Config\StorageComparer; /** * Returns responses for config module routes. @@ -55,6 +56,7 @@ public static function create(ContainerInterface $container) { return new static( $container->get('config.storage'), $container->get('config.storage.staging'), + $container->get('config.storage.snapshot'), $container->get('config.manager'), new FileDownloadController() ); @@ -70,9 +72,10 @@ public static function create(ContainerInterface $container) { * @param \Drupal\system\FileDownloadController $file_download_controller * The file download controller. */ - public function __construct(StorageInterface $target_storage, StorageInterface $source_storage, ConfigManagerInterface $config_manager, FileDownloadController $file_download_controller) { + public function __construct(StorageInterface $target_storage, StorageInterface $source_storage, StorageInterface $snapshot_storage, ConfigManagerInterface $config_manager, FileDownloadController $file_download_controller) { $this->targetStorage = $target_storage; $this->sourceStorage = $source_storage; + $this->snapshotStorage = $snapshot_storage; $this->configManager = $config_manager; $this->fileDownloadController = $file_download_controller; } @@ -136,4 +139,58 @@ public function diff($config_file) { return $build; } + + /** + * Show diff of snapshot and active/staging storage. + * + * @return string + * Table showing a two-way diff between the snapshot and + * active/staged configuration. + */ + public function snapshotDiff() { + $build = array(); + + $build['#title'] = t('View changes between snapshot and active/staging configuration.'); + // Add the CSS for the inline diff. + $build['#attached']['css'][] = drupal_get_path('module', 'system') . '/css/system.diff.css'; + + $active = $this->targetStorage; + $staging = $this->sourceStorage; + $snapshot = $this->snapshotStorage; + + $active_snapshot_comparer = new StorageComparer($active, $snapshot); + + $active_snapshot_comparer->createChangelist(); + $changelist = $active_snapshot_comparer->getChangelist(); + + // Show changes for each configuration object. + foreach ($changelist as $op) { + foreach ($op as $name) { + $diff = $this->configManager->diff($active, $staging, $name); + $formatter = new \DrupalDiffFormatter(); + $formatter->show_header = FALSE; + + $build[$name] = array( + '#type' => 'table', + '#header' => array( + array('data' => $name, 'colspan' => '4'), + ), + '#rows' => $formatter->format($diff), + ); + } + } + + $build['back'] = array( + '#type' => 'link', + '#attributes' => array( + 'class' => array( + 'dialog-cancel', + ), + ), + '#title' => "Back to 'Synchronize configuration' page.", + '#href' => 'admin/config/development/configuration', + ); + + return $build; + } } diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php index bb5f4ed..d881c33 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php @@ -11,6 +11,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Component\Archiver\ArchiveTar; use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Config\StorageComparer; /** * Defines the configuration import form. @@ -30,8 +31,10 @@ class ConfigImportForm extends FormBase { * @param \Drupal\Core\Config\StorageInterface $config_storage * The configuration storage. */ - public function __construct(StorageInterface $config_storage) { + public function __construct(StorageInterface $config_storage, StorageInterface $config_storage_snapshot, StorageInterface $config_storage_active) { $this->configStorage = $config_storage; + $this->configStorageSnapshot = $config_storage_snapshot; + $this->configStorageActive = $config_storage_active; } /** @@ -39,7 +42,9 @@ public function __construct(StorageInterface $config_storage) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('config.storage.staging') + $container->get('config.storage.staging'), + $container->get('config.storage.snapshot'), + $container->get('config.storage') ); } @@ -54,6 +59,15 @@ public function getFormId() { * {@inheritdoc} */ public function buildForm(array $form, array &$form_state) { + $active_snapshot_comparer = new StorageComparer($this->configStorageActive, $this->configStorageSnapshot); + + // Verify that we have an initial snapshot that matches the active + // configuration. + if ($active_snapshot_comparer->createChangelist()->hasChanges()) { + drupal_set_message($this->t('Changes have been made since last import, which might be lost on the next import attempt. You can find and review the differences between snapshot and active/staging storage here: Compare snapshot and active/staging storage.', array( + '@link' => $this->urlGenerator()->generateFromRoute('config.snapshot_diff'))), 'warning'); + } + $form['description'] = array( '#markup' => '

' . $this->t('Use the upload button below.') . '

', ); diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php index c600001..e43ab1c 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php @@ -73,6 +73,14 @@ class ConfigSync extends FormBase { protected $typedConfigManager; /** + * The snapshot configuration object. + * + * @var \Drupal\Core\Config\StorageInterface + */ + protected $snapshotStorage; + + + /** * Constructs the object. * * @param \Drupal\Core\Config\StorageInterface $sourceStorage @@ -90,7 +98,7 @@ class ConfigSync extends FormBase { * @param \Drupal\Core\Config\TypedConfigManager $typed_config * The typed configuration manager. */ - public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, UrlGeneratorInterface $url_generator, TypedConfigManager $typed_config) { + public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, UrlGeneratorInterface $url_generator, TypedConfigManager $typed_config, StorageInterface $snapshotStorage) { $this->sourceStorage = $sourceStorage; $this->targetStorage = $targetStorage; $this->lock = $lock; @@ -98,6 +106,7 @@ public function __construct(StorageInterface $sourceStorage, StorageInterface $t $this->configManager = $config_manager; $this->urlGenerator = $url_generator; $this->typedConfigManager = $typed_config; + $this->snapshotStorage = $snapshotStorage; } /** @@ -111,7 +120,8 @@ public static function create(ContainerInterface $container) { $container->get('event_dispatcher'), $container->get('config.manager'), $container->get('url_generator'), - $container->get('config.typed') + $container->get('config.typed'), + $container->get('config.storage.snapshot') ); } @@ -126,6 +136,15 @@ public function getFormId() { * {@inheritdoc} */ public function buildForm(array $form, array &$form_state) { + $active_snapshot_comparer = new StorageComparer($this->targetStorage, $this->snapshotStorage); + + // Verify that we have an initial snapshot that matches the active + // configuration. + if ($active_snapshot_comparer->createChangelist()->hasChanges()) { + drupal_set_message($this->t('Changes have been made since last import, which might be lost on the next import attempt. You can find and review the differences between snapshot and active/staging storage here: Compare snapshot and active/staging storage.', array( + '@link' => $this->urlGenerator()->generateFromRoute('config.snapshot_diff'))), 'warning'); + } + $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit',