- 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).
|
* 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.
|
// Set up $language, so t() caller functions will still work.
|
||||||
drupal_language_initialize();
|
drupal_language_initialize();
|
||||||
|
|
||||||
include_once DRUPAL_ROOT . '/includes/entity.inc';
|
|
||||||
require_once DRUPAL_ROOT . '/includes/ajax.inc';
|
require_once DRUPAL_ROOT . '/includes/ajax.inc';
|
||||||
$module_list['system']['filename'] = 'modules/system/system.module';
|
$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);
|
module_list(TRUE, FALSE, FALSE, $module_list);
|
||||||
drupal_load('module', 'system');
|
drupal_load('module', 'system');
|
||||||
|
drupal_load('module', 'entity');
|
||||||
drupal_load('module', 'user');
|
drupal_load('module', 'user');
|
||||||
|
|
||||||
// Load the cache infrastructure using a "fake" cache implementation that
|
// Load the cache infrastructure using a "fake" cache implementation that
|
||||||
|
|
|
@ -424,8 +424,9 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
|
||||||
registry_update();
|
registry_update();
|
||||||
// Refresh the schema to include it.
|
// Refresh the schema to include it.
|
||||||
drupal_get_schema(NULL, TRUE);
|
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.
|
// Now install the module if necessary.
|
||||||
if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) {
|
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);
|
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.
|
// Enable the module.
|
||||||
module_invoke($module, 'enable');
|
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
|
// Allow the database system to work even if the registry has not been
|
||||||
// created yet.
|
// created yet.
|
||||||
include_once DRUPAL_ROOT . '/includes/install.inc';
|
include_once DRUPAL_ROOT . '/includes/install.inc';
|
||||||
|
include_once DRUPAL_ROOT . '/modules/entity/entity.controller.inc';
|
||||||
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
|
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
|
||||||
|
|
||||||
// If the site has not updated to Drupal 8 yet, check to make sure that it is
|
// 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.
|
* Perform one update and store the results for display on finished page.
|
||||||
*
|
*
|
||||||
|
|
|
@ -4,6 +4,7 @@ package = Core
|
||||||
version = VERSION
|
version = VERSION
|
||||||
core = 8.x
|
core = 8.x
|
||||||
dependencies[] = text
|
dependencies[] = text
|
||||||
|
dependencies[] = entity
|
||||||
files[] = comment.module
|
files[] = comment.module
|
||||||
files[] = comment.test
|
files[] = comment.test
|
||||||
configure = admin/content/comment
|
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
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for entity controller classes.
|
* @file
|
||||||
*
|
* Entity query API.
|
||||||
* 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. 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.
|
* 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
|
* @param $limit
|
||||||
* An integer specifying the number of elements per page. If passed a false
|
* 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
|
* @param $headers
|
||||||
* An EFQ Header array based on which the order clause is added to the query.
|
* 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),
|
* 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
|
* 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
|
<?php
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Unit test file for the entity API.
|
* Unit test file for the entity API.
|
|
@ -7,5 +7,6 @@ files[] = field.module
|
||||||
files[] = field.attach.inc
|
files[] = field.attach.inc
|
||||||
files[] = tests/field.test
|
files[] = tests/field.test
|
||||||
dependencies[] = field_sql_storage
|
dependencies[] = field_sql_storage
|
||||||
|
dependencies[] = entity
|
||||||
required = TRUE
|
required = TRUE
|
||||||
stylesheets[all][] = theme/field.css
|
stylesheets[all][] = theme/field.css
|
||||||
|
|
|
@ -6,5 +6,6 @@ core = 8.x
|
||||||
files[] = node.module
|
files[] = node.module
|
||||||
files[] = node.test
|
files[] = node.test
|
||||||
required = TRUE
|
required = TRUE
|
||||||
|
dependencies[] = entity
|
||||||
configure = admin/structure/types
|
configure = admin/structure/types
|
||||||
stylesheets[all][] = node.css
|
stylesheets[all][] = node.css
|
||||||
|
|
|
@ -59,392 +59,6 @@ function hook_hook_info_alter(&$hooks) {
|
||||||
$hooks['tokens']['group'] = 'mytokens';
|
$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.
|
* Define administrative paths.
|
||||||
*
|
*
|
||||||
|
@ -491,30 +105,6 @@ function hook_admin_paths_alter(&$paths) {
|
||||||
$paths['node/add/forum'] = FALSE;
|
$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.
|
* Perform periodic actions.
|
||||||
*
|
*
|
||||||
|
@ -2394,6 +1984,30 @@ function hook_flush_caches() {
|
||||||
return array('example');
|
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.
|
* 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() {
|
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
|
version = VERSION
|
||||||
core = 8.x
|
core = 8.x
|
||||||
dependencies[] = options
|
dependencies[] = options
|
||||||
|
dependencies[] = entity
|
||||||
files[] = taxonomy.module
|
files[] = taxonomy.module
|
||||||
files[] = taxonomy.test
|
files[] = taxonomy.test
|
||||||
configure = admin/structure/taxonomy
|
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
|
package = Core
|
||||||
version = VERSION
|
version = VERSION
|
||||||
core = 8.x
|
core = 8.x
|
||||||
files[] = user.module
|
files[] = user.entity.inc
|
||||||
files[] = user.test
|
files[] = user.test
|
||||||
required = TRUE
|
required = TRUE
|
||||||
configure = admin/config/people
|
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);
|
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.
|
* 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/update.inc';
|
||||||
require_once DRUPAL_ROOT . '/includes/common.inc';
|
require_once DRUPAL_ROOT . '/includes/common.inc';
|
||||||
require_once DRUPAL_ROOT . '/includes/file.inc';
|
require_once DRUPAL_ROOT . '/includes/file.inc';
|
||||||
require_once DRUPAL_ROOT . '/includes/entity.inc';
|
|
||||||
require_once DRUPAL_ROOT . '/includes/unicode.inc';
|
require_once DRUPAL_ROOT . '/includes/unicode.inc';
|
||||||
update_prepare_d8_bootstrap();
|
update_prepare_d8_bootstrap();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue