diff --git a/core/includes/config.inc b/core/includes/config.inc index c6435b3..d1e7183 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -1,7 +1,10 @@ get('config.factory')->get($name)->load(); } +/** + * Returns a list of differences between configuration storages. + * + * @param Drupal\Core\Config\StorageInterface $source_storage + * The storage to synchronize configuration from. + * @param Drupal\Core\Config\StorageInterface $target_storage + * The storage to synchronize configuration to. + * + * @return array|bool + * An assocative array containing the differences between source and target + * storage, or FALSE if there are no differences. + */ +function config_sync_get_changes(StorageInterface $source_storage, StorageInterface $target_storage) { + $source_names = $source_storage->listAll(); + $target_names = $target_storage->listAll(); + $config_changes = array( + 'create' => array_diff($source_names, $target_names), + 'change' => array(), + 'delete' => array_diff($target_names, $source_names), + ); + foreach (array_intersect($source_names, $target_names) as $name) { + $source_config_data = $source_storage->read($name); + $target_config_data = $target_storage->read($name); + if ($source_config_data != $target_config_data) { + $config_changes['change'][] = $name; + } + } + + // Do not trigger subsequent synchronization operations if there are no + // changes in either category. + if (empty($config_changes['create']) && empty($config_changes['change']) && empty($config_changes['delete'])) { + return FALSE; + } + return $config_changes; +} + +/** + * Imports configuration from FileStorage to DatabaseStorage. + */ +function config_import() { + $config_changes = config_import_get_changes(); + if (empty($config_changes)) { + return; + } + + if (!lock_acquire(__FUNCTION__)) { + // Another request is synchronizing configuration. + // Return a negative result for UI purposes. We do not make a difference + // between an actual synchronization error and a failed lock, because a + // concurrent request synchronizing configuration is an edge-case in the + // first place and would mean that more than one developer or site builder + // attempts to do it without coordinating with others. + return FALSE; + } + + try { + $remaining_changes = config_import_invoke_sync_hooks($config_changes); + config_import_save_changes($remaining_changes); + // Flush all caches and reset static variables after a successful import. + drupal_flush_all_caches(); + } + catch (ConfigException $e) { + watchdog_exception('config_import', $e); + lock_release(__FUNCTION__); + return FALSE; + } + lock_release(__FUNCTION__); + return TRUE; +} + +/** + * Returns a list of differences between FileStorage and DatabaseStorage. + * + * @see config_sync_get_changes() + */ +function config_import_get_changes() { + // @todo Leverage DI + config.storage.info. + $source_storage = new FileStorage(); + $target_storage = new DatabaseStorage(); + + return config_sync_get_changes($source_storage, $target_storage); +} + +/** + * Writes an array of config file changes to the active store. + * + * @param array $config_changes + * An array of changes to be written. + */ +function config_import_save_changes(array $config_changes) { + // @todo Leverage DI + config.storage.info. + $source_storage = new FileStorage(); + $target_storage = new DatabaseStorage(); + foreach (array('delete', 'create', 'change') as $op) { + foreach ($config_changes[$op] as $name) { + if ($op == 'delete') { + $target_storage->delete($name); + } + else { + $data = $source_storage->read($name); + $target_storage->write($name, $data); + } + } + } +} + +/** + * Invokes hook_config_import() implementations for configuration changes. + * + * @param array $config_changes + * An array of changes to be loaded. + */ +function config_import_invoke_sync_hooks(array $config_changes) { + // @todo Leverage DI + config.storage.info. + $source_storage = new FileStorage(); + $target_storage = new DatabaseStorage(); + $storage_dispatcher = drupal_container()->get('config.storage.dispatcher'); + + // Allow modules to take over configuration change operations for + // higher-level configuration data. + // First pass deleted, then new, and lastly changed configuration, in order to + // handle dependencies correctly. + foreach (array('delete', 'create', 'change') as $op) { + foreach ($config_changes[$op] as $key => $name) { + // Extract owner from configuration object name. + $module = strtok($name, '.'); + // Check whether the module implements hook_config_import() and ask it to + // handle the configuration change. + $handled_by_module = FALSE; + if (module_hook($module, 'config_import')) { + $old_config = new Config($storage_dispatcher); + $old_config + ->setName($name) + ->load(); + + $data = $source_storage->read($name); + $new_config = new Config($storage_dispatcher); + $new_config + ->setName($name) + ->setData($data); + + $handled_by_module = module_invoke($module, 'config_import', $op, $name, $new_config, $old_config); + } + if (!empty($handled_by_module)) { + unset($config_changes[$op][$key]); + } + } + } + return $config_changes; +} + +/** + * Exports configuration from DatabaseStorage to FileStorage. + */ +function config_export() { + $config_changes = config_export_get_changes(); + if (empty($config_changes)) { + return; + } + config_export_save_changes($config_changes); + return TRUE; +} + +/** + * Returns a list of differences between DatabaseStorage and FileStorage. + * + * @see config_sync_get_changes() + */ +function config_export_get_changes() { + // @todo Leverage DI + config.storage.info. + $source_storage = new DatabaseStorage(); + $target_storage = new FileStorage(); + + return config_sync_get_changes($source_storage, $target_storage); +} + +/** + * Writes an array of configuration changes to FileStorage. + * + * @param array $config_changes + * An array of changes to be written. + */ +function config_export_save_changes(array $config_changes) { + // @todo Leverage DI + config.storage.info. + $source_storage = new DatabaseStorage(); + $target_storage = new FileStorage(); + foreach (array('delete', 'create', 'change') as $op) { + foreach ($config_changes[$op] as $name) { + if ($op == 'delete') { + $target_storage->delete($name); + } + else { + $data = $source_storage->read($name); + $target_storage->write($name, $data); + } + } + } +} + diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc new file mode 100644 index 0000000..6f77bf2 --- /dev/null +++ b/core/modules/config/config.admin.inc @@ -0,0 +1,62 @@ + t('There are no configuration changes.'), + ); + return $form; + } + + foreach ($config_changes as $config_change_type => $config_files) { + if (empty($config_files)) { + continue; + } + $form[$config_change_type] = array( + '#type' => 'fieldset', + '#title' => $config_change_type . ' (' . count($config_files) . ')', + '#collapsible' => TRUE, + ); + $form[$config_change_type]['config_files'] = array( + '#theme' => 'table', + '#header' => array('Name'), + ); + foreach ($config_files as $config_file) { + $form[$config_change_type]['config_files']['#rows'][] = array($config_file); + } + } + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Import'), + ); + return $form; +} + +/** + * Form submission handler for config_admin_import_form(). + */ +function config_admin_import_form_submit($form, &$form_state) { + if (config_import()) { + drupal_set_message(t('The configuration was imported successfully.')); + } + else { + // Another request may be synchronizing configuration already. Wait for it + // to complete before returning the error, so already synchronized changes + // do not appear again. + lock_wait(__FUNCTION__); + drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error'); + } +} + diff --git a/core/modules/config/config.api.php b/core/modules/config/config.api.php new file mode 100644 index 0000000..8f95d32 --- /dev/null +++ b/core/modules/config/config.api.php @@ -0,0 +1,56 @@ +get(); + return image_style_delete($style); + } + if ($op == 'create') { + $style = $new_config->get(); + return image_style_save($style); + } + if ($op == 'change') { + $style = $new_config->get(); + return image_style_save($style); + } +} + diff --git a/core/modules/config/config.module b/core/modules/config/config.module index b3d9bbc..3d4fcfe 100644 --- a/core/modules/config/config.module +++ b/core/modules/config/config.module @@ -1 +1,33 @@ t('Import configuration'), + 'restrict access' => TRUE, + ); + return $permissions; +} + +/** + * Implements hook_menu(). + */ +function config_menu() { + $items['admin/config/development/import'] = array( + 'title' => 'Import configuration', + 'description' => 'Import and synchronize configuration changes.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('config_admin_import_form'), + 'access arguments' => array('import configuration'), + 'file' => 'config.admin.inc', + ); + return $items; +} + diff --git a/core/modules/config/config_test/config/config_test.delete.yml b/core/modules/config/config_test/config/config_test.delete.yml new file mode 100644 index 0000000..b8ccb67 --- /dev/null +++ b/core/modules/config/config_test/config/config_test.delete.yml @@ -0,0 +1 @@ +delete_me: bar diff --git a/core/modules/config/config_test/config/config_test.dynamic.default.yml b/core/modules/config/config_test/config/config_test.dynamic.default.yml new file mode 100644 index 0000000..d285439 --- /dev/null +++ b/core/modules/config/config_test/config/config_test.dynamic.default.yml @@ -0,0 +1,2 @@ +id: default +name: Default diff --git a/core/modules/config/config_test/config/config_test.system.yml b/core/modules/config/config_test/config/config_test.system.yml new file mode 100644 index 0000000..20e9ff3 --- /dev/null +++ b/core/modules/config/config_test/config/config_test.system.yml @@ -0,0 +1 @@ +foo: bar diff --git a/core/modules/config/config_test/config_test.info b/core/modules/config/config_test/config_test.info new file mode 100644 index 0000000..8735450 --- /dev/null +++ b/core/modules/config/config_test/config_test.info @@ -0,0 +1,6 @@ +name = Configuration test module +package = Core +version = VERSION +core = 8.x +dependencies[] = config +hidden = TRUE diff --git a/core/modules/config/config_test/config_test.module b/core/modules/config/config_test/config_test.module new file mode 100644 index 0000000..7572224 --- /dev/null +++ b/core/modules/config/config_test/config_test.module @@ -0,0 +1,240 @@ +delete(); + } + if ($op == 'create') { + $config_test = new ConfigTest($new_config); + $config_test->save(); + } + if ($op == 'change') { + $config_test = new ConfigTest($new_config); + $config_test->setOriginal($old_config); + $config_test->save(); + } + return TRUE; +} + +/** + * Implements hook_menu(). + */ +function config_test_menu() { + $items['admin/structure/config_test'] = array( + 'title' => 'Test configuration', + 'page callback' => 'config_test_list_page', + 'access callback' => TRUE, + ); + $items['admin/structure/config_test/add'] = array( + 'title' => 'Add test configuration', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('config_test_form'), + 'access callback' => TRUE, + 'type' => MENU_LOCAL_ACTION, + ); + $items['admin/structure/config_test/manage/%config_test'] = array( + 'title' => 'Edit test configuration', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('config_test_form', 4), + 'access callback' => TRUE, + ); + $items['admin/structure/config_test/manage/%config_test/edit'] = array( + 'title' => 'Edit', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items['admin/structure/config_test/manage/%config_test/delete'] = array( + 'title' => 'Delete', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('config_test_delete_form', 4), + 'access callback' => TRUE, + 'type' => MENU_LOCAL_TASK, + ); + return $items; +} + +/** + * Loads a ConfigTest object. + * + * @param string $id + * The ID of the ConfigTest object to load. + */ +function config_test_load($id) { + $config = config('config_test.dynamic.' . $id); + // @todo Requires a more reliable + generic method to check for whether the + // configuration object exists. + if ($config->get('id') === NULL) { + return FALSE; + } + return new ConfigTest($config); +} + +/** + * Saves a ConfigTest object. + * + * @param Drupal\config_test\ConfigTest $config_test + * The ConfigTest object to save. + */ +function config_test_save(ConfigTest $config_test) { + return $config_test->save(); +} + +/** + * Deletes a ConfigTest object. + * + * @param string $id + * The ID of the ConfigTest object to delete. + */ +function config_test_delete($id) { + $config = config('config_test.dynamic.' . $id); + $config_test = new ConfigTest($config); + return $config_test->delete(); +} + +/** + * Page callback; Lists available ConfigTest objects. + */ +function config_test_list_page() { + $config_names = config_get_storage_names_with_prefix('config_test.dynamic.'); + $rows = array(); + foreach ($config_names as $config_name) { + $config_test = new ConfigTest(config($config_name)); + $row = array(); + $row['name']['data'] = array( + '#type' => 'link', + '#title' => $config_test->getLabel(), + '#href' => $config_test->getUri(), + ); + $row['delete']['data'] = array( + '#type' => 'link', + '#title' => t('Delete'), + '#href' => $config_test->getUri() . '/delete', + ); + $rows[] = $row; + } + $build = array( + '#theme' => 'table', + '#header' => array('Name', 'Operations'), + '#rows' => $rows, + '#empty' => format_string('No test configuration defined. Add some', array( + '@add-url' => url('admin/structure/config_test/add'), + )), + ); + return $build; +} + +/** + * Form constructor to add or edit a ConfigTest object. + * + * @param Drupal\config_test\ConfigTest $config_test + * (optional) An existing ConfigTest object to edit. If omitted, the form + * creates a new ConfigTest. + */ +function config_test_form($form, &$form_state, ConfigTest $config_test = NULL) { + if (!isset($config_test)) { + $config_test = new ConfigTest(config(NULL)); + } + $form_state['config_test'] = $config_test; + + $form['name'] = array( + '#type' => 'textfield', + '#title' => 'Label', + '#default_value' => $config_test->getLabel(), + '#required' => TRUE, + ); + $form['id'] = array( + '#type' => 'machine_name', + '#default_value' => $config_test->getId(), + '#required' => TRUE, + '#machine_name' => array( + 'exists' => 'config_test_load', + ), + ); + $form['style'] = array( + '#type' => 'select', + '#title' => 'Image style', + '#options' => array(), + '#default_value' => $config_test->get('style'), + '#access' => FALSE, + ); + if (module_exists('image')) { + $form['style']['#access'] = TRUE; + $form['style']['#options'] = image_style_options(); + } + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array('#type' => 'submit', '#value' => 'Save'); + + return $form; +} + +/** + * Form submission handler for config_test_form(). + */ +function config_test_form_submit($form, &$form_state) { + form_state_values_clean($form_state); + + $config_test = $form_state['config_test']; + + foreach ($form_state['values'] as $key => $value) { + $config_test->set($key, $value); + } + $config_test->save(); + + if (!empty($config_test->original)) { + drupal_set_message(format_string('%label configuration has been updated.', array('%label' => $config_test->getLabel()))); + } + else { + drupal_set_message(format_string('%label configuration has been created.', array('%label' => $config_test->getLabel()))); + } + + $form_state['redirect'] = 'admin/structure/config_test'; +} + +/** + * Form constructor to delete a ConfigTest object. + * + * @param Drupal\config_test\ConfigTest $config_test + * The ConfigTest object to delete. + */ +function config_test_delete_form($form, &$form_state, ConfigTest $config_test) { + $form_state['config_test'] = $config_test; + + $form['id'] = array('#type' => 'value', '#value' => $config_test->getId()); + return confirm_form($form, + format_string('Are you sure you want to delete %label', array('%label' => $config_test->getLabel())), + 'admin/structure/config_test', + NULL, + 'Delete' + ); +} + +/** + * Form submission handler for config_test_delete_form(). + */ +function config_test_delete_form_submit($form, &$form_state) { + $form_state['config_test']->delete(); + $form_state['redirect'] = 'admin/structure/config_test'; +} diff --git a/core/modules/config/config_test/lib/Drupal/config_test/ConfigTest.php b/core/modules/config/config_test/lib/Drupal/config_test/ConfigTest.php new file mode 100644 index 0000000..2bb25c7 --- /dev/null +++ b/core/modules/config/config_test/lib/Drupal/config_test/ConfigTest.php @@ -0,0 +1,178 @@ +load($config); + } + + /** + * Implements Drupal\Core\Config\ConfigObjectInterface::getConfigPrefix(). + */ + public function getConfigPrefix() { + return 'config_test.dynamic'; + } + + /** + * Implements ConfigObjectInterface::getName(). + */ + public function getConfigName() { + return $this->getConfigPrefix() . '.' . $this->getId; + } + + /** + * Implements ConfigObjectInterface::getId(). + */ + public function getId() { + return $this->config->get($this->idKey); + } + + /** + * Implements ConfigObjectInterface::isNew(). + */ + public function isNew() { + return isset($this->originalId); + } + + /** + * Implements ConfigObjectInterface::getLabel(). + */ + public function getLabel() { + return $this->config->get($this->labelKey); + } + + /** + * Implements ConfigObjectInterface::getUri(). + */ + public function getUri() { + // @todo Check whether this makes sense. + return 'admin/structure/config_test/manage/' . $this->getId(); + } + + /** + * Implements ConfigObjectInterface::get(). + */ + public function get($property_name, $langcode = NULL) { + return $this->config->get($property_name); + } + + /** + * Implements ConfigObjectInterface::set(). + */ + public function set($property_name, $value, $langcode = NULL) { + return $this->config->set($property_name, $value); + } + + /** + * Implements ConfigObjectInterface::setOriginal(). + */ + public function setOriginal($config) { + $this->original = $config; + $this->originalId = $config->get($this->idKey); + return $this; + } + + /** + * Implements ConfigObjectInterface::load(). + */ + public function load($config) { + $this->config = $config; + $this->originalId = $config->get($this->idKey); + return $this; + } + + /** + * Implements ConfigObjectInterface::save(). + */ + public function save() { + // Provide the original configuration in $config->original, if any. + if (isset($this->originalId)) { + $original_config = config($this->getConfigPrefix() . '.' . $this->originalId); + $this->setOriginal(new $this($original_config)); + + // Delete the original configuration, if it was renamed. + if ($this->originalId !== $this->getId()) { + // Configuration data is emptied out upon delete, so back it up and + // re-inject it. Delete the old configuration data directly; hooks will + // get and will be able to react to the data in $this->original. + $original_data = $original_config->get(); + $original_config->delete(); + $original_config->setData($original_data); + } + } + + // Save the new configuration. + $this->config->setName($this->getConfigPrefix() . '.' . $this->getId()); + $this->config->save(); + + return $this; + } + + /** + * Implements ConfigObjectInterface::delete(). + */ + public function delete() { + $this->config->delete(); + } +} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigConfigurableTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigConfigurableTest.php new file mode 100644 index 0000000..68df5f1 --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigConfigurableTest.php @@ -0,0 +1,80 @@ + 'Configurable configuration', + 'description' => 'Tests configurable configuration.', + 'group' => 'Configuration', + ); + } + + function setUp() { + parent::setUp(array('config_test')); + } + + /** + * Tests basic CRUD operations through the UI. + */ + function testCRUD() { + // Create a thingie. + $id = 'thingie'; + $edit = array( + 'id' => $id, + 'name' => 'Thingie', + ); + $this->drupalPost('admin/structure/config_test/add', $edit, 'Save'); + $this->assertResponse(200); + $this->assertText('Thingie'); + + // Update the thingie. + $this->assertLinkByHref('admin/structure/config_test/manage/' . $id); + $edit = array( + 'name' => 'Thongie', + ); + $this->drupalPost('admin/structure/config_test/manage/' . $id, $edit, 'Save'); + $this->assertResponse(200); + $this->assertNoText('Thingie'); + $this->assertText('Thongie'); + + // Delete the thingie. + $this->assertLinkByHref('admin/structure/config_test/manage/' . $id . '/delete'); + $this->drupalPost('admin/structure/config_test/manage/' . $id . '/delete', array(), 'Delete'); + $this->assertResponse(200); + $this->assertNoText('Thingie'); + $this->assertNoText('Thongie'); + + // Re-create a thingie. + $edit = array( + 'id' => $id, + 'name' => 'Thingie', + ); + $this->drupalPost('admin/structure/config_test/add', $edit, 'Save'); + $this->assertResponse(200); + $this->assertText('Thingie'); + + // Rename the thingie's ID/machine name. + $this->assertLinkByHref('admin/structure/config_test/manage/' . $id); + $new_id = 'zingie'; + $edit = array( + 'id' => $new_id, + 'name' => 'Zingie', + ); + $this->drupalPost('admin/structure/config_test/manage/' . $id, $edit, 'Save'); + $this->assertResponse(200); + $this->assertNoText('Thingie'); + $this->assertText('Zingie'); + } +} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php new file mode 100644 index 0000000..09baded --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php @@ -0,0 +1,162 @@ + 'Import configuration', + 'description' => 'Tests importing configuration from files into active store.', + 'group' => 'Configuration', + ); + } + + function setUp() { + parent::setUp('config_test'); + } + + /** + * Tests deletion of configuration during import. + */ + function testDeleted() { + $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'); + + // Export. + config_export(); + + // Delete the configuration objects. + $file_storage = new FileStorage(); + $file_storage->delete($name); + $file_storage->delete($dynamic_name); + + // Import. + config_import(); + + // Verify the values have disappeared. + $database_storage = new DatabaseStorage(); + $this->assertIdentical($database_storage->read($name), array()); + $this->assertIdentical($database_storage->read($dynamic_name), array()); + + $config = config($name); + $this->assertIdentical($config->get('foo'), NULL); + $config = config($dynamic_name); + $this->assertIdentical($config->get('id'), NULL); + } + + /** + * Tests creation of configuration during import. + */ + function testNew() { + $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', + 'name' => 'New', + )); + $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.'); + $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); + + // Import. + config_import(); + + // Verify the values appeared. + $config = config($name); + $this->assertIdentical($config->get('add_me'), 'new value'); + $config = config($dynamic_name); + $this->assertIdentical($config->get('name'), 'New'); + } + + /** + * Tests updating of configuration during import. + */ + function testUpdated() { + $name = 'config_test.system'; + $dynamic_name = 'config_test.dynamic.default'; + + // Export. + config_export(); + + // Replace the file content of the existing configuration objects. + $file_storage = new FileStorage(); + $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.'); + $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); + $file_storage->write($name, array( + 'foo' => 'beer', + )); + $file_storage->write($dynamic_name, array( + 'id' => 'default', + 'name' => 'Updated', + )); + + // Verify the active store still returns the default values. + $config = config($name); + $this->assertIdentical($config->get('foo'), 'bar'); + $config = config($dynamic_name); + $this->assertIdentical($config->get('name'), 'Default'); + + // Import. + config_import(); + + // Verify the values were updated. + $config = config($name); + $this->assertIdentical($config->get('foo'), 'beer'); + $config = config($dynamic_name); + $this->assertIdentical($config->get('name'), 'Updated'); + } + + /** + * Tests config_import() hook invocations. + */ + function testSyncHooks() { + $name = 'config_test.system'; + $dynamic_name = 'config_test.dynamic.default'; + + // Export. + config_export(); + + // Delete a file so that hook_config_import() hooks are run. + $file_storage = new FileStorage(); + $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.'); + $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); + $file_storage->delete($name); + $file_storage->delete($dynamic_name); + + // Import. + config_import(); + + // Verify hook_config_import() was invoked. + $this->assertIdentical($GLOBALS['hook_config_import'], 'config_test_config_import'); + } +} diff --git a/core/modules/image/image.module b/core/modules/image/image.module index f3bf83d..c95af19 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -500,6 +500,36 @@ function image_path_flush($path) { } /** + * Implements hook_config_import(). + */ +function image_config_import($op, $name, $new_config, $old_config) { + // Only image styles require custom handling. Any other module settings can be + // synchronized directly. + if (strpos($name, 'image.style.') !== 0) { + return FALSE; + } + + if ($op == 'delete') { + // @todo image_style_delete() supports the notion of a "replacement style" + // to be used by other modules instead of the deleted style. Good idea. + // But squeezing that into a "delete" operation is the worst idea ever. + // Regardless of Image module insanity, add a 'replaced' stack to + // config_import()? And how can that work? If an 'old_ID' key would be a + // standard, wouldn't this belong into 'changed' instead? + $style = $old_config->get(); + return image_style_delete($style); + } + if ($op == 'create') { + $style = $new_config->get(); + return image_style_save($style); + } + if ($op == 'change') { + $style = $new_config->get(); + return image_style_save($style); + } +} + +/** * Get an array of all styles and their settings. * * @return