annotate sites/all/modules/entity/includes/entity.controller.inc @ 11:b0ee71395280

deleted .DS_Store files
author danieleb <danielebarchiesi@me.com>
date Mon, 28 Oct 2013 16:12:13 +0000
parents ce11bbd8f642
children
rev   line source
danielebarchiesi@4 1 <?php
danielebarchiesi@4 2
danielebarchiesi@4 3 /**
danielebarchiesi@4 4 * @file
danielebarchiesi@4 5 * Provides a controller building upon the core controller but providing more
danielebarchiesi@4 6 * features like full CRUD functionality.
danielebarchiesi@4 7 */
danielebarchiesi@4 8
danielebarchiesi@4 9 /**
danielebarchiesi@4 10 * Interface for EntityControllers compatible with the entity API.
danielebarchiesi@4 11 */
danielebarchiesi@4 12 interface EntityAPIControllerInterface extends DrupalEntityControllerInterface {
danielebarchiesi@4 13
danielebarchiesi@4 14 /**
danielebarchiesi@4 15 * Delete permanently saved entities.
danielebarchiesi@4 16 *
danielebarchiesi@4 17 * In case of failures, an exception is thrown.
danielebarchiesi@4 18 *
danielebarchiesi@4 19 * @param $ids
danielebarchiesi@4 20 * An array of entity IDs.
danielebarchiesi@4 21 */
danielebarchiesi@4 22 public function delete($ids);
danielebarchiesi@4 23
danielebarchiesi@4 24 /**
danielebarchiesi@4 25 * Invokes a hook on behalf of the entity. For hooks that have a respective
danielebarchiesi@4 26 * field API attacher like insert/update/.. the attacher is called too.
danielebarchiesi@4 27 */
danielebarchiesi@4 28 public function invoke($hook, $entity);
danielebarchiesi@4 29
danielebarchiesi@4 30 /**
danielebarchiesi@4 31 * Permanently saves the given entity.
danielebarchiesi@4 32 *
danielebarchiesi@4 33 * In case of failures, an exception is thrown.
danielebarchiesi@4 34 *
danielebarchiesi@4 35 * @param $entity
danielebarchiesi@4 36 * The entity to save.
danielebarchiesi@4 37 *
danielebarchiesi@4 38 * @return
danielebarchiesi@4 39 * SAVED_NEW or SAVED_UPDATED is returned depending on the operation
danielebarchiesi@4 40 * performed.
danielebarchiesi@4 41 */
danielebarchiesi@4 42 public function save($entity);
danielebarchiesi@4 43
danielebarchiesi@4 44 /**
danielebarchiesi@4 45 * Create a new entity.
danielebarchiesi@4 46 *
danielebarchiesi@4 47 * @param array $values
danielebarchiesi@4 48 * An array of values to set, keyed by property name.
danielebarchiesi@4 49 * @return
danielebarchiesi@4 50 * A new instance of the entity type.
danielebarchiesi@4 51 */
danielebarchiesi@4 52 public function create(array $values = array());
danielebarchiesi@4 53
danielebarchiesi@4 54 /**
danielebarchiesi@4 55 * Exports an entity as serialized string.
danielebarchiesi@4 56 *
danielebarchiesi@4 57 * @param $entity
danielebarchiesi@4 58 * The entity to export.
danielebarchiesi@4 59 * @param $prefix
danielebarchiesi@4 60 * An optional prefix for each line.
danielebarchiesi@4 61 *
danielebarchiesi@4 62 * @return
danielebarchiesi@4 63 * The exported entity as serialized string. The format is determined by
danielebarchiesi@4 64 * the controller and has to be compatible with the format that is accepted
danielebarchiesi@4 65 * by the import() method.
danielebarchiesi@4 66 */
danielebarchiesi@4 67 public function export($entity, $prefix = '');
danielebarchiesi@4 68
danielebarchiesi@4 69 /**
danielebarchiesi@4 70 * Imports an entity from a string.
danielebarchiesi@4 71 *
danielebarchiesi@4 72 * @param string $export
danielebarchiesi@4 73 * An exported entity as serialized string.
danielebarchiesi@4 74 *
danielebarchiesi@4 75 * @return
danielebarchiesi@4 76 * An entity object not yet saved.
danielebarchiesi@4 77 */
danielebarchiesi@4 78 public function import($export);
danielebarchiesi@4 79
danielebarchiesi@4 80 /**
danielebarchiesi@4 81 * Builds a structured array representing the entity's content.
danielebarchiesi@4 82 *
danielebarchiesi@4 83 * The content built for the entity will vary depending on the $view_mode
danielebarchiesi@4 84 * parameter.
danielebarchiesi@4 85 *
danielebarchiesi@4 86 * @param $entity
danielebarchiesi@4 87 * An entity object.
danielebarchiesi@4 88 * @param $view_mode
danielebarchiesi@4 89 * View mode, e.g. 'full', 'teaser'...
danielebarchiesi@4 90 * @param $langcode
danielebarchiesi@4 91 * (optional) A language code to use for rendering. Defaults to the global
danielebarchiesi@4 92 * content language of the current request.
danielebarchiesi@4 93 * @return
danielebarchiesi@4 94 * The renderable array.
danielebarchiesi@4 95 */
danielebarchiesi@4 96 public function buildContent($entity, $view_mode = 'full', $langcode = NULL);
danielebarchiesi@4 97
danielebarchiesi@4 98 /**
danielebarchiesi@4 99 * Generate an array for rendering the given entities.
danielebarchiesi@4 100 *
danielebarchiesi@4 101 * @param $entities
danielebarchiesi@4 102 * An array of entities to render.
danielebarchiesi@4 103 * @param $view_mode
danielebarchiesi@4 104 * View mode, e.g. 'full', 'teaser'...
danielebarchiesi@4 105 * @param $langcode
danielebarchiesi@4 106 * (optional) A language code to use for rendering. Defaults to the global
danielebarchiesi@4 107 * content language of the current request.
danielebarchiesi@4 108 * @param $page
danielebarchiesi@4 109 * (optional) If set will control if the entity is rendered: if TRUE
danielebarchiesi@4 110 * the entity will be rendered without its title, so that it can be embeded
danielebarchiesi@4 111 * in another context. If FALSE the entity will be displayed with its title
danielebarchiesi@4 112 * in a mode suitable for lists.
danielebarchiesi@4 113 * If unset, the page mode will be enabled if the current path is the URI
danielebarchiesi@4 114 * of the entity, as returned by entity_uri().
danielebarchiesi@4 115 * This parameter is only supported for entities which controller is a
danielebarchiesi@4 116 * EntityAPIControllerInterface.
danielebarchiesi@4 117 * @return
danielebarchiesi@4 118 * The renderable array, keyed by entity name or numeric id.
danielebarchiesi@4 119 */
danielebarchiesi@4 120 public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL);
danielebarchiesi@4 121 }
danielebarchiesi@4 122
danielebarchiesi@4 123 /**
danielebarchiesi@4 124 * Interface for EntityControllers of entities that support revisions.
danielebarchiesi@4 125 */
danielebarchiesi@4 126 interface EntityAPIControllerRevisionableInterface extends EntityAPIControllerInterface {
danielebarchiesi@4 127
danielebarchiesi@4 128 /**
danielebarchiesi@4 129 * Delete an entity revision.
danielebarchiesi@4 130 *
danielebarchiesi@4 131 * Note that the default revision of an entity cannot be deleted.
danielebarchiesi@4 132 *
danielebarchiesi@4 133 * @param $revision_id
danielebarchiesi@4 134 * The ID of the revision to delete.
danielebarchiesi@4 135 *
danielebarchiesi@4 136 * @return boolean
danielebarchiesi@4 137 * TRUE if the entity revision could be deleted, FALSE otherwise.
danielebarchiesi@4 138 */
danielebarchiesi@4 139 public function deleteRevision($revision_id);
danielebarchiesi@4 140
danielebarchiesi@4 141 }
danielebarchiesi@4 142
danielebarchiesi@4 143 /**
danielebarchiesi@4 144 * A controller implementing EntityAPIControllerInterface for the database.
danielebarchiesi@4 145 */
danielebarchiesi@4 146 class EntityAPIController extends DrupalDefaultEntityController implements EntityAPIControllerRevisionableInterface {
danielebarchiesi@4 147
danielebarchiesi@4 148 protected $cacheComplete = FALSE;
danielebarchiesi@4 149 protected $bundleKey;
danielebarchiesi@4 150 protected $defaultRevisionKey;
danielebarchiesi@4 151
danielebarchiesi@4 152 /**
danielebarchiesi@4 153 * Overridden.
danielebarchiesi@4 154 * @see DrupalDefaultEntityController#__construct()
danielebarchiesi@4 155 */
danielebarchiesi@4 156 public function __construct($entityType) {
danielebarchiesi@4 157 parent::__construct($entityType);
danielebarchiesi@4 158 // If this is the bundle of another entity, set the bundle key.
danielebarchiesi@4 159 if (isset($this->entityInfo['bundle of'])) {
danielebarchiesi@4 160 $info = entity_get_info($this->entityInfo['bundle of']);
danielebarchiesi@4 161 $this->bundleKey = $info['bundle keys']['bundle'];
danielebarchiesi@4 162 }
danielebarchiesi@4 163 $this->defaultRevisionKey = !empty($this->entityInfo['entity keys']['default revision']) ? $this->entityInfo['entity keys']['default revision'] : 'default_revision';
danielebarchiesi@4 164 }
danielebarchiesi@4 165
danielebarchiesi@4 166 /**
danielebarchiesi@4 167 * Overrides DrupalDefaultEntityController::buildQuery().
danielebarchiesi@4 168 */
danielebarchiesi@4 169 protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
danielebarchiesi@4 170 $query = parent::buildQuery($ids, $conditions, $revision_id);
danielebarchiesi@4 171 if ($this->revisionKey) {
danielebarchiesi@4 172 // Compare revision id of the base and revision table, if equal then this
danielebarchiesi@4 173 // is the default revision.
danielebarchiesi@4 174 $query->addExpression('base.' . $this->revisionKey . ' = revision.' . $this->revisionKey, $this->defaultRevisionKey);
danielebarchiesi@4 175 }
danielebarchiesi@4 176 return $query;
danielebarchiesi@4 177 }
danielebarchiesi@4 178
danielebarchiesi@4 179 /**
danielebarchiesi@4 180 * Builds and executes the query for loading.
danielebarchiesi@4 181 *
danielebarchiesi@4 182 * @return The results in a Traversable object.
danielebarchiesi@4 183 */
danielebarchiesi@4 184 public function query($ids, $conditions, $revision_id = FALSE) {
danielebarchiesi@4 185 // Build the query.
danielebarchiesi@4 186 $query = $this->buildQuery($ids, $conditions, $revision_id);
danielebarchiesi@4 187 $result = $query->execute();
danielebarchiesi@4 188 if (!empty($this->entityInfo['entity class'])) {
danielebarchiesi@4 189 $result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType));
danielebarchiesi@4 190 }
danielebarchiesi@4 191 return $result;
danielebarchiesi@4 192 }
danielebarchiesi@4 193
danielebarchiesi@4 194 /**
danielebarchiesi@4 195 * Overridden.
danielebarchiesi@4 196 * @see DrupalDefaultEntityController#load($ids, $conditions)
danielebarchiesi@4 197 *
danielebarchiesi@4 198 * In contrast to the parent implementation we factor out query execution, so
danielebarchiesi@4 199 * fetching can be further customized easily.
danielebarchiesi@4 200 */
danielebarchiesi@4 201 public function load($ids = array(), $conditions = array()) {
danielebarchiesi@4 202 $entities = array();
danielebarchiesi@4 203
danielebarchiesi@4 204 // Revisions are not statically cached, and require a different query to
danielebarchiesi@4 205 // other conditions, so separate the revision id into its own variable.
danielebarchiesi@4 206 if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
danielebarchiesi@4 207 $revision_id = $conditions[$this->revisionKey];
danielebarchiesi@4 208 unset($conditions[$this->revisionKey]);
danielebarchiesi@4 209 }
danielebarchiesi@4 210 else {
danielebarchiesi@4 211 $revision_id = FALSE;
danielebarchiesi@4 212 }
danielebarchiesi@4 213
danielebarchiesi@4 214 // Create a new variable which is either a prepared version of the $ids
danielebarchiesi@4 215 // array for later comparison with the entity cache, or FALSE if no $ids
danielebarchiesi@4 216 // were passed. The $ids array is reduced as items are loaded from cache,
danielebarchiesi@4 217 // and we need to know if it's empty for this reason to avoid querying the
danielebarchiesi@4 218 // database when all requested entities are loaded from cache.
danielebarchiesi@4 219 $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
danielebarchiesi@4 220
danielebarchiesi@4 221 // Try to load entities from the static cache.
danielebarchiesi@4 222 if ($this->cache && !$revision_id) {
danielebarchiesi@4 223 $entities = $this->cacheGet($ids, $conditions);
danielebarchiesi@4 224 // If any entities were loaded, remove them from the ids still to load.
danielebarchiesi@4 225 if ($passed_ids) {
danielebarchiesi@4 226 $ids = array_keys(array_diff_key($passed_ids, $entities));
danielebarchiesi@4 227 }
danielebarchiesi@4 228 }
danielebarchiesi@4 229
danielebarchiesi@4 230 // Support the entitycache module if activated.
danielebarchiesi@4 231 if (!empty($this->entityInfo['entity cache']) && !$revision_id && $ids && !$conditions) {
danielebarchiesi@4 232 $cached_entities = EntityCacheControllerHelper::entityCacheGet($this, $ids, $conditions);
danielebarchiesi@4 233 // If any entities were loaded, remove them from the ids still to load.
danielebarchiesi@4 234 $ids = array_diff($ids, array_keys($cached_entities));
danielebarchiesi@4 235 $entities += $cached_entities;
danielebarchiesi@4 236
danielebarchiesi@4 237 // Add loaded entities to the static cache if we are not loading a
danielebarchiesi@4 238 // revision.
danielebarchiesi@4 239 if ($this->cache && !empty($cached_entities) && !$revision_id) {
danielebarchiesi@4 240 $this->cacheSet($cached_entities);
danielebarchiesi@4 241 }
danielebarchiesi@4 242 }
danielebarchiesi@4 243
danielebarchiesi@4 244 // Load any remaining entities from the database. This is the case if $ids
danielebarchiesi@4 245 // is set to FALSE (so we load all entities), if there are any ids left to
danielebarchiesi@4 246 // load or if loading a revision.
danielebarchiesi@4 247 if (!($this->cacheComplete && $ids === FALSE && !$conditions) && ($ids === FALSE || $ids || $revision_id)) {
danielebarchiesi@4 248 $queried_entities = array();
danielebarchiesi@4 249 foreach ($this->query($ids, $conditions, $revision_id) as $record) {
danielebarchiesi@4 250 // Skip entities already retrieved from cache.
danielebarchiesi@4 251 if (isset($entities[$record->{$this->idKey}])) {
danielebarchiesi@4 252 continue;
danielebarchiesi@4 253 }
danielebarchiesi@4 254
danielebarchiesi@4 255 // For DB-based entities take care of serialized columns.
danielebarchiesi@4 256 if (!empty($this->entityInfo['base table'])) {
danielebarchiesi@4 257 $schema = drupal_get_schema($this->entityInfo['base table']);
danielebarchiesi@4 258
danielebarchiesi@4 259 foreach ($schema['fields'] as $field => $info) {
danielebarchiesi@4 260 if (!empty($info['serialize']) && isset($record->$field)) {
danielebarchiesi@4 261 $record->$field = unserialize($record->$field);
danielebarchiesi@4 262 // Support automatic merging of 'data' fields into the entity.
danielebarchiesi@4 263 if (!empty($info['merge']) && is_array($record->$field)) {
danielebarchiesi@4 264 foreach ($record->$field as $key => $value) {
danielebarchiesi@4 265 $record->$key = $value;
danielebarchiesi@4 266 }
danielebarchiesi@4 267 unset($record->$field);
danielebarchiesi@4 268 }
danielebarchiesi@4 269 }
danielebarchiesi@4 270 }
danielebarchiesi@4 271 }
danielebarchiesi@4 272
danielebarchiesi@4 273 $queried_entities[$record->{$this->idKey}] = $record;
danielebarchiesi@4 274 }
danielebarchiesi@4 275 }
danielebarchiesi@4 276
danielebarchiesi@4 277 // Pass all entities loaded from the database through $this->attachLoad(),
danielebarchiesi@4 278 // which attaches fields (if supported by the entity type) and calls the
danielebarchiesi@4 279 // entity type specific load callback, for example hook_node_load().
danielebarchiesi@4 280 if (!empty($queried_entities)) {
danielebarchiesi@4 281 $this->attachLoad($queried_entities, $revision_id);
danielebarchiesi@4 282 $entities += $queried_entities;
danielebarchiesi@4 283 }
danielebarchiesi@4 284
danielebarchiesi@4 285 // Entitycache module support: Add entities to the entity cache if we are
danielebarchiesi@4 286 // not loading a revision.
danielebarchiesi@4 287 if (!empty($this->entityInfo['entity cache']) && !empty($queried_entities) && !$revision_id) {
danielebarchiesi@4 288 EntityCacheControllerHelper::entityCacheSet($this, $queried_entities);
danielebarchiesi@4 289 }
danielebarchiesi@4 290
danielebarchiesi@4 291 if ($this->cache) {
danielebarchiesi@4 292 // Add entities to the cache if we are not loading a revision.
danielebarchiesi@4 293 if (!empty($queried_entities) && !$revision_id) {
danielebarchiesi@4 294 $this->cacheSet($queried_entities);
danielebarchiesi@4 295
danielebarchiesi@4 296 // Remember if we have cached all entities now.
danielebarchiesi@4 297 if (!$conditions && $ids === FALSE) {
danielebarchiesi@4 298 $this->cacheComplete = TRUE;
danielebarchiesi@4 299 }
danielebarchiesi@4 300 }
danielebarchiesi@4 301 }
danielebarchiesi@4 302 // Ensure that the returned array is ordered the same as the original
danielebarchiesi@4 303 // $ids array if this was passed in and remove any invalid ids.
danielebarchiesi@4 304 if ($passed_ids && $passed_ids = array_intersect_key($passed_ids, $entities)) {
danielebarchiesi@4 305 foreach ($passed_ids as $id => $value) {
danielebarchiesi@4 306 $passed_ids[$id] = $entities[$id];
danielebarchiesi@4 307 }
danielebarchiesi@4 308 $entities = $passed_ids;
danielebarchiesi@4 309 }
danielebarchiesi@4 310 return $entities;
danielebarchiesi@4 311 }
danielebarchiesi@4 312
danielebarchiesi@4 313 /**
danielebarchiesi@4 314 * Overrides DrupalDefaultEntityController::resetCache().
danielebarchiesi@4 315 */
danielebarchiesi@4 316 public function resetCache(array $ids = NULL) {
danielebarchiesi@4 317 $this->cacheComplete = FALSE;
danielebarchiesi@4 318 parent::resetCache($ids);
danielebarchiesi@4 319 // Support the entitycache module.
danielebarchiesi@4 320 if (!empty($this->entityInfo['entity cache'])) {
danielebarchiesi@4 321 EntityCacheControllerHelper::resetEntityCache($this, $ids);
danielebarchiesi@4 322 }
danielebarchiesi@4 323 }
danielebarchiesi@4 324
danielebarchiesi@4 325 /**
danielebarchiesi@4 326 * Implements EntityAPIControllerInterface.
danielebarchiesi@4 327 */
danielebarchiesi@4 328 public function invoke($hook, $entity) {
danielebarchiesi@4 329 // entity_revision_delete() invokes hook_entity_revision_delete() and
danielebarchiesi@4 330 // hook_field_attach_delete_revision() just as node module does. So we need
danielebarchiesi@4 331 // to adjust the name of our revision deletion field attach hook in order to
danielebarchiesi@4 332 // stick to this pattern.
danielebarchiesi@4 333 $field_attach_hook = ($hook == 'revision_delete' ? 'delete_revision' : $hook);
danielebarchiesi@4 334 if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $field_attach_hook)) {
danielebarchiesi@4 335 $function($this->entityType, $entity);
danielebarchiesi@4 336 }
danielebarchiesi@4 337
danielebarchiesi@4 338 if (!empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of'])) {
danielebarchiesi@4 339 $type = $this->entityInfo['bundle of'];
danielebarchiesi@4 340 // Call field API bundle attachers for the entity we are a bundle of.
danielebarchiesi@4 341 if ($hook == 'insert') {
danielebarchiesi@4 342 field_attach_create_bundle($type, $entity->{$this->bundleKey});
danielebarchiesi@4 343 }
danielebarchiesi@4 344 elseif ($hook == 'delete') {
danielebarchiesi@4 345 field_attach_delete_bundle($type, $entity->{$this->bundleKey});
danielebarchiesi@4 346 }
danielebarchiesi@4 347 elseif ($hook == 'update' && $entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) {
danielebarchiesi@4 348 field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey});
danielebarchiesi@4 349 }
danielebarchiesi@4 350 }
danielebarchiesi@4 351 // Invoke the hook.
danielebarchiesi@4 352 module_invoke_all($this->entityType . '_' . $hook, $entity);
danielebarchiesi@4 353 // Invoke the respective entity level hook.
danielebarchiesi@4 354 if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
danielebarchiesi@4 355 module_invoke_all('entity_' . $hook, $entity, $this->entityType);
danielebarchiesi@4 356 }
danielebarchiesi@4 357 // Invoke rules.
danielebarchiesi@4 358 if (module_exists('rules')) {
danielebarchiesi@4 359 rules_invoke_event($this->entityType . '_' . $hook, $entity);
danielebarchiesi@4 360 }
danielebarchiesi@4 361 }
danielebarchiesi@4 362
danielebarchiesi@4 363 /**
danielebarchiesi@4 364 * Implements EntityAPIControllerInterface.
danielebarchiesi@4 365 *
danielebarchiesi@4 366 * @param $transaction
danielebarchiesi@4 367 * Optionally a DatabaseTransaction object to use. Allows overrides to pass
danielebarchiesi@4 368 * in their transaction object.
danielebarchiesi@4 369 */
danielebarchiesi@4 370 public function delete($ids, DatabaseTransaction $transaction = NULL) {
danielebarchiesi@4 371 $entities = $ids ? $this->load($ids) : FALSE;
danielebarchiesi@4 372 if (!$entities) {
danielebarchiesi@4 373 // Do nothing, in case invalid or no ids have been passed.
danielebarchiesi@4 374 return;
danielebarchiesi@4 375 }
danielebarchiesi@4 376 // This transaction causes troubles on MySQL, see
danielebarchiesi@4 377 // http://drupal.org/node/1007830. So we deactivate this by default until
danielebarchiesi@4 378 // is shipped in a point release.
danielebarchiesi@4 379 // $transaction = isset($transaction) ? $transaction : db_transaction();
danielebarchiesi@4 380
danielebarchiesi@4 381 try {
danielebarchiesi@4 382 $ids = array_keys($entities);
danielebarchiesi@4 383
danielebarchiesi@4 384 db_delete($this->entityInfo['base table'])
danielebarchiesi@4 385 ->condition($this->idKey, $ids, 'IN')
danielebarchiesi@4 386 ->execute();
danielebarchiesi@4 387
danielebarchiesi@4 388 if (isset($this->revisionTable)) {
danielebarchiesi@4 389 db_delete($this->revisionTable)
danielebarchiesi@4 390 ->condition($this->idKey, $ids, 'IN')
danielebarchiesi@4 391 ->execute();
danielebarchiesi@4 392 }
danielebarchiesi@4 393 // Reset the cache as soon as the changes have been applied.
danielebarchiesi@4 394 $this->resetCache($ids);
danielebarchiesi@4 395
danielebarchiesi@4 396 foreach ($entities as $id => $entity) {
danielebarchiesi@4 397 $this->invoke('delete', $entity);
danielebarchiesi@4 398 }
danielebarchiesi@4 399 // Ignore slave server temporarily.
danielebarchiesi@4 400 db_ignore_slave();
danielebarchiesi@4 401 }
danielebarchiesi@4 402 catch (Exception $e) {
danielebarchiesi@4 403 if (isset($transaction)) {
danielebarchiesi@4 404 $transaction->rollback();
danielebarchiesi@4 405 }
danielebarchiesi@4 406 watchdog_exception($this->entityType, $e);
danielebarchiesi@4 407 throw $e;
danielebarchiesi@4 408 }
danielebarchiesi@4 409 }
danielebarchiesi@4 410
danielebarchiesi@4 411 /**
danielebarchiesi@4 412 * Implements EntityAPIControllerRevisionableInterface::deleteRevision().
danielebarchiesi@4 413 */
danielebarchiesi@4 414 public function deleteRevision($revision_id) {
danielebarchiesi@4 415 if ($entity_revision = entity_revision_load($this->entityType, $revision_id)) {
danielebarchiesi@4 416 // Prevent deleting the default revision.
danielebarchiesi@4 417 if (entity_revision_is_default($this->entityType, $entity_revision)) {
danielebarchiesi@4 418 return FALSE;
danielebarchiesi@4 419 }
danielebarchiesi@4 420
danielebarchiesi@4 421 db_delete($this->revisionTable)
danielebarchiesi@4 422 ->condition($this->revisionKey, $revision_id)
danielebarchiesi@4 423 ->execute();
danielebarchiesi@4 424
danielebarchiesi@4 425 $this->invoke('revision_delete', $entity_revision);
danielebarchiesi@4 426 return TRUE;
danielebarchiesi@4 427 }
danielebarchiesi@4 428 return FALSE;
danielebarchiesi@4 429 }
danielebarchiesi@4 430
danielebarchiesi@4 431 /**
danielebarchiesi@4 432 * Implements EntityAPIControllerInterface.
danielebarchiesi@4 433 *
danielebarchiesi@4 434 * @param $transaction
danielebarchiesi@4 435 * Optionally a DatabaseTransaction object to use. Allows overrides to pass
danielebarchiesi@4 436 * in their transaction object.
danielebarchiesi@4 437 */
danielebarchiesi@4 438 public function save($entity, DatabaseTransaction $transaction = NULL) {
danielebarchiesi@4 439 $transaction = isset($transaction) ? $transaction : db_transaction();
danielebarchiesi@4 440 try {
danielebarchiesi@4 441 // Load the stored entity, if any.
danielebarchiesi@4 442 if (!empty($entity->{$this->idKey}) && !isset($entity->original)) {
danielebarchiesi@4 443 // In order to properly work in case of name changes, load the original
danielebarchiesi@4 444 // entity using the id key if it is available.
danielebarchiesi@4 445 $entity->original = entity_load_unchanged($this->entityType, $entity->{$this->idKey});
danielebarchiesi@4 446 }
danielebarchiesi@4 447 $entity->is_new = !empty($entity->is_new) || empty($entity->{$this->idKey});
danielebarchiesi@4 448 $this->invoke('presave', $entity);
danielebarchiesi@4 449
danielebarchiesi@4 450 if ($entity->is_new) {
danielebarchiesi@4 451 $return = drupal_write_record($this->entityInfo['base table'], $entity);
danielebarchiesi@4 452 if ($this->revisionKey) {
danielebarchiesi@4 453 $this->saveRevision($entity);
danielebarchiesi@4 454 }
danielebarchiesi@4 455 $this->invoke('insert', $entity);
danielebarchiesi@4 456 }
danielebarchiesi@4 457 else {
danielebarchiesi@4 458 // Update the base table if the entity doesn't have revisions or
danielebarchiesi@4 459 // we are updating the default revision.
danielebarchiesi@4 460 if (!$this->revisionKey || !empty($entity->{$this->defaultRevisionKey})) {
danielebarchiesi@4 461 $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
danielebarchiesi@4 462 }
danielebarchiesi@4 463 if ($this->revisionKey) {
danielebarchiesi@4 464 $return = $this->saveRevision($entity);
danielebarchiesi@4 465 }
danielebarchiesi@4 466 $this->resetCache(array($entity->{$this->idKey}));
danielebarchiesi@4 467 $this->invoke('update', $entity);
danielebarchiesi@4 468
danielebarchiesi@4 469 // Field API always saves as default revision, so if the revision saved
danielebarchiesi@4 470 // is not default we have to restore the field values of the default
danielebarchiesi@4 471 // revision now by invoking field_attach_update() once again.
danielebarchiesi@4 472 if ($this->revisionKey && !$entity->{$this->defaultRevisionKey} && !empty($this->entityInfo['fieldable'])) {
danielebarchiesi@4 473 field_attach_update($this->entityType, $entity->original);
danielebarchiesi@4 474 }
danielebarchiesi@4 475 }
danielebarchiesi@4 476
danielebarchiesi@4 477 // Ignore slave server temporarily.
danielebarchiesi@4 478 db_ignore_slave();
danielebarchiesi@4 479 unset($entity->is_new);
danielebarchiesi@4 480 unset($entity->is_new_revision);
danielebarchiesi@4 481 unset($entity->original);
danielebarchiesi@4 482
danielebarchiesi@4 483 return $return;
danielebarchiesi@4 484 }
danielebarchiesi@4 485 catch (Exception $e) {
danielebarchiesi@4 486 $transaction->rollback();
danielebarchiesi@4 487 watchdog_exception($this->entityType, $e);
danielebarchiesi@4 488 throw $e;
danielebarchiesi@4 489 }
danielebarchiesi@4 490 }
danielebarchiesi@4 491
danielebarchiesi@4 492 /**
danielebarchiesi@4 493 * Saves an entity revision.
danielebarchiesi@4 494 *
danielebarchiesi@4 495 * @param Entity $entity
danielebarchiesi@4 496 * Entity revision to save.
danielebarchiesi@4 497 */
danielebarchiesi@4 498 protected function saveRevision($entity) {
danielebarchiesi@4 499 // Convert the entity into an array as it might not have the same properties
danielebarchiesi@4 500 // as the entity, it is just a raw structure.
danielebarchiesi@4 501 $record = (array) $entity;
danielebarchiesi@4 502 // File fields assumes we are using $entity->revision instead of
danielebarchiesi@4 503 // $entity->is_new_revision, so we also support it and make sure it's set to
danielebarchiesi@4 504 // the same value.
danielebarchiesi@4 505 $entity->is_new_revision = !empty($entity->is_new_revision) || !empty($entity->revision) || $entity->is_new;
danielebarchiesi@4 506 $entity->revision = &$entity->is_new_revision;
danielebarchiesi@4 507 $entity->{$this->defaultRevisionKey} = !empty($entity->{$this->defaultRevisionKey}) || $entity->is_new;
danielebarchiesi@4 508
danielebarchiesi@4 509
danielebarchiesi@4 510
danielebarchiesi@4 511 // When saving a new revision, set any existing revision ID to NULL so as to
danielebarchiesi@4 512 // ensure that a new revision will actually be created.
danielebarchiesi@4 513 if ($entity->is_new_revision && isset($record[$this->revisionKey])) {
danielebarchiesi@4 514 $record[$this->revisionKey] = NULL;
danielebarchiesi@4 515 }
danielebarchiesi@4 516
danielebarchiesi@4 517 if ($entity->is_new_revision) {
danielebarchiesi@4 518 drupal_write_record($this->revisionTable, $record);
danielebarchiesi@4 519 $update_default_revision = $entity->{$this->defaultRevisionKey};
danielebarchiesi@4 520 }
danielebarchiesi@4 521 else {
danielebarchiesi@4 522 drupal_write_record($this->revisionTable, $record, $this->revisionKey);
danielebarchiesi@4 523 // @todo: Fix original entity to be of the same revision and check whether
danielebarchiesi@4 524 // the default revision key has been set.
danielebarchiesi@4 525 $update_default_revision = $entity->{$this->defaultRevisionKey} && $entity->{$this->revisionKey} != $entity->original->{$this->revisionKey};
danielebarchiesi@4 526 }
danielebarchiesi@4 527 // Make sure to update the new revision key for the entity.
danielebarchiesi@4 528 $entity->{$this->revisionKey} = $record[$this->revisionKey];
danielebarchiesi@4 529
danielebarchiesi@4 530 // Mark this revision as the default one.
danielebarchiesi@4 531 if ($update_default_revision) {
danielebarchiesi@4 532 db_update($this->entityInfo['base table'])
danielebarchiesi@4 533 ->fields(array($this->revisionKey => $record[$this->revisionKey]))
danielebarchiesi@4 534 ->condition($this->idKey, $entity->{$this->idKey})
danielebarchiesi@4 535 ->execute();
danielebarchiesi@4 536 }
danielebarchiesi@4 537 return $entity->is_new_revision ? SAVED_NEW : SAVED_UPDATED;
danielebarchiesi@4 538 }
danielebarchiesi@4 539
danielebarchiesi@4 540 /**
danielebarchiesi@4 541 * Implements EntityAPIControllerInterface.
danielebarchiesi@4 542 */
danielebarchiesi@4 543 public function create(array $values = array()) {
danielebarchiesi@4 544 // Add is_new property if it is not set.
danielebarchiesi@4 545 $values += array('is_new' => TRUE);
danielebarchiesi@4 546 if (isset($this->entityInfo['entity class']) && $class = $this->entityInfo['entity class']) {
danielebarchiesi@4 547 return new $class($values, $this->entityType);
danielebarchiesi@4 548 }
danielebarchiesi@4 549 return (object) $values;
danielebarchiesi@4 550 }
danielebarchiesi@4 551
danielebarchiesi@4 552 /**
danielebarchiesi@4 553 * Implements EntityAPIControllerInterface.
danielebarchiesi@4 554 *
danielebarchiesi@4 555 * @return
danielebarchiesi@4 556 * A serialized string in JSON format suitable for the import() method.
danielebarchiesi@4 557 */
danielebarchiesi@4 558 public function export($entity, $prefix = '') {
danielebarchiesi@4 559 $vars = get_object_vars($entity);
danielebarchiesi@4 560 unset($vars['is_new']);
danielebarchiesi@4 561 return entity_var_json_export($vars, $prefix);
danielebarchiesi@4 562 }
danielebarchiesi@4 563
danielebarchiesi@4 564 /**
danielebarchiesi@4 565 * Implements EntityAPIControllerInterface.
danielebarchiesi@4 566 *
danielebarchiesi@4 567 * @param $export
danielebarchiesi@4 568 * A serialized string in JSON format as produced by the export() method.
danielebarchiesi@4 569 */
danielebarchiesi@4 570 public function import($export) {
danielebarchiesi@4 571 $vars = drupal_json_decode($export);
danielebarchiesi@4 572 if (is_array($vars)) {
danielebarchiesi@4 573 return $this->create($vars);
danielebarchiesi@4 574 }
danielebarchiesi@4 575 return FALSE;
danielebarchiesi@4 576 }
danielebarchiesi@4 577
danielebarchiesi@4 578 /**
danielebarchiesi@4 579 * Implements EntityAPIControllerInterface.
danielebarchiesi@4 580 *
danielebarchiesi@4 581 * @param $content
danielebarchiesi@4 582 * Optionally. Allows pre-populating the built content to ease overridding
danielebarchiesi@4 583 * this method.
danielebarchiesi@4 584 */
danielebarchiesi@4 585 public function buildContent($entity, $view_mode = 'full', $langcode = NULL, $content = array()) {
danielebarchiesi@4 586 // Remove previously built content, if exists.
danielebarchiesi@4 587 $entity->content = $content;
danielebarchiesi@4 588 $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
danielebarchiesi@4 589
danielebarchiesi@4 590 // By default add in properties for all defined extra fields.
danielebarchiesi@4 591 if ($extra_field_controller = entity_get_extra_fields_controller($this->entityType)) {
danielebarchiesi@4 592 $wrapper = entity_metadata_wrapper($this->entityType, $entity);
danielebarchiesi@4 593 $extra = $extra_field_controller->fieldExtraFields();
danielebarchiesi@4 594 $type_extra = &$extra[$this->entityType][$this->entityType]['display'];
danielebarchiesi@4 595 $bundle_extra = &$extra[$this->entityType][$wrapper->getBundle()]['display'];
danielebarchiesi@4 596
danielebarchiesi@4 597 foreach ($wrapper as $name => $property) {
danielebarchiesi@4 598 if (isset($type_extra[$name]) || isset($bundle_extra[$name])) {
danielebarchiesi@4 599 $this->renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, $entity->content);
danielebarchiesi@4 600 }
danielebarchiesi@4 601 }
danielebarchiesi@4 602 }
danielebarchiesi@4 603
danielebarchiesi@4 604 // Add in fields.
danielebarchiesi@4 605 if (!empty($this->entityInfo['fieldable'])) {
danielebarchiesi@4 606 // Perform the preparation tasks if they have not been performed yet.
danielebarchiesi@4 607 // An internal flag prevents the operation from running twice.
danielebarchiesi@4 608 $key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL;
danielebarchiesi@4 609 field_attach_prepare_view($this->entityType, array($key => $entity), $view_mode);
danielebarchiesi@4 610 $entity->content += field_attach_view($this->entityType, $entity, $view_mode, $langcode);
danielebarchiesi@4 611 }
danielebarchiesi@4 612 // Invoke hook_ENTITY_view() to allow modules to add their additions.
danielebarchiesi@4 613 if (module_exists('rules')) {
danielebarchiesi@4 614 rules_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode);
danielebarchiesi@4 615 }
danielebarchiesi@4 616 else {
danielebarchiesi@4 617 module_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode);
danielebarchiesi@4 618 }
danielebarchiesi@4 619 module_invoke_all('entity_view', $entity, $this->entityType, $view_mode, $langcode);
danielebarchiesi@4 620 $build = $entity->content;
danielebarchiesi@4 621 unset($entity->content);
danielebarchiesi@4 622 return $build;
danielebarchiesi@4 623 }
danielebarchiesi@4 624
danielebarchiesi@4 625 /**
danielebarchiesi@4 626 * Renders a single entity property.
danielebarchiesi@4 627 */
danielebarchiesi@4 628 protected function renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, &$content) {
danielebarchiesi@4 629 $info = $property->info();
danielebarchiesi@4 630
danielebarchiesi@4 631 $content[$name] = array(
danielebarchiesi@4 632 '#label_hidden' => FALSE,
danielebarchiesi@4 633 '#label' => $info['label'],
danielebarchiesi@4 634 '#entity_wrapped' => $wrapper,
danielebarchiesi@4 635 '#theme' => 'entity_property',
danielebarchiesi@4 636 '#property_name' => $name,
danielebarchiesi@4 637 '#access' => $property->access('view'),
danielebarchiesi@4 638 '#entity_type' => $this->entityType,
danielebarchiesi@4 639 );
danielebarchiesi@4 640 $content['#attached']['css']['entity.theme'] = drupal_get_path('module', 'entity') . '/theme/entity.theme.css';
danielebarchiesi@4 641 }
danielebarchiesi@4 642
danielebarchiesi@4 643 /**
danielebarchiesi@4 644 * Implements EntityAPIControllerInterface.
danielebarchiesi@4 645 */
danielebarchiesi@4 646 public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
danielebarchiesi@4 647 // For Field API and entity_prepare_view, the entities have to be keyed by
danielebarchiesi@4 648 // (numeric) id.
danielebarchiesi@4 649 $entities = entity_key_array_by_property($entities, $this->idKey);
danielebarchiesi@4 650 if (!empty($this->entityInfo['fieldable'])) {
danielebarchiesi@4 651 field_attach_prepare_view($this->entityType, $entities, $view_mode);
danielebarchiesi@4 652 }
danielebarchiesi@4 653 entity_prepare_view($this->entityType, $entities);
danielebarchiesi@4 654 $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
danielebarchiesi@4 655
danielebarchiesi@4 656 $view = array();
danielebarchiesi@4 657 foreach ($entities as $entity) {
danielebarchiesi@4 658 $build = entity_build_content($this->entityType, $entity, $view_mode, $langcode);
danielebarchiesi@4 659 $build += array(
danielebarchiesi@4 660 // If the entity type provides an implementation, use this instead the
danielebarchiesi@4 661 // generic one.
danielebarchiesi@4 662 // @see template_preprocess_entity()
danielebarchiesi@4 663 '#theme' => 'entity',
danielebarchiesi@4 664 '#entity_type' => $this->entityType,
danielebarchiesi@4 665 '#entity' => $entity,
danielebarchiesi@4 666 '#view_mode' => $view_mode,
danielebarchiesi@4 667 '#language' => $langcode,
danielebarchiesi@4 668 '#page' => $page,
danielebarchiesi@4 669 );
danielebarchiesi@4 670 // Allow modules to modify the structured entity.
danielebarchiesi@4 671 drupal_alter(array($this->entityType . '_view', 'entity_view'), $build, $this->entityType);
danielebarchiesi@4 672 $key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL;
danielebarchiesi@4 673 $view[$this->entityType][$key] = $build;
danielebarchiesi@4 674 }
danielebarchiesi@4 675 return $view;
danielebarchiesi@4 676 }
danielebarchiesi@4 677 }
danielebarchiesi@4 678
danielebarchiesi@4 679 /**
danielebarchiesi@4 680 * A controller implementing exportables stored in the database.
danielebarchiesi@4 681 */
danielebarchiesi@4 682 class EntityAPIControllerExportable extends EntityAPIController {
danielebarchiesi@4 683
danielebarchiesi@4 684 protected $entityCacheByName = array();
danielebarchiesi@4 685 protected $nameKey, $statusKey, $moduleKey;
danielebarchiesi@4 686
danielebarchiesi@4 687 /**
danielebarchiesi@4 688 * Overridden.
danielebarchiesi@4 689 *
danielebarchiesi@4 690 * Allows specifying a name key serving as uniform identifier for this entity
danielebarchiesi@4 691 * type while still internally we are using numeric identifieres.
danielebarchiesi@4 692 */
danielebarchiesi@4 693 public function __construct($entityType) {
danielebarchiesi@4 694 parent::__construct($entityType);
danielebarchiesi@4 695 // Use the name key as primary identifier.
danielebarchiesi@4 696 $this->nameKey = isset($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->idKey;
danielebarchiesi@4 697 if (!empty($this->entityInfo['exportable'])) {
danielebarchiesi@4 698 $this->statusKey = isset($this->entityInfo['entity keys']['status']) ? $this->entityInfo['entity keys']['status'] : 'status';
danielebarchiesi@4 699 $this->moduleKey = isset($this->entityInfo['entity keys']['module']) ? $this->entityInfo['entity keys']['module'] : 'module';
danielebarchiesi@4 700 }
danielebarchiesi@4 701 }
danielebarchiesi@4 702
danielebarchiesi@4 703 /**
danielebarchiesi@4 704 * Support loading by name key.
danielebarchiesi@4 705 */
danielebarchiesi@4 706 protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
danielebarchiesi@4 707 // Add the id condition ourself, as we might have a separate name key.
danielebarchiesi@4 708 $query = parent::buildQuery(array(), $conditions, $revision_id);
danielebarchiesi@4 709 if ($ids) {
danielebarchiesi@4 710 // Support loading by numeric ids as well as by machine names.
danielebarchiesi@4 711 $key = is_numeric(reset($ids)) ? $this->idKey : $this->nameKey;
danielebarchiesi@4 712 $query->condition("base.$key", $ids, 'IN');
danielebarchiesi@4 713 }
danielebarchiesi@4 714 return $query;
danielebarchiesi@4 715 }
danielebarchiesi@4 716
danielebarchiesi@4 717 /**
danielebarchiesi@4 718 * Overridden to support passing numeric ids as well as names as $ids.
danielebarchiesi@4 719 */
danielebarchiesi@4 720 public function load($ids = array(), $conditions = array()) {
danielebarchiesi@4 721 $entities = array();
danielebarchiesi@4 722
danielebarchiesi@4 723 // Only do something if loaded by names.
danielebarchiesi@4 724 if (!$ids || $this->nameKey == $this->idKey || is_numeric(reset($ids))) {
danielebarchiesi@4 725 return parent::load($ids, $conditions);
danielebarchiesi@4 726 }
danielebarchiesi@4 727
danielebarchiesi@4 728 // Revisions are not statically cached, and require a different query to
danielebarchiesi@4 729 // other conditions, so separate the revision id into its own variable.
danielebarchiesi@4 730 if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
danielebarchiesi@4 731 $revision_id = $conditions[$this->revisionKey];
danielebarchiesi@4 732 unset($conditions[$this->revisionKey]);
danielebarchiesi@4 733 }
danielebarchiesi@4 734 else {
danielebarchiesi@4 735 $revision_id = FALSE;
danielebarchiesi@4 736 }
danielebarchiesi@4 737 $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
danielebarchiesi@4 738
danielebarchiesi@4 739 // Care about the static cache.
danielebarchiesi@4 740 if ($this->cache && !$revision_id) {
danielebarchiesi@4 741 $entities = $this->cacheGetByName($ids, $conditions);
danielebarchiesi@4 742 }
danielebarchiesi@4 743 // If any entities were loaded, remove them from the ids still to load.
danielebarchiesi@4 744 if ($entities) {
danielebarchiesi@4 745 $ids = array_keys(array_diff_key($passed_ids, $entities));
danielebarchiesi@4 746 }
danielebarchiesi@4 747
danielebarchiesi@4 748 $entities_by_id = parent::load($ids, $conditions);
danielebarchiesi@4 749 $entities += entity_key_array_by_property($entities_by_id, $this->nameKey);
danielebarchiesi@4 750
danielebarchiesi@4 751 // Ensure that the returned array is keyed by numeric id and ordered the
danielebarchiesi@4 752 // same as the original $ids array and remove any invalid ids.
danielebarchiesi@4 753 $return = array();
danielebarchiesi@4 754 foreach ($passed_ids as $name => $value) {
danielebarchiesi@4 755 if (isset($entities[$name])) {
danielebarchiesi@4 756 $return[$entities[$name]->{$this->idKey}] = $entities[$name];
danielebarchiesi@4 757 }
danielebarchiesi@4 758 }
danielebarchiesi@4 759 return $return;
danielebarchiesi@4 760 }
danielebarchiesi@4 761
danielebarchiesi@4 762 /**
danielebarchiesi@4 763 * Overridden.
danielebarchiesi@4 764 * @see DrupalDefaultEntityController::cacheGet()
danielebarchiesi@4 765 */
danielebarchiesi@4 766 protected function cacheGet($ids, $conditions = array()) {
danielebarchiesi@4 767 if (!empty($this->entityCache) && $ids !== array()) {
danielebarchiesi@4 768 $entities = $ids ? array_intersect_key($this->entityCache, array_flip($ids)) : $this->entityCache;
danielebarchiesi@4 769 return $this->applyConditions($entities, $conditions);
danielebarchiesi@4 770 }
danielebarchiesi@4 771 return array();
danielebarchiesi@4 772 }
danielebarchiesi@4 773
danielebarchiesi@4 774 /**
danielebarchiesi@4 775 * Like cacheGet() but keyed by name.
danielebarchiesi@4 776 */
danielebarchiesi@4 777 protected function cacheGetByName($names, $conditions = array()) {
danielebarchiesi@4 778 if (!empty($this->entityCacheByName) && $names !== array() && $names) {
danielebarchiesi@4 779 // First get the entities by ids, then apply the conditions.
danielebarchiesi@4 780 // Generally, we make use of $this->entityCache, but if we are loading by
danielebarchiesi@4 781 // name, we have to use $this->entityCacheByName.
danielebarchiesi@4 782 $entities = array_intersect_key($this->entityCacheByName, array_flip($names));
danielebarchiesi@4 783 return $this->applyConditions($entities, $conditions);
danielebarchiesi@4 784 }
danielebarchiesi@4 785 return array();
danielebarchiesi@4 786 }
danielebarchiesi@4 787
danielebarchiesi@4 788 protected function applyConditions($entities, $conditions = array()) {
danielebarchiesi@4 789 if ($conditions) {
danielebarchiesi@4 790 foreach ($entities as $key => $entity) {
danielebarchiesi@4 791 $entity_values = (array) $entity;
danielebarchiesi@4 792 // We cannot use array_diff_assoc() here because condition values can
danielebarchiesi@4 793 // also be arrays, e.g. '$conditions = array('status' => array(1, 2))'
danielebarchiesi@4 794 foreach ($conditions as $condition_key => $condition_value) {
danielebarchiesi@4 795 if (is_array($condition_value)) {
danielebarchiesi@4 796 if (!isset($entity_values[$condition_key]) || !in_array($entity_values[$condition_key], $condition_value)) {
danielebarchiesi@4 797 unset($entities[$key]);
danielebarchiesi@4 798 }
danielebarchiesi@4 799 }
danielebarchiesi@4 800 elseif (!isset($entity_values[$condition_key]) || $entity_values[$condition_key] != $condition_value) {
danielebarchiesi@4 801 unset($entities[$key]);
danielebarchiesi@4 802 }
danielebarchiesi@4 803 }
danielebarchiesi@4 804 }
danielebarchiesi@4 805 }
danielebarchiesi@4 806 return $entities;
danielebarchiesi@4 807 }
danielebarchiesi@4 808
danielebarchiesi@4 809 /**
danielebarchiesi@4 810 * Overridden.
danielebarchiesi@4 811 * @see DrupalDefaultEntityController::cacheSet()
danielebarchiesi@4 812 */
danielebarchiesi@4 813 protected function cacheSet($entities) {
danielebarchiesi@4 814 $this->entityCache += $entities;
danielebarchiesi@4 815 // If we have a name key, also support static caching when loading by name.
danielebarchiesi@4 816 if ($this->nameKey != $this->idKey) {
danielebarchiesi@4 817 $this->entityCacheByName += entity_key_array_by_property($entities, $this->nameKey);
danielebarchiesi@4 818 }
danielebarchiesi@4 819 }
danielebarchiesi@4 820
danielebarchiesi@4 821 /**
danielebarchiesi@4 822 * Overridden.
danielebarchiesi@4 823 * @see DrupalDefaultEntityController::attachLoad()
danielebarchiesi@4 824 *
danielebarchiesi@4 825 * Changed to call type-specific hook with the entities keyed by name if they
danielebarchiesi@4 826 * have one.
danielebarchiesi@4 827 */
danielebarchiesi@4 828 protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
danielebarchiesi@4 829 // Attach fields.
danielebarchiesi@4 830 if ($this->entityInfo['fieldable']) {
danielebarchiesi@4 831 if ($revision_id) {
danielebarchiesi@4 832 field_attach_load_revision($this->entityType, $queried_entities);
danielebarchiesi@4 833 }
danielebarchiesi@4 834 else {
danielebarchiesi@4 835 field_attach_load($this->entityType, $queried_entities);
danielebarchiesi@4 836 }
danielebarchiesi@4 837 }
danielebarchiesi@4 838
danielebarchiesi@4 839 // Call hook_entity_load().
danielebarchiesi@4 840 foreach (module_implements('entity_load') as $module) {
danielebarchiesi@4 841 $function = $module . '_entity_load';
danielebarchiesi@4 842 $function($queried_entities, $this->entityType);
danielebarchiesi@4 843 }
danielebarchiesi@4 844 // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
danielebarchiesi@4 845 // always the queried entities, followed by additional arguments set in
danielebarchiesi@4 846 // $this->hookLoadArguments.
danielebarchiesi@4 847 // For entities with a name key, pass the entities keyed by name to the
danielebarchiesi@4 848 // specific load hook.
danielebarchiesi@4 849 if ($this->nameKey != $this->idKey) {
danielebarchiesi@4 850 $entities_by_name = entity_key_array_by_property($queried_entities, $this->nameKey);
danielebarchiesi@4 851 }
danielebarchiesi@4 852 else {
danielebarchiesi@4 853 $entities_by_name = $queried_entities;
danielebarchiesi@4 854 }
danielebarchiesi@4 855 $args = array_merge(array($entities_by_name), $this->hookLoadArguments);
danielebarchiesi@4 856 foreach (module_implements($this->entityInfo['load hook']) as $module) {
danielebarchiesi@4 857 call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
danielebarchiesi@4 858 }
danielebarchiesi@4 859 }
danielebarchiesi@4 860
danielebarchiesi@4 861 public function resetCache(array $ids = NULL) {
danielebarchiesi@4 862 $this->cacheComplete = FALSE;
danielebarchiesi@4 863 if (isset($ids)) {
danielebarchiesi@4 864 foreach (array_intersect_key($this->entityCache, array_flip($ids)) as $id => $entity) {
danielebarchiesi@4 865 unset($this->entityCacheByName[$this->entityCache[$id]->{$this->nameKey}]);
danielebarchiesi@4 866 unset($this->entityCache[$id]);
danielebarchiesi@4 867 }
danielebarchiesi@4 868 }
danielebarchiesi@4 869 else {
danielebarchiesi@4 870 $this->entityCache = array();
danielebarchiesi@4 871 $this->entityCacheByName = array();
danielebarchiesi@4 872 }
danielebarchiesi@4 873 }
danielebarchiesi@4 874
danielebarchiesi@4 875 /**
danielebarchiesi@4 876 * Overridden to care about reverted entities.
danielebarchiesi@4 877 */
danielebarchiesi@4 878 public function delete($ids, DatabaseTransaction $transaction = NULL) {
danielebarchiesi@4 879 $entities = $ids ? $this->load($ids) : FALSE;
danielebarchiesi@4 880 if ($entities) {
danielebarchiesi@4 881 parent::delete($ids, $transaction);
danielebarchiesi@4 882
danielebarchiesi@4 883 foreach ($entities as $id => $entity) {
danielebarchiesi@4 884 if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) {
danielebarchiesi@4 885 entity_defaults_rebuild(array($this->entityType));
danielebarchiesi@4 886 break;
danielebarchiesi@4 887 }
danielebarchiesi@4 888 }
danielebarchiesi@4 889 }
danielebarchiesi@4 890 }
danielebarchiesi@4 891
danielebarchiesi@4 892 /**
danielebarchiesi@4 893 * Overridden to care about reverted bundle entities and to skip Rules.
danielebarchiesi@4 894 */
danielebarchiesi@4 895 public function invoke($hook, $entity) {
danielebarchiesi@4 896 if ($hook == 'delete') {
danielebarchiesi@4 897 // To ease figuring out whether this is a revert, make sure that the
danielebarchiesi@4 898 // entity status is updated in case the providing module has been
danielebarchiesi@4 899 // disabled.
danielebarchiesi@4 900 if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && !module_exists($entity->{$this->moduleKey})) {
danielebarchiesi@4 901 $entity->{$this->statusKey} = ENTITY_CUSTOM;
danielebarchiesi@4 902 }
danielebarchiesi@4 903 $is_revert = entity_has_status($this->entityType, $entity, ENTITY_IN_CODE);
danielebarchiesi@4 904 }
danielebarchiesi@4 905
danielebarchiesi@4 906 if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
danielebarchiesi@4 907 $function($this->entityType, $entity);
danielebarchiesi@4 908 }
danielebarchiesi@4 909
danielebarchiesi@4 910 if (isset($this->entityInfo['bundle of']) && $type = $this->entityInfo['bundle of']) {
danielebarchiesi@4 911 // Call field API bundle attachers for the entity we are a bundle of.
danielebarchiesi@4 912 if ($hook == 'insert') {
danielebarchiesi@4 913 field_attach_create_bundle($type, $entity->{$this->bundleKey});
danielebarchiesi@4 914 }
danielebarchiesi@4 915 elseif ($hook == 'delete' && !$is_revert) {
danielebarchiesi@4 916 field_attach_delete_bundle($type, $entity->{$this->bundleKey});
danielebarchiesi@4 917 }
danielebarchiesi@4 918 elseif ($hook == 'update' && $id = $entity->{$this->nameKey}) {
danielebarchiesi@4 919 if ($entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) {
danielebarchiesi@4 920 field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey});
danielebarchiesi@4 921 }
danielebarchiesi@4 922 }
danielebarchiesi@4 923 }
danielebarchiesi@4 924 // Invoke the hook.
danielebarchiesi@4 925 module_invoke_all($this->entityType . '_' . $hook, $entity);
danielebarchiesi@4 926 // Invoke the respective entity level hook.
danielebarchiesi@4 927 if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
danielebarchiesi@4 928 module_invoke_all('entity_' . $hook, $entity, $this->entityType);
danielebarchiesi@4 929 }
danielebarchiesi@4 930 }
danielebarchiesi@4 931
danielebarchiesi@4 932 /**
danielebarchiesi@4 933 * Overridden to care exportables that are overridden.
danielebarchiesi@4 934 */
danielebarchiesi@4 935 public function save($entity, DatabaseTransaction $transaction = NULL) {
danielebarchiesi@4 936 // Preload $entity->original by name key if necessary.
danielebarchiesi@4 937 if (!empty($entity->{$this->nameKey}) && empty($entity->{$this->idKey}) && !isset($entity->original)) {
danielebarchiesi@4 938 $entity->original = entity_load_unchanged($this->entityType, $entity->{$this->nameKey});
danielebarchiesi@4 939 }
danielebarchiesi@4 940 // Update the status for entities getting overridden.
danielebarchiesi@4 941 if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && empty($entity->is_rebuild)) {
danielebarchiesi@4 942 $entity->{$this->statusKey} |= ENTITY_CUSTOM;
danielebarchiesi@4 943 }
danielebarchiesi@4 944 return parent::save($entity, $transaction);
danielebarchiesi@4 945 }
danielebarchiesi@4 946
danielebarchiesi@4 947 /**
danielebarchiesi@4 948 * Overridden.
danielebarchiesi@4 949 */
danielebarchiesi@4 950 public function export($entity, $prefix = '') {
danielebarchiesi@4 951 $vars = get_object_vars($entity);
danielebarchiesi@4 952 unset($vars[$this->statusKey], $vars[$this->moduleKey], $vars['is_new']);
danielebarchiesi@4 953 if ($this->nameKey != $this->idKey) {
danielebarchiesi@4 954 unset($vars[$this->idKey]);
danielebarchiesi@4 955 }
danielebarchiesi@4 956 return entity_var_json_export($vars, $prefix);
danielebarchiesi@4 957 }
danielebarchiesi@4 958
danielebarchiesi@4 959 /**
danielebarchiesi@4 960 * Implements EntityAPIControllerInterface.
danielebarchiesi@4 961 */
danielebarchiesi@4 962 public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
danielebarchiesi@4 963 $view = parent::view($entities, $view_mode, $langcode, $page);
danielebarchiesi@4 964
danielebarchiesi@4 965 if ($this->nameKey != $this->idKey) {
danielebarchiesi@4 966 // Re-key the view array to be keyed by name.
danielebarchiesi@4 967 $return = array();
danielebarchiesi@4 968 foreach ($view[$this->entityType] as $id => $content) {
danielebarchiesi@4 969 $key = isset($content['#entity']->{$this->nameKey}) ? $content['#entity']->{$this->nameKey} : NULL;
danielebarchiesi@4 970 $return[$this->entityType][$key] = $content;
danielebarchiesi@4 971 }
danielebarchiesi@4 972 $view = $return;
danielebarchiesi@4 973 }
danielebarchiesi@4 974 return $view;
danielebarchiesi@4 975 }
danielebarchiesi@4 976 }