diff --git a/core/includes/form.inc b/core/includes/form.inc index 65ef9bc..7b2a932 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -3542,6 +3542,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 10bf925..cfede74 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -320,6 +320,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->storage->delete($this->name); $this->isNew = TRUE; diff --git a/core/lib/Drupal/Core/File/FileStorageController.php b/core/lib/Drupal/Core/File/FileStorageController.php index 940e1fd..dd8ee7a 100644 --- a/core/lib/Drupal/Core/File/FileStorageController.php +++ b/core/lib/Drupal/Core/File/FileStorageController.php @@ -8,7 +8,7 @@ namespace Drupal\Core\File; use Drupal\entity\DatabaseStorageController; -use Drupal\entity\EntityInterface; +use Drupal\entity\StorableInterface; /** * File storage controller for files. @@ -34,7 +34,7 @@ class FileStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::presave(). */ - protected function preSave(EntityInterface $entity) { + protected function preSave(StorableInterface $entity) { $entity->timestamp = REQUEST_TIME; $entity->filesize = filesize($entity->uri); if (!isset($entity->langcode)) { diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php index 3ca70a9..59296a6 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php @@ -7,7 +7,7 @@ namespace Drupal\comment; -use Drupal\entity\EntityInterface; +use Drupal\entity\StorableInterface; use Drupal\entity\DatabaseStorageController; use LogicException; @@ -58,7 +58,7 @@ class CommentStorageController extends DatabaseStorageController { * @see comment_int_to_alphadecimal() * @see comment_alphadecimal_to_int() */ - protected function preSave(EntityInterface $comment) { + protected function preSave(StorableInterface $comment) { global $user; if (!isset($comment->status)) { @@ -151,7 +151,7 @@ class CommentStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::postSave(). */ - protected function postSave(EntityInterface $comment, $update) { + protected function postSave(StorableInterface $comment, $update) { $this->releaseThreadLock(); // Update the {node_comment_statistics} table prior to executing the hook. $this->updateNodeStatistics($comment->nid); diff --git a/core/modules/config/config.api.php b/core/modules/config/config.api.php index f0c3afa..6aef002 100644 --- a/core/modules/config/config.api.php +++ b/core/modules/config/config.api.php @@ -31,12 +31,12 @@ * A configuration object containing the old configuration data. */ function MODULE_config_import_create($name, $new_config, $old_config) { - // Only configurable thingies require custom handling. Any other module + // Only configurable entities require custom handling. Any other module // settings can be synchronized directly. if (strpos($name, 'config_test.dynamic.') !== 0) { return FALSE; } - $config_test = new ConfigTest($new_config); + $config_test = entity_create('config_test', $new_config->get()); $config_test->save(); return TRUE; } @@ -60,13 +60,25 @@ function MODULE_config_import_create($name, $new_config, $old_config) { * A configuration object containing the old configuration data. */ function MODULE_config_import_change($name, $new_config, $old_config) { - // Only configurable thingies require custom handling. Any other module + // Only configurable entities require custom handling. Any other module // settings can be synchronized directly. if (strpos($name, 'config_test.dynamic.') !== 0) { return FALSE; } - $config_test = new ConfigTest($new_config); - $config_test->setOriginal($old_config); + + // @todo Make this less ugly. + $id = substr($name, strlen(ConfigTest::getConfigPrefix()) + 1); + $config_test = entity_load('config_test', $id); + + $config_test->original = clone $config_test; + foreach ($old_config->get() as $property => $value) { + $config_test->original->$property = $value; + } + + foreach ($new_config->get() as $property => $value) { + $config_test->$property = $value; + } + $config_test->save(); return TRUE; } @@ -90,7 +102,7 @@ function MODULE_config_import_change($name, $new_config, $old_config) { * A configuration object containing the old configuration data. */ function MODULE_config_import_delete($name, $new_config, $old_config) { - // Only configurable thingies require custom handling. Any other module + // Only configurable entities require custom handling. Any other module // settings can be synchronized directly. if (strpos($name, 'config_test.dynamic.') !== 0) { return FALSE; @@ -100,8 +112,8 @@ function MODULE_config_import_delete($name, $new_config, $old_config) { // But that is impossible currently, since the config system only knows // about deleted and added changes. Introduce an 'old_ID' key within // config objects as a standard? - $config_test = new ConfigTest($old_config); - $config_test->delete(); + $id = substr($name, strlen(ConfigTest::getConfigPrefix()) + 1); + config_test_delete($id); return TRUE; } diff --git a/core/modules/config/lib/Drupal/config/ConfigStorageController.php b/core/modules/config/lib/Drupal/config/ConfigStorageController.php new file mode 100644 index 0000000..ccabf2d --- /dev/null +++ b/core/modules/config/lib/Drupal/config/ConfigStorageController.php @@ -0,0 +1,346 @@ +entityType = $entityType; + $this->entityInfo = entity_get_info($entityType); + $this->hookLoadArguments = array(); + $this->idKey = $this->entityInfo['entity keys']['id']; + + // The UUID key and property is hard-coded for all configurables. + $this->uuidKey = 'uuid'; + } + + /** + * Implements Drupal\entity\StorageControllerInterface::resetCache(). + */ + public function resetCache(array $ids = NULL) { + // The configuration system is fast enough and/or implements its own + // (advanced) caching mechanism already. + } + + /** + * Implements Drupal\entity\StorageControllerInterface::load(). + */ + public function load($ids = array(), $conditions = array()) { + $entities = array(); + + // Create a new variable which is either a prepared version of the $ids + // array for later comparison with the entity cache, or FALSE if no $ids + // were passed. + $passed_ids = !empty($ids) ? array_flip($ids) : FALSE; + + // Load any remaining entities. This is the case if $ids + // is set to FALSE (so we load all entities), + // or if $conditions was passed without $ids. + if ($ids === FALSE || $ids || ($conditions && !$passed_ids)) { + $queried_entities = $this->buildQuery($ids, $conditions); + } + + // Pass all entities loaded from the database through $this->attachLoad(), + // which calls the + // entity type specific load callback, for example hook_node_type_load(). + if (!empty($queried_entities)) { + $this->attachLoad($queried_entities); + $entities += $queried_entities; + } + + // Ensure that the returned array is ordered the same as the original + // $ids array if this was passed in and remove any invalid ids. + if ($passed_ids) { + // Remove any invalid ids from the array. + $passed_ids = array_intersect_key($passed_ids, $entities); + foreach ($entities as $entity) { + $passed_ids[$entity->{$this->idKey}] = $entity; + } + $entities = $passed_ids; + } + + return $entities; + } + + /** + * Builds the query to load the entity. + * + * This has full revision support. For entities requiring special queries, + * the class can be extended, and the default query can be constructed by + * calling parent::buildQuery(). This is usually necessary when the object + * being loaded needs to be augmented with additional data from another + * table, such as loading node type into comments or vocabulary machine name + * into terms, however it can also support $conditions on different tables. + * See Drupal\comment\CommentStorageController::buildQuery() or + * Drupal\taxonomy\TermStorageController::buildQuery() for examples. + * + * @param $ids + * An array of entity IDs, or FALSE to load all entities. + * @param $conditions + * An array of conditions in the form 'field' => $value. + * @param $revision_id + * The ID of the revision to load, or FALSE if this query is asking for the + * most current revision(s). + * + * @return SelectQuery + * A SelectQuery object for loading the entity. + */ + protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { + $config_class = $this->entityInfo['entity class']; + $prefix = $config_class::getConfigPrefix() . '.'; + + // @todo Handle $conditions? + if ($ids === FALSE) { + $names = drupal_container()->get('config.storage')->listAll($prefix); + $result = array(); + foreach ($names as $name) { + $config = config($name); + $result[$config->get($this->idKey)] = new $config_class($config->get(), $this->entityType); + } + return $result; + } + else { + $result = array(); + foreach ($ids as $id) { + $config = config($prefix . $id); + if (!$config->isNew()) { + $result[$id] = new $config_class($config->get(), $this->entityType); + } + } + return $result; + } + } + + /** + * Attaches data to entities upon loading. + * + * This will attach fields, if the entity is fieldable. It calls + * hook_entity_load() for modules which need to add data to all entities. + * It also calls hook_TYPE_load() on the loaded entities. For example + * hook_node_load() or hook_user_load(). If your hook_TYPE_load() + * expects special parameters apart from the queried entities, you can set + * $this->hookLoadArguments prior to calling the method. + * See Drupal\node\NodeStorageController::attachLoad() for an example. + * + * @param $queried_entities + * Associative array of query results, keyed on the entity ID. + * @param $revision_id + * ID of the revision that was loaded, or FALSE if the most current revision + * was loaded. + */ + protected function attachLoad(&$queried_entities, $revision_id = FALSE) { + // Call hook_entity_load(). + foreach (module_implements('entity_load') as $module) { + $function = $module . '_entity_load'; + $function($queried_entities, $this->entityType); + } + // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are + // always the queried entities, followed by additional arguments set in + // $this->hookLoadArguments. + $args = array_merge(array($queried_entities), $this->hookLoadArguments); + foreach (module_implements($this->entityType . '_load') as $module) { + call_user_func_array($module . '_' . $this->entityType . '_load', $args); + } + } + + /** + * Implements Drupal\entity\StorageControllerInterface::create(). + */ + public function create(array $values) { + $class = isset($this->entityInfo['entity class']) ? $this->entityInfo['entity class'] : 'Drupal\entity\Entity'; + + $entity = new $class($values, $this->entityType); + + // Assign a new UUID if there is none yet. + if (!isset($entity->{$this->uuidKey})) { + $uuid = new Uuid(); + $entity->{$this->uuidKey} = $uuid->generate(); + } + + return $entity; + } + + /** + * Implements Drupal\entity\StorageControllerInterface::delete(). + */ + public function delete($ids) { + $entities = $ids ? $this->load($ids) : FALSE; + if (!$entities) { + // If no IDs or invalid IDs were passed, do nothing. + return; + } + + $this->preDelete($entities); + foreach ($entities as $id => $entity) { + $this->invokeHook('predelete', $entity); + } + + foreach ($entities as $id => $entity) { + $config = config($entity::getConfigPrefix() . '.' . $entity->id()); + $config->delete(); + } + + $this->postDelete($entities); + foreach ($entities as $id => $entity) { + $this->invokeHook('delete', $entity); + } + } + + /** + * Implements Drupal\entity\StorageControllerInterface::save(). + */ + public function save(StorableInterface $entity) { + $prefix = $entity::getConfigPrefix() . '.'; + + // Load the stored entity, if any. + if ($entity->getOriginalID()) { + $id = $entity->getOriginalID(); + } + else { + $id = $entity->id(); + } + $config = config($prefix . $id); + $config->setName($prefix . $entity->id()); + + if (!$config->isNew() && !isset($entity->original)) { + $entity->original = entity_load_unchanged($this->entityType, $id); + } + + $this->preSave($entity); + $this->invokeHook('presave', $entity); + + // Configuration objects do not have a schema. Extract all key names from + // class properties. + $class_info = new \ReflectionClass($entity); + foreach ($class_info->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $name = $property->getName(); + $config->set($name, $entity->$name); + } + + if (!$config->isNew()) { + $return = SAVED_NEW; + $config->save(); + $this->postSave($entity, TRUE); + $this->invokeHook('update', $entity); + } + else { + $return = SAVED_UPDATED; + $config->save(); + $entity->enforceIsNew(FALSE); + $this->postSave($entity, FALSE); + $this->invokeHook('insert', $entity); + } + + unset($entity->original); + + return $return; + } + + /** + * Acts on an entity before the presave hook is invoked. + * + * Used before the entity is saved and before invoking the presave hook. + */ + protected function preSave(StorableInterface $entity) { + } + + /** + * Acts on a saved entity before the insert or update hook is invoked. + * + * Used after the entity is saved, but before invoking the insert or update + * hook. + * + * @param $update + * (bool) TRUE if the entity has been updated, or FALSE if it has been + * inserted. + */ + protected function postSave(StorableInterface $entity, $update) { + // Delete the original configurable entity, in case the entity ID was + // renamed. + if ($update && !empty($entity->original) && $entity->{$this->idKey} !== $entity->original->{$this->idKey}) { + // @todo This should just delete the original config object without going + // through the API, no? + $entity->original->delete(); + } + } + + /** + * Acts on entities before they are deleted. + * + * Used before the entities are deleted and before invoking the delete hook. + */ + protected function preDelete($entities) { + } + + /** + * Acts on deleted entities before the delete hook is invoked. + * + * Used after the entities are deleted but before invoking the delete hook. + */ + protected function postDelete($entities) { + } + + /** + * Invokes a hook on behalf of the entity. + * + * @param $hook + * One of 'presave', 'insert', 'update', 'predelete', or 'delete'. + * @param $entity + * The entity object. + */ + protected function invokeHook($hook, StorableInterface $entity) { + // Invoke the hook. + module_invoke_all($this->entityType . '_' . $hook, $entity); + // Invoke the respective entity-level hook. + module_invoke_all('entity_' . $hook, $entity, $this->entityType); + } +} diff --git a/core/modules/config/lib/Drupal/config/ConfigurableBase.php b/core/modules/config/lib/Drupal/config/ConfigurableBase.php new file mode 100644 index 0000000..a8bc318 --- /dev/null +++ b/core/modules/config/lib/Drupal/config/ConfigurableBase.php @@ -0,0 +1,102 @@ +id()) { + $this->originalID = $original_id; + } + } + + /** + * Implements ConfigurableInterface::getOriginalID(). + */ + public function getOriginalID() { + return $this->originalID; + } + + /** + * Overrides Entity::isNew(). + * + * EntityInterface::enforceIsNew() is not supported by configurable entities, + * since each Configurable is unique. + */ + final public function isNew() { + return !$this->id(); + } + + /** + * Overrides Entity::bundle(). + * + * EntityInterface::bundle() is not supported by configurable entities, since + * a Configurable is a bundle. + */ + final public function bundle() { + return $this->entityType; + } + + /** + * Overrides Entity::get(). + * + * EntityInterface::get() implements support for fieldable entities, but + * configurable entities are not fieldable. + */ + public function get($property_name, $langcode = NULL) { + // @todo: Add support for translatable properties being not fields. + return isset($this->{$property_name}) ? $this->{$property_name} : NULL; + } + + /** + * Overrides Entity::set(). + * + * EntityInterface::set() implements support for fieldable entities, but + * configurable entities are not fieldable. + */ + public function set($property_name, $value, $langcode = NULL) { + // @todo: Add support for translatable properties being not fields. + $this->{$property_name} = $value; + } + + /** + * Helper callback for uasort() to sort Configurable entities by weight and label. + */ + public static function sort($a, $b) { + $a_weight = isset($a->weight) ? $a->weight : 0; + $b_weight = isset($b->weight) ? $b->weight : 0; + if ($a_weight == $b_weight) { + $a_label = $a->label(); + $b_label = $b->label(); + return strnatcasecmp($a_label, $b_label); + } + return ($a_weight < $b_weight) ? -1 : 1; + } +} diff --git a/core/modules/config/lib/Drupal/config/ConfigurableInterface.php b/core/modules/config/lib/Drupal/config/ConfigurableInterface.php new file mode 100644 index 0000000..4d75122 --- /dev/null +++ b/core/modules/config/lib/Drupal/config/ConfigurableInterface.php @@ -0,0 +1,34 @@ + 'Configurable entities', + 'description' => 'Tests configurable entities.', + 'group' => 'Configuration', + ); + } + + /** + * Tests basic CRUD operations through the UI. + */ + function testCRUD() { + // Create a configurable entity. + $id = 'thingie'; + $edit = array( + 'id' => $id, + 'label' => 'Thingie', + ); + $this->drupalPost('admin/structure/config_test/add', $edit, 'Save'); + $this->assertResponse(200); + $this->assertText('Thingie'); + + // Update the configurable entity. + $this->assertLinkByHref('admin/structure/config_test/manage/' . $id); + $edit = array( + 'label' => 'Thongie', + ); + $this->drupalPost('admin/structure/config_test/manage/' . $id, $edit, 'Save'); + $this->assertResponse(200); + $this->assertNoText('Thingie'); + $this->assertText('Thongie'); + + // Delete the configurable entity. + $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 configurable entity. + $edit = array( + 'id' => $id, + 'label' => 'Thingie', + ); + $this->drupalPost('admin/structure/config_test/add', $edit, 'Save'); + $this->assertResponse(200); + $this->assertText('Thingie'); + + // Rename the configurable entity's ID/machine name. + $this->assertLinkByHref('admin/structure/config_test/manage/' . $id); + $new_id = 'zingie'; + $edit = array( + 'id' => $new_id, + 'label' => '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 index ec92fdf..7265abaa 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php @@ -31,6 +31,33 @@ class ConfigImportTest extends WebTestBase { ); } + function setUp() { + parent::setUp(); + + // Clear out any possibly existing hook invocation records. + unset($GLOBALS['hook_config_test']); + } + + /** + * 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'])); + + // Export. + config_export(); + + // Verify that config_export() does not involve module APIs. + $this->assertFalse(isset($GLOBALS['hook_config_test'])); + } + /** * Tests deletion of configuration during import. */ @@ -64,6 +91,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']['load'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['presave'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['insert'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['update'])); + $this->assertTrue(isset($GLOBALS['hook_config_test']['predelete'])); + $this->assertTrue(isset($GLOBALS['hook_config_test']['delete'])); } /** @@ -100,6 +135,14 @@ class ConfigImportTest extends WebTestBase { $this->assertIdentical($config->get('add_me'), 'new value'); $config = config($dynamic_name); $this->assertIdentical($config->get('label'), 'New'); + + // Verify that appropriate module API hooks have been invoked. + $this->assertFalse(isset($GLOBALS['hook_config_test']['load'])); + $this->assertTrue(isset($GLOBALS['hook_config_test']['presave'])); + $this->assertTrue(isset($GLOBALS['hook_config_test']['insert'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['update'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['predelete'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['delete'])); } /** @@ -138,5 +181,13 @@ class ConfigImportTest extends WebTestBase { $this->assertIdentical($config->get('foo'), 'beer'); $config = config($dynamic_name); $this->assertIdentical($config->get('label'), 'Updated'); + + // Verify that appropriate module API hooks have been invoked. + $this->assertTrue(isset($GLOBALS['hook_config_test']['load'])); + $this->assertTrue(isset($GLOBALS['hook_config_test']['presave'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['insert'])); + $this->assertTrue(isset($GLOBALS['hook_config_test']['update'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['predelete'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['delete'])); } } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php index 7ec6d8e..d730554 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php @@ -26,12 +26,12 @@ class ConfigInstallTest extends WebTestBase { */ function testModuleInstallation() { $default_config = 'config_test.system'; - $default_thingie = 'config_test.dynamic.default'; + $default_configurable = 'config_test.dynamic.default'; // Verify that default module config does not exist before installation yet. $config = config($default_config); $this->assertIdentical($config->isNew(), TRUE); - $config = config($default_thingie); + $config = config($default_configurable); $this->assertIdentical($config->isNew(), TRUE); // Install the test module. @@ -40,11 +40,20 @@ class ConfigInstallTest extends WebTestBase { // Verify that default module config exists. $config = config($default_config); $this->assertIdentical($config->isNew(), FALSE); - $config = config($default_thingie); + $config = config($default_configurable); $this->assertIdentical($config->isNew(), FALSE); // Verify that configuration import callback was invoked for the dynamic - // thingie. + // configurable entity. $this->assertTrue($GLOBALS['hook_config_import']); + + // Verify that config_test API hooks were invoked for the dynamic default + // configurable entity. + $this->assertFalse(isset($GLOBALS['hook_config_test']['load'])); + $this->assertTrue(isset($GLOBALS['hook_config_test']['presave'])); + $this->assertTrue(isset($GLOBALS['hook_config_test']['insert'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['update'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['predelete'])); + $this->assertFalse(isset($GLOBALS['hook_config_test']['delete'])); } } diff --git a/core/modules/config/tests/config_test/config_test.hooks.inc b/core/modules/config/tests/config_test/config_test.hooks.inc new file mode 100644 index 0000000..80f3381 --- /dev/null +++ b/core/modules/config/tests/config_test/config_test.hooks.inc @@ -0,0 +1,52 @@ +save(); + $config_test = entity_create('config_test', $new_config->get()); + $config_test->save(); return TRUE; } @@ -20,15 +23,26 @@ function config_test_config_import_create($name, $new_config, $old_config) { * Implements MODULE_config_import_change(). */ function config_test_config_import_change($name, $new_config, $old_config) { - // Only configurable thingies require custom handling. Any other module - // settings can be synchronized directly. if (strpos($name, 'config_test.dynamic.') !== 0) { return FALSE; } // Set a global value we can check in test code. $GLOBALS['hook_config_import'] = __FUNCTION__; - $new_config->save(); + // @todo Make this less ugly. + $id = substr($name, strlen(ConfigTest::getConfigPrefix()) + 1); + $config_test = entity_load('config_test', $id); + + $config_test->original = clone $config_test; + foreach ($old_config->get() as $property => $value) { + $config_test->original->$property = $value; + } + + foreach ($new_config->get() as $property => $value) { + $config_test->$property = $value; + } + + $config_test->save(); return TRUE; } @@ -36,15 +50,248 @@ function config_test_config_import_change($name, $new_config, $old_config) { * Implements MODULE_config_import_delete(). */ function config_test_config_import_delete($name, $new_config, $old_config) { - // Only configurable thingies require custom handling. Any other module - // settings can be synchronized directly. if (strpos($name, 'config_test.dynamic.') !== 0) { return FALSE; } // Set a global value we can check in test code. $GLOBALS['hook_config_import'] = __FUNCTION__; - $old_config->delete(); + $id = substr($name, strlen(ConfigTest::getConfigPrefix()) + 1); + config_test_delete($id); return TRUE; } +/** + * Implements hook_entity_info(). + */ +function config_test_entity_info() { + $types['config_test'] = array( + 'label' => 'Test configuration', + 'controller class' => 'Drupal\config\ConfigStorageController', + 'entity class' => 'Drupal\config_test\ConfigTest', + 'uri callback' => 'config_test_uri', + 'entity keys' => array( + 'id' => 'id', + 'label' => 'label', + 'uuid' => 'uuid', + ), + ); + return $types; +} + +/** + * Entity uri callback. + * + * @param Drupal\config_test\ConfigTest $config_test + * A ConfigTest entity. + */ +function config_test_uri(ConfigTest $config_test) { + return array( + 'path' => 'admin/structure/config_test/manage/' . $config_test->id(), + ); +} + +/** + * 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) { + return entity_load('config_test', $id); +} + +/** + * 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) { + entity_delete_multiple('config_test', array($id)); +} + +/** + * Page callback; Lists available ConfigTest objects. + */ +function config_test_list_page() { + $entities = entity_load_multiple('config_test', FALSE); + uasort($entities, 'Drupal\config\ConfigurableBase::sort'); + + $rows = array(); + foreach ($entities as $config_test) { + $uri = $config_test->uri(); + $row = array(); + $row['name']['data'] = array( + '#type' => 'link', + '#title' => $config_test->label(), + '#href' => $uri['path'], + '#options' => $uri['options'], + ); + $row['delete']['data'] = array( + '#type' => 'link', + '#title' => t('Delete'), + '#href' => $uri['path'] . '/delete', + '#options' => $uri['options'], + ); + $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) { + // Standard procedure for handling the entity argument in entity forms, taking + // potential form caching and rebuilds properly into account. + // @see http://drupal.org/node/1499596 + if (!isset($form_state['config_test'])) { + if (!isset($config_test)) { + $config_test = entity_create('config_test', array()); + } + $form_state['config_test'] = $config_test; + } + else { + $config_test = $form_state['config_test']; + } + + $form['label'] = array( + '#type' => 'textfield', + '#title' => 'Label', + '#default_value' => $config_test->label(), + '#required' => TRUE, + ); + $form['id'] = array( + '#type' => 'machine_name', + '#default_value' => $config_test->id(), + '#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( + '#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']; + entity_form_submit_build_entity('config_test', $config_test, $form, $form_state); + + $status = $config_test->save(); + + if ($status == SAVED_UPDATED) { + drupal_set_message(format_string('%label configuration has been updated.', array('%label' => $config_test->label()))); + } + else { + drupal_set_message(format_string('%label configuration has been created.', array('%label' => $config_test->label()))); + } + + $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->id()); + return confirm_form($form, + format_string('Are you sure you want to delete %label', array('%label' => $config_test->label())), + '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/tests/config_test/lib/Drupal/config_test/ConfigTest.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php new file mode 100644 index 0000000..104c11d --- /dev/null +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php @@ -0,0 +1,34 @@ +entityInfo['entity class'])) { // We provide the necessary arguments for PDO to create objects of the // specified entity class. - // @see Drupal\entity\EntityInterface::__construct() + // @see Drupal\entity\StorableInterface::__construct() $query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType)); } $queried_entities = $query_result->fetchAllAssoc($this->idKey); @@ -438,7 +438,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface { /** * Implements Drupal\entity\EntityStorageControllerInterface::save(). */ - public function save(EntityInterface $entity) { + public function save(StorableInterface $entity) { $transaction = db_transaction(); try { // Load the stored entity, if any. @@ -483,7 +483,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface { * * Used before the entity is saved and before invoking the presave hook. */ - protected function preSave(EntityInterface $entity) { } + protected function preSave(StorableInterface $entity) { } /** * Acts on a saved entity before the insert or update hook is invoked. @@ -495,7 +495,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface { * (bool) TRUE if the entity has been updated, or FALSE if it has been * inserted. */ - protected function postSave(EntityInterface $entity, $update) { } + protected function postSave(StorableInterface $entity, $update) { } /** * Acts on entities before they are deleted. @@ -519,7 +519,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface { * @param $entity * The entity object. */ - protected function invokeHook($hook, EntityInterface $entity) { + protected function invokeHook($hook, StorableInterface $entity) { if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) { $function($this->entityType, $entity); } diff --git a/core/modules/entity/lib/Drupal/entity/Entity.php b/core/modules/entity/lib/Drupal/entity/Entity.php index 7e73024..6660fc7 100644 --- a/core/modules/entity/lib/Drupal/entity/Entity.php +++ b/core/modules/entity/lib/Drupal/entity/Entity.php @@ -17,265 +17,6 @@ use Drupal\Component\Uuid\Uuid; * This class can be used as-is by simple entity types. Entity types requiring * special handling can extend the class. */ -class Entity implements EntityInterface { +class Entity extends StorableBase implements EntityInterface { - /** - * The language code of the entity's default language. - * - * @var string - */ - public $langcode = LANGUAGE_NOT_SPECIFIED; - - /** - * The entity type. - * - * @var string - */ - protected $entityType; - - /** - * Boolean indicating whether the entity should be forced to be new. - * - * @var bool - */ - protected $enforceIsNew; - - /** - * Indicates whether this is the current revision. - * - * @var bool - */ - public $isCurrentRevision = TRUE; - - /** - * Constructs a new entity object. - */ - public function __construct(array $values = array(), $entity_type) { - $this->entityType = $entity_type; - // Set initial values. - foreach ($values as $key => $value) { - $this->$key = $value; - } - } - - /** - * Implements EntityInterface::id(). - */ - public function id() { - return isset($this->id) ? $this->id : NULL; - } - - /** - * Implements EntityInterface::isNew(). - */ - public function isNew() { - return !empty($this->enforceIsNew) || !$this->id(); - } - - /** - * Implements EntityInterface::enforceIsNew(). - */ - public function enforceIsNew($value = TRUE) { - $this->enforceIsNew = $value; - } - - /** - * Implements EntityInterface::entityType(). - */ - public function entityType() { - return $this->entityType; - } - - /** - * Implements EntityInterface::bundle(). - */ - public function bundle() { - return $this->entityType; - } - - /** - * Implements EntityInterface::label(). - */ - public function label($langcode = NULL) { - $label = FALSE; - $entity_info = $this->entityInfo(); - if (isset($entity_info['label callback']) && function_exists($entity_info['label callback'])) { - $label = $entity_info['label callback']($this->entityType, $this, $langcode); - } - elseif (!empty($entity_info['entity keys']['label']) && isset($this->{$entity_info['entity keys']['label']})) { - $label = $this->{$entity_info['entity keys']['label']}; - } - return $label; - } - - /** - * Implements EntityInterface::uri(). - * - * @see entity_uri() - */ - public function uri() { - $bundle = $this->bundle(); - // A bundle-specific callback takes precedence over the generic one for the - // entity type. - $entity_info = $this->entityInfo(); - if (isset($entity_info['bundles'][$bundle]['uri callback'])) { - $uri_callback = $entity_info['bundles'][$bundle]['uri callback']; - } - elseif (isset($entity_info['uri callback'])) { - $uri_callback = $entity_info['uri callback']; - } - else { - return NULL; - } - - // Invoke the callback to get the URI. If there is no callback, return NULL. - if (isset($uri_callback) && function_exists($uri_callback)) { - $uri = $uri_callback($this); - // Pass the entity data to url() so that alter functions do not need to - // look up this entity again. - $uri['options']['entity_type'] = $this->entityType; - $uri['options']['entity'] = $this; - return $uri; - } - } - - /** - * Implements EntityInterface::language(). - */ - public function language() { - // @todo: Check for language.module instead, once Field API language - // handling depends upon it too. - return module_exists('locale') ? language_load($this->langcode) : FALSE; - } - - /** - * Implements EntityInterface::translations(). - */ - public function translations() { - $languages = array(); - $entity_info = $this->entityInfo(); - if ($entity_info['fieldable'] && ($default_language = $this->language())) { - // Go through translatable properties and determine all languages for - // which translated values are available. - foreach (field_info_instances($this->entityType, $this->bundle()) as $field_name => $instance) { - $field = field_info_field($field_name); - if (field_is_translatable($this->entityType, $field) && isset($this->$field_name)) { - foreach ($this->$field_name as $langcode => $value) { - $languages[$langcode] = TRUE; - } - } - } - // Remove the default language from the translations. - unset($languages[$default_language->langcode]); - $languages = array_intersect_key(language_list(), $languages); - } - return $languages; - } - - /** - * Implements EntityInterface::get(). - */ - public function get($property_name, $langcode = NULL) { - // Handle fields. - $entity_info = $this->entityInfo(); - if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) { - $field = field_info_field($property_name); - $langcode = $this->getFieldLangcode($field, $langcode); - return isset($this->{$property_name}[$langcode]) ? $this->{$property_name}[$langcode] : NULL; - } - else { - // Handle properties being not fields. - // @todo: Add support for translatable properties being not fields. - return isset($this->{$property_name}) ? $this->{$property_name} : NULL; - } - } - - /** - * Implements EntityInterface::set(). - */ - public function set($property_name, $value, $langcode = NULL) { - // Handle fields. - $entity_info = $this->entityInfo(); - if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) { - $field = field_info_field($property_name); - $langcode = $this->getFieldLangcode($field, $langcode); - $this->{$property_name}[$langcode] = $value; - } - else { - // Handle properties being not fields. - // @todo: Add support for translatable properties being not fields. - $this->{$property_name} = $value; - } - } - - /** - * Determines the language code to use for accessing a field value in a certain language. - */ - protected function getFieldLangcode($field, $langcode = NULL) { - // Only apply the given langcode if the entity is language-specific. - // Otherwise translatable fields are handled as non-translatable fields. - if (field_is_translatable($this->entityType, $field) && ($default_language = $this->language()) && !language_is_locked($this->langcode)) { - // For translatable fields the values in default language are stored using - // the language code of the default language. - return isset($langcode) ? $langcode : $default_language->langcode; - } - else { - // If there is a langcode defined for this field, just return it. Otherwise - // return LANGUAGE_NOT_SPECIFIED. - return (isset($this->langcode) ? $this->langcode : LANGUAGE_NOT_SPECIFIED); - } - } - - /** - * Implements EntityInterface::save(). - */ - public function save() { - return entity_get_controller($this->entityType)->save($this); - } - - /** - * Implements EntityInterface::delete(). - */ - public function delete() { - if (!$this->isNew()) { - entity_get_controller($this->entityType)->delete(array($this->id())); - } - } - - /** - * Implements EntityInterface::createDuplicate(). - */ - public function createDuplicate() { - $duplicate = clone $this; - $entity_info = $this->entityInfo(); - $this->{$entity_info['entity keys']['id']} = NULL; - - // Check if the entity type supports UUIDs and generate a new one if so. - if (!empty($entity_info['entity keys']['uuid'])) { - $uuid = new Uuid(); - $duplicate->{$entity_info['entity keys']['uuid']} = $uuid->generate(); - } - return $duplicate; - } - - /** - * Implements EntityInterface::entityInfo(). - */ - public function entityInfo() { - return entity_get_info($this->entityType); - } - - /** - * Implements Drupal\entity\EntityInterface::getRevisionId(). - */ - public function getRevisionId() { - return NULL; - } - - /** - * Implements Drupal\entity\EntityInterface::isCurrentRevision(). - */ - public function isCurrentRevision() { - return $this->isCurrentRevision; - } } diff --git a/core/modules/entity/lib/Drupal/entity/EntityInterface.php b/core/modules/entity/lib/Drupal/entity/EntityInterface.php index 7aedb48..1fc1d0a 100644 --- a/core/modules/entity/lib/Drupal/entity/EntityInterface.php +++ b/core/modules/entity/lib/Drupal/entity/EntityInterface.php @@ -10,198 +10,6 @@ namespace Drupal\entity; /** * Defines a common interface for all entity objects. */ -interface EntityInterface { +interface EntityInterface extends StorableInterface { - /** - * Constructs a new entity object. - * - * @param $values - * An array of values to set, keyed by property name. If the entity type - * has bundles, the bundle key has to be specified. - * @param $entity_type - * The type of the entity to create. - */ - public function __construct(array $values, $entity_type); - - /** - * Returns the entity identifier (the entity's machine name or numeric ID). - * - * @return - * The identifier of the entity, or NULL if the entity does not yet have - * an identifier. - */ - public function id(); - - /** - * Returns whether the entity is new. - * - * Usually an entity is new if no ID exists for it yet. However, entities may - * be enforced to be new with existing IDs too. - * - * @return - * TRUE if the entity is new, or FALSE if the entity has already been saved. - * - * @see Drupal\entity\EntityInterface::enforceIsNew() - */ - public function isNew(); - - /** - * Enforces an entity to be new. - * - * Allows migrations to create entities with pre-defined IDs by forcing the - * entity to be new before saving. - * - * @param bool $value - * (optional) Whether the entity should be forced to be new. Defaults to - * TRUE. - * - * @see Drupal\entity\EntityInterface::isNew() - */ - public function enforceIsNew($value = TRUE); - - /** - * Returns the type of the entity. - * - * @return - * The type of the entity. - */ - public function entityType(); - - /** - * Returns the bundle of the entity. - * - * @return - * The bundle of the entity. Defaults to the entity type if the entity type - * does not make use of different bundles. - */ - public function bundle(); - - /** - * Returns the label of the entity. - * - * @param $langcode - * (optional) The language code of the language that should be used for - * getting the label. If set to NULL, the entity's default language is - * used. - * - * @return - * The label of the entity, or NULL if there is no label defined. - */ - public function label($langcode = NULL); - - /** - * Returns the URI elements of the entity. - * - * @return - * An array containing the 'path' and 'options' keys used to build the URI - * of the entity, and matching the signature of url(). NULL if the entity - * has no URI of its own. - */ - public function uri(); - - /** - * Returns the default language of a language-specific entity. - * - * @return - * The language object of the entity's default language, or FALSE if the - * entity is not language-specific. - * - * @see Drupal\entity\EntityInterface::translations() - */ - public function language(); - - /** - * Returns the languages the entity is translated to. - * - * @return - * An array of language objects, keyed by language codes. - * - * @see Drupal\entity\EntityInterface::language() - */ - public function translations(); - - /** - * Returns the value of an entity property. - * - * @param $property_name - * The name of the property to return; e.g., 'title'. - * @param $langcode - * (optional) If the property is translatable, the language code of the - * language that should be used for getting the property. If set to NULL, - * the entity's default language is being used. - * - * @return - * The property value, or NULL if it is not defined. - * - * @see Drupal\entity\EntityInterface::language() - */ - public function get($property_name, $langcode = NULL); - - /** - * Sets the value of an entity property. - * - * @param $property_name - * The name of the property to set; e.g., 'title'. - * @param $value - * The value to set, or NULL to unset the property. - * @param $langcode - * (optional) If the property is translatable, the language code of the - * language that should be used for getting the property. If set to - * NULL, the entity's default language is being used. - * - * @see Drupal\entity\EntityInterface::language() - */ - public function set($property_name, $value, $langcode = NULL); - - /** - * Saves an entity permanently. - * - * @return - * Either SAVED_NEW or SAVED_UPDATED, depending on the operation performed. - * - * @throws Drupal\entity\EntityStorageException - * In case of failures an exception is thrown. - */ - public function save(); - - /** - * Deletes an entity permanently. - * - * @throws Drupal\entity\EntityStorageException - * In case of failures an exception is thrown. - */ - public function delete(); - - /** - * Creates a duplicate of the entity. - * - * @return Drupal\entity\EntityInterface - * A clone of the current entity with all identifiers unset, so saving - * it inserts a new entity into the storage system. - */ - public function createDuplicate(); - - /** - * Returns the info of the type of the entity. - * - * @see entity_get_info() - */ - public function entityInfo(); - - /** - * Returns the revision identifier of the entity. - * - * @return - * The revision identifier of the entity, or NULL if the entity does not - * have a revision identifier. - */ - public function getRevisionId(); - - /** - * Checks if this entity is the current revision. - * - * @return bool - * TRUE if the entity is the current revision, FALSE otherwise. - */ - public function isCurrentRevision(); } diff --git a/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php b/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php index 7f27e96..a215184 100644 --- a/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php +++ b/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php @@ -18,74 +18,6 @@ namespace Drupal\entity; * Drupal\entity\DatabaseStorageController instead of implementing this * interface directly. */ -interface EntityStorageControllerInterface { - - /** - * Constructs a new Drupal\entity\EntityStorageControllerInterface object. - * - * @param $entityType - * The entity type for which the instance is created. - */ - public function __construct($entityType); - - /** - * Resets the internal, static entity cache. - * - * @param $ids - * (optional) If specified, the cache is reset for the entities with the - * given ids only. - */ - public function resetCache(array $ids = NULL); - - /** - * Loads one or more entities. - * - * @param $ids - * An array of entity IDs, or FALSE to load all entities. - * @param $conditions - * An array of conditions in the form 'field' => $value. - * - * @return - * An array of entity objects indexed by their ids. - */ - public function load($ids = array(), $conditions = array()); - - /** - * Constructs a new entity object, without permanently saving it. - * - * @param $values - * An array of values to set, keyed by property name. If the entity type has - * bundles the bundle key has to be specified. - * - * @return Drupal\entity\EntityInterface - * A new entity object. - */ - public function create(array $values); - - /** - * Deletes permanently saved entities. - * - * @param $ids - * An array of entity IDs. - * - * @throws Drupal\entity\EntityStorageException - * In case of failures, an exception is thrown. - */ - public function delete($ids); - - /** - * Saves the entity permanently. - * - * @param Drupal\entity\EntityInterface $entity - * The entity to save. - * - * @return - * SAVED_NEW or SAVED_UPDATED is returned depending on the operation - * performed. - * - * @throws Drupal\entity\EntityStorageException - * In case of failures, an exception is thrown. - */ - public function save(EntityInterface $entity); +interface EntityStorageControllerInterface extends StorageControllerInterface { } diff --git a/core/modules/entity/lib/Drupal/entity/Entity.php b/core/modules/entity/lib/Drupal/entity/StorableBase.php similarity index 85% copy from core/modules/entity/lib/Drupal/entity/Entity.php copy to core/modules/entity/lib/Drupal/entity/StorableBase.php index 7e73024..25cc1d7 100644 --- a/core/modules/entity/lib/Drupal/entity/Entity.php +++ b/core/modules/entity/lib/Drupal/entity/StorableBase.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\entity\Entity. + * Definition of Drupal\entity\StorableBase. */ namespace Drupal\entity; @@ -12,12 +12,12 @@ use Drupal\Component\Uuid\Uuid; /** * Defines a base entity class. * - * Default implementation of EntityInterface. + * Default implementation of StorableInterface. * * This class can be used as-is by simple entity types. Entity types requiring * special handling can extend the class. */ -class Entity implements EntityInterface { +abstract class StorableBase implements StorableInterface { /** * The language code of the entity's default language. @@ -45,7 +45,7 @@ class Entity implements EntityInterface { * * @var bool */ - public $isCurrentRevision = TRUE; + protected $isCurrentRevision = TRUE; /** * Constructs a new entity object. @@ -59,42 +59,42 @@ class Entity implements EntityInterface { } /** - * Implements EntityInterface::id(). + * Implements StorableInterface::id(). */ public function id() { return isset($this->id) ? $this->id : NULL; } /** - * Implements EntityInterface::isNew(). + * Implements StorableInterface::isNew(). */ public function isNew() { return !empty($this->enforceIsNew) || !$this->id(); } /** - * Implements EntityInterface::enforceIsNew(). + * Implements StorableInterface::enforceIsNew(). */ public function enforceIsNew($value = TRUE) { $this->enforceIsNew = $value; } /** - * Implements EntityInterface::entityType(). + * Implements StorableInterface::entityType(). */ public function entityType() { return $this->entityType; } /** - * Implements EntityInterface::bundle(). + * Implements StorableInterface::bundle(). */ public function bundle() { return $this->entityType; } /** - * Implements EntityInterface::label(). + * Implements StorableInterface::label(). */ public function label($langcode = NULL) { $label = FALSE; @@ -109,7 +109,7 @@ class Entity implements EntityInterface { } /** - * Implements EntityInterface::uri(). + * Implements StorableInterface::uri(). * * @see entity_uri() */ @@ -140,7 +140,7 @@ class Entity implements EntityInterface { } /** - * Implements EntityInterface::language(). + * Implements StorableInterface::language(). */ public function language() { // @todo: Check for language.module instead, once Field API language @@ -149,7 +149,7 @@ class Entity implements EntityInterface { } /** - * Implements EntityInterface::translations(). + * Implements StorableInterface::translations(). */ public function translations() { $languages = array(); @@ -173,7 +173,7 @@ class Entity implements EntityInterface { } /** - * Implements EntityInterface::get(). + * Implements StorableInterface::get(). */ public function get($property_name, $langcode = NULL) { // Handle fields. @@ -191,7 +191,7 @@ class Entity implements EntityInterface { } /** - * Implements EntityInterface::set(). + * Implements StorableInterface::set(). */ public function set($property_name, $value, $langcode = NULL) { // Handle fields. @@ -227,14 +227,14 @@ class Entity implements EntityInterface { } /** - * Implements EntityInterface::save(). + * Implements StorableInterface::save(). */ public function save() { return entity_get_controller($this->entityType)->save($this); } /** - * Implements EntityInterface::delete(). + * Implements StorableInterface::delete(). */ public function delete() { if (!$this->isNew()) { @@ -243,7 +243,7 @@ class Entity implements EntityInterface { } /** - * Implements EntityInterface::createDuplicate(). + * Implements StorableInterface::createDuplicate(). */ public function createDuplicate() { $duplicate = clone $this; @@ -259,23 +259,27 @@ class Entity implements EntityInterface { } /** - * Implements EntityInterface::entityInfo(). + * Implements StorableInterface::entityInfo(). */ public function entityInfo() { return entity_get_info($this->entityType); } /** - * Implements Drupal\entity\EntityInterface::getRevisionId(). + * Implements Drupal\entity\StorableInterface::getRevisionId(). */ public function getRevisionId() { return NULL; } /** - * Implements Drupal\entity\EntityInterface::isCurrentRevision(). + * Implements Drupal\entity\StorableInterface::isCurrentRevision(). */ - public function isCurrentRevision() { - return $this->isCurrentRevision; + public function isCurrentRevision($new_value = NULL) { + $return = $this->isCurrentRevision; + if (isset($new_value)) { + $this->isCurrentRevision = (bool) $new_value; + } + return $return; } } diff --git a/core/modules/entity/lib/Drupal/entity/EntityInterface.php b/core/modules/entity/lib/Drupal/entity/StorableInterface.php similarity index 88% copy from core/modules/entity/lib/Drupal/entity/EntityInterface.php copy to core/modules/entity/lib/Drupal/entity/StorableInterface.php index 7aedb48..d101998 100644 --- a/core/modules/entity/lib/Drupal/entity/EntityInterface.php +++ b/core/modules/entity/lib/Drupal/entity/StorableInterface.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\entity\EntityInterface. + * Definition of Drupal\entity\StorableInterface. */ namespace Drupal\entity; @@ -10,7 +10,7 @@ namespace Drupal\entity; /** * Defines a common interface for all entity objects. */ -interface EntityInterface { +interface StorableInterface { /** * Constructs a new entity object. @@ -41,7 +41,7 @@ interface EntityInterface { * @return * TRUE if the entity is new, or FALSE if the entity has already been saved. * - * @see Drupal\entity\EntityInterface::enforceIsNew() + * @see Drupal\entity\StorableInterface::enforceIsNew() */ public function isNew(); @@ -55,7 +55,7 @@ interface EntityInterface { * (optional) Whether the entity should be forced to be new. Defaults to * TRUE. * - * @see Drupal\entity\EntityInterface::isNew() + * @see Drupal\entity\StorableInterface::isNew() */ public function enforceIsNew($value = TRUE); @@ -106,7 +106,7 @@ interface EntityInterface { * The language object of the entity's default language, or FALSE if the * entity is not language-specific. * - * @see Drupal\entity\EntityInterface::translations() + * @see Drupal\entity\StorableInterface::translations() */ public function language(); @@ -116,7 +116,7 @@ interface EntityInterface { * @return * An array of language objects, keyed by language codes. * - * @see Drupal\entity\EntityInterface::language() + * @see Drupal\entity\StorableInterface::language() */ public function translations(); @@ -133,7 +133,7 @@ interface EntityInterface { * @return * The property value, or NULL if it is not defined. * - * @see Drupal\entity\EntityInterface::language() + * @see Drupal\entity\StorableInterface::language() */ public function get($property_name, $langcode = NULL); @@ -149,7 +149,7 @@ interface EntityInterface { * language that should be used for getting the property. If set to * NULL, the entity's default language is being used. * - * @see Drupal\entity\EntityInterface::language() + * @see Drupal\entity\StorableInterface::language() */ public function set($property_name, $value, $langcode = NULL); @@ -175,7 +175,7 @@ interface EntityInterface { /** * Creates a duplicate of the entity. * - * @return Drupal\entity\EntityInterface + * @return Drupal\entity\StorableInterface * A clone of the current entity with all identifiers unset, so saving * it inserts a new entity into the storage system. */ @@ -200,8 +200,13 @@ interface EntityInterface { /** * Checks if this entity is the current revision. * + * @param bool $new_value + * (optional) A Boolean to (re)set the isCurrentRevision flag. + * * @return bool - * TRUE if the entity is the current revision, FALSE otherwise. + * TRUE if the entity is the current revision, FALSE otherwise. If + * $new_value was passed, the previous value is returned. */ - public function isCurrentRevision(); + public function isCurrentRevision($new_value = NULL); + } diff --git a/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php b/core/modules/entity/lib/Drupal/entity/StorageControllerInterface.php similarity index 86% copy from core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php copy to core/modules/entity/lib/Drupal/entity/StorageControllerInterface.php index 7f27e96..93e643b 100644 --- a/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php +++ b/core/modules/entity/lib/Drupal/entity/StorageControllerInterface.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\entity\EntityStorageControllerInterface. + * Definition of Drupal\entity\StorageControllerInterface. */ namespace Drupal\entity; @@ -18,10 +18,10 @@ namespace Drupal\entity; * Drupal\entity\DatabaseStorageController instead of implementing this * interface directly. */ -interface EntityStorageControllerInterface { +interface StorageControllerInterface { /** - * Constructs a new Drupal\entity\EntityStorageControllerInterface object. + * Constructs a new Drupal\entity\StorageControllerInterface object. * * @param $entityType * The entity type for which the instance is created. @@ -57,7 +57,7 @@ interface EntityStorageControllerInterface { * An array of values to set, keyed by property name. If the entity type has * bundles the bundle key has to be specified. * - * @return Drupal\entity\EntityInterface + * @return Drupal\entity\StorableInterface * A new entity object. */ public function create(array $values); @@ -76,7 +76,7 @@ interface EntityStorageControllerInterface { /** * Saves the entity permanently. * - * @param Drupal\entity\EntityInterface $entity + * @param Drupal\entity\StorableInterface $entity * The entity to save. * * @return @@ -86,6 +86,6 @@ interface EntityStorageControllerInterface { * @throws Drupal\entity\EntityStorageException * In case of failures, an exception is thrown. */ - public function save(EntityInterface $entity); + public function save(StorableInterface $entity); } diff --git a/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php index 147f030..84dfa65 100644 --- a/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php +++ b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php @@ -9,7 +9,7 @@ namespace Drupal\entity_test; use PDO; -use Drupal\entity\EntityInterface; +use Drupal\entity\StorableInterface; use Drupal\entity\DatabaseStorageController; /** @@ -90,7 +90,7 @@ class EntityTestStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::postSave(). */ - protected function postSave(EntityInterface $entity, $update) { + protected function postSave(StorableInterface $entity, $update) { $default_langcode = ($language = $entity->language()) ? $language->langcode : LANGUAGE_NOT_SPECIFIED; $langcodes = array_keys($entity->translations()); $langcodes[] = $default_langcode; diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php index 3bdab77..49129ca 100644 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php @@ -8,7 +8,7 @@ namespace Drupal\field_test; use Drupal\entity\DatabaseStorageController; -use Drupal\entity\EntityInterface; +use Drupal\entity\StorableInterface; /** * Controller class for the test entity entity types. @@ -18,7 +18,7 @@ class TestEntityController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::preSave(). */ - public function preSave(EntityInterface $entity) { + public function preSave(StorableInterface $entity) { // Prepare for a new revision. if (!$entity->isNew() && !empty($entity->revision)) { $entity->old_ftvid = $entity->ftvid; @@ -29,7 +29,7 @@ class TestEntityController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::postSave(). */ - public function postSave(EntityInterface $entity, $update) { + public function postSave(StorableInterface $entity, $update) { // Only the test_entity entity type has revisions. if ($entity->entityType() == 'test_entity') { $update_entity = TRUE; diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php index 0fa7b33..0ee63e0 100644 --- a/core/modules/node/lib/Drupal/node/NodeStorageController.php +++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php @@ -8,7 +8,7 @@ namespace Drupal\node; use Drupal\entity\DatabaseStorageController; -use Drupal\entity\EntityInterface; +use Drupal\entity\StorableInterface; use Drupal\entity\EntityStorageException; use Exception; @@ -82,7 +82,7 @@ class NodeStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::save(). */ - public function save(EntityInterface $entity) { + public function save(StorableInterface $entity) { $transaction = db_transaction(); try { // Load the stored entity, if any. @@ -129,10 +129,10 @@ class NodeStorageController extends DatabaseStorageController { /** * Saves a node revision. * - * @param Drupal\entity\EntityInterface $node + * @param Drupal\entity\StorableInterface $node * The node entity. */ - protected function saveRevision(EntityInterface $entity) { + protected function saveRevision(StorableInterface $entity) { $record = clone $entity; $record->uid = $entity->revision_uid; $record->timestamp = $entity->revision_timestamp; @@ -151,7 +151,7 @@ class NodeStorageController extends DatabaseStorageController { $entity->{$this->revisionKey} = $record->{$this->revisionKey}; // Mark this revision as the current one. - $entity->isCurrentRevision = TRUE; + $entity->isCurrentRevision(TRUE); } /** @@ -197,7 +197,7 @@ class NodeStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::invokeHook(). */ - protected function invokeHook($hook, EntityInterface $node) { + protected function invokeHook($hook, StorableInterface $node) { if ($hook == 'insert' || $hook == 'update') { node_invoke($node, $hook); } @@ -246,7 +246,7 @@ class NodeStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::preSave(). */ - protected function preSave(EntityInterface $node) { + protected function preSave(StorableInterface $node) { // Before saving the node, set changed and revision times. $node->changed = REQUEST_TIME; @@ -262,7 +262,7 @@ class NodeStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::postSave(). */ - function postSave(EntityInterface $node, $update) { + function postSave(StorableInterface $node, $update) { node_access_acquire_grants($node, $update); } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php index 442e203..23b6895 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php @@ -7,7 +7,7 @@ namespace Drupal\taxonomy; -use Drupal\entity\EntityInterface; +use Drupal\entity\StorableInterface; use Drupal\entity\DatabaseStorageController; /** @@ -112,7 +112,7 @@ class TermStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::postSave(). */ - protected function postSave(EntityInterface $entity, $update) { + protected function postSave(StorableInterface $entity, $update) { if (isset($entity->parent)) { db_delete('taxonomy_term_hierarchy') ->condition('tid', $entity->tid) diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php index a5ad5c3..59d4b5b 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php @@ -7,7 +7,7 @@ namespace Drupal\taxonomy; -use Drupal\entity\EntityInterface; +use Drupal\entity\StorableInterface; use Drupal\entity\DatabaseStorageController; /** @@ -29,7 +29,7 @@ class VocabularyStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::postSave(). */ - protected function postSave(EntityInterface $entity, $update) { + protected function postSave(StorableInterface $entity, $update) { if (!$update) { field_attach_create_bundle('taxonomy_term', $entity->machine_name); } diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php index bde430c..67dcd38 100644 --- a/core/modules/user/lib/Drupal/user/UserStorageController.php +++ b/core/modules/user/lib/Drupal/user/UserStorageController.php @@ -7,7 +7,7 @@ namespace Drupal\user; -use Drupal\entity\EntityInterface; +use Drupal\entity\StorableInterface; use Drupal\entity\EntityMalformedException; use Drupal\entity\DatabaseStorageController; @@ -75,7 +75,7 @@ class UserStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::save(). */ - public function save(EntityInterface $entity) { + public function save(StorableInterface $entity) { if (empty($entity->uid)) { $entity->uid = db_next_id(db_query('SELECT MAX(uid) FROM {users}')->fetchField()); $entity->enforceIsNew(); @@ -86,7 +86,7 @@ class UserStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::preSave(). */ - protected function preSave(EntityInterface $entity) { + protected function preSave(StorableInterface $entity) { // Update the user password if it has changed. if ($entity->isNew() || (!empty($entity->pass) && $entity->pass != $entity->original->pass)) { // Allow alternate password hashing schemes. @@ -160,7 +160,7 @@ class UserStorageController extends DatabaseStorageController { /** * Overrides Drupal\entity\DatabaseStorageController::postSave(). */ - protected function postSave(EntityInterface $entity, $update) { + protected function postSave(StorableInterface $entity, $update) { if ($update) { // If the password has been changed, delete all open sessions for the