diff --git a/core/modules/config/config.local_tasks.yml b/core/modules/config/config.local_tasks.yml new file mode 100644 index 0000000..027d3f9 --- /dev/null +++ b/core/modules/config/config.local_tasks.yml @@ -0,0 +1,38 @@ +config.sync_tab: + route_name: config.management + title: Synchronize + tab_root_id: config.sync_tab + +config.full_tab: + route_name: config.import + title: 'Full Import/Export' + tab_root_id: config.sync_tab + +config.single_tab: + route_name: config.import_single + title: 'Single Import/Export' + tab_root_id: config.sync_tab + +config.full_export_tab: + route_name: config.export + title: Export + tab_root_id: config.sync_tab + tab_parent_id: config.full_tab + +config.full_import_tab: + route_name: config.import + title: Import + tab_root_id: config.sync_tab + tab_parent_id: config.full_tab + +config.single_export_tab: + route_name: config.export_single + title: Export + tab_root_id: config.sync_tab + tab_parent_id: config.single_tab + +config.single_import_tab: + route_name: config.import_single + title: Import + tab_root_id: config.sync_tab + tab_parent_id: config.single_tab diff --git a/core/modules/config/config.module b/core/modules/config/config.module index bad0582..bb9288b 100644 --- a/core/modules/config/config.module +++ b/core/modules/config/config.module @@ -64,51 +64,12 @@ function config_menu() { $items['admin/config/development/configuration'] = array( 'title' => 'Configuration management', 'description' => 'Import, export, or synchronize your site configuration.', - 'route_name' => 'config_management', - ); - $items['admin/config/development/configuration/sync'] = array( - 'title' => 'Synchronize', - 'description' => 'Synchronize configuration changes.', - 'route_name' => 'config.sync', - 'type' => MENU_DEFAULT_LOCAL_TASK, - 'weight' => 0, - ); - $items['admin/config/development/configuration/export'] = array( - 'title' => 'Export', - 'description' => 'Export your site configuration', - 'route_name' => 'config.export', - 'type' => MENU_LOCAL_TASK, - 'weight' => 1, - ); - $items['admin/config/development/configuration/import'] = array( - 'title' => 'Import', - 'description' => 'Import configuration for your site', - 'route_name' => 'config.import', - 'type' => MENU_LOCAL_TASK, - 'weight' => 2, - ); - $items['admin/config/development/configuration/export-single'] = array( - 'title' => 'Export single config', - 'description' => 'Export a single configuration file for your site', - 'route_name' => 'config.export_single', - 'type' => MENU_LOCAL_TASK, - 'weight' => 15, - ); - $items['admin/config/development/configuration/import-single'] = array( - 'title' => 'Import single config', - 'description' => 'Import a single configuration file for your site', - 'route_name' => 'config.import_single', - 'type' => MENU_LOCAL_TASK, - 'weight' => 20, + 'route_name' => 'config.management', ); $items['admin/config/development/configuration/sync/diff/%'] = array( 'title' => 'Configuration file diff', 'description' => 'Diff between active and staged configuration.', 'route_name' => 'config.diff', ); - $items['admin/config/development/configuration/sync/import'] = array( - 'title' => 'Import', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); return $items; } diff --git a/core/modules/config/config.routing.yml b/core/modules/config/config.routing.yml index 67c0624..9b2229d 100644 --- a/core/modules/config/config.routing.yml +++ b/core/modules/config/config.routing.yml @@ -5,44 +5,48 @@ config.diff: requirements: _permission: 'synchronize configuration' -config_management: +config.management: path: '/admin/config/development/configuration' defaults: _form: '\Drupal\config\Form\ConfigSync' requirements: _permission: 'synchronize configuration' -config_export_download: - path: '/admin/config/development/configuration/export-download' +config.export_download: + path: '/admin/config/development/configuration/full/export-download' defaults: _controller: '\Drupal\config\Controller\ConfigController::downloadExport' requirements: _permission: 'export configuration' config.export: - path: '/admin/config/development/configuration/export' + path: '/admin/config/development/configuration/full/export' defaults: + _title: Export _form: '\Drupal\config\Form\ConfigExportForm' requirements: _permission: 'export configuration' config.import: - path: '/admin/config/development/configuration/import' + path: '/admin/config/development/configuration/full/import' defaults: + _title: Import _form: '\Drupal\config\Form\ConfigImportForm' requirements: _permission: 'import configuration' config.import_single: - path: '/admin/config/development/configuration/import-single' + path: '/admin/config/development/configuration/single/import' defaults: + _title: 'Single import' _form: '\Drupal\config\Form\ConfigSingleImportForm' requirements: _permission: 'import configuration' config.export_single: - path: '/admin/config/development/configuration/export-single/{config_type}/{config_name}' + path: '/admin/config/development/configuration/single/export/{config_type}/{config_name}' defaults: + _title: 'Single export' _form: '\Drupal\config\Form\ConfigSingleExportForm' config_type: NULL config_name: NULL @@ -52,6 +56,7 @@ config.export_single: config.sync: path: '/admin/config/development/configuration/sync' defaults: + _title: Synchronize _form: '\Drupal\config\Form\ConfigSync' requirements: _permission: 'synchronize configuration' diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigExportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigExportForm.php index c405c64..dc562dc 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigExportForm.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigExportForm.php @@ -39,7 +39,7 @@ public function buildForm(array $form, array &$form_state) { * {@inheritdoc} */ public function submitForm(array &$form, array &$form_state) { - $form_state['redirect'] = 'admin/config/development/configuration/export-download'; + $form_state['redirect'] = 'admin/config/development/configuration/full/export-download'; } } diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php index e45272c..10c3af6 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php @@ -9,13 +9,13 @@ use Drupal\Core\Config\StorageInterface; use Drupal\Core\Entity\EntityManager; -use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\ConfirmFormBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a form for importing a single configuration file. */ -class ConfigSingleImportForm extends FormBase { +class ConfigSingleImportForm extends ConfirmFormBase { /** * The entity manager. @@ -32,6 +32,20 @@ class ConfigSingleImportForm extends FormBase { protected $configStorage; /** + * If the config exists, this is that object. Otherwise, FALSE. + * + * @var \Drupal\Core\Config\Config|\Drupal\Core\Config\Entity\ConfigEntityInterface|bool + */ + protected $configExists = FALSE; + + /** + * The submitted data needing to be confirmed. + * + * @var array + */ + protected $data = array(); + + /** * Constructs a new ConfigSingleImportForm. * * @param \Drupal\Core\Entity\EntityManager $entity_manager @@ -64,7 +78,48 @@ public function getFormID() { /** * {@inheritdoc} */ + public function getCancelRoute() { + return array( + 'route_name' => 'config.import_single', + ); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + if ($this->data['config_type'] === 'system.simple') { + $name = $this->data['config_name']; + $type = $this->t('Simple configuration'); + } + else { + $definition = $this->entityManager->getDefinition($this->data['config_type']); + $name = $this->data['import'][$definition['entity_keys']['id']]; + $type = $definition['label']; + } + + $args = array( + '%name' => $name, + '@type' => strtolower($type), + ); + if ($this->configExists) { + $question = $this->t('Are you sure you want to update the %name @type?', $args); + } + else { + $question = $this->t('Are you sure you want to create new %name @type?', $args); + } + return $question; + } + + /** + * {@inheritdoc} + */ public function buildForm(array $form, array &$form_state) { + // When this is the confirmation step fall through to the confirmation form. + if ($this->data) { + return parent::buildForm($form, $form_state); + } + $config_types = array( 'system.simple' => $this->t('Simple configuration'), ); @@ -109,6 +164,11 @@ public function buildForm(array $form, array &$form_state) { * {@inheritdoc} */ public function validateForm(array &$form, array &$form_state) { + // The confirmation step needs no additional validation. + if ($this->data) { + return; + } + // Decode the submitted import. $data = $this->configStorage->decode($form_state['values']['import']); @@ -125,6 +185,7 @@ public function validateForm(array &$form, array &$form_state) { $uuid_key = $definition['entity_keys']['uuid']; // If there is an existing entity, ensure matching ID and UUID. if ($entity = $entity_storage->load($data[$id_key])) { + $this->configExists = $entity; if (!isset($data[$uuid_key])) { form_set_error('import', $this->t('An entity with this machine name already exists but the import did not specify a UUID.')); return; @@ -139,8 +200,12 @@ public function validateForm(array &$form, array &$form_state) { form_set_error('import', $this->t('An entity with this UUID already exists but the machine name does not match.')); } } + else { + $config = $this->config($form_state['values']['config_name']); + $this->configExists = $config->isNew() ? $config : FALSE; + } - // Replace the submitted import with the decoded version. + // Store the decoded version of the submitted import. form_set_value($form['import'], $data, $form_state); } @@ -148,16 +213,23 @@ public function validateForm(array &$form, array &$form_state) { * {@inheritdoc} */ public function submitForm(array &$form, array &$form_state) { + // If this form has not yet been confirmed, store the values and rebuild. + if (!$this->data) { + $form_state['rebuild'] = TRUE; + $this->data = $form_state['values']; + return; + } + // If a simple configuration file was added, set the data and save. - if ($form_state['values']['config_type'] === 'system.simple') { - $this->config($form_state['values']['config_name'])->setData($form_state['values']['import'])->save(); - drupal_set_message($this->t('The %name configuration was imported.', array('%name' => $form_state['values']['config_name']))); + if ($this->data['config_type'] === 'system.simple') { + $this->config($this->data['config_name'])->setData($this->data['import'])->save(); + drupal_set_message($this->t('The %name configuration was imported.', array('%name' => $this->data['config_name']))); } // For a config entity, create a new entity and save it. else { $entity = $this->entityManager - ->getStorageController($form_state['values']['config_type']) - ->create($form_state['values']['import']); + ->getStorageController($this->data['config_type']) + ->create($this->data['import']); $entity->save(); drupal_set_message($this->t('The @entity_type %label was imported.', array('@entity_type' => $entity->entityType(), '%label' => $entity->label()))); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigExportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigExportUITest.php index 71c6762..45776e5 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigExportUITest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigExportUITest.php @@ -44,11 +44,11 @@ protected function setUp() { */ function testExport() { // Verify the export page with export submit button is available. - $this->drupalGet('admin/config/development/configuration/export'); + $this->drupalGet('admin/config/development/configuration/full/export'); $this->assertFieldById('edit-submit', t('Export')); // Submit the export form and verify response. - $this->drupalPostForm('admin/config/development/configuration/export', array(), t('Export')); + $this->drupalPostForm('admin/config/development/configuration/full/export', array(), t('Export')); $this->assertResponse(200, 'User can access the download callback.'); // Get the archived binary file provided to user for download. diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUploadTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUploadTest.php index b78639b..025c2f4 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUploadTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUploadTest.php @@ -36,13 +36,13 @@ function setUp() { */ function testImport() { // Verify access to the config upload form. - $this->drupalGet('admin/config/development/configuration/import'); + $this->drupalGet('admin/config/development/configuration/full/import'); $this->assertResponse(200); // Attempt to upload a non-tar file. $text_file = current($this->drupalGetTestFiles('text')); $edit = array('files[import_tarball]' => drupal_realpath($text_file->uri)); - $this->drupalPostForm('admin/config/development/configuration/import', $edit, t('Upload')); + $this->drupalPostForm('admin/config/development/configuration/full/import', $edit, t('Upload')); $this->assertText(t('Could not extract the contents of the tar file')); } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php index 7798e19..6d610a0 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php @@ -33,9 +33,12 @@ public static function getInfo() { * Tests importing a single configuration file. */ public function testImport() { + $storage = \Drupal::entityManager()->getStorageController('config_test'); + $uuid = \Drupal::service('uuid'); + $this->drupalLogin($this->drupalCreateUser(array('import configuration'))); $import = << 'config_test', 'import' => $import, ); - $this->drupalPostForm('admin/config/development/configuration/import-single', $edit, t('Import')); + // Attempt an import with a missing ID. + $this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import')); $this->assertText(t('Missing ID key "@id_key" for this @entity_type import.', array('@id_key' => 'id', '@entity_type' => 'Test configuration'))); - $edit['import'] = "id: new\n" . $edit['import']; - $this->drupalPostForm('admin/config/development/configuration/import-single', $edit, t('Import')); - $entity = entity_load('config_test', 'new'); - $this->assertEqual($entity->label(), 'New'); + // Perform an import with no specified UUID and a unique ID. + $this->assertNull($storage->load('first')); + $edit['import'] = "id: first\n" . $edit['import']; + $this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import')); + $this->assertRaw(t('Are you sure you want to create new %name @type?', array('%name' => 'first', '@type' => 'test configuration'))); + $this->drupalPostForm(NULL, array(), t('Confirm')); + $entity = $storage->load('first'); + $this->assertIdentical($entity->label(), 'First'); + $this->assertIdentical($entity->id(), 'first'); $this->assertTrue($entity->status()); $this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label()))); - $this->drupalPostForm('admin/config/development/configuration/import-single', $edit, t('Import')); + // Attempt an import with an existing ID but missing UUID. + $this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import')); $this->assertText(t('An entity with this machine name already exists but the import did not specify a UUID.')); - $edit['import'] .= "\nuuid: " . \Drupal::service('uuid')->generate(); - $this->drupalPostForm('admin/config/development/configuration/import-single', $edit, t('Import')); + // Attempt an import with a mismatched UUID and existing ID. + $edit['import'] .= "\nuuid: " . $uuid->generate(); + $this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import')); $this->assertText(t('An entity with this machine name already exists but the UUID does not match.')); + + // Perform an import with a unique ID and UUID. + $import = << 'config_test', + 'import' => $import, + ); + $second_uuid = $uuid->generate(); + $edit['import'] .= "\nuuid: " . $second_uuid; + $this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import')); + $this->assertRaw(t('Are you sure you want to create new %name @type?', array('%name' => 'second', '@type' => 'test configuration'))); + $this->drupalPostForm(NULL, array(), t('Confirm')); + $entity = $storage->load('second'); + $this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label()))); + $this->assertIdentical($entity->label(), 'Second'); + $this->assertIdentical($entity->id(), 'second'); + $this->assertFalse($entity->status()); + $this->assertIdentical($entity->uuid(), $second_uuid); } /** @@ -68,7 +103,7 @@ public function testImport() { public function testExport() { $this->drupalLogin($this->drupalCreateUser(array('export configuration'))); - $this->drupalGet('admin/config/development/configuration/export-single/system.simple'); + $this->drupalGet('admin/config/development/configuration/single/export/system.simple'); $this->assertFieldByXPath('//select[@name="config_type"]//option', t('Date format'), 'The date format entity type is selected when specified in the URL.'); // Spot check several known simple configuration files. $element = $this->xpath('//select[@name="config_name"]'); @@ -79,13 +114,13 @@ public function testExport() { } $this->assertIdentical($expected_options, array_intersect($expected_options, $options), 'The expected configuration files are listed.'); - $this->drupalGet('admin/config/development/configuration/export-single/system.simple/system.image'); + $this->drupalGet('admin/config/development/configuration/single/export/system.simple/system.image'); $this->assertFieldByXPath('//textarea[@name="export"]', "toolkit: gd\n", 'The expected system configuration is displayed.'); - $this->drupalGet('admin/config/development/configuration/export-single/date_format'); + $this->drupalGet('admin/config/development/configuration/single/export/date_format'); $this->assertFieldByXPath('//select[@name="config_type"]//option', t('Date format'), 'The date format entity type is selected when specified in the URL.'); - $this->drupalGet('admin/config/development/configuration/export-single/date_format/fallback'); + $this->drupalGet('admin/config/development/configuration/single/export/date_format/fallback'); $this->assertFieldByXPath('//select[@name="config_name"]//option', t('Fallback date format'), 'The fallback date format config entity is selected when specified in the URL.'); $fallback_date = \Drupal::entityManager()->getStorageController('date_format')->load('fallback');