- Patch #1018602 by fago, catch, aspilicious: move entity system to a module.
parent
33fe4c1d4f
commit
58a5b82f90
|
@ -7277,413 +7277,6 @@ function drupal_check_incompatibility($v, $current_version) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity info array of an entity type.
|
||||
*
|
||||
* @see hook_entity_info()
|
||||
* @see hook_entity_info_alter()
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
function entity_get_info($entity_type = NULL) {
|
||||
global $language;
|
||||
|
||||
// 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->language;
|
||||
|
||||
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' => 'DrupalDefaultEntityController',
|
||||
'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
|
||||
// DrupalEntityControllerInterface::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:');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to extract id, vid, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to assemble 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 structure, 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load entities from the database.
|
||||
*
|
||||
* The entities are stored in a static memory cache, 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
|
||||
* DrupalEntityControllerInterface interface. By default,
|
||||
* DrupalDefaultEntityController 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
|
||||
* DrupalEntityControllerInterface interface, or, most commonly, extend the
|
||||
* DrupalDefaultEntityController class. See node_entity_info() and the
|
||||
* NodeController in node.module as an example.
|
||||
*
|
||||
* @see hook_entity_info()
|
||||
* @see DrupalEntityControllerInterface
|
||||
* @see DrupalDefaultEntityController
|
||||
* @see EntityFieldQuery
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type to load, e.g. node or user.
|
||||
* @param $ids
|
||||
* An array of entity IDs, or FALSE to load all entities.
|
||||
* @param $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 $reset
|
||||
* Whether to reset the internal cache for the requested entity type.
|
||||
*
|
||||
* @return
|
||||
* An array of entity objects indexed by their ids. When no results are
|
||||
* found, an empty array is returned.
|
||||
*
|
||||
* @todo Remove $conditions in Drupal 8.
|
||||
*/
|
||||
function entity_load($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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity controller class for an entity type.
|
||||
*/
|
||||
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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke 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.
|
||||
* @see hook_entity_prepare_view()
|
||||
*
|
||||
* @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.
|
||||
* @param $langcode
|
||||
* (optional) A language code to be used for rendering. Defaults to the global
|
||||
* content language of the current request.
|
||||
*/
|
||||
function entity_prepare_view($entity_type, $entities, $langcode = NULL) {
|
||||
if (!isset($langcode)) {
|
||||
$langcode = $GLOBALS['language_content']->language;
|
||||
}
|
||||
|
||||
// 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, $langcode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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) && function_exists($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.
|
||||
*/
|
||||
function entity_label($entity_type, $entity) {
|
||||
$label = FALSE;
|
||||
$info = entity_get_info($entity_type);
|
||||
if (isset($info['label callback']) && function_exists($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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for attaching 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for copying 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs one or more XML-RPC request(s).
|
||||
*
|
||||
|
|
|
@ -253,12 +253,13 @@ function install_begin_request(&$install_state) {
|
|||
// Set up $language, so t() caller functions will still work.
|
||||
drupal_language_initialize();
|
||||
|
||||
include_once DRUPAL_ROOT . '/includes/entity.inc';
|
||||
require_once DRUPAL_ROOT . '/includes/ajax.inc';
|
||||
$module_list['system']['filename'] = 'modules/system/system.module';
|
||||
$module_list['user']['filename'] = 'modules/user/user.module';
|
||||
$module_list['entity']['filename'] = 'modules/entity/entity.module';
|
||||
$module_list['user']['filename'] = 'modules/user/user.module';
|
||||
module_list(TRUE, FALSE, FALSE, $module_list);
|
||||
drupal_load('module', 'system');
|
||||
drupal_load('module', 'entity');
|
||||
drupal_load('module', 'user');
|
||||
|
||||
// Load the cache infrastructure using a "fake" cache implementation that
|
||||
|
|
|
@ -424,8 +424,9 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
|
|||
registry_update();
|
||||
// Refresh the schema to include it.
|
||||
drupal_get_schema(NULL, TRUE);
|
||||
// Clear entity cache.
|
||||
entity_info_cache_clear();
|
||||
|
||||
// Allow modules to react prior to the installation of a module.
|
||||
module_invoke_all('modules_preinstall', array($module));
|
||||
|
||||
// Now install the module if necessary.
|
||||
if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) {
|
||||
|
@ -450,6 +451,9 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
|
|||
watchdog('system', '%module module installed.', array('%module' => $module), WATCHDOG_INFO);
|
||||
}
|
||||
|
||||
// Allow modules to react prior to the enabling of a module.
|
||||
module_invoke_all('modules_preenable', array($module));
|
||||
|
||||
// Enable the module.
|
||||
module_invoke($module, 'enable');
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ function update_prepare_d8_bootstrap() {
|
|||
// Allow the database system to work even if the registry has not been
|
||||
// created yet.
|
||||
include_once DRUPAL_ROOT . '/includes/install.inc';
|
||||
include_once DRUPAL_ROOT . '/modules/entity/entity.controller.inc';
|
||||
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
|
||||
|
||||
// If the site has not updated to Drupal 8 yet, check to make sure that it is
|
||||
|
@ -120,6 +121,35 @@ function update_fix_d8_requirements() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to install a new module in Drupal 8 via hook_update_N().
|
||||
*/
|
||||
function update_module_enable(array $modules) {
|
||||
foreach ($modules as $module) {
|
||||
// Check for initial schema and install it. The schema version of a newly
|
||||
// installed module is always 0. Using 8000 here would be inconsistent
|
||||
// since $module_update_8000() may involve a schema change, and we want
|
||||
// to install the schema as it was before any updates were added.
|
||||
$function = $module . '_schema_0';
|
||||
if (function_exists($function)) {
|
||||
$schema = $function();
|
||||
foreach ($schema as $table => $spec) {
|
||||
db_create_table($table, $spec);
|
||||
}
|
||||
}
|
||||
// Change the schema version from SCHEMA_UNINSTALLED to 0, so any module
|
||||
// updates since the module's inception are executed in a core upgrade.
|
||||
db_update('system')
|
||||
->condition('type', 'module')
|
||||
->condition('name', $module)
|
||||
->fields(array('schema_version' => 0))
|
||||
->execute();
|
||||
|
||||
system_list_reset();
|
||||
// @todo: figure out what to do about hook_install() and hook_enable().
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform one update and store the results for display on finished page.
|
||||
*
|
||||
|
|
|
@ -4,6 +4,7 @@ package = Core
|
|||
version = VERSION
|
||||
core = 8.x
|
||||
dependencies[] = text
|
||||
dependencies[] = entity
|
||||
files[] = comment.module
|
||||
files[] = comment.test
|
||||
configure = admin/content/comment
|
||||
|
|
|
@ -0,0 +1,414 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks provided the Entity module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Inform the base system and the Field API about one or more entity types.
|
||||
*
|
||||
* Inform the system about one or more entity types (i.e., object types that
|
||||
* can be loaded via entity_load() and, optionally, to which fields can be
|
||||
* attached).
|
||||
*
|
||||
* @return
|
||||
* An array whose keys are entity type names and whose values identify
|
||||
* properties of those types that the system needs to know about:
|
||||
* - label: The human-readable name of the type.
|
||||
* - controller class: The name of the class that is used to load the objects.
|
||||
* The class has to implement the DrupalEntityControllerInterface interface.
|
||||
* Leave blank to use the DrupalDefaultEntityController implementation.
|
||||
* - base table: (used by DrupalDefaultEntityController) The name of the
|
||||
* entity type's base table.
|
||||
* - static cache: (used by DrupalDefaultEntityController) FALSE to disable
|
||||
* static caching of entities during a page request. Defaults to TRUE.
|
||||
* - field cache: (used by Field API loading and saving of field data) FALSE
|
||||
* to disable Field API's persistent cache of field data. Only recommended
|
||||
* if a higher level persistent cache is available for the entity type.
|
||||
* Defaults to TRUE.
|
||||
* - load hook: The name of the hook which should be invoked by
|
||||
* DrupalDefaultEntityController:attachLoad(), for example 'node_load'.
|
||||
* - uri callback: A function taking an entity as argument and returning the
|
||||
* uri elements of the entity, e.g. 'path' and 'options'. The actual entity
|
||||
* uri can be constructed by passing these elements to url().
|
||||
* - label callback: (optional) A function taking an entity as argument and
|
||||
* returning the label of the entity. The entity label is the main string
|
||||
* associated with an entity; for example, the title of a node or the
|
||||
* subject of a comment. If there is an entity object property that defines
|
||||
* the label, use the 'label' element of the 'entity keys' return
|
||||
* value component to provide this information (see below). If more complex
|
||||
* logic is needed to determine the label of an entity, you can instead
|
||||
* specify a callback function here, which will be called to determine the
|
||||
* entity label. See also the entity_label() function, which implements this
|
||||
* logic.
|
||||
* - fieldable: Set to TRUE if you want your entity type to be fieldable.
|
||||
* - translation: An associative array of modules registered as field
|
||||
* translation handlers. Array keys are the module names, array values
|
||||
* can be any data structure the module uses to provide field translation.
|
||||
* Any empty value disallows the module to appear as a translation handler.
|
||||
* - entity keys: An array describing how the Field API can extract the
|
||||
* information it needs from the objects of the type. Elements:
|
||||
* - id: The name of the property that contains the primary id of the
|
||||
* entity. Every entity object passed to the Field API must have this
|
||||
* property and its value must be numeric.
|
||||
* - revision: The name of the property that contains the revision id of
|
||||
* the entity. The Field API assumes that all revision ids are unique
|
||||
* across all entities of a type. This entry can be omitted if the
|
||||
* entities of this type are not versionable.
|
||||
* - bundle: The name of the property that contains the bundle name for the
|
||||
* entity. The bundle name defines which set of fields are attached to
|
||||
* the entity (e.g. what nodes call "content type"). This entry can be
|
||||
* omitted if this entity type exposes a single bundle (all entities have
|
||||
* the same collection of fields). The name of this single bundle will be
|
||||
* the same as the entity type.
|
||||
* - label: The name of the property that contains the entity label. For
|
||||
* example, if the entity's label is located in $entity->subject, then
|
||||
* 'subject' should be specified here. If complex logic is required to
|
||||
* build the label, a 'label callback' should be defined instead (see
|
||||
* the 'label callback' section above for details).
|
||||
* - bundle keys: An array describing how the Field API can extract the
|
||||
* information it needs from the bundle objects for this type (e.g
|
||||
* $vocabulary objects for terms; not applicable for nodes). This entry can
|
||||
* be omitted if this type's bundles do not exist as standalone objects.
|
||||
* Elements:
|
||||
* - bundle: The name of the property that contains the name of the bundle
|
||||
* object.
|
||||
* - bundles: An array describing all bundles for this object type. Keys are
|
||||
* bundles machine names, as found in the objects' 'bundle' property
|
||||
* (defined in the 'entity keys' entry above). Elements:
|
||||
* - label: The human-readable name of the bundle.
|
||||
* - uri callback: Same as the 'uri callback' key documented above for the
|
||||
* entity type, but for the bundle only. When determining the URI of an
|
||||
* entity, if a 'uri callback' is defined for both the entity type and
|
||||
* the bundle, the one for the bundle is used.
|
||||
* - admin: An array of information that allows Field UI pages to attach
|
||||
* themselves to the existing administration pages for the bundle.
|
||||
* Elements:
|
||||
* - path: the path of the bundle's main administration page, as defined
|
||||
* in hook_menu(). If the path includes a placeholder for the bundle,
|
||||
* the 'bundle argument', 'bundle helper' and 'real path' keys below
|
||||
* are required.
|
||||
* - bundle argument: The position of the placeholder in 'path', if any.
|
||||
* - real path: The actual path (no placeholder) of the bundle's main
|
||||
* administration page. This will be used to generate links.
|
||||
* - access callback: As in hook_menu(). 'user_access' will be assumed if
|
||||
* no value is provided.
|
||||
* - access arguments: As in hook_menu().
|
||||
* - view modes: An array describing the view modes for the entity type. View
|
||||
* modes let entities be displayed differently depending on the context.
|
||||
* For instance, a node can be displayed differently on its own page
|
||||
* ('full' mode), on the home page or taxonomy listings ('teaser' mode), or
|
||||
* in an RSS feed ('rss' mode). Modules taking part in the display of the
|
||||
* entity (notably the Field API) can adjust their behavior depending on
|
||||
* the requested view mode. An additional 'default' view mode is available
|
||||
* for all entity types. This view mode is not intended for actual entity
|
||||
* display, but holds default display settings. For each available view
|
||||
* mode, administrators can configure whether it should use its own set of
|
||||
* field display settings, or just replicate the settings of the 'default'
|
||||
* view mode, thus reducing the amount of display configurations to keep
|
||||
* track of. Keys of the array are view mode names. Each view mode is
|
||||
* described by an array with the following key/value pairs:
|
||||
* - label: The human-readable name of the view mode
|
||||
* - custom settings: A boolean specifying whether the view mode should by
|
||||
* default use its own custom field display settings. If FALSE, entities
|
||||
* displayed in this view mode will reuse the 'default' display settings
|
||||
* by default (e.g. right after the module exposing the view mode is
|
||||
* enabled), but administrators can later use the Field UI to apply custom
|
||||
* display settings specific to the view mode.
|
||||
*
|
||||
* @see entity_load()
|
||||
* @see hook_entity_info_alter()
|
||||
*/
|
||||
function hook_entity_info() {
|
||||
$return = array(
|
||||
'node' => array(
|
||||
'label' => t('Node'),
|
||||
'controller class' => 'NodeController',
|
||||
'base table' => 'node',
|
||||
'revision table' => 'node_revision',
|
||||
'uri callback' => 'node_uri',
|
||||
'fieldable' => TRUE,
|
||||
'translation' => array(
|
||||
'locale' => TRUE,
|
||||
),
|
||||
'entity keys' => array(
|
||||
'id' => 'nid',
|
||||
'revision' => 'vid',
|
||||
'bundle' => 'type',
|
||||
),
|
||||
'bundle keys' => array(
|
||||
'bundle' => 'type',
|
||||
),
|
||||
'bundles' => array(),
|
||||
'view modes' => array(
|
||||
'full' => array(
|
||||
'label' => t('Full content'),
|
||||
'custom settings' => FALSE,
|
||||
),
|
||||
'teaser' => array(
|
||||
'label' => t('Teaser'),
|
||||
'custom settings' => TRUE,
|
||||
),
|
||||
'rss' => array(
|
||||
'label' => t('RSS'),
|
||||
'custom settings' => FALSE,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Search integration is provided by node.module, so search-related
|
||||
// view modes for nodes are defined here and not in search.module.
|
||||
if (module_exists('search')) {
|
||||
$return['node']['view modes'] += array(
|
||||
'search_index' => array(
|
||||
'label' => t('Search index'),
|
||||
'custom settings' => FALSE,
|
||||
),
|
||||
'search_result' => array(
|
||||
'label' => t('Search result'),
|
||||
'custom settings' => FALSE,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Bundles must provide a human readable name so we can create help and error
|
||||
// messages, and the path to attach Field admin pages to.
|
||||
foreach (node_type_get_names() as $type => $name) {
|
||||
$return['node']['bundles'][$type] = array(
|
||||
'label' => $name,
|
||||
'admin' => array(
|
||||
'path' => 'admin/structure/types/manage/%node_type',
|
||||
'real path' => 'admin/structure/types/manage/' . str_replace('_', '-', $type),
|
||||
'bundle argument' => 4,
|
||||
'access arguments' => array('administer content types'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the entity info.
|
||||
*
|
||||
* Modules may implement this hook to alter the information that defines an
|
||||
* entity. All properties that are available in hook_entity_info() can be
|
||||
* altered here.
|
||||
*
|
||||
* @param $entity_info
|
||||
* The entity info array, keyed by entity name.
|
||||
*
|
||||
* @see hook_entity_info()
|
||||
*/
|
||||
function hook_entity_info_alter(&$entity_info) {
|
||||
// Set the controller class for nodes to an alternate implementation of the
|
||||
// DrupalEntityController interface.
|
||||
$entity_info['node']['controller class'] = 'MyCustomNodeController';
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities when loaded.
|
||||
*
|
||||
* This is a generic load hook called for all entity types loaded via the
|
||||
* entity API.
|
||||
*
|
||||
* @param $entities
|
||||
* The entities keyed by entity ID.
|
||||
* @param $type
|
||||
* The type of entities being loaded (i.e. node, user, comment).
|
||||
*/
|
||||
function hook_entity_load($entities, $type) {
|
||||
foreach ($entities as $entity) {
|
||||
$entity->foo = mymodule_add_something($entity, $type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on an entity before it is about to be created or updated.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
* @param $type
|
||||
* The type of entity being saved (i.e. node, user, comment).
|
||||
*/
|
||||
function hook_entity_presave($entity, $type) {
|
||||
$entity->changed = REQUEST_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities when inserted.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
* @param $type
|
||||
* The type of entity being inserted (i.e. node, user, comment).
|
||||
*/
|
||||
function hook_entity_insert($entity, $type) {
|
||||
// Insert the new entity into a fictional table of all entities.
|
||||
$info = entity_get_info($type);
|
||||
list($id) = entity_extract_ids($type, $entity);
|
||||
db_insert('example_entity')
|
||||
->fields(array(
|
||||
'type' => $type,
|
||||
'id' => $id,
|
||||
'created' => REQUEST_TIME,
|
||||
'updated' => REQUEST_TIME,
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities when updated.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
* @param $type
|
||||
* The type of entity being updated (i.e. node, user, comment).
|
||||
*/
|
||||
function hook_entity_update($entity, $type) {
|
||||
// Update the entity's entry in a fictional table of all entities.
|
||||
$info = entity_get_info($type);
|
||||
list($id) = entity_extract_ids($type, $entity);
|
||||
db_update('example_entity')
|
||||
->fields(array(
|
||||
'updated' => REQUEST_TIME,
|
||||
))
|
||||
->condition('type', $type)
|
||||
->condition('id', $id)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities when deleted.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
* @param $type
|
||||
* The type of entity being deleted (i.e. node, user, comment).
|
||||
*/
|
||||
function hook_entity_delete($entity, $type) {
|
||||
// Delete the entity's entry from a fictional table of all entities.
|
||||
$info = entity_get_info($type);
|
||||
list($id) = entity_extract_ids($type, $entity);
|
||||
db_delete('example_entity')
|
||||
->condition('type', $type)
|
||||
->condition('id', $id)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter or execute an EntityFieldQuery.
|
||||
*
|
||||
* @param EntityFieldQuery $query
|
||||
* An EntityFieldQuery. One of the most important properties to be changed is
|
||||
* EntityFieldQuery::executeCallback. If this is set to an existing function,
|
||||
* this function will get the query as its single argument and its result
|
||||
* will be the returned as the result of EntityFieldQuery::execute(). This can
|
||||
* be used to change the behavior of EntityFieldQuery entirely. For example,
|
||||
* the default implementation can only deal with one field storage engine, but
|
||||
* it is possible to write a module that can query across field storage
|
||||
* engines. Also, the default implementation presumes entities are stored in
|
||||
* SQL, but the execute callback could instead query any other entity storage,
|
||||
* local or remote.
|
||||
*
|
||||
* Note the $query->altered attribute which is TRUE in case the query has
|
||||
* already been altered once. This happens with cloned queries.
|
||||
* If there is a pager, then such a cloned query will be executed to count
|
||||
* all elements. This query can be detected by checking for
|
||||
* ($query->pager && $query->count), allowing the driver to return 0 from
|
||||
* the count query and disable the pager.
|
||||
*/
|
||||
function hook_entity_query_alter($query) {
|
||||
$query->executeCallback = 'my_module_query_callback';
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities being assembled before rendering.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
* @param $type
|
||||
* The type of entity being rendered (i.e. node, user, comment).
|
||||
* @param $view_mode
|
||||
* The view mode the entity is rendered in.
|
||||
* @param $langcode
|
||||
* The language code used for rendering.
|
||||
*
|
||||
* The module may add elements to $entity->content prior to rendering. The
|
||||
* structure of $entity->content is a renderable array as expected by
|
||||
* drupal_render().
|
||||
*
|
||||
* @see hook_entity_view_alter()
|
||||
* @see hook_comment_view()
|
||||
* @see hook_node_view()
|
||||
* @see hook_user_view()
|
||||
*/
|
||||
function hook_entity_view($entity, $type, $view_mode, $langcode) {
|
||||
$entity->content['my_additional_field'] = array(
|
||||
'#markup' => $additional_field,
|
||||
'#weight' => 10,
|
||||
'#theme' => 'mymodule_my_additional_field',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the results of ENTITY_view().
|
||||
*
|
||||
* This hook is called after the content has been assembled in a structured
|
||||
* array and may be used for doing processing which requires that the complete
|
||||
* entity content structure has been built.
|
||||
*
|
||||
* If a module wishes to act on the rendered HTML of the entity rather than the
|
||||
* structured content array, it may use this hook to add a #post_render
|
||||
* callback. Alternatively, it could also implement hook_preprocess_ENTITY().
|
||||
* See drupal_render() and theme() for details.
|
||||
*
|
||||
* @param $build
|
||||
* A renderable array representing the entity content.
|
||||
* @param $type
|
||||
* The type of entity being rendered (i.e. node, user, comment).
|
||||
*
|
||||
* @see hook_entity_view()
|
||||
* @see hook_comment_view_alter()
|
||||
* @see hook_node_view_alter()
|
||||
* @see hook_taxonomy_term_view_alter()
|
||||
* @see hook_user_view_alter()
|
||||
*/
|
||||
function hook_entity_view_alter(&$build, $type) {
|
||||
if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
|
||||
// Change its weight.
|
||||
$build['an_additional_field']['#weight'] = -10;
|
||||
|
||||
// Add a #post_render callback to act on the rendered HTML of the entity.
|
||||
$build['#post_render'][] = 'my_module_node_post_render';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities as they are being prepared for view.
|
||||
*
|
||||
* Allows you to operate on multiple entities as they are being prepared for
|
||||
* view. Only use this if attaching the data during the entity_load() phase
|
||||
* is not appropriate, for example when attaching other 'entity' style objects.
|
||||
*
|
||||
* @param $entities
|
||||
* The entities keyed by entity ID.
|
||||
* @param $type
|
||||
* The type of entities being loaded (i.e. node, user, comment).
|
||||
*/
|
||||
function hook_entity_prepare_view($entities, $type) {
|
||||
// Load a specific node into the user object for later theming.
|
||||
if ($type == 'user') {
|
||||
$nodes = mymodule_get_user_nodes(array_keys($entities));
|
||||
foreach ($entities as $uid => $entity) {
|
||||
$entity->user_node = $nodes[$uid];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,390 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Entity API controller classes and interface.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for entity controller classes.
|
||||
*
|
||||
* All entity controller classes specified via the 'controller class' key
|
||||
* returned by hook_entity_info() or hook_entity_info_alter() have to implement
|
||||
* this interface.
|
||||
*
|
||||
* Most simple, SQL-based entity controllers will do better by extending
|
||||
* DrupalDefaultEntityController instead of implementing this interface
|
||||
* directly.
|
||||
*/
|
||||
interface DrupalEntityControllerInterface {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param $entityType
|
||||
* The entity type for which the instance is created.
|
||||
*/
|
||||
public function __construct($entityType);
|
||||
|
||||
/**
|
||||
* Resets the internal, static entity cache.
|
||||
*
|
||||
* @param $ids
|
||||
* (optional) If specified, the cache is reset for the entities with the
|
||||
* given ids only.
|
||||
*/
|
||||
public function resetCache(array $ids = NULL);
|
||||
|
||||
/**
|
||||
* Loads one or more entities.
|
||||
*
|
||||
* @param $ids
|
||||
* An array of entity IDs, or FALSE to load all entities.
|
||||
* @param $conditions
|
||||
* An array of conditions in the form 'field' => $value.
|
||||
*
|
||||
* @return
|
||||
* An array of entity objects indexed by their ids.
|
||||
*/
|
||||
public function load($ids = array(), $conditions = array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of DrupalEntityControllerInterface.
|
||||
*
|
||||
* This class can be used as-is by most simple entity types. Entity types
|
||||
* requiring special handling can extend the class.
|
||||
*/
|
||||
class DrupalDefaultEntityController implements DrupalEntityControllerInterface {
|
||||
|
||||
/**
|
||||
* Static cache of entities.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entityCache;
|
||||
|
||||
/**
|
||||
* Entity type for this controller instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* Array of information about the entity.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see entity_get_info()
|
||||
*/
|
||||
protected $entityInfo;
|
||||
|
||||
/**
|
||||
* Additional arguments to pass to hook_TYPE_load().
|
||||
*
|
||||
* Set before calling DrupalDefaultEntityController::attachLoad().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hookLoadArguments;
|
||||
|
||||
/**
|
||||
* Name of the entity's ID field in the entity database table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $idKey;
|
||||
|
||||
/**
|
||||
* Name of entity's revision database table field, if it supports revisions.
|
||||
*
|
||||
* Has the value FALSE if this entity does not use revisions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $revisionKey;
|
||||
|
||||
/**
|
||||
* The table that stores revisions, if the entity supports revisions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $revisionTable;
|
||||
|
||||
/**
|
||||
* Whether this entity type should use the static cache.
|
||||
*
|
||||
* Set by entity info.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Constructor: sets basic variables.
|
||||
*/
|
||||
public function __construct($entityType) {
|
||||
$this->entityType = $entityType;
|
||||
$this->entityInfo = entity_get_info($entityType);
|
||||
$this->entityCache = array();
|
||||
$this->hookLoadArguments = array();
|
||||
$this->idKey = $this->entityInfo['entity keys']['id'];
|
||||
|
||||
// Check if the entity type supports revisions.
|
||||
if (!empty($this->entityInfo['entity keys']['revision'])) {
|
||||
$this->revisionKey = $this->entityInfo['entity keys']['revision'];
|
||||
$this->revisionTable = $this->entityInfo['revision table'];
|
||||
}
|
||||
else {
|
||||
$this->revisionKey = FALSE;
|
||||
}
|
||||
|
||||
// Check if the entity type supports static caching of loaded entities.
|
||||
$this->cache = !empty($this->entityInfo['static cache']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements DrupalEntityControllerInterface::resetCache().
|
||||
*/
|
||||
public function resetCache(array $ids = NULL) {
|
||||
if (isset($ids)) {
|
||||
foreach ($ids as $id) {
|
||||
unset($this->entityCache[$id]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->entityCache = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements DrupalEntityControllerInterface::load().
|
||||
*/
|
||||
public function load($ids = array(), $conditions = array()) {
|
||||
$entities = array();
|
||||
|
||||
// Revisions are not statically cached, and require a different query to
|
||||
// other conditions, so separate the revision id into its own variable.
|
||||
if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
|
||||
$revision_id = $conditions[$this->revisionKey];
|
||||
unset($conditions[$this->revisionKey]);
|
||||
}
|
||||
else {
|
||||
$revision_id = FALSE;
|
||||
}
|
||||
|
||||
// Create a new variable which is either a prepared version of the $ids
|
||||
// array for later comparison with the entity cache, or FALSE if no $ids
|
||||
// were passed. The $ids array is reduced as items are loaded from cache,
|
||||
// and we need to know if it's empty for this reason to avoid querying the
|
||||
// database when all requested entities are loaded from cache.
|
||||
$passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
|
||||
// Try to load entities from the static cache, if the entity type supports
|
||||
// static caching.
|
||||
if ($this->cache && !$revision_id) {
|
||||
$entities += $this->cacheGet($ids, $conditions);
|
||||
// If any entities were loaded, remove them from the ids still to load.
|
||||
if ($passed_ids) {
|
||||
$ids = array_keys(array_diff_key($passed_ids, $entities));
|
||||
}
|
||||
}
|
||||
|
||||
// Load any remaining entities from the database. This is the case if $ids
|
||||
// is set to FALSE (so we load all entities), if there are any ids left to
|
||||
// load, if loading a revision, or if $conditions was passed without $ids.
|
||||
if ($ids === FALSE || $ids || $revision_id || ($conditions && !$passed_ids)) {
|
||||
// Build the query.
|
||||
$query = $this->buildQuery($ids, $conditions, $revision_id);
|
||||
$queried_entities = $query
|
||||
->execute()
|
||||
->fetchAllAssoc($this->idKey);
|
||||
}
|
||||
|
||||
// Pass all entities loaded from the database through $this->attachLoad(),
|
||||
// which attaches fields (if supported by the entity type) and calls the
|
||||
// entity type specific load callback, for example hook_node_load().
|
||||
if (!empty($queried_entities)) {
|
||||
$this->attachLoad($queried_entities, $revision_id);
|
||||
$entities += $queried_entities;
|
||||
}
|
||||
|
||||
if ($this->cache) {
|
||||
// Add entities to the cache if we are not loading a revision.
|
||||
if (!empty($queried_entities) && !$revision_id) {
|
||||
$this->cacheSet($queried_entities);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the returned array is ordered the same as the original
|
||||
// $ids array if this was passed in and remove any invalid ids.
|
||||
if ($passed_ids) {
|
||||
// Remove any invalid ids from the array.
|
||||
$passed_ids = array_intersect_key($passed_ids, $entities);
|
||||
foreach ($entities as $entity) {
|
||||
$passed_ids[$entity->{$this->idKey}] = $entity;
|
||||
}
|
||||
$entities = $passed_ids;
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the query to load the entity.
|
||||
*
|
||||
* This has full revision support. For entities requiring special queries,
|
||||
* the class can be extended, and the default query can be constructed by
|
||||
* calling parent::buildQuery(). This is usually necessary when the object
|
||||
* being loaded needs to be augmented with additional data from another
|
||||
* table, such as loading node type into comments or vocabulary machine name
|
||||
* into terms, however it can also support $conditions on different tables.
|
||||
* See CommentController::buildQuery() or TaxonomyTermController::buildQuery()
|
||||
* for examples.
|
||||
*
|
||||
* @param $ids
|
||||
* An array of entity IDs, or FALSE to load all entities.
|
||||
* @param $conditions
|
||||
* An array of conditions in the form 'field' => $value.
|
||||
* @param $revision_id
|
||||
* The ID of the revision to load, or FALSE if this query is asking for the
|
||||
* most current revision(s).
|
||||
*
|
||||
* @return SelectQuery
|
||||
* A SelectQuery object for loading the entity.
|
||||
*/
|
||||
protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
|
||||
$query = db_select($this->entityInfo['base table'], 'base');
|
||||
|
||||
$query->addTag($this->entityType . '_load_multiple');
|
||||
|
||||
if ($revision_id) {
|
||||
$query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} = :revisionId", array(':revisionId' => $revision_id));
|
||||
}
|
||||
elseif ($this->revisionKey) {
|
||||
$query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}");
|
||||
}
|
||||
|
||||
// Add fields from the {entity} table.
|
||||
$entity_fields = $this->entityInfo['schema_fields_sql']['base table'];
|
||||
|
||||
if ($this->revisionKey) {
|
||||
// Add all fields from the {entity_revision} table.
|
||||
$entity_revision_fields = drupal_map_assoc($this->entityInfo['schema_fields_sql']['revision table']);
|
||||
// The id field is provided by entity, so remove it.
|
||||
unset($entity_revision_fields[$this->idKey]);
|
||||
|
||||
// Remove all fields from the base table that are also fields by the same
|
||||
// name in the revision table.
|
||||
$entity_field_keys = array_flip($entity_fields);
|
||||
foreach ($entity_revision_fields as $key => $name) {
|
||||
if (isset($entity_field_keys[$name])) {
|
||||
unset($entity_fields[$entity_field_keys[$name]]);
|
||||
}
|
||||
}
|
||||
$query->fields('revision', $entity_revision_fields);
|
||||
}
|
||||
|
||||
$query->fields('base', $entity_fields);
|
||||
|
||||
if ($ids) {
|
||||
$query->condition("base.{$this->idKey}", $ids, 'IN');
|
||||
}
|
||||
if ($conditions) {
|
||||
foreach ($conditions as $field => $value) {
|
||||
$query->condition('base.' . $field, $value);
|
||||
}
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches data to entities upon loading.
|
||||
*
|
||||
* This will attach fields, if the entity is fieldable. It calls
|
||||
* hook_entity_load() for modules which need to add data to all entities.
|
||||
* It also calls hook_TYPE_load() on the loaded entities. For example
|
||||
* hook_node_load() or hook_user_load(). If your hook_TYPE_load()
|
||||
* expects special parameters apart from the queried entities, you can set
|
||||
* $this->hookLoadArguments prior to calling the method.
|
||||
* See NodeController::attachLoad() for an example.
|
||||
*
|
||||
* @param $queried_entities
|
||||
* Associative array of query results, keyed on the entity ID.
|
||||
* @param $revision_id
|
||||
* ID of the revision that was loaded, or FALSE if teh most current revision
|
||||
* was loaded.
|
||||
*/
|
||||
protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
|
||||
// Attach fields.
|
||||
if ($this->entityInfo['fieldable']) {
|
||||
if ($revision_id) {
|
||||
field_attach_load_revision($this->entityType, $queried_entities);
|
||||
}
|
||||
else {
|
||||
field_attach_load($this->entityType, $queried_entities);
|
||||
}
|
||||
}
|
||||
|
||||
// Call hook_entity_load().
|
||||
foreach (module_implements('entity_load') as $module) {
|
||||
$function = $module . '_entity_load';
|
||||
$function($queried_entities, $this->entityType);
|
||||
}
|
||||
// Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
|
||||
// always the queried entities, followed by additional arguments set in
|
||||
// $this->hookLoadArguments.
|
||||
$args = array_merge(array($queried_entities), $this->hookLoadArguments);
|
||||
foreach (module_implements($this->entityInfo['load hook']) as $module) {
|
||||
call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets entities from the static cache.
|
||||
*
|
||||
* @param $ids
|
||||
* If not empty, return entities that match these IDs.
|
||||
* @param $conditions
|
||||
* If set, return entities that match all of these conditions.
|
||||
*
|
||||
* @return
|
||||
* Array of entities from the entity cache.
|
||||
*/
|
||||
protected function cacheGet($ids, $conditions = array()) {
|
||||
$entities = array();
|
||||
// Load any available entities from the internal cache.
|
||||
if (!empty($this->entityCache)) {
|
||||
if ($ids) {
|
||||
$entities += array_intersect_key($this->entityCache, array_flip($ids));
|
||||
}
|
||||
// If loading entities only by conditions, fetch all available entities
|
||||
// from the cache. Entities which don't match are removed later.
|
||||
elseif ($conditions) {
|
||||
$entities = $this->entityCache;
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude any entities loaded from cache if they don't match $conditions.
|
||||
// This ensures the same behavior whether loading from memory or database.
|
||||
if ($conditions) {
|
||||
foreach ($entities as $entity) {
|
||||
$entity_values = (array) $entity;
|
||||
if (array_diff_assoc($conditions, $entity_values)) {
|
||||
unset($entities[$entity->{$this->idKey}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores entities in the static entity cache.
|
||||
*
|
||||
* @param $entities
|
||||
* Entities to store in the cache.
|
||||
*/
|
||||
protected function cacheSet($entities) {
|
||||
$this->entityCache += $entities;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
name = Entity
|
||||
description = API for managing entities like nodes and users.
|
||||
package = Core
|
||||
version = VERSION
|
||||
core = 8.x
|
||||
required = TRUE
|
||||
files[] = entity.query.inc
|
||||
files[] = entity.controller.inc
|
|
@ -0,0 +1,442 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Entity API for handling entities like nodes or users.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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/handbook/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.
|
||||
*
|
||||
* @see hook_entity_info()
|
||||
* @see hook_entity_info_alter()
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
function entity_get_info($entity_type = NULL) {
|
||||
global $language;
|
||||
|
||||
// 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->language;
|
||||
|
||||
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' => 'DrupalDefaultEntityController',
|
||||
'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
|
||||
// DrupalEntityControllerInterface::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:');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to extract id, vid, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to assemble 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 structure, 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 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
|
||||
* DrupalEntityControllerInterface interface. By default,
|
||||
* DrupalDefaultEntityController 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
|
||||
* DrupalEntityControllerInterface interface, or, most commonly, extend the
|
||||
* DrupalDefaultEntityController class. See node_entity_info() and the
|
||||
* NodeController in node.module as an example.
|
||||
*
|
||||
* @see hook_entity_info()
|
||||
* @see DrupalEntityControllerInterface
|
||||
* @see DrupalDefaultEntityController
|
||||
* @see EntityFieldQuery
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type to load, e.g. node or user.
|
||||
* @param $ids
|
||||
* An array of entity IDs, or FALSE to load all entities.
|
||||
* @param $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 $reset
|
||||
* Whether to reset the internal cache for the requested entity type.
|
||||
*
|
||||
* @return
|
||||
* An array of entity objects indexed by their ids.
|
||||
*
|
||||
* @todo Remove $conditions in Drupal 8.
|
||||
*/
|
||||
function entity_load($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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity controller class for an entity type.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @see hook_entity_prepare_view()
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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) && function_exists($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.
|
||||
*/
|
||||
function entity_label($entity_type, $entity) {
|
||||
$label = FALSE;
|
||||
$info = entity_get_info($entity_type);
|
||||
if (isset($info['label callback']) && function_exists($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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for attaching 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for copying 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when a malformed entity is passed.
|
||||
*/
|
||||
class EntityMalformedException extends Exception { }
|
||||
|
|
@ -1,388 +1,9 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Interface for entity controller classes.
|
||||
*
|
||||
* All entity controller classes specified via the 'controller class' key
|
||||
* returned by hook_entity_info() or hook_entity_info_alter() have to implement
|
||||
* this interface.
|
||||
*
|
||||
* Most simple, SQL-based entity controllers will do better by extending
|
||||
* DrupalDefaultEntityController instead of implementing this interface
|
||||
* directly.
|
||||
* @file
|
||||
* Entity query API.
|
||||
*/
|
||||
interface DrupalEntityControllerInterface {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param $entityType
|
||||
* The entity type for which the instance is created.
|
||||
*/
|
||||
public function __construct($entityType);
|
||||
|
||||
/**
|
||||
* Resets the internal, static entity cache.
|
||||
*
|
||||
* @param $ids
|
||||
* (optional) If specified, the cache is reset for the entities with the
|
||||
* given ids only.
|
||||
*/
|
||||
public function resetCache(array $ids = NULL);
|
||||
|
||||
/**
|
||||
* Loads one or more entities.
|
||||
*
|
||||
* @param $ids
|
||||
* An array of entity IDs, or FALSE to load all entities.
|
||||
* @param $conditions
|
||||
* An array of conditions in the form 'field' => $value.
|
||||
*
|
||||
* @return
|
||||
* An array of entity objects indexed by their ids. When no results are
|
||||
* found, an empty array is returned.
|
||||
*/
|
||||
public function load($ids = array(), $conditions = array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of DrupalEntityControllerInterface.
|
||||
*
|
||||
* This class can be used as-is by most simple entity types. Entity types
|
||||
* requiring special handling can extend the class.
|
||||
*/
|
||||
class DrupalDefaultEntityController implements DrupalEntityControllerInterface {
|
||||
|
||||
/**
|
||||
* Static cache of entities.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entityCache;
|
||||
|
||||
/**
|
||||
* Entity type for this controller instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* Array of information about the entity.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see entity_get_info()
|
||||
*/
|
||||
protected $entityInfo;
|
||||
|
||||
/**
|
||||
* Additional arguments to pass to hook_TYPE_load().
|
||||
*
|
||||
* Set before calling DrupalDefaultEntityController::attachLoad().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hookLoadArguments;
|
||||
|
||||
/**
|
||||
* Name of the entity's ID field in the entity database table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $idKey;
|
||||
|
||||
/**
|
||||
* Name of entity's revision database table field, if it supports revisions.
|
||||
*
|
||||
* Has the value FALSE if this entity does not use revisions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $revisionKey;
|
||||
|
||||
/**
|
||||
* The table that stores revisions, if the entity supports revisions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $revisionTable;
|
||||
|
||||
/**
|
||||
* Whether this entity type should use the static cache.
|
||||
*
|
||||
* Set by entity info.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Constructor: sets basic variables.
|
||||
*/
|
||||
public function __construct($entityType) {
|
||||
$this->entityType = $entityType;
|
||||
$this->entityInfo = entity_get_info($entityType);
|
||||
$this->entityCache = array();
|
||||
$this->hookLoadArguments = array();
|
||||
$this->idKey = $this->entityInfo['entity keys']['id'];
|
||||
|
||||
// Check if the entity type supports revisions.
|
||||
if (!empty($this->entityInfo['entity keys']['revision'])) {
|
||||
$this->revisionKey = $this->entityInfo['entity keys']['revision'];
|
||||
$this->revisionTable = $this->entityInfo['revision table'];
|
||||
}
|
||||
else {
|
||||
$this->revisionKey = FALSE;
|
||||
}
|
||||
|
||||
// Check if the entity type supports static caching of loaded entities.
|
||||
$this->cache = !empty($this->entityInfo['static cache']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements DrupalEntityControllerInterface::resetCache().
|
||||
*/
|
||||
public function resetCache(array $ids = NULL) {
|
||||
if (isset($ids)) {
|
||||
foreach ($ids as $id) {
|
||||
unset($this->entityCache[$id]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->entityCache = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements DrupalEntityControllerInterface::load().
|
||||
*/
|
||||
public function load($ids = array(), $conditions = array()) {
|
||||
$entities = array();
|
||||
|
||||
// Revisions are not statically cached, and require a different query to
|
||||
// other conditions, so separate the revision id into its own variable.
|
||||
if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
|
||||
$revision_id = $conditions[$this->revisionKey];
|
||||
unset($conditions[$this->revisionKey]);
|
||||
}
|
||||
else {
|
||||
$revision_id = FALSE;
|
||||
}
|
||||
|
||||
// Create a new variable which is either a prepared version of the $ids
|
||||
// array for later comparison with the entity cache, or FALSE if no $ids
|
||||
// were passed. The $ids array is reduced as items are loaded from cache,
|
||||
// and we need to know if it's empty for this reason to avoid querying the
|
||||
// database when all requested entities are loaded from cache.
|
||||
$passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
|
||||
// Try to load entities from the static cache, if the entity type supports
|
||||
// static caching.
|
||||
if ($this->cache && !$revision_id) {
|
||||
$entities += $this->cacheGet($ids, $conditions);
|
||||
// If any entities were loaded, remove them from the ids still to load.
|
||||
if ($passed_ids) {
|
||||
$ids = array_keys(array_diff_key($passed_ids, $entities));
|
||||
}
|
||||
}
|
||||
|
||||
// Load any remaining entities from the database. This is the case if $ids
|
||||
// is set to FALSE (so we load all entities), if there are any ids left to
|
||||
// load, if loading a revision, or if $conditions was passed without $ids.
|
||||
if ($ids === FALSE || $ids || $revision_id || ($conditions && !$passed_ids)) {
|
||||
// Build the query.
|
||||
$query = $this->buildQuery($ids, $conditions, $revision_id);
|
||||
$queried_entities = $query
|
||||
->execute()
|
||||
->fetchAllAssoc($this->idKey);
|
||||
}
|
||||
|
||||
// Pass all entities loaded from the database through $this->attachLoad(),
|
||||
// which attaches fields (if supported by the entity type) and calls the
|
||||
// entity type specific load callback, for example hook_node_load().
|
||||
if (!empty($queried_entities)) {
|
||||
$this->attachLoad($queried_entities, $revision_id);
|
||||
$entities += $queried_entities;
|
||||
}
|
||||
|
||||
if ($this->cache) {
|
||||
// Add entities to the cache if we are not loading a revision.
|
||||
if (!empty($queried_entities) && !$revision_id) {
|
||||
$this->cacheSet($queried_entities);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the returned array is ordered the same as the original
|
||||
// $ids array if this was passed in and remove any invalid ids.
|
||||
if ($passed_ids) {
|
||||
// Remove any invalid ids from the array.
|
||||
$passed_ids = array_intersect_key($passed_ids, $entities);
|
||||
foreach ($entities as $entity) {
|
||||
$passed_ids[$entity->{$this->idKey}] = $entity;
|
||||
}
|
||||
$entities = $passed_ids;
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the query to load the entity.
|
||||
*
|
||||
* This has full revision support. For entities requiring special queries,
|
||||
* the class can be extended, and the default query can be constructed by
|
||||
* calling parent::buildQuery(). This is usually necessary when the object
|
||||
* being loaded needs to be augmented with additional data from another
|
||||
* table, such as loading node type into comments or vocabulary machine name
|
||||
* into terms, however it can also support $conditions on different tables.
|
||||
* See CommentController::buildQuery() or TaxonomyTermController::buildQuery()
|
||||
* for examples.
|
||||
*
|
||||
* @param $ids
|
||||
* An array of entity IDs, or FALSE to load all entities.
|
||||
* @param $conditions
|
||||
* An array of conditions in the form 'field' => $value.
|
||||
* @param $revision_id
|
||||
* The ID of the revision to load, or FALSE if this query is asking for the
|
||||
* most current revision(s).
|
||||
*
|
||||
* @return SelectQuery
|
||||
* A SelectQuery object for loading the entity.
|
||||
*/
|
||||
protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
|
||||
$query = db_select($this->entityInfo['base table'], 'base');
|
||||
|
||||
$query->addTag($this->entityType . '_load_multiple');
|
||||
|
||||
if ($revision_id) {
|
||||
$query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} = :revisionId", array(':revisionId' => $revision_id));
|
||||
}
|
||||
elseif ($this->revisionKey) {
|
||||
$query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}");
|
||||
}
|
||||
|
||||
// Add fields from the {entity} table.
|
||||
$entity_fields = $this->entityInfo['schema_fields_sql']['base table'];
|
||||
|
||||
if ($this->revisionKey) {
|
||||
// Add all fields from the {entity_revision} table.
|
||||
$entity_revision_fields = drupal_map_assoc($this->entityInfo['schema_fields_sql']['revision table']);
|
||||
// The id field is provided by entity, so remove it.
|
||||
unset($entity_revision_fields[$this->idKey]);
|
||||
|
||||
// Remove all fields from the base table that are also fields by the same
|
||||
// name in the revision table.
|
||||
$entity_field_keys = array_flip($entity_fields);
|
||||
foreach ($entity_revision_fields as $key => $name) {
|
||||
if (isset($entity_field_keys[$name])) {
|
||||
unset($entity_fields[$entity_field_keys[$name]]);
|
||||
}
|
||||
}
|
||||
$query->fields('revision', $entity_revision_fields);
|
||||
}
|
||||
|
||||
$query->fields('base', $entity_fields);
|
||||
|
||||
if ($ids) {
|
||||
$query->condition("base.{$this->idKey}", $ids, 'IN');
|
||||
}
|
||||
if ($conditions) {
|
||||
foreach ($conditions as $field => $value) {
|
||||
$query->condition('base.' . $field, $value);
|
||||
}
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches data to entities upon loading.
|
||||
* This will attach fields, if the entity is fieldable. It calls
|
||||
* hook_entity_load() for modules which need to add data to all entities.
|
||||
* It also calls hook_TYPE_load() on the loaded entities. For example
|
||||
* hook_node_load() or hook_user_load(). If your hook_TYPE_load()
|
||||
* expects special parameters apart from the queried entities, you can set
|
||||
* $this->hookLoadArguments prior to calling the method.
|
||||
* See NodeController::attachLoad() for an example.
|
||||
*
|
||||
* @param $queried_entities
|
||||
* Associative array of query results, keyed on the entity ID.
|
||||
* @param $revision_id
|
||||
* ID of the revision that was loaded, or FALSE if teh most current revision
|
||||
* was loaded.
|
||||
*/
|
||||
protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
|
||||
// Attach fields.
|
||||
if ($this->entityInfo['fieldable']) {
|
||||
if ($revision_id) {
|
||||
field_attach_load_revision($this->entityType, $queried_entities);
|
||||
}
|
||||
else {
|
||||
field_attach_load($this->entityType, $queried_entities);
|
||||
}
|
||||
}
|
||||
|
||||
// Call hook_entity_load().
|
||||
foreach (module_implements('entity_load') as $module) {
|
||||
$function = $module . '_entity_load';
|
||||
$function($queried_entities, $this->entityType);
|
||||
}
|
||||
// Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
|
||||
// always the queried entities, followed by additional arguments set in
|
||||
// $this->hookLoadArguments.
|
||||
$args = array_merge(array($queried_entities), $this->hookLoadArguments);
|
||||
foreach (module_implements($this->entityInfo['load hook']) as $module) {
|
||||
call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets entities from the static cache.
|
||||
*
|
||||
* @param $ids
|
||||
* If not empty, return entities that match these IDs.
|
||||
* @param $conditions
|
||||
* If set, return entities that match all of these conditions.
|
||||
*
|
||||
* @return
|
||||
* Array of entities from the entity cache.
|
||||
*/
|
||||
protected function cacheGet($ids, $conditions = array()) {
|
||||
$entities = array();
|
||||
// Load any available entities from the internal cache.
|
||||
if (!empty($this->entityCache)) {
|
||||
if ($ids) {
|
||||
$entities += array_intersect_key($this->entityCache, array_flip($ids));
|
||||
}
|
||||
// If loading entities only by conditions, fetch all available entities
|
||||
// from the cache. Entities which don't match are removed later.
|
||||
elseif ($conditions) {
|
||||
$entities = $this->entityCache;
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude any entities loaded from cache if they don't match $conditions.
|
||||
// This ensures the same behavior whether loading from memory or database.
|
||||
if ($conditions) {
|
||||
foreach ($entities as $entity) {
|
||||
$entity_values = (array) $entity;
|
||||
if (array_diff_assoc($conditions, $entity_values)) {
|
||||
unset($entities[$entity->{$this->idKey}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores entities in the static entity cache.
|
||||
*
|
||||
* @param $entities
|
||||
* Entities to store in the cache.
|
||||
*/
|
||||
protected function cacheSet($entities) {
|
||||
$this->entityCache += $entities;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown by EntityFieldQuery() on unsupported query syntax.
|
||||
|
@ -938,7 +559,7 @@ class EntityFieldQuery {
|
|||
}
|
||||
|
||||
/**
|
||||
* Enable a pager for the query.
|
||||
* Enables a pager for the query.
|
||||
*
|
||||
* @param $limit
|
||||
* An integer specifying the number of elements per page. If passed a false
|
||||
|
@ -966,7 +587,7 @@ class EntityFieldQuery {
|
|||
}
|
||||
|
||||
/**
|
||||
* Enable sortable tables for this query.
|
||||
* Enables sortable tables for this query.
|
||||
*
|
||||
* @param $headers
|
||||
* An EFQ Header array based on which the order clause is added to the query.
|
||||
|
@ -1243,7 +864,7 @@ class EntityFieldQuery {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the total number of results and initialize a pager for the query.
|
||||
* Gets the total number of results and initialize a pager for the query.
|
||||
*
|
||||
* This query can be detected by checking for ($this->pager && $this->count),
|
||||
* which allows a driver to return 0 from the count query and disable
|
||||
|
@ -1330,7 +951,3 @@ class EntityFieldQuery {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when a malformed entity is passed.
|
||||
*/
|
||||
class EntityMalformedException extends Exception { }
|
|
@ -1,6 +1,5 @@
|
|||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Unit test file for the entity API.
|
|
@ -7,5 +7,6 @@ files[] = field.module
|
|||
files[] = field.attach.inc
|
||||
files[] = tests/field.test
|
||||
dependencies[] = field_sql_storage
|
||||
dependencies[] = entity
|
||||
required = TRUE
|
||||
stylesheets[all][] = theme/field.css
|
||||
|
|
|
@ -6,5 +6,6 @@ core = 8.x
|
|||
files[] = node.module
|
||||
files[] = node.test
|
||||
required = TRUE
|
||||
dependencies[] = entity
|
||||
configure = admin/structure/types
|
||||
stylesheets[all][] = node.css
|
||||
|
|
|
@ -59,392 +59,6 @@ function hook_hook_info_alter(&$hooks) {
|
|||
$hooks['tokens']['group'] = 'mytokens';
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform the base system and the Field API about one or more entity types.
|
||||
*
|
||||
* Inform the system about one or more entity types (i.e., object types that
|
||||
* can be loaded via entity_load() and, optionally, to which fields can be
|
||||
* attached).
|
||||
*
|
||||
* @return
|
||||
* An array whose keys are entity type names and whose values identify
|
||||
* properties of those types that the system needs to know about:
|
||||
* - label: The human-readable name of the type.
|
||||
* - controller class: The name of the class that is used to load the objects.
|
||||
* The class has to implement the DrupalEntityControllerInterface interface.
|
||||
* Leave blank to use the DrupalDefaultEntityController implementation.
|
||||
* - base table: (used by DrupalDefaultEntityController) The name of the
|
||||
* entity type's base table.
|
||||
* - revision table: The name of the entity type's revision table (if any).
|
||||
* - static cache: (used by DrupalDefaultEntityController) FALSE to disable
|
||||
* static caching of entities during a page request. Defaults to TRUE.
|
||||
* - field cache: (used by Field API loading and saving of field data) FALSE
|
||||
* to disable Field API's persistent cache of field data. Only recommended
|
||||
* if a higher level persistent cache is available for the entity type.
|
||||
* Defaults to TRUE.
|
||||
* - load hook: The name of the hook which should be invoked by
|
||||
* DrupalDefaultEntityController:attachLoad(), for example 'node_load'.
|
||||
* - uri callback: A function taking an entity as argument and returning the
|
||||
* uri elements of the entity, e.g. 'path' and 'options'. The actual entity
|
||||
* uri can be constructed by passing these elements to url().
|
||||
* - label callback: (optional) A function taking an entity type and an entity
|
||||
* as arguments and returning the label of the entity. The entity label is
|
||||
* the main string associated with an entity; for example, the title of a
|
||||
* node or the subject of a comment. If there is an entity object property
|
||||
* that defines the label, use the 'label' element of the 'entity keys'
|
||||
* return value component to provide this information (see below). If more
|
||||
* complex logic is needed to determine the label of an entity, you can
|
||||
* instead specify a callback function here, which will be called to
|
||||
* determine the entity label. See also the entity_label() function, which
|
||||
* implements this logic.
|
||||
* - fieldable: Set to TRUE if you want your entity type to accept fields
|
||||
* being attached to it.
|
||||
* - translation: An associative array of modules registered as field
|
||||
* translation handlers. Array keys are the module names, array values
|
||||
* can be any data structure the module uses to provide field translation.
|
||||
* Any empty value disallows the module to appear as a translation handler.
|
||||
* - entity keys: An array describing how the Field API can extract the
|
||||
* information it needs from the objects of the type. Elements:
|
||||
* - id: The name of the property that contains the primary id of the
|
||||
* entity. Every entity object passed to the Field API must have this
|
||||
* property and its value must be numeric.
|
||||
* - revision: The name of the property that contains the revision id of
|
||||
* the entity. The Field API assumes that all revision ids are unique
|
||||
* across all entities of a type. This entry can be omitted if the
|
||||
* entities of this type are not versionable.
|
||||
* - bundle: The name of the property that contains the bundle name for the
|
||||
* entity. The bundle name defines which set of fields are attached to
|
||||
* the entity (e.g. what nodes call "content type"). This entry can be
|
||||
* omitted if this entity type exposes a single bundle (all entities have
|
||||
* the same collection of fields). The name of this single bundle will be
|
||||
* the same as the entity type.
|
||||
* - label: The name of the property that contains the entity label. For
|
||||
* example, if the entity's label is located in $entity->subject, then
|
||||
* 'subject' should be specified here. If complex logic is required to
|
||||
* build the label, a 'label callback' should be defined instead (see
|
||||
* the 'label callback' section above for details).
|
||||
* - bundle keys: An array describing how the Field API can extract the
|
||||
* information it needs from the bundle objects for this type. This entry
|
||||
* is required if the 'path' provided in the 'bundles'/'admin' section
|
||||
* identifies the bundle using a named menu placeholder whose loader
|
||||
* callback returns an object (e.g., $vocabulary for taxonomy terms, or
|
||||
* $node_type for nodes). If the path does not include the bundle, or the
|
||||
* bundle is just a string rather than an automatically loaded object, then
|
||||
* this can be omitted. Elements:
|
||||
* - bundle: The name of the property of the bundle object that contains
|
||||
* the name of the bundle object.
|
||||
* - bundles: An array describing all bundles for this object type. Keys are
|
||||
* bundles machine names, as found in the objects' 'bundle' property
|
||||
* (defined in the 'entity keys' entry above). Elements:
|
||||
* - label: The human-readable name of the bundle.
|
||||
* - uri callback: Same as the 'uri callback' key documented above for the
|
||||
* entity type, but for the bundle only. When determining the URI of an
|
||||
* entity, if a 'uri callback' is defined for both the entity type and
|
||||
* the bundle, the one for the bundle is used.
|
||||
* - admin: An array of information that allows Field UI pages to attach
|
||||
* themselves to the existing administration pages for the bundle.
|
||||
* Elements:
|
||||
* - path: the path of the bundle's main administration page, as defined
|
||||
* in hook_menu(). If the path includes a placeholder for the bundle,
|
||||
* the 'bundle argument' and 'real path' keys below are required.
|
||||
* - bundle argument: The position of the bundle placeholder in 'path', if
|
||||
* any.
|
||||
* - real path: The actual path (no placeholder) of the bundle's main
|
||||
* administration page. This will be used to generate links.
|
||||
* - access callback: As in hook_menu(). 'user_access' will be assumed if
|
||||
* no value is provided.
|
||||
* - access arguments: As in hook_menu().
|
||||
* - view modes: An array describing the view modes for the entity type. View
|
||||
* modes let entities be displayed differently depending on the context.
|
||||
* For instance, a node can be displayed differently on its own page
|
||||
* ('full' mode), on the home page or taxonomy listings ('teaser' mode), or
|
||||
* in an RSS feed ('rss' mode). Modules taking part in the display of the
|
||||
* entity (notably the Field API) can adjust their behavior depending on
|
||||
* the requested view mode. An additional 'default' view mode is available
|
||||
* for all entity types. This view mode is not intended for actual entity
|
||||
* display, but holds default display settings. For each available view
|
||||
* mode, administrators can configure whether it should use its own set of
|
||||
* field display settings, or just replicate the settings of the 'default'
|
||||
* view mode, thus reducing the amount of display configurations to keep
|
||||
* track of. Keys of the array are view mode names. Each view mode is
|
||||
* described by an array with the following key/value pairs:
|
||||
* - label: The human-readable name of the view mode
|
||||
* - custom settings: A boolean specifying whether the view mode should by
|
||||
* default use its own custom field display settings. If FALSE, entities
|
||||
* displayed in this view mode will reuse the 'default' display settings
|
||||
* by default (e.g. right after the module exposing the view mode is
|
||||
* enabled), but administrators can later use the Field UI to apply custom
|
||||
* display settings specific to the view mode.
|
||||
*
|
||||
* @see entity_load()
|
||||
* @see hook_entity_info_alter()
|
||||
*/
|
||||
function hook_entity_info() {
|
||||
$return = array(
|
||||
'node' => array(
|
||||
'label' => t('Node'),
|
||||
'controller class' => 'NodeController',
|
||||
'base table' => 'node',
|
||||
'revision table' => 'node_revision',
|
||||
'uri callback' => 'node_uri',
|
||||
'fieldable' => TRUE,
|
||||
'translation' => array(
|
||||
'locale' => TRUE,
|
||||
),
|
||||
'entity keys' => array(
|
||||
'id' => 'nid',
|
||||
'revision' => 'vid',
|
||||
'bundle' => 'type',
|
||||
),
|
||||
'bundle keys' => array(
|
||||
'bundle' => 'type',
|
||||
),
|
||||
'bundles' => array(),
|
||||
'view modes' => array(
|
||||
'full' => array(
|
||||
'label' => t('Full content'),
|
||||
'custom settings' => FALSE,
|
||||
),
|
||||
'teaser' => array(
|
||||
'label' => t('Teaser'),
|
||||
'custom settings' => TRUE,
|
||||
),
|
||||
'rss' => array(
|
||||
'label' => t('RSS'),
|
||||
'custom settings' => FALSE,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Search integration is provided by node.module, so search-related
|
||||
// view modes for nodes are defined here and not in search.module.
|
||||
if (module_exists('search')) {
|
||||
$return['node']['view modes'] += array(
|
||||
'search_index' => array(
|
||||
'label' => t('Search index'),
|
||||
'custom settings' => FALSE,
|
||||
),
|
||||
'search_result' => array(
|
||||
'label' => t('Search result'),
|
||||
'custom settings' => FALSE,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Bundles must provide a human readable name so we can create help and error
|
||||
// messages, and the path to attach Field admin pages to.
|
||||
foreach (node_type_get_names() as $type => $name) {
|
||||
$return['node']['bundles'][$type] = array(
|
||||
'label' => $name,
|
||||
'admin' => array(
|
||||
'path' => 'admin/structure/types/manage/%node_type',
|
||||
'real path' => 'admin/structure/types/manage/' . str_replace('_', '-', $type),
|
||||
'bundle argument' => 4,
|
||||
'access arguments' => array('administer content types'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the entity info.
|
||||
*
|
||||
* Modules may implement this hook to alter the information that defines an
|
||||
* entity. All properties that are available in hook_entity_info() can be
|
||||
* altered here.
|
||||
*
|
||||
* @param $entity_info
|
||||
* The entity info array, keyed by entity name.
|
||||
*
|
||||
* @see hook_entity_info()
|
||||
*/
|
||||
function hook_entity_info_alter(&$entity_info) {
|
||||
// Set the controller class for nodes to an alternate implementation of the
|
||||
// DrupalEntityController interface.
|
||||
$entity_info['node']['controller class'] = 'MyCustomNodeController';
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities when loaded.
|
||||
*
|
||||
* This is a generic load hook called for all entity types loaded via the
|
||||
* entity API.
|
||||
*
|
||||
* @param $entities
|
||||
* The entities keyed by entity ID.
|
||||
* @param $type
|
||||
* The type of entities being loaded (i.e. node, user, comment).
|
||||
*/
|
||||
function hook_entity_load($entities, $type) {
|
||||
foreach ($entities as $entity) {
|
||||
$entity->foo = mymodule_add_something($entity, $type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on an entity before it is about to be created or updated.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
* @param $type
|
||||
* The type of entity being saved (i.e. node, user, comment).
|
||||
*/
|
||||
function hook_entity_presave($entity, $type) {
|
||||
$entity->changed = REQUEST_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities when inserted.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
* @param $type
|
||||
* The type of entity being inserted (i.e. node, user, comment).
|
||||
*/
|
||||
function hook_entity_insert($entity, $type) {
|
||||
// Insert the new entity into a fictional table of all entities.
|
||||
$info = entity_get_info($type);
|
||||
list($id) = entity_extract_ids($type, $entity);
|
||||
db_insert('example_entity')
|
||||
->fields(array(
|
||||
'type' => $type,
|
||||
'id' => $id,
|
||||
'created' => REQUEST_TIME,
|
||||
'updated' => REQUEST_TIME,
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities when updated.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
* @param $type
|
||||
* The type of entity being updated (i.e. node, user, comment).
|
||||
*/
|
||||
function hook_entity_update($entity, $type) {
|
||||
// Update the entity's entry in a fictional table of all entities.
|
||||
$info = entity_get_info($type);
|
||||
list($id) = entity_extract_ids($type, $entity);
|
||||
db_update('example_entity')
|
||||
->fields(array(
|
||||
'updated' => REQUEST_TIME,
|
||||
))
|
||||
->condition('type', $type)
|
||||
->condition('id', $id)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities when deleted.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
* @param $type
|
||||
* The type of entity being deleted (i.e. node, user, comment).
|
||||
*/
|
||||
function hook_entity_delete($entity, $type) {
|
||||
// Delete the entity's entry from a fictional table of all entities.
|
||||
$info = entity_get_info($type);
|
||||
list($id) = entity_extract_ids($type, $entity);
|
||||
db_delete('example_entity')
|
||||
->condition('type', $type)
|
||||
->condition('id', $id)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter or execute an EntityFieldQuery.
|
||||
*
|
||||
* @param EntityFieldQuery $query
|
||||
* An EntityFieldQuery. One of the most important properties to be changed is
|
||||
* EntityFieldQuery::executeCallback. If this is set to an existing function,
|
||||
* this function will get the query as its single argument and its result
|
||||
* will be the returned as the result of EntityFieldQuery::execute(). This can
|
||||
* be used to change the behavior of EntityFieldQuery entirely. For example,
|
||||
* the default implementation can only deal with one field storage engine, but
|
||||
* it is possible to write a module that can query across field storage
|
||||
* engines. Also, the default implementation presumes entities are stored in
|
||||
* SQL, but the execute callback could instead query any other entity storage,
|
||||
* local or remote.
|
||||
*
|
||||
* Note the $query->altered attribute which is TRUE in case the query has
|
||||
* already been altered once. This happens with cloned queries.
|
||||
* If there is a pager, then such a cloned query will be executed to count
|
||||
* all elements. This query can be detected by checking for
|
||||
* ($query->pager && $query->count), allowing the driver to return 0 from
|
||||
* the count query and disable the pager.
|
||||
*/
|
||||
function hook_entity_query_alter($query) {
|
||||
$query->executeCallback = 'my_module_query_callback';
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities being assembled before rendering.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
* @param $type
|
||||
* The type of entity being rendered (i.e. node, user, comment).
|
||||
* @param $view_mode
|
||||
* The view mode the entity is rendered in.
|
||||
* @param $langcode
|
||||
* The language code used for rendering.
|
||||
*
|
||||
* The module may add elements to $entity->content prior to rendering. The
|
||||
* structure of $entity->content is a renderable array as expected by
|
||||
* drupal_render().
|
||||
*
|
||||
* @see hook_entity_view_alter()
|
||||
* @see hook_comment_view()
|
||||
* @see hook_node_view()
|
||||
* @see hook_user_view()
|
||||
*/
|
||||
function hook_entity_view($entity, $type, $view_mode, $langcode) {
|
||||
$entity->content['my_additional_field'] = array(
|
||||
'#markup' => $additional_field,
|
||||
'#weight' => 10,
|
||||
'#theme' => 'mymodule_my_additional_field',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the results of ENTITY_view().
|
||||
*
|
||||
* This hook is called after the content has been assembled in a structured
|
||||
* array and may be used for doing processing which requires that the complete
|
||||
* entity content structure has been built.
|
||||
*
|
||||
* If a module wishes to act on the rendered HTML of the entity rather than the
|
||||
* structured content array, it may use this hook to add a #post_render
|
||||
* callback. Alternatively, it could also implement hook_preprocess_ENTITY().
|
||||
* See drupal_render() and theme() for details.
|
||||
*
|
||||
* @param $build
|
||||
* A renderable array representing the entity content.
|
||||
* @param $type
|
||||
* The type of entity being rendered (i.e. node, user, comment).
|
||||
*
|
||||
* @see hook_entity_view()
|
||||
* @see hook_comment_view_alter()
|
||||
* @see hook_node_view_alter()
|
||||
* @see hook_taxonomy_term_view_alter()
|
||||
* @see hook_user_view_alter()
|
||||
*/
|
||||
function hook_entity_view_alter(&$build, $type) {
|
||||
if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
|
||||
// Change its weight.
|
||||
$build['an_additional_field']['#weight'] = -10;
|
||||
|
||||
// Add a #post_render callback to act on the rendered HTML of the entity.
|
||||
$build['#post_render'][] = 'my_module_node_post_render';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define administrative paths.
|
||||
*
|
||||
|
@ -491,30 +105,6 @@ function hook_admin_paths_alter(&$paths) {
|
|||
$paths['node/add/forum'] = FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on entities as they are being prepared for view.
|
||||
*
|
||||
* Allows you to operate on multiple entities as they are being prepared for
|
||||
* view. Only use this if attaching the data during the entity_load() phase
|
||||
* is not appropriate, for example when attaching other 'entity' style objects.
|
||||
*
|
||||
* @param $entities
|
||||
* The entities keyed by entity ID.
|
||||
* @param $type
|
||||
* The type of entities being loaded (i.e. node, user, comment).
|
||||
* @param $langcode
|
||||
* The language to display the entity in.
|
||||
*/
|
||||
function hook_entity_prepare_view($entities, $type, $langcode) {
|
||||
// Load a specific node into the user object for later theming.
|
||||
if ($type == 'user') {
|
||||
$nodes = mymodule_get_user_nodes(array_keys($entities));
|
||||
foreach ($entities as $uid => $entity) {
|
||||
$entity->user_node = $nodes[$uid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform periodic actions.
|
||||
*
|
||||
|
@ -2394,6 +1984,30 @@ function hook_flush_caches() {
|
|||
return array('example');
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform necessary actions before modules are installed.
|
||||
*
|
||||
* This function allows all modules to react prior to a module being installed.
|
||||
*
|
||||
* @param $modules
|
||||
* An array of modules about to be installed.
|
||||
*/
|
||||
function hook_modules_preinstall($modules) {
|
||||
mymodule_cache_clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform necessary actions before modules are enabled.
|
||||
*
|
||||
* This function allows all modules to react prior to a module being enabled.
|
||||
*
|
||||
* @param $module
|
||||
* An array of modules about to be enabled.
|
||||
*/
|
||||
function hook_modules_preenable($modules) {
|
||||
mymodule_cache_clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform necessary actions after modules are installed.
|
||||
*
|
||||
|
|
|
@ -1623,10 +1623,10 @@ function system_update_last_removed() {
|
|||
*/
|
||||
|
||||
/**
|
||||
* Placeholder update to set the schema version to 8000.
|
||||
* Enable entity module.
|
||||
*/
|
||||
function system_update_8000() {
|
||||
// Fill in the first update to Drupal 8 when needed.
|
||||
update_module_enable(array('entity'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,6 +4,7 @@ package = Core
|
|||
version = VERSION
|
||||
core = 8.x
|
||||
dependencies[] = options
|
||||
dependencies[] = entity
|
||||
files[] = taxonomy.module
|
||||
files[] = taxonomy.test
|
||||
configure = admin/structure/taxonomy
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file Controller class for users.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller class for users.
|
||||
*
|
||||
* This extends the DrupalDefaultEntityController class, adding required
|
||||
* special handling for user objects.
|
||||
*/
|
||||
class UserController extends DrupalDefaultEntityController {
|
||||
|
||||
function attachLoad(&$queried_users, $revision_id = FALSE) {
|
||||
// Build an array of user picture IDs so that these can be fetched later.
|
||||
$picture_fids = array();
|
||||
foreach ($queried_users as $key => $record) {
|
||||
$picture_fids[] = $record->picture;
|
||||
$queried_users[$key]->data = unserialize($record->data);
|
||||
$queried_users[$key]->roles = array();
|
||||
if ($record->uid) {
|
||||
$queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
|
||||
}
|
||||
else {
|
||||
$queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
|
||||
}
|
||||
}
|
||||
|
||||
// Add any additional roles from the database.
|
||||
$result = db_query('SELECT r.rid, r.name, ur.uid FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid IN (:uids)', array(':uids' => array_keys($queried_users)));
|
||||
foreach ($result as $record) {
|
||||
$queried_users[$record->uid]->roles[$record->rid] = $record->name;
|
||||
}
|
||||
|
||||
// Add the full file objects for user pictures if enabled.
|
||||
if (!empty($picture_fids) && variable_get('user_pictures', 1) == 1) {
|
||||
$pictures = file_load_multiple($picture_fids);
|
||||
foreach ($queried_users as $account) {
|
||||
if (!empty($account->picture) && isset($pictures[$account->picture])) {
|
||||
$account->picture = $pictures[$account->picture];
|
||||
}
|
||||
else {
|
||||
$account->picture = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Call the default attachLoad() method. This will add fields and call
|
||||
// hook_user_load().
|
||||
parent::attachLoad($queried_users, $revision_id);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ description = Manages the user registration and login system.
|
|||
package = Core
|
||||
version = VERSION
|
||||
core = 8.x
|
||||
files[] = user.module
|
||||
files[] = user.entity.inc
|
||||
files[] = user.test
|
||||
required = TRUE
|
||||
configure = admin/config/people
|
||||
|
|
|
@ -287,53 +287,6 @@ function user_load_multiple($uids = array(), $conditions = array(), $reset = FAL
|
|||
return entity_load('user', $uids, $conditions, $reset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller class for users.
|
||||
*
|
||||
* This extends the DrupalDefaultEntityController class, adding required
|
||||
* special handling for user objects.
|
||||
*/
|
||||
class UserController extends DrupalDefaultEntityController {
|
||||
|
||||
function attachLoad(&$queried_users, $revision_id = FALSE) {
|
||||
// Build an array of user picture IDs so that these can be fetched later.
|
||||
$picture_fids = array();
|
||||
foreach ($queried_users as $key => $record) {
|
||||
$picture_fids[] = $record->picture;
|
||||
$queried_users[$key]->data = unserialize($record->data);
|
||||
$queried_users[$key]->roles = array();
|
||||
if ($record->uid) {
|
||||
$queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
|
||||
}
|
||||
else {
|
||||
$queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
|
||||
}
|
||||
}
|
||||
|
||||
// Add any additional roles from the database.
|
||||
$result = db_query('SELECT r.rid, r.name, ur.uid FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid IN (:uids)', array(':uids' => array_keys($queried_users)));
|
||||
foreach ($result as $record) {
|
||||
$queried_users[$record->uid]->roles[$record->rid] = $record->name;
|
||||
}
|
||||
|
||||
// Add the full file objects for user pictures if enabled.
|
||||
if (!empty($picture_fids) && variable_get('user_pictures', 1) == 1) {
|
||||
$pictures = file_load_multiple($picture_fids);
|
||||
foreach ($queried_users as $account) {
|
||||
if (!empty($account->picture) && isset($pictures[$account->picture])) {
|
||||
$account->picture = $pictures[$account->picture];
|
||||
}
|
||||
else {
|
||||
$account->picture = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Call the default attachLoad() method. This will add fields and call
|
||||
// hook_user_load().
|
||||
parent::attachLoad($queried_users, $revision_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a user object.
|
||||
*
|
||||
|
|
|
@ -345,7 +345,6 @@ require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
|
|||
require_once DRUPAL_ROOT . '/includes/update.inc';
|
||||
require_once DRUPAL_ROOT . '/includes/common.inc';
|
||||
require_once DRUPAL_ROOT . '/includes/file.inc';
|
||||
require_once DRUPAL_ROOT . '/includes/entity.inc';
|
||||
require_once DRUPAL_ROOT . '/includes/unicode.inc';
|
||||
update_prepare_d8_bootstrap();
|
||||
|
||||
|
|
Loading…
Reference in New Issue