diff --git a/core/modules/config/config.local_tasks.yml b/core/modules/config/config.local_tasks.yml index 5a894f0..bd976de 100644 --- a/core/modules/config/config.local_tasks.yml +++ b/core/modules/config/config.local_tasks.yml @@ -32,3 +32,21 @@ config.import_single: route_name: config.import_single title: Import parent_id: config.single + +config.staging_active: + route_name: config.sync + title: Compare staging with active + parent_id: config.sync + weight: -1 + +config.snapshot_active: + route_name: config.snapshot_active + title: Compare snapshot with active + parent_id: config.sync + weight: 0 + +config.snapshot_staging: + route_name: config.snapshot_staging + title: Compare snapshot with staging + parent_id: config.sync + weight: 1 diff --git a/core/modules/config/config.routing.yml b/core/modules/config/config.routing.yml index f2a944a..7d295d7 100644 --- a/core/modules/config/config.routing.yml +++ b/core/modules/config/config.routing.yml @@ -2,12 +2,12 @@ config.sync: path: '/admin/config/development/configuration' defaults: _form: '\Drupal\config\Form\ConfigSync' - _title: 'Synchronize' + _title: 'Synchronize Configuration' requirements: _permission: 'synchronize configuration' config.diff: - path: '/admin/config/development/configuration/sync/diff/{source_name}/{target_name}' + path: '/admin/config/development/configuration/sync/diff/{source}/{target}/{source_name}/{target_name}' defaults: _content: '\Drupal\config\Controller\ConfigController::diff' target_name: NULL @@ -54,3 +54,23 @@ config.export_single: config_name: NULL requirements: _permission: 'export configuration' + +config.snapshot_active: + path: '/admin/config/development/configuration/compare/active' + defaults: + _title: 'Changes between snapshot and active configuration' + _form: '\Drupal\config\Form\ConfigSync' + source: "snapshot" + target: "active" + requirements: + _permission: 'synchronize configuration' + +config.snapshot_staging: + path: '/admin/config/development/configuration/compare/staging' + defaults: + _title: 'Changes between snapshot and staging configuration' + _form: '\Drupal\config\Form\ConfigSync' + source: "snapshot" + target: "staging" + requirements: + _permission: 'synchronize configuration' diff --git a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php index 88cdeab..56c8ca7 100644 --- a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php +++ b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php @@ -11,29 +11,36 @@ use Drupal\Component\Serialization\Yaml; use Drupal\Core\Config\ConfigManagerInterface; use Drupal\Core\Config\StorageInterface; -use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\system\FileDownloadController; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; +use Drupal\Core\Controller\ControllerBase; /** * Returns responses for config module routes. */ -class ConfigController implements ContainerInjectionInterface { +class ConfigController extends ControllerBase { /** - * The target storage. + * The active storage. * * @var \Drupal\Core\Config\StorageInterface */ - protected $targetStorage; + protected $activeStorage; /** - * The source storage. + * The staging storage. * * @var \Drupal\Core\Config\StorageInterface */ - protected $sourceStorage; + protected $stagingStorage; + + /** + * The snapshot storage. + * + * @var \Drupal\Core\Config\StorageInterface + */ + protected $snapshotStorage; /** * The configuration manager. @@ -56,6 +63,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() ); @@ -64,16 +72,19 @@ public static function create(ContainerInterface $container) { /** * Constructs a ConfigController object. * - * @param \Drupal\Core\Config\StorageInterface $target_storage - * The target storage. - * @param \Drupal\Core\Config\StorageInterface $source_storage - * The source storage + * @param \Drupal\Core\Config\StorageInterface $active_storage + * The active storage. + * @param \Drupal\Core\Config\StorageInterface $staging_storage + * The staging storage + * @param \Drupal\Core\Config\StorageInterface $snapshot_storage + * The snapshot storage * @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) { - $this->targetStorage = $target_storage; - $this->sourceStorage = $source_storage; + public function __construct(StorageInterface $active_storage, StorageInterface $staging_storage, StorageInterface $snapshot_storage, ConfigManagerInterface $config_manager, FileDownloadController $file_download_controller) { + $this->activeStorage = $active_storage; + $this->stagingStorage = $staging_storage; + $this->snapshotStorage = $snapshot_storage; $this->configManager = $config_manager; $this->fileDownloadController = $file_download_controller; } @@ -96,29 +107,41 @@ public function downloadExport() { /** * Shows diff of specificed configuration file. * - * @param string $config_file - * The name of the configuration file. + * @param string $source + * The source storage container name. + * + * @param string $target + * The target storage container name. + * + * @param string $source_name + * The source configuration item name to compare. + * + * @param string $target_name + * The target configuration item name to compare. * * @return string * Table showing a two-way diff between the active and staged configuration. */ - public function diff($source_name, $target_name = NULL) { + public function diff($source = 'staging', $target = 'active', $source_name, $target_name = NULL) { + + $source_storage = $source . 'Storage'; + $target_storage = $target . 'Storage'; - $diff = $this->configManager->diff($this->targetStorage, $this->sourceStorage, $source_name, $target_name); + $diff = $this->configManager->diff($this->$target_storage, $this->$source_storage, $source_name, $target_name); $formatter = new \DrupalDiffFormatter(); $formatter->show_header = FALSE; $build = array(); - $build['#title'] = t('View changes of @config_file', array('@config_file' => $source_name)); + $build['#title'] = $this->t('View changes of @config_file', array('@config_file' => $source_name)); // Add the CSS for the inline diff. $build['#attached']['css'][] = drupal_get_path('module', 'system') . '/css/system.diff.css'; $build['diff'] = array( '#type' => 'table', '#header' => array( - array('data' => t('Old'), 'colspan' => '2'), - array('data' => t('New'), 'colspan' => '2'), + array('data' => $target, 'colspan' => '2'), + array('data' => $source, 'colspan' => '2'), ), '#rows' => $formatter->format($diff), ); @@ -131,7 +154,7 @@ public function diff($source_name, $target_name = NULL) { ), ), '#title' => "Back to 'Synchronize configuration' page.", - '#href' => 'admin/config/development/configuration', + '#route_name' => 'config.sync', ); return $build; diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php index a9eaac7..f670217 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php @@ -36,18 +36,25 @@ class ConfigSync extends FormBase { protected $lock; /** - * The source configuration object. + * The staging configuration object. * * @var \Drupal\Core\Config\StorageInterface */ - protected $sourceStorage; + protected $stagingStorage; /** - * The target configuration object. + * The active configuration object. * * @var \Drupal\Core\Config\StorageInterface */ - protected $targetStorage; + protected $activeStorage; + + /** + * The snapshot configuration object. + * + * @var \Drupal\Core\Config\StorageInterface + */ + protected $snapshotStorage; /** * Event dispatcher. @@ -94,10 +101,12 @@ class ConfigSync extends FormBase { /** * Constructs the object. * - * @param \Drupal\Core\Config\StorageInterface $sourceStorage - * The source storage object. - * @param \Drupal\Core\Config\StorageInterface $targetStorage - * The target storage manager. + * @param \Drupal\Core\Config\StorageInterface $stagingStorage + * The staging storage object. + * @param \Drupal\Core\Config\StorageInterface $activeStorage + * The active storage manager. + * @param \Drupal\Core\Config\StorageInterface $snapshotStorage + * The snapshot storage manager. * @param \Drupal\Core\Lock\LockBackendInterface $lock * The lock object. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher @@ -113,9 +122,10 @@ class ConfigSync extends FormBase { * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler */ - public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, UrlGeneratorInterface $url_generator, TypedConfigManager $typed_config, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) { - $this->sourceStorage = $sourceStorage; - $this->targetStorage = $targetStorage; + public function __construct(StorageInterface $stagingStorage, StorageInterface $activeStorage, StorageInterface $snapshotStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, UrlGeneratorInterface $url_generator, TypedConfigManager $typed_config, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) { + $this->stagingStorage = $stagingStorage; + $this->activeStorage = $activeStorage; + $this->snapshotStorage = $snapshotStorage; $this->lock = $lock; $this->eventDispatcher = $event_dispatcher; $this->configManager = $config_manager; @@ -132,6 +142,7 @@ public static function create(ContainerInterface $container) { return new static( $container->get('config.storage.staging'), $container->get('config.storage'), + $container->get('config.storage.snapshot'), $container->get('lock'), $container->get('event_dispatcher'), $container->get('config.manager'), @@ -152,15 +163,29 @@ public function getFormId() { /** * {@inheritdoc} */ - public function buildForm(array $form, array &$form_state) { + public function buildForm(array $form, array &$form_state, $source = 'staging', $target = 'active') { + + $active_snapshot_comparer = new StorageComparer($this->activeStorage, $this->snapshotStorage); + + // Verify that we have an initial snapshot that matches the active + // configuration. + if (empty($form_state['input']) && $active_snapshot_comparer->createChangelist()->hasChanges()) { + drupal_set_message($this->t('Changes have been made to your active configuration, 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->url('config.snapshot_active'))), 'warning'); + + } + + $source_storage = $source . 'Storage'; + $target_storage = $target . 'Storage'; + $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => $this->t('Import all'), ); - $source_list = $this->sourceStorage->listAll(); - $storage_comparer = new StorageComparer($this->sourceStorage, $this->targetStorage); + $source_list = $this->$source_storage->listAll(); + $storage_comparer = new StorageComparer($this->$source_storage, $this->$target_storage); if (empty($source_list) || !$storage_comparer->createChangelist()->hasChanges()) { $form['no_changes'] = array( '#type' => 'table', @@ -220,11 +245,11 @@ public function buildForm(array $form, array &$form_state) { foreach ($config_names as $config_name) { if ($config_change_type == 'rename') { $names = $storage_comparer->extractRenameNames($config_name); - $href = $this->urlGenerator->getPathFromRoute('config.diff', array('source_name' => $names['old_name'], 'target_name' => $names['new_name'])); + $href = $this->urlGenerator->getPathFromRoute('config.diff', array('source' => $source, 'target' => $target, 'source_name' => $names['old_name'], 'target_name' => $names['new_name'])); $config_name = $this->t('!source_name to !target_name', array('!source_name' => $names['old_name'], '!target_name' => $names['new_name'])); } else { - $href = $this->urlGenerator->getPathFromRoute('config.diff', array('source_name' => $config_name)); + $href = $this->urlGenerator->getPathFromRoute('config.diff', array('source' => $source, 'target' => $target, 'source_name' => $config_name)); } $links['view_diff'] = array( 'title' => $this->t('View differences'), @@ -233,7 +258,7 @@ public function buildForm(array $form, array &$form_state) { 'class' => array('use-ajax'), 'data-accepts' => 'application/vnd.drupal-modal', 'data-dialog-options' => json_encode(array( - 'width' => 700 + 'width' => 700, )), ), ); @@ -269,16 +294,16 @@ public function submitForm(array &$form, array &$form_state) { if ($config_importer->alreadyImporting()) { drupal_set_message($this->t('Another request may be synchronizing configuration already.')); } - else{ + else { try { $sync_steps = $config_importer->initialize(); $batch = array( 'operations' => array(), 'finished' => array(get_class($this), 'finishBatch'), - 'title' => t('Synchronizing configuration'), - 'init_message' => t('Starting configuration synchronization.'), - 'progress_message' => t('Completed @current step of @total.'), - 'error_message' => t('Configuration synchronization has encountered an error.'), + 'title' => $this->t('Synchronizing configuration'), + 'init_message' => $this->t('Starting configuration synchronization.'), + 'progress_message' => $this->t('Completed @current step of @total.'), + 'error_message' => $this->t('Configuration synchronization has encountered an error.'), 'file' => drupal_get_path('module', 'config') . '/config.admin.inc', ); foreach ($sync_steps as $sync_step) { diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php index b95a666..5dccdff 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php @@ -286,7 +286,7 @@ function testImportDiff() { $staging->write($config_name, $staging_data); // Load the diff UI and verify that the diff reflects the change. - $this->drupalGet('admin/config/development/configuration/sync/diff/' . $config_name); + $this->drupalGet('admin/config/development/configuration/sync/diff/staging/active/' . $config_name); $this->assertTitle(format_string('View changes of @config_name | Drupal', array('@config_name' => $config_name))); // Reset data back to original, and remove a key @@ -295,7 +295,7 @@ function testImportDiff() { $staging->write($config_name, $staging_data); // Load the diff UI and verify that the diff reflects a removed key. - $this->drupalGet('admin/config/development/configuration/sync/diff/' . $config_name); + $this->drupalGet('admin/config/development/configuration/sync/diff/staging/active/' . $config_name); // Reset data back to original and add a key $staging_data = $original_data; @@ -303,7 +303,7 @@ function testImportDiff() { $staging->write($config_name, $staging_data); // Load the diff UI and verify that the diff reflects an added key. - $this->drupalGet('admin/config/development/configuration/sync/diff/' . $config_name); + $this->drupalGet('admin/config/development/configuration/sync/diff/staging/active/' . $config_name); } /** diff --git a/core/modules/config/tests/Drupal/config/Tests/Menu/ConfigLocalTasksTest.php b/core/modules/config/tests/Drupal/config/Tests/Menu/ConfigLocalTasksTest.php index 627f61e..49d0def 100644 --- a/core/modules/config/tests/Drupal/config/Tests/Menu/ConfigLocalTasksTest.php +++ b/core/modules/config/tests/Drupal/config/Tests/Menu/ConfigLocalTasksTest.php @@ -44,11 +44,36 @@ public function testConfigAdminLocalTasks($route, $expected) { */ public function getConfigAdminRoutes() { return array( - array('config.sync', array(array('config.sync', 'config.full', 'config.single'))), - array('config.export_full', array(array('config.sync', 'config.full', 'config.single'), array('config.export_full', 'config.import_full'))), - array('config.import_full', array(array('config.sync', 'config.full', 'config.single'), array('config.export_full', 'config.import_full'))), - array('config.export_single', array(array('config.sync', 'config.full', 'config.single'), array('config.export_single', 'config.import_single'))), - array('config.import_single', array(array('config.sync', 'config.full', 'config.single'), array('config.export_single', 'config.import_single'))), + array('config.sync', + array( + array('config.sync', 'config.full', 'config.single'), + array('config.staging_active', 'config.snapshot_active', 'config.snapshot_staging'), + ), + ), + array('config.export_full', + array( + array('config.sync', 'config.full', 'config.single'), + array('config.export_full', 'config.import_full'), + ), + ), + array('config.import_full', + array( + array('config.sync', 'config.full', 'config.single'), + array('config.export_full', 'config.import_full'), + ), + ), + array('config.export_single', + array( + array('config.sync', 'config.full', 'config.single'), + array('config.export_single', 'config.import_single'), + ), + ), + array('config.import_single', + array( + array('config.sync', 'config.full', 'config.single'), + array('config.export_single', 'config.import_single'), + ), + ), ); } diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTypeRenameConfigImportTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTypeRenameConfigImportTest.php index 2bb230e..d42751c 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeTypeRenameConfigImportTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTypeRenameConfigImportTest.php @@ -92,9 +92,9 @@ public function testConfigurationRename() { $this->drupalGet('admin/config/development/configuration'); foreach ($expected as $rename) { $names = $this->configImporter()->getStorageComparer()->extractRenameNames($rename); - $this->assertText(String::format('!source_name to !target_name', array('!source_name' => $names['old_name'], '!target_name' => $names['new_name']))); + $this->assertText(String::format('!source_name to !target_name', array('source' => 'staging', 'target' => 'active', '!source_name' => $names['old_name'], '!target_name' => $names['new_name']))); // Test that the diff link is present for each renamed item. - $href = \Drupal::urlGenerator()->getPathFromRoute('config.diff', array('source_name' => $names['old_name'], 'target_name' => $names['new_name'])); + $href = \Drupal::urlGenerator()->getPathFromRoute('config.diff', array('source' => 'staging', 'target' => 'active', 'source_name' => $names['old_name'], 'target_name' => $names['new_name'])); $this->assertLinkByHref($href); $hrefs[$rename] = $href; }