drupal/core/modules/entity/entity.module

500 lines
18 KiB
Plaintext

<?php
/**
* @file
* Entity API for handling entities like nodes or users.
*/
use Drupal\entity\EntityMalformedException;
/**
* Implements hook_help().
*/
function entity_help($path, $arg) {
switch ($path) {
case 'admin/help#entity':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Entity module provides an API for managing entities like nodes and users, i.e. an API for loading and identifying entities. For more information, see the online handbook entry for <a href="!url">Entity module</a>', array('!url' => 'http://drupal.org/documentation/modules/entity')) . '</p>';
return $output;
}
}
/**
* Implements hook_modules_preenable().
*/
function entity_modules_preenable() {
entity_info_cache_clear();
}
/**
* Implements hook_modules_disabled().
*/
function entity_modules_disabled() {
entity_info_cache_clear();
}
/**
* Gets the entity info array of an entity type.
*
* @param $entity_type
* The entity type, e.g. node, for which the info shall be returned, or NULL
* to return an array with info about all types.
*
* @see hook_entity_info()
* @see hook_entity_info_alter()
*/
function entity_get_info($entity_type = NULL) {
global $language_interface;
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['entity_info'] = &drupal_static(__FUNCTION__);
}
$entity_info = &$drupal_static_fast['entity_info'];
// hook_entity_info() includes translated strings, so each language is cached
// separately.
$langcode = $language_interface->langcode;
if (empty($entity_info)) {
if ($cache = cache()->get("entity_info:$langcode")) {
$entity_info = $cache->data;
}
else {
$entity_info = module_invoke_all('entity_info');
// Merge in default values.
foreach ($entity_info as $name => $data) {
$entity_info[$name] += array(
'fieldable' => FALSE,
'controller class' => 'Drupal\entity\EntityController',
'static cache' => TRUE,
'field cache' => TRUE,
'load hook' => $name . '_load',
'bundles' => array(),
'view modes' => array(),
'entity keys' => array(),
'translation' => array(),
);
$entity_info[$name]['entity keys'] += array(
'revision' => '',
'bundle' => '',
);
foreach ($entity_info[$name]['view modes'] as $view_mode => $view_mode_info) {
$entity_info[$name]['view modes'][$view_mode] += array(
'custom settings' => FALSE,
);
}
// If no bundle key is provided, assume a single bundle, named after
// the entity type.
if (empty($entity_info[$name]['entity keys']['bundle']) && empty($entity_info[$name]['bundles'])) {
$entity_info[$name]['bundles'] = array($name => array('label' => $entity_info[$name]['label']));
}
// Prepare entity schema fields SQL info for
// Drupal\entity\EntityControllerInterface::buildQuery().
if (isset($entity_info[$name]['base table'])) {
$entity_info[$name]['schema_fields_sql']['base table'] = drupal_schema_fields_sql($entity_info[$name]['base table']);
if (isset($entity_info[$name]['revision table'])) {
$entity_info[$name]['schema_fields_sql']['revision table'] = drupal_schema_fields_sql($entity_info[$name]['revision table']);
}
}
}
// Let other modules alter the entity info.
drupal_alter('entity_info', $entity_info);
cache()->set("entity_info:$langcode", $entity_info);
}
}
if (empty($entity_type)) {
return $entity_info;
}
elseif (isset($entity_info[$entity_type])) {
return $entity_info[$entity_type];
}
}
/**
* Resets the cached information about entity types.
*/
function entity_info_cache_clear() {
drupal_static_reset('entity_get_info');
// Clear all languages.
cache()->deletePrefix('entity_info:');
}
/**
* Extracts ID, revision ID, and bundle name from an entity.
*
* @param $entity_type
* The entity type; e.g. 'node' or 'user'.
* @param $entity
* The entity from which to extract values.
*
* @return
* A numerically indexed array (not a hash table) containing these
* elements:
* - 0: Primary ID of the entity.
* - 1: Revision ID of the entity, or NULL if $entity_type is not versioned.
* - 2: Bundle name of the entity.
*/
function entity_extract_ids($entity_type, $entity) {
$info = entity_get_info($entity_type);
// Objects being created might not have id/vid yet.
$id = isset($entity->{$info['entity keys']['id']}) ? $entity->{$info['entity keys']['id']} : NULL;
$vid = ($info['entity keys']['revision'] && isset($entity->{$info['entity keys']['revision']})) ? $entity->{$info['entity keys']['revision']} : NULL;
if (!empty($info['entity keys']['bundle'])) {
// Explicitly fail for malformed entities missing the bundle property.
if (!isset($entity->{$info['entity keys']['bundle']}) || $entity->{$info['entity keys']['bundle']} === '') {
throw new EntityMalformedException(t('Missing bundle property on entity of type @entity_type.', array('@entity_type' => $entity_type)));
}
$bundle = $entity->{$info['entity keys']['bundle']};
}
else {
// The entity type provides no bundle key: assume a single bundle, named
// after the entity type.
$bundle = $entity_type;
}
return array($id, $vid, $bundle);
}
/**
* Assembles an object structure with initial IDs.
*
* This function can be seen as reciprocal to entity_extract_ids().
*
* @param $entity_type
* The entity type; e.g. 'node' or 'user'.
* @param $ids
* A numerically indexed array, as returned by entity_extract_ids(),
* containing these elements:
* - 0: Primary ID of the entity.
* - 1: Revision ID of the entity, or NULL if $entity_type is not versioned.
* - 2: Bundle name of the entity, or NULL if $entity_type has no bundles.
*
* @return
* An entity object, initialized with the IDs provided.
*/
function entity_create_stub_entity($entity_type, $ids) {
$entity = new stdClass();
$info = entity_get_info($entity_type);
$entity->{$info['entity keys']['id']} = $ids[0];
if (!empty($info['entity keys']['revision']) && isset($ids[1])) {
$entity->{$info['entity keys']['revision']} = $ids[1];
}
if (!empty($info['entity keys']['bundle']) && isset($ids[2])) {
$entity->{$info['entity keys']['bundle']} = $ids[2];
}
return $entity;
}
/**
* Loads an entity from the database.
*
* @param string $entity_type
* The entity type to load, e.g. node or user.
* @param int $id
* The id of the entity to load.
* @param bool $reset
* Whether to reset the internal cache for the requested entity type.
*
* @return object
* The entity object, or FALSE if there is no entity with the given id.
*
* @see hook_entity_info()
* @see entity_load_multiple()
* @see Drupal\entity\EntityControllerInterface
* @see Drupal\entity\EntityController
* @see Drupal\entity\EntityFieldQuery
*/
function entity_load($entity_type, $id, $reset = FALSE) {
$entities = entity_load_multiple($entity_type, array($id), array(), $reset);
return isset($entities[$id]) ? $entities[$id] : FALSE;
}
/**
* Loads multiple entities from the database.
*
* This function should be used whenever you need to load more than one entity
* from the database. The entities are loaded into memory and will not require
* database access if loaded again during the same page request.
*
* The actual loading is done through a class that has to implement the
* Drupal\entity\EntityControllerInterface interface. By default,
* Drupal\entity\EntityController is used. Entity types can specify
* that a different class should be used by setting the 'controller class' key
* in hook_entity_info(). These classes can either implement the
* Drupal\entity\EntityControllerInterface interface, or, most
* commonly, extend the Drupal\entity\EntityController class. See
* node_entity_info() and the NodeController in node.module as an example.
*
* @param string $entity_type
* The entity type to load, e.g. node or user.
* @param array|bool $ids
* An array of entity IDs, or FALSE to load all entities.
* @param array $conditions
* (deprecated) An associative array of conditions on the base table, where
* the keys are the database fields and the values are the values those
* fields must have. Instead, it is preferable to use EntityFieldQuery to
* retrieve a list of entity IDs loadable by this function.
* @param bool $reset
* Whether to reset the internal cache for the requested entity type.
*
* @return array
* An array of entity objects indexed by their ids.
*
* @todo Remove $conditions in Drupal 8.
*
* @see hook_entity_info()
* @see Drupal\entity\EntityControllerInterface
* @see Drupal\entity\EntityController
* @see Drupal\entity\EntityFieldQuery
*/
function entity_load_multiple($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) {
if ($reset) {
entity_get_controller($entity_type)->resetCache();
}
return entity_get_controller($entity_type)->load($ids, $conditions);
}
/**
* Loads the unchanged, i.e. not modified, entity from the database.
*
* Unlike entity_load() this function ensures the entity is directly loaded from
* the database, thus bypassing any static cache. In particular, this function
* is useful to determine changes by comparing the entity being saved to the
* stored entity.
*
* @param $entity_type
* The entity type to load, e.g. node or user.
* @param $id
* The ID of the entity to load.
*
* @return
* The unchanged entity, or FALSE if the entity cannot be loaded.
*/
function entity_load_unchanged($entity_type, $id) {
entity_get_controller($entity_type)->resetCache(array($id));
$result = entity_get_controller($entity_type)->load(array($id));
return reset($result);
}
/**
* Deletes multiple entities permanently.
*
* @param $entity_type
* The type of the entity.
* @param $ids
* An array of entity IDs of the entities to delete.
*/
function entity_delete_multiple($entity_type, $ids) {
entity_get_controller($entity_type)->delete($ids);
}
/**
* Constructs a new entity object, without permanently saving it.
*
* @param $entity_type
* The type of the entity.
* @param $values
* An array of values to set, keyed by property name. If the entity type has
* bundles the bundle key has to be specified.
*
* @return Drupal\entity\EntityInterface
* A new entity object.
*/
function entity_create($entity_type, array $values) {
return entity_get_controller($entity_type)->create($values);
}
/**
* Gets the entity controller class for an entity type.
*
* @return Drupal\entity\EntityStorageControllerInterface
*/
function entity_get_controller($entity_type) {
$controllers = &drupal_static(__FUNCTION__, array());
if (!isset($controllers[$entity_type])) {
$type_info = entity_get_info($entity_type);
$class = $type_info['controller class'];
$controllers[$entity_type] = new $class($entity_type);
}
return $controllers[$entity_type];
}
/**
* Invokes hook_entity_prepare_view().
*
* If adding a new entity similar to nodes, comments or users, you should
* invoke this function during the ENTITY_build_content() or
* ENTITY_view_multiple() phases of rendering to allow other modules to alter
* the objects during this phase. This is needed for situations where
* information needs to be loaded outside of ENTITY_load() - particularly
* when loading entities into one another - i.e. a user object into a node, due
* to the potential for unwanted side-effects such as caching and infinite
* recursion. By convention, entity_prepare_view() is called after
* field_attach_prepare_view() to allow entity level hooks to act on content
* loaded by field API.
*
* @param $entity_type
* The type of entity, i.e. 'node', 'user'.
* @param $entities
* The entity objects which are being prepared for view, keyed by object ID.
*
* @see hook_entity_prepare_view()
*/
function entity_prepare_view($entity_type, $entities) {
// To ensure hooks are only run once per entity, check for an
// entity_view_prepared flag and only process items without it.
// @todo: resolve this more generally for both entity and field level hooks.
$prepare = array();
foreach ($entities as $id => $entity) {
if (empty($entity->entity_view_prepared)) {
// Add this entity to the items to be prepared.
$prepare[$id] = $entity;
// Mark this item as prepared.
$entity->entity_view_prepared = TRUE;
}
}
if (!empty($prepare)) {
module_invoke_all('entity_prepare_view', $prepare, $entity_type);
}
}
/**
* Returns the uri elements of an entity.
*
* @param $entity_type
* The entity type; e.g. 'node' or 'user'.
* @param $entity
* The entity for which to generate a path.
*
* @return
* An array containing the 'path' and 'options' keys used to build the uri of
* the entity, and matching the signature of url(). NULL if the entity has no
* uri of its own.
*
* @todo
* Remove once all entity types are implementing the EntityInterface.
*/
function entity_uri($entity_type, $entity) {
$info = entity_get_info($entity_type);
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
// A bundle-specific callback takes precedence over the generic one for the
// entity type.
if (isset($info['bundles'][$bundle]['uri callback'])) {
$uri_callback = $info['bundles'][$bundle]['uri callback'];
}
elseif (isset($info['uri callback'])) {
$uri_callback = $info['uri callback'];
}
else {
return NULL;
}
// Invoke the callback to get the URI. If there is no callback, return NULL.
if (isset($uri_callback)) {
$uri = $uri_callback($entity);
// Pass the entity data to url() so that alter functions do not need to
// lookup this entity again.
$uri['options']['entity_type'] = $entity_type;
$uri['options']['entity'] = $entity;
return $uri;
}
}
/**
* Returns the label of an entity.
*
* See the 'label callback' component of the hook_entity_info() return value
* for more information.
*
* @param $entity_type
* The entity type; e.g., 'node' or 'user'.
* @param $entity
* The entity for which to generate the label.
*
* @return
* The entity label, or FALSE if not found.
*
* @todo
* Remove once all entity types are implementing the EntityInterface.
*/
function entity_label($entity_type, $entity) {
$label = FALSE;
$info = entity_get_info($entity_type);
if (isset($info['label callback'])) {
$label = $info['label callback']($entity_type, $entity);
}
elseif (!empty($info['entity keys']['label']) && isset($entity->{$info['entity keys']['label']})) {
$label = $entity->{$info['entity keys']['label']};
}
return $label;
}
/**
* Attaches field API validation to entity forms.
*/
function entity_form_field_validate($entity_type, $form, &$form_state) {
// All field attach API functions act on an entity object, but during form
// validation, we don't have one. $form_state contains the entity as it was
// prior to processing the current form submission, and we must not update it
// until we have fully validated the submitted input. Therefore, for
// validation, act on a pseudo entity created out of the form values.
$pseudo_entity = (object) $form_state['values'];
field_attach_form_validate($entity_type, $pseudo_entity, $form, $form_state);
}
/**
* Copies submitted values to entity properties for simple entity forms.
*
* During the submission handling of an entity form's "Save", "Preview", and
* possibly other buttons, the form state's entity needs to be updated with the
* submitted form values. Each entity form implements its own builder function
* for doing this, appropriate for the particular entity and form, whereas
* modules may specify additional builder functions in $form['#entity_builders']
* for copying the form values of added form elements to entity properties.
* Many of the main entity builder functions can call this helper function to
* re-use its logic of copying $form_state['values'][PROPERTY] values to
* $entity->PROPERTY for all entries in $form_state['values'] that are not field
* data, and calling field_attach_submit() to copy field data. Apart from that
* this helper invokes any additional builder functions that have been specified
* in $form['#entity_builders'].
*
* For some entity forms (e.g., forms with complex non-field data and forms that
* simultaneously edit multiple entities), this behavior may be inappropriate,
* so the builder function for such forms needs to implement the required
* functionality instead of calling this function.
*/
function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_state) {
$info = entity_get_info($entity_type);
list(, , $bundle) = entity_extract_ids($entity_type, $entity);
// Copy top-level form values that are not for fields to entity properties,
// without changing existing entity properties that are not being edited by
// this form. Copying field values must be done using field_attach_submit().
$values_excluding_fields = $info['fieldable'] ? array_diff_key($form_state['values'], field_info_instances($entity_type, $bundle)) : $form_state['values'];
foreach ($values_excluding_fields as $key => $value) {
$entity->$key = $value;
}
// Invoke all specified builders for copying form values to entity properties.
if (isset($form['#entity_builders'])) {
foreach ($form['#entity_builders'] as $function) {
$function($entity_type, $entity, $form, $form_state);
}
}
// Copy field values to the entity.
if ($info['fieldable']) {
field_attach_submit($entity_type, $entity, $form, $form_state);
}
}