entity.controller.inc

  1. cis7 sites/all/modules/ulmus/entity/includes/entity.controller.inc
  2. cle7 sites/all/modules/ulmus/entity/includes/entity.controller.inc
  3. ecd7 sites/all/modules/ulmus/entity/includes/entity.controller.inc
  4. elmsmedia7 sites/all/modules/ulmus/entity/includes/entity.controller.inc
  5. harmony7 sites/all/modules/ulmus/entity/includes/entity.controller.inc
  6. icor7 sites/all/modules/ulmus/entity/includes/entity.controller.inc
  7. meedjum_blog7 sites/all/modules/ulmus/entity/includes/entity.controller.inc
  8. mooc7 sites/all/modules/ulmus/entity/includes/entity.controller.inc

Provides a controller building upon the core controller but providing more features like full CRUD functionality.

Classes

Namesort descending Description
EntityAPIController A controller implementing EntityAPIControllerInterface for the database.
EntityAPIControllerExportable A controller implementing exportables stored in the database.

Interfaces

Namesort descending Description
EntityAPIControllerInterface Interface for EntityControllers compatible with the entity API.
EntityAPIControllerRevisionableInterface Interface for EntityControllers of entities that support revisions.

File

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

Error

×

Error message

  • Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/elmsln_community/api.elmsln.org/includes/common.inc:2791) in drupal_send_headers() (line 1499 of /var/www/html/elmsln_community/api.elmsln.org/includes/bootstrap.inc).
  • Error: Call to undefined function apc_delete() in DrupalAPCCache->clear() (line 289 of /var/www/html/elmsln_community/api.elmsln.org/sites/all/modules/apc/drupal_apc_cache.inc).
The website encountered an unexpected error. Please try again later.