diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc index 6f77bf2..1fe14b2 100644 --- a/core/modules/config/config.admin.inc +++ b/core/modules/config/config.admin.inc @@ -5,14 +5,24 @@ * Admin page callbacks for the config module. */ +use Drupal\Core\Config\DatabaseStorage; +use Drupal\Core\Config\FileStorage; +use Drupal\Core\Config\StorageInterface; + /** - * Form constructor for configuration import form. + * Helper function to construct the storage changes in a configuration synchronization form. * - * @see config_admin_import_form_submit() + * @param array $form + * The form structure to add to. Passed by reference. + * @param array $form_state + * The current state of the form. Passed by reference. + * @param Drupal\Core\Config\StorageInterface $source_storage + * The source storage to retrieve differences from. + * @param Drupal\Core\Config\StorageInterface $target_storage + * The target storage to compare differences to. */ -function config_admin_import_form($form, &$form_state) { - $config_changes = config_import_get_changes(); - +function config_admin_sync_form(array &$form, array &$form_state, StorageInterface $source_storage, StorageInterface $target_storage) { + $config_changes = config_sync_get_changes($source_storage, $target_storage); if (empty($config_changes)) { $form['no_changes'] = array( '#markup' => t('There are no configuration changes.'), @@ -37,7 +47,23 @@ function config_admin_import_form($form, &$form_state) { $form[$config_change_type]['config_files']['#rows'][] = array($config_file); } } - $form['submit'] = array( +} + +/** + * Form constructor for configuration import form. + * + * @see config_admin_import_form_submit() + */ +function config_admin_import_form($form, &$form_state) { + // Retrieve a list of differences between FileStorage and DatabaseStorage. + // @todo Leverage DI + config.storage.info. + $source_storage = new FileStorage(); + $target_storage = new DatabaseStorage(); + + config_admin_sync_form($form, $form_state, $source_storage, $target_storage); + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Import'), ); @@ -60,3 +86,32 @@ function config_admin_import_form_submit($form, &$form_state) { } } +/** + * Form constructor for configuration export form. + * + * @see config_admin_export_form_submit() + */ +function config_admin_export_form($form, &$form_state) { + // Retrieve a list of differences between DatabaseStorage and FileStorage. + // @todo Leverage DI + config.storage.info. + $source_storage = new DatabaseStorage(); + $target_storage = new FileStorage(); + + config_admin_sync_form($form, $form_state, $source_storage, $target_storage); + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Export'), + ); + return $form; +} + +/** + * Form submission handler for config_admin_export_form(). + */ +function config_admin_export_form_submit($form, &$form_state) { + config_export(); + drupal_set_message(t('The configuration was exported successfully.')); +} + diff --git a/core/modules/config/config.module b/core/modules/config/config.module index 3d4fcfe..fd38fd2 100644 --- a/core/modules/config/config.module +++ b/core/modules/config/config.module @@ -9,8 +9,8 @@ * Implements hook_permission(). */ function config_permission() { - $permissions['import configuration'] = array( - 'title' => t('Import configuration'), + $permissions['synchronize configuration'] = array( + 'title' => t('Synchronize configuration'), 'restrict access' => TRUE, ); return $permissions; @@ -20,14 +20,27 @@ function config_permission() { * Implements hook_menu(). */ function config_menu() { - $items['admin/config/development/import'] = array( - 'title' => 'Import configuration', - 'description' => 'Import and synchronize configuration changes.', + $items['admin/config/development/sync'] = array( + 'title' => 'Synchronize configuration', + 'description' => 'Synchronize configuration changes.', 'page callback' => 'drupal_get_form', 'page arguments' => array('config_admin_import_form'), - 'access arguments' => array('import configuration'), + 'access arguments' => array('synchronize configuration'), 'file' => 'config.admin.inc', ); + $items['admin/config/development/sync/import'] = array( + 'title' => 'Import', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items['admin/config/development/sync/export'] = array( + 'title' => 'Export', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('config_admin_export_form'), + 'access arguments' => array('synchronize configuration'), + 'file' => 'config.admin.inc', + 'type' => MENU_LOCAL_TASK, + ); return $items; } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php new file mode 100644 index 0000000..6b64b22 --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php @@ -0,0 +1,130 @@ + 'Import/Export UI', + 'description' => 'Tests the user interface for importing/exporting configuration.', + 'group' => 'Configuration', + ); + } + + function setUp() { + parent::setUp(array('config', 'config_test')); + + $this->web_user = $this->drupalCreateUser(array('synchronize configuration')); + $this->drupalLogin($this->web_user); + } + + /** + * Tests exporting configuration. + */ + function testExport() { + $name = 'config_test.system'; + $dynamic_name = 'config_test.dynamic.default'; + + // Verify the default configuration values exist. + $config = config($name); + $this->assertIdentical($config->get('foo'), 'bar'); + $config = config($dynamic_name); + $this->assertIdentical($config->get('id'), 'default'); + + // Verify that both appear as deleted by default. + $this->drupalGet('admin/config/development/sync/export'); + $this->assertText($name); + $this->assertText($dynamic_name); + + // Export and verify that both do not appear anymore. + $this->drupalPost(NULL, array(), t('Export')); + $this->assertUrl('admin/config/development/sync/export'); + $this->assertNoText($name); + $this->assertNoText($dynamic_name); + + // Verify that there are no further changes to export. + $this->assertText(t('There are no configuration changes.')); + + // Verify that the import screen shows no changes either. + $this->drupalGet('admin/config/development/sync'); + $this->assertText(t('There are no configuration changes.')); + } + + /** + * Tests importing configuration. + */ + function testImport() { + $name = 'config_test.new'; + $dynamic_name = 'config_test.dynamic.new'; + + // Verify the configuration to create does not exist yet. + $file_storage = new FileStorage(); + $this->assertIdentical($file_storage->exists($name), FALSE, $name . ' not found.'); + $this->assertIdentical($file_storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.'); + + // Export. + config_export(); + + // Create new configuration objects. + $file_storage->write($name, array( + 'add_me' => 'new value', + )); + $file_storage->write($dynamic_name, array( + 'id' => 'new', + 'label' => 'New', + )); + $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.'); + $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); + + // Verify that both appear as new. + $this->drupalGet('admin/config/development/sync'); + $this->assertText($name); + $this->assertText($dynamic_name); + + // Import and verify that both do not appear anymore. + $this->drupalPost(NULL, array(), t('Import')); + $this->assertUrl('admin/config/development/sync'); + $this->assertNoText($name); + $this->assertNoText($dynamic_name); + + // Verify that there are no further changes to import. + $this->assertText(t('There are no configuration changes.')); + + // Verify that the export screen shows no changes either. + $this->drupalGet('admin/config/development/sync/export'); + $this->assertText(t('There are no configuration changes.')); + } + + /** + * Tests concurrent importing of configuration. + */ + function testImportLock() { + // Verify that there are configuration differences to import. + $this->drupalGet('admin/config/development/sync'); + $this->assertNoText(t('There are no configuration changes.')); + + // Acquire a fake-lock on the import mechanism. + $lock_name = 'config_import'; + lock_acquire($lock_name); + + // Attempt to import configuration and verify that an error message appears. + $this->drupalPost(NULL, array(), t('Import')); + $this->assertUrl('admin/config/development/sync'); + $this->assertText(t('The import failed due to an error. Any errors have been logged.')); + + // Release the lock, just to keep testing sane. + lock_release($lock_name); + } +}