diff --git a/core/includes/form.inc b/core/includes/form.inc index 571a439..9635106 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -3532,6 +3532,7 @@ function form_process_machine_name($element, &$form_state) { // 'source' only) would leave all other properties undefined, if the defaults // were defined in hook_element_info(). Therefore, we apply the defaults here. $element['#machine_name'] += array( + // @todo Use 'label' by default. 'source' => array('name'), 'target' => '#' . $element['#id'], 'label' => t('Machine name'), diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index a749a4b..ea14595 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -268,6 +268,7 @@ class Config { * Deletes the configuration object. */ public function delete() { + // @todo Consider to remove the pruning of data for Config::delete(). $this->data = array(); $this->storageDispatcher->selectStorage('write', $this->name)->delete($this->name); $this->isNew = TRUE; diff --git a/core/lib/Drupal/Core/ConfigThingie/ConfigThingieBase.php b/core/lib/Drupal/Core/ConfigThingie/ConfigThingieBase.php new file mode 100644 index 0000000..0511708 --- /dev/null +++ b/core/lib/Drupal/Core/ConfigThingie/ConfigThingieBase.php @@ -0,0 +1,205 @@ +setConfig($config); + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::getConfigPrefix(). + */ + public function getConfigPrefix() { + return 'config_test.dynamic'; + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::getConfigName(). + */ + public function getConfigName() { + return $this->getConfigPrefix() . '.' . $this->getID(); + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::getEventBasename(). + */ + public function getEventBasename() { + return str_replace('.', '_', $this->getConfigPrefix()); + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::getID(). + */ + public function getID() { + return $this->config->get($this->idKey); + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::isNew(). + */ + public function isNew() { + return $this->config->isNew(); + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::getLabel(). + */ + public function getLabel() { + return $this->config->get($this->labelKey); + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::get(). + */ + public function get($property_name) { + return $this->config->get($property_name); + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::set(). + */ + public function set($property_name, $value) { + return $this->config->set($property_name, $value); + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::setConfig(). + */ + public function setConfig(Config $config) { + $this->config = $config; + // Only set originalID, if the passed in configuration object is stored + // already. + if (!$this->config->isNew()) { + $this->originalID = $this->getID(); + } + + // Allow modules to react upon load. + module_invoke_all($this->getEventBasename() . '_load', $this); + + return $this; + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::setOriginal(). + */ + public function setOriginal(Config $config) { + $this->original = new $this($config); + // Ensure that originalID contains the ID of the supplied original + // configuration object. setOriginal() may be called from outside of this + // class (e.g., hook_config_import()) in order to set a specific original. + $this->originalID = $this->original->getID(); + return $this; + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::save(). + */ + public function save() { + // Provide the original configuration thingie in $this->original, if any. + // originalID is only set, if this configuration thingie already existed + // prior to saving. + if (isset($this->originalID)) { + // Load the original configuration object. + // This cannot use ConfigThingieBase::getConfigName(), since that would + // yield the name using the current/new ID. + $original_config = config($this->getConfigPrefix() . '.' . $this->originalID); + // Given the original configuration object, instantiate a new class of the + // current class, and provide it in $this->original. + $this->setOriginal($original_config); + } + + // Ensure that the configuration object name uses the current ID. + $this->config->setName($this->getConfigName()); + + // Allow modules to react prior to saving. + module_invoke_all($this->getEventBasename() . '_presave', $this); + + // Save the configuration object. + $this->config->save(); + + if (isset($this->originalID)) { + // Allow modules to react after inserting new configuration. + module_invoke_all($this->getEventBasename() . '_update', $this); + + // 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. + // @todo Consider to remove the pruning of data for Config::delete(). + $original_data = $original_config->get(); + $original_config->delete(); + $original_config->setData($original_data); + } + } + else { + // Allow modules to react after updating existing configuration. + module_invoke_all($this->getEventBasename() . '_insert', $this); + } + + return $this; + } + + /** + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::delete(). + */ + public function delete() { + // Allow modules to react prior to deleting the configuration. + module_invoke_all($this->getEventBasename() . '_predelete', $this); + + // Delete the configuration object. + $this->config->delete(); + + // Allow modules to react after deleting the configuration. + module_invoke_all($this->getEventBasename() . '_delete', $this); + } +} diff --git a/core/lib/Drupal/Core/ConfigThingie/ConfigThingieInterface.php b/core/lib/Drupal/Core/ConfigThingie/ConfigThingieInterface.php new file mode 100644 index 0000000..79daaa4 --- /dev/null +++ b/core/lib/Drupal/Core/ConfigThingie/ConfigThingieInterface.php @@ -0,0 +1,147 @@ + $id, - 'name' => 'Thingie', + 'label' => 'Thingie', ); $this->drupalPost('admin/structure/config_test/add', $edit, 'Save'); $this->assertResponse(200); @@ -42,7 +42,7 @@ class ConfigConfigurableTest extends WebTestBase { // Update the thingie. $this->assertLinkByHref('admin/structure/config_test/manage/' . $id); $edit = array( - 'name' => 'Thongie', + 'label' => 'Thongie', ); $this->drupalPost('admin/structure/config_test/manage/' . $id, $edit, 'Save'); $this->assertResponse(200); @@ -59,7 +59,7 @@ class ConfigConfigurableTest extends WebTestBase { // Re-create a thingie. $edit = array( 'id' => $id, - 'name' => 'Thingie', + 'label' => 'Thingie', ); $this->drupalPost('admin/structure/config_test/add', $edit, 'Save'); $this->assertResponse(200); @@ -70,7 +70,7 @@ class ConfigConfigurableTest extends WebTestBase { $new_id = 'zingie'; $edit = array( 'id' => $new_id, - 'name' => 'Zingie', + 'label' => 'Zingie', ); $this->drupalPost('admin/structure/config_test/manage/' . $id, $edit, 'Save'); $this->assertResponse(200); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php index 46fad9b..2248616 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\config\Tests\ConfigImportTestCase. + * Definition of Drupal\config\Tests\ConfigImportTest. */ namespace Drupal\config\Tests; @@ -12,7 +12,7 @@ use Drupal\Core\Config\FileStorage; use Drupal\simpletest\WebTestBase; /** - * Tests config_import() functionality. + * Tests importing configuration from files into active store. */ class ConfigImportTest extends WebTestBase { public static function getInfo() { @@ -24,7 +24,30 @@ class ConfigImportTest extends WebTestBase { } function setUp() { - parent::setUp('config_test'); + parent::setUp(array('config_test')); + + // Clear out any possibly existing hook invocation records. + unset($GLOBALS['hook_config_test_dynamic']); + } + + /** + * Tests omission of module APIs for bare configuration operations. + */ + function testNoImport() { + $dynamic_name = 'config_test.dynamic.default'; + + // Verify the default configuration values exist. + $config = config($dynamic_name); + $this->assertIdentical($config->get('id'), 'default'); + + // Verify that a bare config() does not involve module APIs. + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic'])); + + // Export. + config_export(); + + // Verify that config_export() does not involve module APIs. + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic'])); } /** @@ -60,6 +83,14 @@ class ConfigImportTest extends WebTestBase { $this->assertIdentical($config->get('foo'), NULL); $config = config($dynamic_name); $this->assertIdentical($config->get('id'), NULL); + + // Verify that appropriate module API hooks have been invoked. + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['load'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['presave'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['insert'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['update'])); + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['predelete'])); + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['delete'])); } /** @@ -83,7 +114,7 @@ class ConfigImportTest extends WebTestBase { )); $file_storage->write($dynamic_name, array( 'id' => 'new', - 'name' => 'New', + 'label' => 'New', )); $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.'); $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); @@ -95,7 +126,15 @@ class ConfigImportTest extends WebTestBase { $config = config($name); $this->assertIdentical($config->get('add_me'), 'new value'); $config = config($dynamic_name); - $this->assertIdentical($config->get('name'), 'New'); + $this->assertIdentical($config->get('label'), 'New'); + + // Verify that appropriate module API hooks have been invoked. + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['load'])); + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['presave'])); + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['insert'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['update'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['predelete'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['delete'])); } /** @@ -117,14 +156,14 @@ class ConfigImportTest extends WebTestBase { )); $file_storage->write($dynamic_name, array( 'id' => 'default', - 'name' => 'Updated', + 'label' => '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'); + $this->assertIdentical($config->get('label'), 'Default'); // Import. config_import(); @@ -133,7 +172,15 @@ class ConfigImportTest extends WebTestBase { $config = config($name); $this->assertIdentical($config->get('foo'), 'beer'); $config = config($dynamic_name); - $this->assertIdentical($config->get('name'), 'Updated'); + $this->assertIdentical($config->get('label'), 'Updated'); + + // Verify that appropriate module API hooks have been invoked. + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['load'])); + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['presave'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['insert'])); + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['update'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['predelete'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['delete'])); } /** diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php index 3d13d98..a9676ef 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php @@ -49,10 +49,11 @@ class ConfigInstallTest extends WebTestBase { // Verify that config_test API hooks were invoked for the dynamic default // thingie. - $this->assertTrue($GLOBALS['hook_config_test_load']); - $this->assertTrue($GLOBALS['hook_config_test_presave']); - $this->assertTrue($GLOBALS['hook_config_test_insert']); - $this->assertFalse(isset($GLOBALS['hook_config_test_update'])); - $this->assertFalse(isset($GLOBALS['hook_config_test_delete'])); + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['load'])); + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['presave'])); + $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['insert'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['update'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['predelete'])); + $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['delete'])); } } diff --git a/core/modules/config/tests/config_test/config/config_test.dynamic.default.yml b/core/modules/config/tests/config_test/config/config_test.dynamic.default.yml index d285439..3e50e3b 100644 --- a/core/modules/config/tests/config_test/config/config_test.dynamic.default.yml +++ b/core/modules/config/tests/config_test/config/config_test.dynamic.default.yml @@ -1,2 +1,2 @@ id: default -name: Default +label: Default diff --git a/core/modules/config/tests/config_test/config_test.hooks.inc b/core/modules/config/tests/config_test/config_test.hooks.inc index 56f690f..2ec6831 100644 --- a/core/modules/config/tests/config_test/config_test.hooks.inc +++ b/core/modules/config/tests/config_test/config_test.hooks.inc @@ -10,36 +10,43 @@ */ /** - * Implements hook_config_test_load(). + * Implements hook_config_test_dynamic_load(). */ -function config_test_config_test_load() { - $GLOBALS['hook_config_test_load'] = __FUNCTION__; +function config_test_config_test_dynamic_load() { + $GLOBALS['hook_config_test_dynamic']['load'] = __FUNCTION__; } /** - * Implements hook_config_test_presave(). + * Implements hook_config_test_dynamic_presave(). */ -function config_test_config_test_presave() { - $GLOBALS['hook_config_test_presave'] = __FUNCTION__; +function config_test_config_test_dynamic_presave() { + $GLOBALS['hook_config_test_dynamic']['presave'] = __FUNCTION__; } /** - * Implements hook_config_test_insert(). + * Implements hook_config_test_dynamic_insert(). */ -function config_test_config_test_insert() { - $GLOBALS['hook_config_test_insert'] = __FUNCTION__; +function config_test_config_test_dynamic_insert() { + $GLOBALS['hook_config_test_dynamic']['insert'] = __FUNCTION__; } /** - * Implements hook_config_test_update(). + * Implements hook_config_test_dynamic_update(). */ -function config_test_config_test_update() { - $GLOBALS['hook_config_test_update'] = __FUNCTION__; +function config_test_config_test_dynamic_update() { + $GLOBALS['hook_config_test_dynamic']['update'] = __FUNCTION__; } /** - * Implements hook_config_test_delete(). + * Implements hook_config_test_dynamic_predelete(). */ -function config_test_config_test_delete() { - $GLOBALS['hook_config_test_delete'] = __FUNCTION__; +function config_test_config_test_dynamic_predelete() { + $GLOBALS['hook_config_test_dynamic']['predelete'] = __FUNCTION__; +} + +/** + * Implements hook_config_test_dynamic_delete(). + */ +function config_test_config_test_dynamic_delete() { + $GLOBALS['hook_config_test_dynamic']['delete'] = __FUNCTION__; } diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module index e4b14ab..e0a5ba5 100644 --- a/core/modules/config/tests/config_test/config_test.module +++ b/core/modules/config/tests/config_test/config_test.module @@ -1,6 +1,5 @@ 'textfield', '#title' => 'Label', '#default_value' => $config_test->getLabel(), @@ -160,10 +159,12 @@ function config_test_form($form, &$form_state, ConfigTest $config_test = NULL) { ); $form['id'] = array( '#type' => 'machine_name', - '#default_value' => $config_test->getId(), + '#default_value' => $config_test->getID(), '#required' => TRUE, '#machine_name' => array( 'exists' => 'config_test_load', + // @todo Update form_process_machine_name() to use 'label' by default. + 'source' => array('label'), ), ); $form['style'] = array( @@ -216,7 +217,7 @@ function config_test_form_submit($form, &$form_state) { 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()); + $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', diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php index 5d95d50..4efaae5 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php @@ -7,50 +7,12 @@ namespace Drupal\config_test; +use Drupal\Core\ConfigThingie\ConfigThingieBase; + /** * Defines the ConfigTest configuration thingie. - * - * @todo At minimum, introduce a consistent interface for configurable thingies. - * @todo Consider to introduce a ConfigObjectBase for all configurable thingies. */ -class ConfigTest { - /** - * The configuration object. - * - * @var Drupal\Core\Config\ConfigObject - */ - protected $config; - - /** - * The key of the thingie's ID (machine name). - * - * @var string - */ - public $idKey = 'id'; - - /** - * The key of the thingie's label. - * - * @var string - */ - public $labelKey = 'name'; - - /** - * The thingie's original ID, if any. - * - * @var string|null - */ - public $originalId; - - /** - * The original configuration object, if any, upon update. - * - * @var Drupal\Core\Config\ConfigObject - */ - public $original; - - // -- Custom properties start -- - +class ConfigTest extends ConfigThingieBase { /** * The image style to use. * @@ -58,130 +20,17 @@ class ConfigTest { */ public $style; - // -- Custom properties end -- - - /** - * Constructs a new ConfigTest thingie. - */ - public function __construct($config) { - $this->load($config); - } - /** - * Implements Drupal\Core\Config\ConfigObjectInterface::getConfigPrefix(). + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::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); - module_invoke_all('config_test_load', $this); - 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)); - } - - // Save the new configuration. - $this->config->setName($this->getConfigPrefix() . '.' . $this->getId()); - module_invoke_all('config_test_presave', $this); - $this->config->save(); - - if (isset($this->originalId)) { - module_invoke_all('config_test_update', $this); - // 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); - } - } - else { - module_invoke_all('config_test_insert', $this); - } - - return $this; - } - - /** - * Implements ConfigObjectInterface::delete(). + * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::getURI(). */ - public function delete() { - $this->config->delete(); - module_invoke_all('config_test_delete', $this); + public function getURI() { + return 'admin/structure/config_test/manage/' . $this->getID(); } }