diff --git entity.api.php entity.api.php
index 146ffb6..c5e08b6 100644
--- entity.api.php
+++ entity.api.php
@@ -32,7 +32,9 @@
* type, as described by the documentation of hook_entity_info().
* - module: The module providing the entity type. Optionally, but suggested.
* - exportable: (optional) Whether the entity is exportable. Defaults to FALSE.
- * If enabled, a name key should be specified. See 'entity keys' below.
+ * If enabled, a name key should be specified and db columns for the module
+ * and status key as defined by entity_exportable_schema_fields() have to
+ * exist in the entity's base table. Also see 'entity keys' below.
* - entity keys: An array of keys as defined by Drupal core. The following
* additional keys are used by the entity CRUD API:
* - name: (optional) The key of the entity property containing the unique,
@@ -45,10 +47,10 @@
* For exportable entities, it's strongly recommended to use a machine name
* here as those are more portable across systems.
* - module: (optional) A key for the module property used by the entity CRUD
- * API to provide the source module name for exportable entities, which are
+ * API to save the source module name for exportable entities, which are
* provided in code. Defaults to 'module'.
* - status: (optional) The name of the entity property used by the entity
- * CRUD API to provide the exportable entity status using defined bit flags.
+ * CRUD API to save the exportable entity status using defined bit flags.
* Defaults to 'status'.
* - export: (optional) An array of information used for exporting. For ctools
* exportables compatibility any export-keys supported by ctools may be added
@@ -333,41 +335,5 @@ function entity_hook_field_info() {
}
/**
- * Respond when exportable entities are enabled.
- *
- * This hook is invoked for entities as soon as new enabled entities are
- * available to the system - either as a new entity has been inserted into the
- * database or modules with entities in code have been enabled.
- *
- * @param $entities
- * The entities keyed by entity ID.
- * @param $entity_type
- * The type of entities being enabled (i.e. profile2_type, rules_config, ..).
- *
- * @see hook_entity_disabled()
- */
-function hook_entity_enabled($entities, $entity_type) {
- mymodule_initialize($entities, $entity_type);
-}
-
-/**
- * Respond when exportable entities are disabled.
- *
- * This hook is invoked for entities when exportable entities have been disabled
- * or disappeared - either as an customly created entity has been deleted from
- * the database or modules providing default configurations have been disabled.
- *
- * @param $entities
- * The entities keyed by entity ID.
- * @param $entity_type
- * The type of entities being disabled (i.e. profile2_type, rules_config, ..).
- *
- * @see hook_entity_enabled()
- */
-function hook_entity_disabled($entities, $entity_type) {
- mymodule_deactivate($entities, $entity_type);
-}
-
-/**
* @} End of "addtogroup hooks".
*/
diff --git entity.install entity.install
index 65cec65..d3859d8 100644
--- entity.install
+++ entity.install
@@ -7,6 +7,28 @@
*/
/**
+ * Defines schema fields required for exportable entities.
+ * To be utilized by modules defining exportable entities.
+ */
+function entity_exportable_schema_fields($module_col = 'module', $status_col = 'status') {
+ return array(
+ $status_col => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => ENTITY_CUSTOM,
+ 'size' => 'tiny',
+ 'description' => 'The exportable status of the entity.',
+ ),
+ $module_col => array(
+ 'description' => 'The name of the providing module if the entity has been defined in code.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ ),
+ );
+}
+
+/**
* The entity API modules have been merged into a single module.
*/
function entity_update_7000() {
diff --git entity.module entity.module
index 83da48f..49be41b 100644
--- entity.module
+++ entity.module
@@ -17,6 +17,16 @@ module_load_include('inc', 'entity', 'includes/entity.property');
/**
* A bit flag used to let us know if an entity is in the database.
*/
+
+
+/**
+ * A bit flag used to let us know if an entity has been customly defined.
+ */
+define('ENTITY_CUSTOM', 0x01);
+
+/**
+ * Deprecated, but still here for backward compatibility.
+ */
define('ENTITY_IN_DB', 0x01);
/**
@@ -27,7 +37,7 @@ define('ENTITY_IN_CODE', 0x02);
/**
* A bit flag used to mark entities as overridden, e.g. they were originally
* definded in code and are saved now in the database. Same as
- * (ENTITY_IN_DB | ENTITY_IN_CODE).
+ * (ENTITY_CUSTOM | ENTITY_IN_CODE).
*/
define('ENTITY_OVERRIDDEN', 0x03);
@@ -35,7 +45,7 @@ define('ENTITY_OVERRIDDEN', 0x03);
* A bit flag used to mark entities as fixed, thus not changeable for any
* user.
*/
-define('ENTITY_FIXED', 0x04);
+define('ENTITY_FIXED', 0x04 | 0x02);
@@ -343,7 +353,7 @@ function entity_crud_get_info() {
* @param $entity
* The entity to check the status on.
* @param $status
- * The constant status like ENTITY_IN_DB, ENTITY_IN_CODE, ENTITY_OVERRIDDEN
+ * The constant status like ENTITY_CUSTOM, ENTITY_IN_CODE, ENTITY_OVERRIDDEN
* or ENTITY_FIXED.
*
* @return
@@ -429,63 +439,161 @@ function entity_var_json_export($var, $prefix = '') {
}
/**
- * Implements hook_modules_enabled().
+ * Rebuild the default entities provided in code.
+ *
+ * Exportable entities provided in code get saved to the database once a module
+ * providing defaults in code is activated. This allows module and entity_load()
+ * to easily deal with exportable entities just by relying on the database.
*
- * Invokes hook_entity_enabled() for new default entities.
+ * The defaults get rebuilt if the cache is cleared or new modules providing
+ * defaults are enabled, such that the defaults in the database are up to date.
+ * A default entity gets updated with the latest defaults in code during rebuild
+ * as long as the default has not been overridden. Once a module providing
+ * defaults is disabled, its default entities get removed from the database
+ * unless they have been overridden. In that case the overridden entity is left
+ * in the database, but its status gets updated to 'custom'.
+ *
+ * @param $entity_types
+ * (optional) If specified, only the defaults of the given entity types are
+ * rebuilt.
*/
-function entity_modules_enabled($modules) {
- foreach (entity_crud_get_info() as $entity_type => $info) {
- if (!empty($info['exportable']) && $entities = _entity_modules_get_defaults($entity_type, $modules)) {
- module_invoke_all($entity_type . '_enabled', $entities);
- module_invoke_all('entity_enabled', $entities, $entity_type);
+function entity_defaults_rebuild($entity_types = NULL) {
+ if (isset($entity_types)) {
+ $rebuild = variable_get('entity_defaults_built', array());
+ variable_set('entity_defaults_built', array_diff_key($rebuild, array_flip($entity_types)));
+ }
+ else {
+ variable_set('entity_defaults_built', array());
+ }
+}
+
+/**
+ * Actually rebuild the defaults, triggered by entity_load().
+ */
+function _entity_defaults_rebuild($entity_type) {
+ if (lock_acquire('entity_rebuild_' . $entity_type)) {
+ $built = variable_get('entity_defaults_built', array());
+ $built[$entity_type] = TRUE;
+ variable_set('entity_defaults_built', $built);
+
+ $info = entity_get_info($entity_type);
+ $hook = isset($info['export']['default hook']) ? $info['export']['default hook'] : 'default_' . $entity_type;
+ $keys = $info['entity keys'] + array('module' => 'module', 'status' => 'status', 'name' => $info['entity keys']['id']);
+
+ // Check for the existence of the module and status columns.
+ if (!in_array($keys['status'], $info['schema_fields_sql']['base table']) || !in_array($keys['module'], $info['schema_fields_sql']['base table'])) {
+ trigger_error("Missing database columns for the exportable entity $entity_type as defined by entity_exportable_schema_fields(). Update the according module and run update.php!", E_USER_WARNING);
+ return;
}
+
+ // Invoke the hook and collect default entities.
+ $entities = array();
+ foreach (module_implements($hook) as $module) {
+ foreach ((array) module_invoke($module, $hook) as $name => $entity) {
+ $entity->{$keys['name']} = $name;
+ $entity->{$keys['module']} = $module;
+ $entities[$name] = $entity;
+ }
+ }
+ drupal_alter($hook, $entities);
+
+ // Remove defaults which are already overridden.
+ $overridden_entities = entity_load($entity_type, array_keys($entities), array($keys['status'] => ENTITY_OVERRIDDEN));
+ $entities = array_diff_key($entities, $overridden_entities);
+
+ // Determine already existing, custom entities and mark them as overridden.
+ $existing_entities = entity_load($entity_type, array_keys($entities), array($keys['status'] => ENTITY_CUSTOM));
+ foreach ($existing_entities as $name => $entity) {
+ $entity->{$keys['status']} |= ENTITY_OVERRIDDEN;
+ $entity->{$keys['module']} = $entities[$name]->{$keys['module']};
+ entity_save($entity_type, $entity);
+ }
+
+ // Save the defaults being not overridden to the db, possible over-writing
+ // previous defaults already saved in the db.
+ foreach (array_diff_key($entities, $existing_entities) as $name => $entity) {
+ $entity->{$keys['status']} |= ENTITY_IN_CODE;
+ // For updating the numeric db identifier has to be set.
+ if ($entity->original = entity_load_unchanged($entity_type, $name)) {
+ $entity->{$keys['id']} = $entity->original->{$keys['id']};
+ unset($entity->is_new);
+ }
+ $entity->is_rebuild = TRUE;
+ entity_save($entity_type, $entity);
+ }
+
+ lock_release('entity_rebuild_' . $entity_type);
+ }
+}
+
+/**
+ * Implements hook_modules_enabled().
+ */
+function entity_modules_enabled($modules) {
+ if ($entity_types = _entity_modules_get_default_types($modules)) {
+ entity_defaults_rebuild($entity_types);
}
}
/**
* Implements hook_modules_disabled().
- *
- * Invokes hook_entity_disabled() for new default entities.
*/
function entity_modules_disabled($modules) {
- foreach (entity_crud_get_info() as $entity_type => $info) {
- if (!empty($info['exportable']) && $entities = _entity_modules_get_defaults($entity_type, $modules)) {
- module_invoke_all($entity_type . '_disabled', $entities);
- module_invoke_all('entity_disabled', $entities, $entity_type);
- // Make sure the cache is cleared afterwards, so removed default entities
- // are not left there.
- entity_get_controller($entity_type)->resetCache();
+ foreach (_entity_modules_get_default_types($modules) as $entity_type) {
+ $info = entity_get_info($entity_type);
+ $keys = $info['entity keys'] + array('module' => 'module', 'status' => 'status', 'name' => $info['entity keys']['id']);
+ // Remove entities provided in code.
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', $entity_type, '=')
+ ->propertyCondition($keys['module'], $modules, 'IN')
+ ->propertyCondition($keys['status'], array(ENTITY_IN_CODE, ENTITY_FIXED), 'IN');
+ $result = $query->execute();
+ if (isset($result[$entity_type])) {
+ $entities = entity_load($entity_type, array_keys($result[$entity_type]));
+ entity_delete_multiple($entity_type, array_keys($entities));
}
- }
-}
-function _entity_modules_get_defaults($entity_type, $modules) {
- $info = entity_get_info($entity_type);
- $entities = array();
- $hook = isset($info['export']['default hook']) ? $info['export']['default hook'] : 'default_' . $entity_type;
- $module_key = isset($info['entity keys']['module']) ? $info['entity keys']['module'] : 'module';
- // Collect the ids of all new entities.
- foreach ($modules as $module) {
- if (module_hook($module, $hook)) {
- $result = module_invoke($module, $hook);
- if ($result && is_array($result)) {
- $entities += $result;
+ // Update overridden entities to be now custom.
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', $entity_type, '=')
+ ->propertyCondition($keys['module'], $modules, 'IN')
+ ->propertyCondition($keys['status'], ENTITY_OVERRIDDEN, '=');
+ $result = $query->execute();
+ if (isset($result[$entity_type])) {
+ foreach (entity_load($entity_type, array_keys($result[$entity_type])) as $name => $entity) {
+ $entity->{$keys['status']} = ENTITY_CUSTOM;
+ $entity->{$keys['module']} = NULL;
+ entity_save($entity_type, $entity);
}
}
}
- if ($ids = array_keys($entities)) {
- // To make sure the new default entities are retrieved, make sure the cache
- // is cleared.
- entity_get_controller($entity_type)->resetCache();
- // Check whether some of the new entities are already overridden.
- $entities = array();
- foreach (entity_load($entity_type, $ids) as $id => $entity) {
- if (!entity_has_status($entity_type, $entity, ENTITY_OVERRIDDEN)) {
- $entities[$id] = $entity;
+
+}
+
+/**
+ * Gets all entity types for which defaults are provided by the $modules.
+ */
+function _entity_modules_get_default_types($modules) {
+ $types = array();
+ foreach (entity_crud_get_info() as $entity_type => $info) {
+ if (!empty($info['exportable'])) {
+ $hook = isset($info['export']['default hook']) ? $info['export']['default hook'] : 'default_' . $entity_type;
+ foreach ($modules as $module) {
+ if (module_hook($module, $hook) || module_hook($module, $hook . '_alter')) {
+ $types[] = $entity_type;
+ }
}
}
- return $entities;
}
+ return $types;
+}
+
+/**
+ * Implements hook_flush_caches().
+ */
+function entity_flush_caches() {
+ entity_property_info_cache_clear();
+ entity_defaults_rebuild();
}
/**
@@ -513,7 +621,7 @@ function entity_theme() {
function theme_entity_status($variables) {
$status = $variables['status'];
$html = $variables['html'];
- if ($status & ENTITY_FIXED) {
+ if (($status & ENTITY_FIXED) == ENTITY_FIXED) {
$label = t('Fixed');
$help = t('The configuration is fixed and cannot be changed.');
return $html ? "" . $label . "" : $label;
@@ -528,7 +636,7 @@ function theme_entity_status($variables) {
$help = t('A module provides this configuration.');
return $html ? "" . $label . "" : $label;
}
- elseif ($status & ENTITY_IN_DB) {
+ elseif ($status & ENTITY_CUSTOM) {
$label = t('Custom');
$help = t('A custom configuration by a user.');
return $html ? "" . $label . "" : $label;
diff --git entity.test entity.test
index 189da1e..794b607 100644
--- entity.test
+++ entity.test
@@ -109,7 +109,7 @@ class EntityAPITestCase extends DrupalWebTestCase {
$types = entity_load('entity_test_type', array('test', 'test2'));
$this->assertEqual($types['test']->label, 'label', 'Default type loaded.');
- $this->assertTrue($types['test']->status & ENTITY_IN_CODE && !($types['test']->status & ENTITY_IN_DB), 'Default type status is correct.');
+ $this->assertTrue($types['test']->status & ENTITY_IN_CODE && !($types['test']->status & ENTITY_CUSTOM), 'Default type status is correct.');
// Test using a condition, which has to be applied on the defaults.
$types = entity_load('entity_test_type', FALSE, array('label' => 'label'));
@@ -136,7 +136,18 @@ class EntityAPITestCase extends DrupalWebTestCase {
$types = entity_load('entity_test_type', array('test', 'test2'));
$this->assertEqual($types['test']->label, 'modified', 'Modified default type loaded by id.');
- $this->assertTrue($types['test']->status & ENTITY_IN_CODE && $types['test']->status & ENTITY_IN_DB, 'Status of modified type is correct.');
+ $this->assertTrue(entity_has_status('entity_test_type', $types['test'], ENTITY_OVERRIDDEN), 'Status of overridden type is correct.');
+
+ // Test rebuilding the defaults and make sure overridden entities stay.
+ entity_defaults_rebuild();
+ $types = entity_load('entity_test_type', array('test', 'test2'));
+ $this->assertEqual($types['test']->label, 'modified', 'Overridden entity is still overridden.');
+ $this->assertTrue(entity_has_status('entity_test_type', $types['test'], ENTITY_OVERRIDDEN), 'Status of overridden type is correct.');
+
+ // Test reverting.
+ $types['test']->delete();
+ $types = entity_load('entity_test_type', array('test', 'test2'));
+ $this->assertEqual($types['test']->label, 'label', 'Entity has been reverted.');
// Test loading an exportable by its numeric id.
$result = entity_load('entity_test_type', array($types['test']->id));
@@ -152,17 +163,17 @@ class EntityAPITestCase extends DrupalWebTestCase {
}
/**
- * Tests hook_entity_enabled() and hook_entity_disabled().
+ * Make sure insert() and update() hooks for exportables are invoked.
*/
- function testExportablesActivation() {
+ function testExportableHooks() {
$_SESSION['entity_hook_test'] = array();
// Enabling the module should invoke the enabled hook for the other
// entities provided in code.
module_enable(array('entity_feature'));
- $all = array('main', 'test', 'test2');
- $this->assertTrue($_SESSION['entity_hook_test']['entity_enabled'] == array($all), 'Hook entity_enabled has been invoked.');
- $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_enabled'] == array($all), 'Hook entity_test_type_enabled has been invoked.');
+ $insert = array('main', 'test', 'test2');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_insert'] == $insert, 'Hook entity_insert has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_insert'] == $insert, 'Hook entity_test_type_insert has been invoked.');
// Add a new test entity in DB and make sure the hook is invoked too.
$test3 = entity_create('entity_test_type', array(
@@ -171,35 +182,37 @@ class EntityAPITestCase extends DrupalWebTestCase {
'weight' => 0,
));
$test3->save();
- $this->assertTrue($_SESSION['entity_hook_test']['entity_enabled'] == array($all, array('test3')), 'Hook entity_enabled has been invoked.');
- $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_enabled'] == array($all, array('test3')), 'Hook entity_test_type_enabled has been invoked.');
- // Now override the 'test' entity and make sure it doesn't invoke the
- // enabling hook as it is already enabled. Then make sure it stays enabled
- // when the feature module is disabled.
+ $insert[] = 'test3';
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_insert'] == $insert, 'Hook entity_insert has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_insert'] == $insert, 'Hook entity_test_type_insert has been invoked.');
+
+ // Now override the 'test' entity and make sure it invokes the update hook.
$result = entity_load('entity_test_type', array('test'));
$result['test']->label = 'modified';
$result['test']->save();
- $this->assertTrue($_SESSION['entity_hook_test']['entity_enabled'] == array($all, array('test3')), 'Hook entity_enabled has been invoked.');
- $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_enabled'] == array($all, array('test3')), 'Hook entity_test_type_enabled has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_update'] == array('test'), 'Hook entity_update has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_update'] == array('test'), 'Hook entity_test_type_update has been invoked.');
// 'test' has to remain enabled, as it has been overridden.
- $all = array('main', 'test2');
+ $delete = array('main', 'test2');
module_disable(array('entity_feature'));
- $this->assertTrue($_SESSION['entity_hook_test']['entity_disabled'] == array($all), 'Hook entity_disabled has been invoked.');
- $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_disabled'] == array($all), 'Hook entity_test_type_disabled has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_delete'] == $delete, 'Hook entity_deleted has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_delete'] == $delete, 'Hook entity_test_type_deleted has been invoked.');
// Now make sure 'test' is not overridden any more, but custom.
$result = entity_load('entity_test_type', array('test'));
$this->assertTrue(!entity_has_status('entity_test_type', $result['test'], ENTITY_OVERRIDDEN), 'Entity is not marked as overridden any more.');
+ $this->assertTrue(entity_has_status('entity_test_type', $result['test'], ENTITY_CUSTOM), 'Entity is marked as custom.');
- // Test disabled hook for deleting the remaining entities from DB.
+ // Test deleting the remaining entities from DB.
entity_delete_multiple('entity_test_type', array('test', 'test3'));
- $all2 = array('test', 'test3');
- $this->assertTrue($_SESSION['entity_hook_test']['entity_disabled'] == array($all, $all2), 'Hook entity_disabled has been invoked.');
- $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_disabled'] == array($all, $all2), 'Hook entity_test_type_disabled has been invoked.');
+ $delete[] = 'test';
+ $delete[] = 'test3';
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_delete'] == $delete, 'Hook entity_deleted has been invoked.');
+ $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_delete'] == $delete, 'Hook entity_test_type_deleted has been invoked.');
}
/**
diff --git includes/entity.controller.inc includes/entity.controller.inc
index d7a1b85..fb68ff5 100644
--- includes/entity.controller.inc
+++ includes/entity.controller.inc
@@ -118,8 +118,8 @@ interface EntityAPIControllerInterface extends DrupalEntityControllerInterface {
*/
class EntityAPIController extends DrupalDefaultEntityController implements EntityAPIControllerInterface {
- protected $defaultEntities, $cacheComplete = FALSE;
- protected $nameKey, $statusKey, $bundleKey, $moduleKey;
+ protected $cacheComplete = FALSE;
+ protected $nameKey, $statusKey, $bundleKey;
/**
* Overridden.
@@ -133,9 +133,7 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
// Use the name key as primary identifier.
$this->nameKey = isset($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->idKey;
if (!empty($this->entityInfo['exportable'])) {
- $this->entityInfo['entity keys'] += array('module' => 'module', 'status' => 'status');
- $this->statusKey = $this->entityInfo['entity keys']['status'];
- $this->moduleKey = $this->entityInfo['entity keys']['module'];
+ $this->statusKey = isset($this->entityInfo['entity keys']['status']) ? $this->entityInfo['entity keys']['status'] : 'status';
}
// If this is the bundle of another entity, set the bundle key.
if (isset($this->entityInfo['bundle of'])) {
@@ -181,6 +179,11 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
public function load($ids = array(), $conditions = array()) {
$entities = array();
+ // For exportable entities, make sure the defaults have been built.
+ if (!empty($this->entityInfo['exportable']) && empty($GLOBALS['conf']['entity_defaults_built'][$this->entityType])) {
+ _entity_defaults_rebuild($this->entityType);
+ }
+
// Revisions are not statically cached, and require a different query to
// other conditions, so separate the revision id into its own variable.
if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
@@ -198,10 +201,6 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
// database when all requested entities are loaded from cache.
$passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
- // Add default entities defined in code to the cache.
- if (!empty($this->entityInfo['exportable']) && !isset($this->defaultEntities)) {
- $this->getDefaults();
- }
// Try to load entities from the static cache.
if (!$revision_id) {
$entities = $this->cacheGet($ids, $conditions);
@@ -213,9 +212,8 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
// Load any remaining entities from the database. This is the case if $ids
// is set to FALSE (so we load all entities), if there are any ids left to
- // load, if loading a revision, or if $conditions was passed without $ids.
- if (!$this->cacheComplete && ($ids === FALSE || $ids || $revision_id || ($conditions && !$passed_ids))) {
- $schema = drupal_get_schema($this->entityInfo['base table']);
+ // load or if loading a revision.
+ if (!$this->cacheComplete && ($ids === FALSE || $ids || $revision_id)) {
$queried_entities = array();
foreach ($this->query($ids, $conditions, $revision_id) as $record) {
// Skip entities already retrieved from cache.
@@ -224,6 +222,8 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
}
// Care for serialized columns.
+ $schema = drupal_get_schema($this->entityInfo['base table']);
+
foreach ($schema['fields'] as $field => $info) {
if (!empty($info['serialize']) && isset($record->$field)) {
$record->$field = unserialize($record->$field);
@@ -236,15 +236,7 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
}
}
}
- if (isset($this->statusKey)) {
- // Care for setting the status key properly.
- $record->{$this->statusKey} |= ENTITY_IN_DB;
- $id = $record->{$this->nameKey};
- if (isset($this->defaultEntities[$id])) {
- $record->{$this->statusKey} |= ENTITY_IN_CODE;
- $record->{$this->moduleKey} = $this->defaultEntities[$id]->{$this->moduleKey};
- }
- }
+
$queried_entities[$record->{$this->nameKey}] = $record;
}
}
@@ -279,30 +271,6 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
return $entities;
}
- /**
- * For exportables, incorporate defaults into the cache.
- */
- protected function getDefaults() {
- $this->defaultEntities = array();
- $hook = isset($this->entityInfo['export']['default hook']) ? $this->entityInfo['export']['default hook'] : 'default_' . $this->entityType;
- // Invoke the hook and collect default entities.
- foreach (module_implements($hook) as $module) {
- foreach ((array) module_invoke($module, $hook) as $id => $entity) {
- $entity->{$this->statusKey} |= ENTITY_IN_CODE;
- $entity->{$this->nameKey} = $id;
- $entity->{$this->moduleKey} = $module;
- $this->defaultEntities[$id] = $entity;
- }
- }
- drupal_alter($hook, $this->defaultEntities);
- // Remove any default entities that have been overridden.
- foreach ($this->query(array_keys($this->defaultEntities), array()) as $record) {
- $overridden[$record->{$this->nameKey}] = TRUE;
- }
- // Handle fetching defaults via the cache.
- $this->cacheSet(isset($overridden) ? array_diff_key($this->defaultEntities, $overridden) : $this->defaultEntities);
- }
-
protected function applyConditions($entities, $conditions = array()) {
if ($conditions) {
foreach ($entities as $key => $entity) {
@@ -373,16 +341,8 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
}
public function resetCache(array $ids = NULL) {
- unset($this->defaultEntities);
$this->cacheComplete = FALSE;
- if (isset($ids)) {
- foreach ($ids as $id) {
- unset($this->entityCache[$id]);
- }
- }
- else {
- $this->entityCache = array();
- }
+ parent::resetCache($ids);
}
/**
@@ -392,6 +352,7 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
$function($this->entityType, $entity);
}
+
if (isset($this->entityInfo['bundle of']) && $type = $this->entityInfo['bundle of']) {
// Call field API bundle attachers for the entity we are a bundle of.
if ($hook == 'insert') {
@@ -441,14 +402,10 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
foreach ($entities as $id => $entity) {
$this->invoke('delete', $entity);
- if (!empty($this->entityInfo['exportable']) && !($entity->{$this->statusKey} & ENTITY_IN_CODE)) {
- $disabled_entities[$id] = $entity;
+ if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) {
+ $rebuild = TRUE;
}
}
- if (!empty($disabled_entities)) {
- module_invoke_all($this->entityType . '_disabled', $disabled_entities);
- module_invoke_all('entity_disabled', $disabled_entities, $this->entityType);
- }
// Ignore slave server temporarily.
db_ignore_slave();
}
@@ -457,6 +414,13 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
watchdog_exception($this->entityType, $e);
throw $e;
}
+ // We have to postpone rebuilding defaults until the transaction has been
+ // committed. This is as variable_set() triggers a merge query and dies with
+ // a savepoint exception if it is inside a transaction.
+ if (!empty($rebuild)) {
+ $transaction = NULL;
+ entity_defaults_rebuild(array($this->entityType));
+ }
}
/**
@@ -473,6 +437,10 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
if (!empty($entity->{$this->nameKey}) && !isset($entity->original)) {
$entity->original = entity_load_unchanged($this->entityType, $entity->{$this->nameKey});
}
+ // Update the status for entities getting overridden.
+ if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && empty($entity->is_rebuild)) {
+ $entity->{$this->statusKey} |= ENTITY_CUSTOM;
+ }
$this->invoke('presave', $entity);
@@ -484,16 +452,6 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
else {
$return = drupal_write_record($this->entityInfo['base table'], $entity);
$this->invoke('insert', $entity);
-
- if (!empty($this->entityInfo['exportable'])) {
- $entity->{$this->statusKey} |= ENTITY_IN_DB;
- $this->resetCache(array($entity->{$this->nameKey}));
- if (!entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) {
- $entities = array($entity->{$this->nameKey} => $entity);
- module_invoke_all($this->entityType . '_enabled', $entities);
- module_invoke_all('entity_enabled', $entities, $this->entityType);
- }
- }
}
// Ignore slave server temporarily.
db_ignore_slave();
diff --git includes/entity.inc includes/entity.inc
index a06e6e1..e3f05a3 100644
--- includes/entity.inc
+++ includes/entity.inc
@@ -49,11 +49,9 @@ class Entity {
* Returns the internal, numeric identifier.
*
* For exportable entities, this differs to the uniform identifier returned
- * by Entity::identifier(). The internal identifier is supposed to be used
- * internally in order to refer to a specify, stored (ENTITY_IN_DB)
- * entity. For referring to entities which might not live in the DB, use the
- * uniform identifier.
- * If unsure, use Entity:identifier().
+ * by Entity::identifier(). The internal identifier is supposed to be only
+ * used internally to refer to an entity, i.e. in the database. If unsure, use
+ * Entity:identifier().
*/
public function internalIdentifier() {
return isset($this->{$this->idKey}) ? $this->{$this->idKey} : NULL;
diff --git includes/entity.ui.inc includes/entity.ui.inc
index 26c8d93..250a8ec 100644
--- includes/entity.ui.inc
+++ includes/entity.ui.inc
@@ -396,8 +396,7 @@ class EntityDefaultUIController {
// First check if there is any existing entity with the same ID.
$id = entity_id($this->entityType, $entity);
$entities = entity_load($this->entityType, array($id));
- $existing_entity = reset($entities);
- if ($existing_entity && entity_has_status($this->entityType, $existing_entity, ENTITY_IN_DB)) {
+ if ($existing_entity = reset($entities)) {
// Copy DB id and remove the new indicator to overwrite the DB record.
$idkey = $this->entityInfo['entity keys']['id'];
$entity->{$idkey} = $existing_entity->{$idkey};
diff --git tests/entity_test.install tests/entity_test.install
index a879864..43dbd84 100644
--- tests/entity_test.install
+++ tests/entity_test.install
@@ -113,7 +113,7 @@ function entity_test_schema() {
'description' => 'A serialized array of additional data related to this entity_test type.',
'merge' => TRUE,
),
- ),
+ ) + entity_exportable_schema_fields(),
'primary key' => array('id'),
'unique keys' => array(
'name' => array('name'),
diff --git tests/entity_test.module tests/entity_test.module
index 2e3d896..6aed96a 100644
--- tests/entity_test.module
+++ tests/entity_test.module
@@ -147,54 +147,62 @@ class EntityClass extends Entity {
/**
- * Implements hook_entity_enabled().
+ * Implements hook_entity_insert().
*/
-function entity_test_entity_enabled($entities) {
- $_SESSION['entity_hook_test']['entity_enabled'][] = array_keys($entities);
+function entity_test_entity_insert($entity, $entity_type) {
+ $_SESSION['entity_hook_test']['entity_insert'][] = entity_id($entity_type, $entity);
}
/**
- * Implements hook_entity_disabled().
+ * Implements hook_entity_update().
*/
-function entity_test_entity_disabled($entities) {
- $_SESSION['entity_hook_test']['entity_disabled'][] = array_keys($entities);
+function entity_test_entity_update($entity, $entity_type) {
+ $_SESSION['entity_hook_test']['entity_update'][] = entity_id($entity_type, $entity);
}
/**
- * Implements hook_entity_test_type_enabled().
+ * Implements hook_entity_delete().
*/
-function entity_test_entity_test_type_enabled($entities) {
- $_SESSION['entity_hook_test']['entity_test_type_enabled'][] = array_keys($entities);
+function entity_test_entity_delete($entity, $entity_type) {
+ $_SESSION['entity_hook_test']['entity_delete'][] = entity_id($entity_type, $entity);
}
/**
- * Implements hook_entity_test_type_disabled().
+ * Implements hook_entity_test_type_insert().
*/
-function entity_test_entity_test_type_disabled($entities) {
- $_SESSION['entity_hook_test']['entity_test_type_disabled'][] = array_keys($entities);
+function entity_test_entity_test_type_insert($entity) {
+ $_SESSION['entity_hook_test']['entity_test_type_insert'][] = $entity->identifier();
}
-
/**
- * Implements hook_entity_test_type_presave().
+ * Implements hook_entity_test_type_update().
*/
-function entity_test_entity_test_type_presave($entity) {
- // Determine changes.
+function entity_test_entity_test_type_update($entity) {
+ $_SESSION['entity_hook_test']['entity_test_type_update'][] = $entity->identifier();
+
+ // Determine changes on update.
if (!empty($entity->original) && $entity->original->label == 'test_changes') {
if ($entity->original->label != $entity->label) {
- $entity->label .= '_presave';
+ $entity->label .= '_update';
}
}
}
/**
- * Implements hook_entity_test_type_update().
+ * Implements hook_entity_test_type_delete().
*/
-function entity_test_entity_test_type_update($entity) {
- // Determine changes on update.
+function entity_test_entity_test_type_delete($entity) {
+ $_SESSION['entity_hook_test']['entity_test_type_delete'][] = $entity->identifier();
+}
+
+/**
+ * Implements hook_entity_test_type_presave().
+ */
+function entity_test_entity_test_type_presave($entity) {
+ // Determine changes.
if (!empty($entity->original) && $entity->original->label == 'test_changes') {
if ($entity->original->label != $entity->label) {
- $entity->label .= '_update';
+ $entity->label .= '_presave';
}
}
}