diff --git a/includes/common.inc b/includes/common.inc index 1ef681f..1490d0f 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -7267,427 +7267,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_clear_all('entity_info:', 'cache', TRUE); -} - -/** - * 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) { - // This check enables the URI of an entity to be easily overridden from what - // the callback for the entity type or bundle would return, and it helps - // minimize performance overhead when entity_uri() is called multiple times - // for the same entity. - if (!isset($entity->uri)) { - $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 { - $uri_callback = NULL; - } - - // Invoke the callback to get the URI. If there is no callback, set the - // entity's 'uri' property to FALSE to indicate that it is known to not have - // a URI. - if (isset($uri_callback) && function_exists($uri_callback)) { - $entity->uri = $uri_callback($entity); - if (!isset($entity->uri['options'])) { - $entity->uri['options'] = array(); - } - // Pass the entity data to url() so that alter functions do not need to - // lookup this entity again. - $entity->uri['options']['entity_type'] = $entity_type; - $entity->uri['options']['entity'] = $entity; - } - else { - $entity->uri = FALSE; - } - } - return $entity->uri ? $entity->uri : NULL; -} - -/** - * 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). * * Usage example: diff --git a/includes/entity.inc b/includes/entity.inc deleted file mode 100644 index 31679fd..0000000 --- a/includes/entity.inc +++ /dev/null @@ -1,1332 +0,0 @@ - $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. - * - * Some storage modules might not support the full range of the syntax for - * conditions, and will raise an EntityFieldQueryException when an unsupported - * condition was specified. - */ -class EntityFieldQueryException extends Exception {} - -/** - * Retrieves entities matching a given set of conditions. - * - * This class allows finding entities based on entity properties (for example, - * node->changed), field values, and generic entity meta data (bundle, - * entity type, entity id, and revision ID). It is not possible to query across - * multiple entity types. For example, there is no facility to find published - * nodes written by users created in the last hour, as this would require - * querying both node->status and user->created. - * - * Normally we would not want to have public properties on the object, as that - * allows the object's state to become inconsistent too easily. However, this - * class's standard use case involves primarily code that does need to have - * direct access to the collected properties in order to handle alternate - * execution routines. We therefore use public properties for simplicity. Note - * that code that is simply creating and running a field query should still use - * the appropriate methods to add conditions on the query. - * - * Storage engines are not required to support every type of query. By default, - * an EntityFieldQueryException will be raised if an unsupported condition is - * specified or if the query has field conditions or sorts that are stored in - * different field storage engines. However, this logic can be overridden in - * hook_entity_query(). - * - * Also note that this query does not automatically respect entity access - * restrictions. Node access control is performed by the SQL storage engine but - * other storage engines might not do this. - */ -class EntityFieldQuery { - /** - * Indicates that both deleted and non-deleted fields should be returned. - * - * @see EntityFieldQuery::deleted() - */ - const RETURN_ALL = NULL; - - /** - * TRUE if the query has already been altered, FALSE if it hasn't. - * - * Used in alter hooks to check for cloned queries that have already been - * altered prior to the clone (for example, the pager count query). - * - * @var boolean - */ - public $altered = FALSE; - - /** - * Associative array of entity-generic metadata conditions. - * - * @var array - * - * @see EntityFieldQuery::entityCondition() - */ - public $entityConditions = array(); - - /** - * List of field conditions. - * - * @var array - * - * @see EntityFieldQuery::fieldCondition() - */ - public $fieldConditions = array(); - - /** - * List of field meta conditions (language and delta). - * - * Field conditions operate on columns specified by hook_field_schema(), - * the meta conditions operate on columns added by the system: delta - * and language. These can not be mixed with the field conditions because - * field columns can have any name including delta and language. - * - * @var array - * - * @see EntityFieldQuery::fieldLanguageCondition() - * @see EntityFieldQuery::fielDeltaCondition() - */ - public $fieldMetaConditions = array(); - - /** - * List of property conditions. - * - * @var array - * - * @see EntityFieldQuery::propertyCondition() - */ - public $propertyConditions = array(); - - /** - * List of order clauses. - * - * @var array - */ - public $order = array(); - - /** - * The query range. - * - * @var array - * - * @see EntityFieldQuery::range() - */ - public $range = array(); - - /** - * The query pager data. - * - * @var array - * - * @see EntityFieldQuery::pager() - */ - public $pager = array(); - - /** - * Query behavior for deleted data. - * - * TRUE to return only deleted data, FALSE to return only non-deleted data, - * EntityFieldQuery::RETURN_ALL to return everything. - * - * @see EntityFieldQuery::deleted() - */ - public $deleted = FALSE; - - /** - * A list of field arrays used. - * - * Field names passed to EntityFieldQuery::fieldCondition() and - * EntityFieldQuery::fieldOrderBy() are run through field_info_field() before - * stored in this array. This way, the elements of this array are field - * arrays. - * - * @var array - */ - public $fields = array(); - - /** - * TRUE if this is a count query, FALSE if it isn't. - * - * @var boolean - */ - public $count = FALSE; - - /** - * Flag indicating whether this is querying current or all revisions. - * - * @var int - * - * @see EntityFieldQuery::age() - */ - public $age = FIELD_LOAD_CURRENT; - - /** - * A list of the tags added to this query. - * - * @var array - * - * @see EntityFieldQuery::addTag() - */ - public $tags = array(); - - /** - * A list of metadata added to this query. - * - * @var array - * - * @see EntityFieldQuery::addMetaData() - */ - public $metaData = array(); - - /** - * The ordered results. - * - * @var array - * - * @see EntityFieldQuery::execute(). - */ - public $orderedResults = array(); - - /** - * The method executing the query, if it is overriding the default. - * - * @var string - * - * @see EntityFieldQuery::execute(). - */ - public $executeCallback = ''; - - /** - * Adds a condition on entity-generic metadata. - * - * If the overall query contains only entity conditions or ordering, or if - * there are property conditions, then specifying the entity type is - * mandatory. If there are field conditions or ordering but no property - * conditions or ordering, then specifying an entity type is optional. While - * the field storage engine might support field conditions on more than one - * entity type, there is no way to query across multiple entity base tables by - * default. To specify the entity type, pass in 'entity_type' for $name, - * the type as a string for $value, and no $operator (it's disregarded). - * - * 'bundle', 'revision_id' and 'entity_id' have no such restrictions. - * - * Note: The "comment" and "taxonomy_term" entity types don't support bundle - * conditions. For "taxonomy_term", propertyCondition('vid') can be used - * instead. - * - * @param $name - * 'entity_type', 'bundle', 'revision_id' or 'entity_id'. - * @param $value - * The value for $name. In most cases, this is a scalar. For more complex - * options, it is an array. The meaning of each element in the array is - * dependent on $operator. - * @param $operator - * Possible values: - * - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These - * operators expect $value to be a literal of the same type as the - * column. - * - 'IN', 'NOT IN': These operators expect $value to be an array of - * literals of the same type as the column. - * - 'BETWEEN': This operator expects $value to be an array of two literals - * of the same type as the column. - * - * @return EntityFieldQuery - * The called object. - */ - public function entityCondition($name, $value, $operator = NULL) { - $this->entityConditions[$name] = array( - 'value' => $value, - 'operator' => $operator, - ); - return $this; - } - - /** - * Adds a condition on field values. - * - * @param $field - * Either a field name or a field array. - * @param $column - * The column that should hold the value to be matched. - * @param $value - * The value to test the column value against. - * @param $operator - * The operator to be used to test the given value. - * @param $delta_group - * An arbitrary identifier: conditions in the same group must have the same - * $delta_group. - * @param $language_group - * An arbitrary identifier: conditions in the same group must have the same - * $language_group. - * - * @return EntityFieldQuery - * The called object. - * - * @see EntityFieldQuery::addFieldCondition - * @see EntityFieldQuery::deleted - */ - public function fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { - return $this->addFieldCondition($this->fieldConditions, $field, $column, $value, $operator, $delta_group, $language_group); - } - - /** - * Adds a condition on the field language column. - * - * @param $field - * Either a field name or a field array. - * @param $value - * The value to test the column value against. - * @param $operator - * The operator to be used to test the given value. - * @param $delta_group - * An arbitrary identifier: conditions in the same group must have the same - * $delta_group. - * @param $language_group - * An arbitrary identifier: conditions in the same group must have the same - * $language_group. - * - * @return EntityFieldQuery - * The called object. - * - * @see EntityFieldQuery::addFieldCondition - * @see EntityFieldQuery::deleted - */ - public function fieldLanguageCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { - return $this->addFieldCondition($this->fieldMetaConditions, $field, 'language', $value, $operator, $delta_group, $language_group); - } - - /** - * Adds a condition on the field delta column. - * - * @param $field - * Either a field name or a field array. - * @param $value - * The value to test the column value against. - * @param $operator - * The operator to be used to test the given value. - * @param $delta_group - * An arbitrary identifier: conditions in the same group must have the same - * $delta_group. - * @param $language_group - * An arbitrary identifier: conditions in the same group must have the same - * $language_group. - * - * @return EntityFieldQuery - * The called object. - * - * @see EntityFieldQuery::addFieldCondition - * @see EntityFieldQuery::deleted - */ - public function fieldDeltaCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { - return $this->addFieldCondition($this->fieldMetaConditions, $field, 'delta', $value, $operator, $delta_group, $language_group); - } - - /** - * Adds the given condition to the proper condition array. - * - * @param $conditions - * A reference to an array of conditions. - * @param $field - * Either a field name or a field array. - * @param $column - * A column defined in the hook_field_schema() of this field. If this is - * omitted then the query will find only entities that have data in this - * field, using the entity and property conditions if there are any. - * @param $value - * The value to test the column value against. In most cases, this is a - * scalar. For more complex options, it is an array. The meaning of each - * element in the array is dependent on $operator. - * @param $operator - * Possible values: - * - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These - * operators expect $value to be a literal of the same type as the - * column. - * - 'IN', 'NOT IN': These operators expect $value to be an array of - * literals of the same type as the column. - * - 'BETWEEN': This operator expects $value to be an array of two literals - * of the same type as the column. - * @param $delta_group - * An arbitrary identifier: conditions in the same group must have the same - * $delta_group. For example, let's presume a multivalue field which has - * two columns, 'color' and 'shape', and for entity id 1, there are two - * values: red/square and blue/circle. Entity ID 1 does not have values - * corresponding to 'red circle', however if you pass 'red' and 'circle' as - * conditions, it will appear in the results - by default queries will run - * against any combination of deltas. By passing the conditions with the - * same $delta_group it will ensure that only values attached to the same - * delta are matched, and entity 1 would then be excluded from the results. - * @param $language_group - * An arbitrary identifier: conditions in the same group must have the same - * $language_group. - * - * @return EntityFieldQuery - * The called object. - */ - protected function addFieldCondition(&$conditions, $field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { - if (is_scalar($field)) { - $field_definition = field_info_field($field); - if (empty($field_definition)) { - throw new EntityFieldQueryException(t('Unknown field: @field_name', array('@field_name' => $field))); - } - $field = $field_definition; - } - // Ensure the same index is used for field conditions as for fields. - $index = count($this->fields); - $this->fields[$index] = $field; - if (isset($column)) { - $conditions[$index] = array( - 'field' => $field, - 'column' => $column, - 'value' => $value, - 'operator' => $operator, - 'delta_group' => $delta_group, - 'language_group' => $language_group, - ); - } - return $this; - } - - /** - * Adds a condition on an entity-specific property. - * - * An $entity_type must be specified by calling - * EntityFieldCondition::entityCondition('entity_type', $entity_type) before - * executing the query. Also, by default only entities stored in SQL are - * supported; however, EntityFieldQuery::executeCallback can be set to handle - * different entity storage. - * - * @param $column - * A column defined in the hook_schema() of the base table of the entity. - * @param $value - * The value to test the field against. In most cases, this is a scalar. For - * more complex options, it is an array. The meaning of each element in the - * array is dependent on $operator. - * @param $operator - * Possible values: - * - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These - * operators expect $value to be a literal of the same type as the - * column. - * - 'IN', 'NOT IN': These operators expect $value to be an array of - * literals of the same type as the column. - * - 'BETWEEN': This operator expects $value to be an array of two literals - * of the same type as the column. - * The operator can be omitted, and will default to 'IN' if the value is an - * array, or to '=' otherwise. - * - * @return EntityFieldQuery - * The called object. - */ - public function propertyCondition($column, $value, $operator = NULL) { - $this->propertyConditions[] = array( - 'column' => $column, - 'value' => $value, - 'operator' => $operator, - ); - return $this; - } - - /** - * Orders the result set by entity-generic metadata. - * - * If called multiple times, the query will order by each specified column in - * the order this method is called. - * - * Note: The "comment" and "taxonomy_term" entity types don't support ordering - * by bundle. For "taxonomy_term", propertyOrderBy('vid') can be used instead. - * - * @param $name - * 'entity_type', 'bundle', 'revision_id' or 'entity_id'. - * @param $direction - * The direction to sort. Legal values are "ASC" and "DESC". - * - * @return EntityFieldQuery - * The called object. - */ - public function entityOrderBy($name, $direction = 'ASC') { - $this->order[] = array( - 'type' => 'entity', - 'specifier' => $name, - 'direction' => $direction, - ); - return $this; - } - - /** - * Orders the result set by a given field column. - * - * If called multiple times, the query will order by each specified column in - * the order this method is called. - * - * @param $field - * Either a field name or a field array. - * @param $column - * A column defined in the hook_field_schema() of this field. entity_id and - * bundle can also be used. - * @param $direction - * The direction to sort. Legal values are "ASC" and "DESC". - * - * @return EntityFieldQuery - * The called object. - */ - public function fieldOrderBy($field, $column, $direction = 'ASC') { - if (is_scalar($field)) { - $field_definition = field_info_field($field); - if (empty($field_definition)) { - throw new EntityFieldQueryException(t('Unknown field: @field_name', array('@field_name' => $field))); - } - $field = $field_definition; - } - // Save the index used for the new field, for later use in field storage. - $index = count($this->fields); - $this->fields[$index] = $field; - $this->order[] = array( - 'type' => 'field', - 'specifier' => array( - 'field' => $field, - 'index' => $index, - 'column' => $column, - ), - 'direction' => $direction, - ); - return $this; - } - - /** - * Orders the result set by an entity-specific property. - * - * An $entity_type must be specified by calling - * EntityFieldCondition::entityCondition('entity_type', $entity_type) before - * executing the query. - * - * If called multiple times, the query will order by each specified column in - * the order this method is called. - * - * @param $column - * The column on which to order. - * @param $direction - * The direction to sort. Legal values are "ASC" and "DESC". - * - * @return EntityFieldQuery - * The called object. - */ - public function propertyOrderBy($column, $direction = 'ASC') { - $this->order[] = array( - 'type' => 'property', - 'specifier' => $column, - 'direction' => $direction, - ); - return $this; - } - - /** - * Sets the query to be a count query only. - * - * @return EntityFieldQuery - * The called object. - */ - public function count() { - $this->count = TRUE; - return $this; - } - - /** - * Restricts a query to a given range in the result set. - * - * @param $start - * The first entity from the result set to return. If NULL, removes any - * range directives that are set. - * @param $length - * The number of entities to return from the result set. - * - * @return EntityFieldQuery - * The called object. - */ - public function range($start = NULL, $length = NULL) { - $this->range = array( - 'start' => $start, - 'length' => $length, - ); - return $this; - } - - /** - * Enable a pager for the query. - * - * @param $limit - * An integer specifying the number of elements per page. If passed a false - * value (FALSE, 0, NULL), the pager is disabled. - * @param $element - * An optional integer to distinguish between multiple pagers on one page. - * If not provided, one is automatically calculated. - * - * @return EntityFieldQuery - * The called object. - */ - public function pager($limit = 10, $element = NULL) { - if (!isset($element)) { - $element = PagerDefault::$maxElement++; - } - elseif ($element >= PagerDefault::$maxElement) { - PagerDefault::$maxElement = $element + 1; - } - - $this->pager = array( - 'limit' => $limit, - 'element' => $element, - ); - return $this; - } - - /** - * Enable sortable tables for this query. - * - * @param $headers - * An EFQ Header array based on which the order clause is added to the query. - * - * @return EntityFieldQuery - * The called object. - */ - public function tableSort(&$headers) { - // If 'field' is not initialized, the header columns aren't clickable - foreach ($headers as $key =>$header) { - if (is_array($header) && isset($header['specifier'])) { - $headers[$key]['field'] = ''; - } - } - - $order = tablesort_get_order($headers); - $direction = tablesort_get_sort($headers); - foreach ($headers as $header) { - if (is_array($header) && ($header['data'] == $order['name'])) { - if ($header['type'] == 'field') { - $this->fieldOrderBy($header['specifier']['field'], $header['specifier']['column'], $direction); - } - else { - $header['direction'] = $direction; - $this->order[] = $header; - } - } - } - - return $this; - } - - /** - * Filters on the data being deleted. - * - * @param $deleted - * TRUE to only return deleted data, FALSE to return non-deleted data, - * EntityFieldQuery::RETURN_ALL to return everything. Defaults to FALSE. - * - * @return EntityFieldQuery - * The called object. - */ - public function deleted($deleted = TRUE) { - $this->deleted = $deleted; - return $this; - } - - /** - * Queries the current or every revision. - * - * Note that this only affects field conditions. Property conditions always - * apply to the current revision. - * @TODO: Once revision tables have been cleaned up, revisit this. - * - * @param $age - * - FIELD_LOAD_CURRENT (default): Query the most recent revisions for all - * entities. The results will be keyed by entity type and entity ID. - * - FIELD_LOAD_REVISION: Query all revisions. The results will be keyed by - * entity type and entity revision ID. - * - * @return EntityFieldQuery - * The called object. - */ - public function age($age) { - $this->age = $age; - return $this; - } - - /** - * Adds a tag to the query. - * - * Tags are strings that mark a query so that hook_query_alter() and - * hook_query_TAG_alter() implementations may decide if they wish to alter - * the query. A query may have any number of tags, and they must be valid PHP - * identifiers (composed of letters, numbers, and underscores). For example, - * queries involving nodes that will be displayed for a user need to add the - * tag 'node_access', so that the node module can add access restrictions to - * the query. - * - * If an entity field query has tags, it must also have an entity type - * specified, because the alter hook will need the entity base table. - * - * @param string $tag - * The tag to add. - * - * @return EntityFieldQuery - * The called object. - */ - public function addTag($tag) { - $this->tags[$tag] = $tag; - return $this; - } - - /** - * Adds additional metadata to the query. - * - * Sometimes a query may need to provide additional contextual data for the - * alter hook. The alter hook implementations may then use that information - * to decide if and how to take action. - * - * @param $key - * The unique identifier for this piece of metadata. Must be a string that - * follows the same rules as any other PHP identifier. - * @param $object - * The additional data to add to the query. May be any valid PHP variable. - * - * @return EntityFieldQuery - * The called object. - */ - public function addMetaData($key, $object) { - $this->metaData[$key] = $object; - return $this; - } - - /** - * Executes the query. - * - * After executing the query, $this->orderedResults will contain a list of - * the same stub entities in the order returned by the query. This is only - * relevant if there are multiple entity types in the returned value and - * a field ordering was requested. In every other case, the returned value - * contains everything necessary for processing. - * - * @return - * Either a number if count() was called or an array of associative - * arrays of stub entities. The outer array keys are entity types, and the - * inner array keys are the relevant ID. (In most this cases this will be - * the entity ID. The only exception is when age=FIELD_LOAD_REVISION is used - * and field conditions or sorts are present -- in this case, the key will - * be the revision ID.) The inner array values are always stub entities, as - * returned by entity_create_stub_entity(). To traverse the returned array: - * @code - * foreach ($query->execute() as $entity_type => $entities) { - * foreach ($entities as $entity_id => $entity) { - * @endcode - * Note if the entity type is known, then the following snippet will load - * the entities found: - * @code - * $result = $query->execute(); - * $entities = entity_load($my_type, array_keys($result[$my_type])); - * @endcode - */ - public function execute() { - // Give a chance to other modules to alter the query. - drupal_alter('entity_query', $this); - $this->altered = TRUE; - - // Initialize the pager. - $this->initializePager(); - - // Execute the query using the correct callback. - $result = call_user_func($this->queryCallback(), $this); - - return $result; - } - - /** - * Determines the query callback to use for this entity query. - * - * @return - * A callback that can be used with call_user_func(). - */ - public function queryCallback() { - // Use the override from $this->executeCallback. It can be set either - // while building the query, or using hook_entity_query_alter(). - if (function_exists($this->executeCallback)) { - return $this->executeCallback; - } - // If there are no field conditions and sorts, and no execute callback - // then we default to querying entity tables in SQL. - if (empty($this->fields)) { - return array($this, 'propertyQuery'); - } - // If no override, find the storage engine to be used. - foreach ($this->fields as $field) { - if (!isset($storage)) { - $storage = $field['storage']['module']; - } - elseif ($storage != $field['storage']['module']) { - throw new EntityFieldQueryException(t("Can't handle more than one field storage engine")); - } - } - if ($storage) { - // Use hook_field_storage_query() from the field storage. - return $storage . '_field_storage_query'; - } - else { - throw new EntityFieldQueryException(t("Field storage engine not found.")); - } - } - - /** - * Queries entity tables in SQL for property conditions and sorts. - * - * This method is only used if there are no field conditions and sorts. - * - * @return - * See EntityFieldQuery::execute(). - */ - protected function propertyQuery() { - if (empty($this->entityConditions['entity_type'])) { - throw new EntityFieldQueryException(t('For this query an entity type must be specified.')); - } - $entity_type = $this->entityConditions['entity_type']['value']; - $entity_info = entity_get_info($entity_type); - if (empty($entity_info['base table'])) { - throw new EntityFieldQueryException(t('Entity %entity has no base table.', array('%entity' => $entity_type))); - } - $base_table = $entity_info['base table']; - $base_table_schema = drupal_get_schema($base_table); - $select_query = db_select($base_table); - $select_query->addExpression(':entity_type', 'entity_type', array(':entity_type' => $entity_type)); - // Process the property conditions. - foreach ($this->propertyConditions as $property_condition) { - $this->addCondition($select_query, "$base_table." . $property_condition['column'], $property_condition); - } - // Process the four possible entity condition. - // The id field is always present in entity keys. - $sql_field = $entity_info['entity keys']['id']; - $id_map['entity_id'] = $sql_field; - $select_query->addField($base_table, $sql_field, 'entity_id'); - if (isset($this->entityConditions['entity_id'])) { - $this->addCondition($select_query, $sql_field, $this->entityConditions['entity_id']); - } - - // If there is a revision key defined, use it. - if (!empty($entity_info['entity keys']['revision'])) { - $sql_field = $entity_info['entity keys']['revision']; - $select_query->addField($base_table, $sql_field, 'revision_id'); - if (isset($this->entityConditions['revision_id'])) { - $this->addCondition($select_query, $sql_field, $this->entityConditions['revision_id']); - } - } - else { - $sql_field = 'revision_id'; - $select_query->addExpression('NULL', 'revision_id'); - } - $id_map['revision_id'] = $sql_field; - - // Handle bundles. - if (!empty($entity_info['entity keys']['bundle'])) { - $sql_field = $entity_info['entity keys']['bundle']; - $having = FALSE; - - if (!empty($base_table_schema['fields'][$sql_field])) { - $select_query->addField($base_table, $sql_field, 'bundle'); - } - } - else { - $sql_field = 'bundle'; - $select_query->addExpression(':bundle', 'bundle', array(':bundle' => $entity_type)); - $having = TRUE; - } - $id_map['bundle'] = $sql_field; - if (isset($this->entityConditions['bundle'])) { - $this->addCondition($select_query, $sql_field, $this->entityConditions['bundle'], $having); - } - - // Order the query. - foreach ($this->order as $order) { - if ($order['type'] == 'entity') { - $key = $order['specifier']; - if (!isset($id_map[$key])) { - throw new EntityFieldQueryException(t('Do not know how to order on @key for @entity_type', array('@key' => $key, '@entity_type' => $entity_type))); - } - $select_query->orderBy($id_map[$key], $order['direction']); - } - elseif ($order['type'] == 'property') { - $select_query->orderBy("$base_table." . $order['specifier'], $order['direction']); - } - } - - return $this->finishQuery($select_query); - } - - /** - * Get the total number of results and initialize a pager for the query. - * - * This query can be detected by checking for ($this->pager && $this->count), - * which allows a driver to return 0 from the count query and disable - * the pager. - */ - function initializePager() { - if ($this->pager && !$this->count) { - $page = pager_find_page($this->pager['element']); - $count_query = clone $this; - $this->pager['total'] = $count_query->count()->execute(); - $this->pager['start'] = $page * $this->pager['limit']; - pager_default_initialize($this->pager['total'], $this->pager['limit'], $this->pager['element']); - $this->range($this->pager['start'], $this->pager['limit']); - } - } - - /** - * Finishes the query. - * - * Adds tags, metaData, range and returns the requested list or count. - * - * @param SelectQuery $select_query - * A SelectQuery which has entity_type, entity_id, revision_id and bundle - * fields added. - * @param $id_key - * Which field's values to use as the returned array keys. - * - * @return - * See EntityFieldQuery::execute(). - */ - function finishQuery($select_query, $id_key = 'entity_id') { - foreach ($this->tags as $tag) { - $select_query->addTag($tag); - } - foreach ($this->metaData as $key => $object) { - $select_query->addMetaData($key, $object); - } - $select_query->addMetaData('entity_field_query', $this); - if ($this->range) { - $select_query->range($this->range['start'], $this->range['length']); - } - if ($this->count) { - return $select_query->countQuery()->execute()->fetchField(); - } - $return = array(); - foreach ($select_query->execute() as $partial_entity) { - $bundle = isset($partial_entity->bundle) ? $partial_entity->bundle : NULL; - $entity = entity_create_stub_entity($partial_entity->entity_type, array($partial_entity->entity_id, $partial_entity->revision_id, $bundle)); - $return[$partial_entity->entity_type][$partial_entity->$id_key] = $entity; - $this->ordered_results[] = $partial_entity; - } - return $return; - } - - /** - * Adds a condition to an already built SelectQuery (internal function). - * - * This is a helper for hook_entity_query() and hook_field_storage_query(). - * - * @param SelectQuery $select_query - * A SelectQuery object. - * @param $sql_field - * The name of the field. - * @param $condition - * A condition as described in EntityFieldQuery::fieldCondition() and - * EntityFieldQuery::entityCondition(). - * @param $having - * HAVING or WHERE. This is necessary because SQL can't handle WHERE - * conditions on aliased columns. - */ - public function addCondition(SelectQuery $select_query, $sql_field, $condition, $having = FALSE) { - $method = $having ? 'havingCondition' : 'condition'; - $like_prefix = ''; - switch ($condition['operator']) { - case 'CONTAINS': - $like_prefix = '%'; - case 'STARTS_WITH': - $select_query->$method($sql_field, $like_prefix . db_like($condition['value']) . '%', 'LIKE'); - break; - default: - $select_query->$method($sql_field, $condition['value'], $condition['operator']); - } - } - -} - -/** - * Exception thrown when a malformed entity is passed. - */ -class EntityMalformedException extends Exception { } diff --git a/includes/install.core.inc b/includes/install.core.inc index a74dfdf..ba00d83 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -253,12 +253,14 @@ function install_begin_request(&$install_state) { // Set up $language, so t() caller functions will still work. drupal_language_initialize(); - include_once DRUPAL_ROOT . '/includes/entity.inc'; require_once DRUPAL_ROOT . '/includes/ajax.inc'; $module_list['system']['filename'] = 'modules/system/system.module'; - $module_list['user']['filename'] = 'modules/user/user.module'; + $module_list['entity']['filename'] = 'modules/entity/entity.module'; + $module_list['user']['filename'] = 'modules/user/user.module'; module_list(TRUE, FALSE, FALSE, $module_list); drupal_load('module', 'system'); + drupal_load('module', 'entity'); + //require_once DRUPAL_ROOT . '/modules/entity/entity.controller.inc'; drupal_load('module', 'user'); // Load the cache infrastructure using a "fake" cache implementation that diff --git a/includes/module.inc b/includes/module.inc index 66c77f5..f39f29a 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -425,8 +425,9 @@ function module_enable($module_list, $enable_dependencies = TRUE) { registry_update(); // Refresh the schema to include it. drupal_get_schema(NULL, TRUE); - // Clear entity cache. - entity_info_cache_clear(); + + // Allow modules to react prior to the installation of a module. + module_invoke_all('modules_preinstall', array($module)); // Now install the module if necessary. if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) { @@ -451,6 +452,9 @@ function module_enable($module_list, $enable_dependencies = TRUE) { watchdog('system', '%module module installed.', array('%module' => $module), WATCHDOG_INFO); } + // Allow modules to react prior to the enabling of a module. + module_invoke_all('modules_preenable', array($module)); + // Enable the module. module_invoke($module, 'enable'); diff --git a/modules/comment/comment.info b/modules/comment/comment.info index a5837af..949ffc2 100644 --- a/modules/comment/comment.info +++ b/modules/comment/comment.info @@ -4,6 +4,7 @@ package = Core version = VERSION core = 8.x dependencies[] = text +dependencies[] = entity files[] = comment.module files[] = comment.test configure = admin/content/comment diff --git a/modules/entity/entity.api.php b/modules/entity/entity.api.php new file mode 100644 index 0000000..9b19477 --- /dev/null +++ b/modules/entity/entity.api.php @@ -0,0 +1,414 @@ +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]; + } + } +} diff --git a/modules/entity/entity.controller.inc b/modules/entity/entity.controller.inc new file mode 100644 index 0000000..8327bc6 --- /dev/null +++ b/modules/entity/entity.controller.inc @@ -0,0 +1,390 @@ + $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; + } +} diff --git a/modules/entity/entity.info b/modules/entity/entity.info new file mode 100644 index 0000000..3f4591e --- /dev/null +++ b/modules/entity/entity.info @@ -0,0 +1,8 @@ +name = Entity +description = Entity API for handling entities like nodes or users. +package = Core +version = VERSION +core = 8.x +required = TRUE +files[] = entity.query.inc +files[] = entity.controller.inc diff --git a/modules/entity/entity.module b/modules/entity/entity.module new file mode 100644 index 0000000..49a4c66 --- /dev/null +++ b/modules/entity/entity.module @@ -0,0 +1,443 @@ +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_clear_all('entity_info:', 'cache', TRUE); +} + +/** + * 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) { + // This check enables the URI of an entity to be easily overridden from what + // the callback for the entity type or bundle would return, and it helps + // minimize performance overhead when entity_uri() is called multiple times + // for the same entity. + if (!isset($entity->uri)) { + $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 { + $uri_callback = NULL; + } + + // Invoke the callback to get the URI. If there is no callback, set the + // entity's 'uri' property to FALSE to indicate that it is known to not have + // a URI. + if (isset($uri_callback) && function_exists($uri_callback)) { + $entity->uri = $uri_callback($entity); + if (!isset($entity->uri['options'])) { + $entity->uri['options'] = array(); + } + // Pass the entity data to url() so that alter functions do not need to + // lookup this entity again. + $entity->uri['options']['entity_type'] = $entity_type; + $entity->uri['options']['entity'] = $entity; + } + else { + $entity->uri = FALSE; + } + } + return $entity->uri ? $entity->uri : NULL; +} + +/** + * 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 { } + diff --git a/modules/entity/entity.query.inc b/modules/entity/entity.query.inc new file mode 100644 index 0000000..8083448 --- /dev/null +++ b/modules/entity/entity.query.inc @@ -0,0 +1,953 @@ +changed), field values, and generic entity meta data (bundle, + * entity type, entity id, and revision ID). It is not possible to query across + * multiple entity types. For example, there is no facility to find published + * nodes written by users created in the last hour, as this would require + * querying both node->status and user->created. + * + * Normally we would not want to have public properties on the object, as that + * allows the object's state to become inconsistent too easily. However, this + * class's standard use case involves primarily code that does need to have + * direct access to the collected properties in order to handle alternate + * execution routines. We therefore use public properties for simplicity. Note + * that code that is simply creating and running a field query should still use + * the appropriate methods to add conditions on the query. + * + * Storage engines are not required to support every type of query. By default, + * an EntityFieldQueryException will be raised if an unsupported condition is + * specified or if the query has field conditions or sorts that are stored in + * different field storage engines. However, this logic can be overridden in + * hook_entity_query(). + * + * Also note that this query does not automatically respect entity access + * restrictions. Node access control is performed by the SQL storage engine but + * other storage engines might not do this. + */ +class EntityFieldQuery { + /** + * Indicates that both deleted and non-deleted fields should be returned. + * + * @see EntityFieldQuery::deleted() + */ + const RETURN_ALL = NULL; + + /** + * TRUE if the query has already been altered, FALSE if it hasn't. + * + * Used in alter hooks to check for cloned queries that have already been + * altered prior to the clone (for example, the pager count query). + * + * @var boolean + */ + public $altered = FALSE; + + /** + * Associative array of entity-generic metadata conditions. + * + * @var array + * + * @see EntityFieldQuery::entityCondition() + */ + public $entityConditions = array(); + + /** + * List of field conditions. + * + * @var array + * + * @see EntityFieldQuery::fieldCondition() + */ + public $fieldConditions = array(); + + /** + * List of field meta conditions (language and delta). + * + * Field conditions operate on columns specified by hook_field_schema(), + * the meta conditions operate on columns added by the system: delta + * and language. These can not be mixed with the field conditions because + * field columns can have any name including delta and language. + * + * @var array + * + * @see EntityFieldQuery::fieldLanguageCondition() + * @see EntityFieldQuery::fielDeltaCondition() + */ + public $fieldMetaConditions = array(); + + /** + * List of property conditions. + * + * @var array + * + * @see EntityFieldQuery::propertyCondition() + */ + public $propertyConditions = array(); + + /** + * List of order clauses. + * + * @var array + */ + public $order = array(); + + /** + * The query range. + * + * @var array + * + * @see EntityFieldQuery::range() + */ + public $range = array(); + + /** + * The query pager data. + * + * @var array + * + * @see EntityFieldQuery::pager() + */ + public $pager = array(); + + /** + * Query behavior for deleted data. + * + * TRUE to return only deleted data, FALSE to return only non-deleted data, + * EntityFieldQuery::RETURN_ALL to return everything. + * + * @see EntityFieldQuery::deleted() + */ + public $deleted = FALSE; + + /** + * A list of field arrays used. + * + * Field names passed to EntityFieldQuery::fieldCondition() and + * EntityFieldQuery::fieldOrderBy() are run through field_info_field() before + * stored in this array. This way, the elements of this array are field + * arrays. + * + * @var array + */ + public $fields = array(); + + /** + * TRUE if this is a count query, FALSE if it isn't. + * + * @var boolean + */ + public $count = FALSE; + + /** + * Flag indicating whether this is querying current or all revisions. + * + * @var int + * + * @see EntityFieldQuery::age() + */ + public $age = FIELD_LOAD_CURRENT; + + /** + * A list of the tags added to this query. + * + * @var array + * + * @see EntityFieldQuery::addTag() + */ + public $tags = array(); + + /** + * A list of metadata added to this query. + * + * @var array + * + * @see EntityFieldQuery::addMetaData() + */ + public $metaData = array(); + + /** + * The ordered results. + * + * @var array + * + * @see EntityFieldQuery::execute(). + */ + public $orderedResults = array(); + + /** + * The method executing the query, if it is overriding the default. + * + * @var string + * + * @see EntityFieldQuery::execute(). + */ + public $executeCallback = ''; + + /** + * Adds a condition on entity-generic metadata. + * + * If the overall query contains only entity conditions or ordering, or if + * there are property conditions, then specifying the entity type is + * mandatory. If there are field conditions or ordering but no property + * conditions or ordering, then specifying an entity type is optional. While + * the field storage engine might support field conditions on more than one + * entity type, there is no way to query across multiple entity base tables by + * default. To specify the entity type, pass in 'entity_type' for $name, + * the type as a string for $value, and no $operator (it's disregarded). + * + * 'bundle', 'revision_id' and 'entity_id' have no such restrictions. + * + * Note: The "comment" and "taxonomy_term" entity types don't support bundle + * conditions. For "taxonomy_term", propertyCondition('vid') can be used + * instead. + * + * @param $name + * 'entity_type', 'bundle', 'revision_id' or 'entity_id'. + * @param $value + * The value for $name. In most cases, this is a scalar. For more complex + * options, it is an array. The meaning of each element in the array is + * dependent on $operator. + * @param $operator + * Possible values: + * - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These + * operators expect $value to be a literal of the same type as the + * column. + * - 'IN', 'NOT IN': These operators expect $value to be an array of + * literals of the same type as the column. + * - 'BETWEEN': This operator expects $value to be an array of two literals + * of the same type as the column. + * + * @return EntityFieldQuery + * The called object. + */ + public function entityCondition($name, $value, $operator = NULL) { + $this->entityConditions[$name] = array( + 'value' => $value, + 'operator' => $operator, + ); + return $this; + } + + /** + * Adds a condition on field values. + * + * @param $field + * Either a field name or a field array. + * @param $column + * The column that should hold the value to be matched. + * @param $value + * The value to test the column value against. + * @param $operator + * The operator to be used to test the given value. + * @param $delta_group + * An arbitrary identifier: conditions in the same group must have the same + * $delta_group. + * @param $language_group + * An arbitrary identifier: conditions in the same group must have the same + * $language_group. + * + * @return EntityFieldQuery + * The called object. + * + * @see EntityFieldQuery::addFieldCondition + * @see EntityFieldQuery::deleted + */ + public function fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { + return $this->addFieldCondition($this->fieldConditions, $field, $column, $value, $operator, $delta_group, $language_group); + } + + /** + * Adds a condition on the field language column. + * + * @param $field + * Either a field name or a field array. + * @param $value + * The value to test the column value against. + * @param $operator + * The operator to be used to test the given value. + * @param $delta_group + * An arbitrary identifier: conditions in the same group must have the same + * $delta_group. + * @param $language_group + * An arbitrary identifier: conditions in the same group must have the same + * $language_group. + * + * @return EntityFieldQuery + * The called object. + * + * @see EntityFieldQuery::addFieldCondition + * @see EntityFieldQuery::deleted + */ + public function fieldLanguageCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { + return $this->addFieldCondition($this->fieldMetaConditions, $field, 'language', $value, $operator, $delta_group, $language_group); + } + + /** + * Adds a condition on the field delta column. + * + * @param $field + * Either a field name or a field array. + * @param $value + * The value to test the column value against. + * @param $operator + * The operator to be used to test the given value. + * @param $delta_group + * An arbitrary identifier: conditions in the same group must have the same + * $delta_group. + * @param $language_group + * An arbitrary identifier: conditions in the same group must have the same + * $language_group. + * + * @return EntityFieldQuery + * The called object. + * + * @see EntityFieldQuery::addFieldCondition + * @see EntityFieldQuery::deleted + */ + public function fieldDeltaCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { + return $this->addFieldCondition($this->fieldMetaConditions, $field, 'delta', $value, $operator, $delta_group, $language_group); + } + + /** + * Adds the given condition to the proper condition array. + * + * @param $conditions + * A reference to an array of conditions. + * @param $field + * Either a field name or a field array. + * @param $column + * A column defined in the hook_field_schema() of this field. If this is + * omitted then the query will find only entities that have data in this + * field, using the entity and property conditions if there are any. + * @param $value + * The value to test the column value against. In most cases, this is a + * scalar. For more complex options, it is an array. The meaning of each + * element in the array is dependent on $operator. + * @param $operator + * Possible values: + * - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These + * operators expect $value to be a literal of the same type as the + * column. + * - 'IN', 'NOT IN': These operators expect $value to be an array of + * literals of the same type as the column. + * - 'BETWEEN': This operator expects $value to be an array of two literals + * of the same type as the column. + * @param $delta_group + * An arbitrary identifier: conditions in the same group must have the same + * $delta_group. For example, let's presume a multivalue field which has + * two columns, 'color' and 'shape', and for entity id 1, there are two + * values: red/square and blue/circle. Entity ID 1 does not have values + * corresponding to 'red circle', however if you pass 'red' and 'circle' as + * conditions, it will appear in the results - by default queries will run + * against any combination of deltas. By passing the conditions with the + * same $delta_group it will ensure that only values attached to the same + * delta are matched, and entity 1 would then be excluded from the results. + * @param $language_group + * An arbitrary identifier: conditions in the same group must have the same + * $language_group. + * + * @return EntityFieldQuery + * The called object. + */ + protected function addFieldCondition(&$conditions, $field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { + if (is_scalar($field)) { + $field_definition = field_info_field($field); + if (empty($field_definition)) { + throw new EntityFieldQueryException(t('Unknown field: @field_name', array('@field_name' => $field))); + } + $field = $field_definition; + } + // Ensure the same index is used for field conditions as for fields. + $index = count($this->fields); + $this->fields[$index] = $field; + if (isset($column)) { + $conditions[$index] = array( + 'field' => $field, + 'column' => $column, + 'value' => $value, + 'operator' => $operator, + 'delta_group' => $delta_group, + 'language_group' => $language_group, + ); + } + return $this; + } + + /** + * Adds a condition on an entity-specific property. + * + * An $entity_type must be specified by calling + * EntityFieldCondition::entityCondition('entity_type', $entity_type) before + * executing the query. Also, by default only entities stored in SQL are + * supported; however, EntityFieldQuery::executeCallback can be set to handle + * different entity storage. + * + * @param $column + * A column defined in the hook_schema() of the base table of the entity. + * @param $value + * The value to test the field against. In most cases, this is a scalar. For + * more complex options, it is an array. The meaning of each element in the + * array is dependent on $operator. + * @param $operator + * Possible values: + * - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These + * operators expect $value to be a literal of the same type as the + * column. + * - 'IN', 'NOT IN': These operators expect $value to be an array of + * literals of the same type as the column. + * - 'BETWEEN': This operator expects $value to be an array of two literals + * of the same type as the column. + * The operator can be omitted, and will default to 'IN' if the value is an + * array, or to '=' otherwise. + * + * @return EntityFieldQuery + * The called object. + */ + public function propertyCondition($column, $value, $operator = NULL) { + $this->propertyConditions[] = array( + 'column' => $column, + 'value' => $value, + 'operator' => $operator, + ); + return $this; + } + + /** + * Orders the result set by entity-generic metadata. + * + * If called multiple times, the query will order by each specified column in + * the order this method is called. + * + * Note: The "comment" and "taxonomy_term" entity types don't support ordering + * by bundle. For "taxonomy_term", propertyOrderBy('vid') can be used instead. + * + * @param $name + * 'entity_type', 'bundle', 'revision_id' or 'entity_id'. + * @param $direction + * The direction to sort. Legal values are "ASC" and "DESC". + * + * @return EntityFieldQuery + * The called object. + */ + public function entityOrderBy($name, $direction = 'ASC') { + $this->order[] = array( + 'type' => 'entity', + 'specifier' => $name, + 'direction' => $direction, + ); + return $this; + } + + /** + * Orders the result set by a given field column. + * + * If called multiple times, the query will order by each specified column in + * the order this method is called. + * + * @param $field + * Either a field name or a field array. + * @param $column + * A column defined in the hook_field_schema() of this field. entity_id and + * bundle can also be used. + * @param $direction + * The direction to sort. Legal values are "ASC" and "DESC". + * + * @return EntityFieldQuery + * The called object. + */ + public function fieldOrderBy($field, $column, $direction = 'ASC') { + if (is_scalar($field)) { + $field_definition = field_info_field($field); + if (empty($field_definition)) { + throw new EntityFieldQueryException(t('Unknown field: @field_name', array('@field_name' => $field))); + } + $field = $field_definition; + } + // Save the index used for the new field, for later use in field storage. + $index = count($this->fields); + $this->fields[$index] = $field; + $this->order[] = array( + 'type' => 'field', + 'specifier' => array( + 'field' => $field, + 'index' => $index, + 'column' => $column, + ), + 'direction' => $direction, + ); + return $this; + } + + /** + * Orders the result set by an entity-specific property. + * + * An $entity_type must be specified by calling + * EntityFieldCondition::entityCondition('entity_type', $entity_type) before + * executing the query. + * + * If called multiple times, the query will order by each specified column in + * the order this method is called. + * + * @param $column + * The column on which to order. + * @param $direction + * The direction to sort. Legal values are "ASC" and "DESC". + * + * @return EntityFieldQuery + * The called object. + */ + public function propertyOrderBy($column, $direction = 'ASC') { + $this->order[] = array( + 'type' => 'property', + 'specifier' => $column, + 'direction' => $direction, + ); + return $this; + } + + /** + * Sets the query to be a count query only. + * + * @return EntityFieldQuery + * The called object. + */ + public function count() { + $this->count = TRUE; + return $this; + } + + /** + * Restricts a query to a given range in the result set. + * + * @param $start + * The first entity from the result set to return. If NULL, removes any + * range directives that are set. + * @param $length + * The number of entities to return from the result set. + * + * @return EntityFieldQuery + * The called object. + */ + public function range($start = NULL, $length = NULL) { + $this->range = array( + 'start' => $start, + 'length' => $length, + ); + return $this; + } + + /** + * Enables a pager for the query. + * + * @param $limit + * An integer specifying the number of elements per page. If passed a false + * value (FALSE, 0, NULL), the pager is disabled. + * @param $element + * An optional integer to distinguish between multiple pagers on one page. + * If not provided, one is automatically calculated. + * + * @return EntityFieldQuery + * The called object. + */ + public function pager($limit = 10, $element = NULL) { + if (!isset($element)) { + $element = PagerDefault::$maxElement++; + } + elseif ($element >= PagerDefault::$maxElement) { + PagerDefault::$maxElement = $element + 1; + } + + $this->pager = array( + 'limit' => $limit, + 'element' => $element, + ); + return $this; + } + + /** + * Enables sortable tables for this query. + * + * @param $headers + * An EFQ Header array based on which the order clause is added to the query. + * + * @return EntityFieldQuery + * The called object. + */ + public function tableSort(&$headers) { + // If 'field' is not initialized, the header columns aren't clickable + foreach ($headers as $key =>$header) { + if (is_array($header) && isset($header['specifier'])) { + $headers[$key]['field'] = ''; + } + } + + $order = tablesort_get_order($headers); + $direction = tablesort_get_sort($headers); + foreach ($headers as $header) { + if (is_array($header) && ($header['data'] == $order['name'])) { + if ($header['type'] == 'field') { + $this->fieldOrderBy($header['specifier']['field'], $header['specifier']['column'], $direction); + } + else { + $header['direction'] = $direction; + $this->order[] = $header; + } + } + } + + return $this; + } + + /** + * Filters on the data being deleted. + * + * @param $deleted + * TRUE to only return deleted data, FALSE to return non-deleted data, + * EntityFieldQuery::RETURN_ALL to return everything. Defaults to FALSE. + * + * @return EntityFieldQuery + * The called object. + */ + public function deleted($deleted = TRUE) { + $this->deleted = $deleted; + return $this; + } + + /** + * Queries the current or every revision. + * + * Note that this only affects field conditions. Property conditions always + * apply to the current revision. + * @TODO: Once revision tables have been cleaned up, revisit this. + * + * @param $age + * - FIELD_LOAD_CURRENT (default): Query the most recent revisions for all + * entities. The results will be keyed by entity type and entity ID. + * - FIELD_LOAD_REVISION: Query all revisions. The results will be keyed by + * entity type and entity revision ID. + * + * @return EntityFieldQuery + * The called object. + */ + public function age($age) { + $this->age = $age; + return $this; + } + + /** + * Adds a tag to the query. + * + * Tags are strings that mark a query so that hook_query_alter() and + * hook_query_TAG_alter() implementations may decide if they wish to alter + * the query. A query may have any number of tags, and they must be valid PHP + * identifiers (composed of letters, numbers, and underscores). For example, + * queries involving nodes that will be displayed for a user need to add the + * tag 'node_access', so that the node module can add access restrictions to + * the query. + * + * If an entity field query has tags, it must also have an entity type + * specified, because the alter hook will need the entity base table. + * + * @param string $tag + * The tag to add. + * + * @return EntityFieldQuery + * The called object. + */ + public function addTag($tag) { + $this->tags[$tag] = $tag; + return $this; + } + + /** + * Adds additional metadata to the query. + * + * Sometimes a query may need to provide additional contextual data for the + * alter hook. The alter hook implementations may then use that information + * to decide if and how to take action. + * + * @param $key + * The unique identifier for this piece of metadata. Must be a string that + * follows the same rules as any other PHP identifier. + * @param $object + * The additional data to add to the query. May be any valid PHP variable. + * + * @return EntityFieldQuery + * The called object. + */ + public function addMetaData($key, $object) { + $this->metaData[$key] = $object; + return $this; + } + + /** + * Executes the query. + * + * After executing the query, $this->orderedResults will contain a list of + * the same stub entities in the order returned by the query. This is only + * relevant if there are multiple entity types in the returned value and + * a field ordering was requested. In every other case, the returned value + * contains everything necessary for processing. + * + * @return + * Either a number if count() was called or an array of associative + * arrays of stub entities. The outer array keys are entity types, and the + * inner array keys are the relevant ID. (In most this cases this will be + * the entity ID. The only exception is when age=FIELD_LOAD_REVISION is used + * and field conditions or sorts are present -- in this case, the key will + * be the revision ID.) The inner array values are always stub entities, as + * returned by entity_create_stub_entity(). To traverse the returned array: + * @code + * foreach ($query->execute() as $entity_type => $entities) { + * foreach ($entities as $entity_id => $entity) { + * @endcode + * Note if the entity type is known, then the following snippet will load + * the entities found: + * @code + * $result = $query->execute(); + * $entities = entity_load($my_type, array_keys($result[$my_type])); + * @endcode + */ + public function execute() { + // Give a chance to other modules to alter the query. + drupal_alter('entity_query', $this); + $this->altered = TRUE; + + // Initialize the pager. + $this->initializePager(); + + // Execute the query using the correct callback. + $result = call_user_func($this->queryCallback(), $this); + + return $result; + } + + /** + * Determines the query callback to use for this entity query. + * + * @return + * A callback that can be used with call_user_func(). + */ + public function queryCallback() { + // Use the override from $this->executeCallback. It can be set either + // while building the query, or using hook_entity_query_alter(). + if (function_exists($this->executeCallback)) { + return $this->executeCallback; + } + // If there are no field conditions and sorts, and no execute callback + // then we default to querying entity tables in SQL. + if (empty($this->fields)) { + return array($this, 'propertyQuery'); + } + // If no override, find the storage engine to be used. + foreach ($this->fields as $field) { + if (!isset($storage)) { + $storage = $field['storage']['module']; + } + elseif ($storage != $field['storage']['module']) { + throw new EntityFieldQueryException(t("Can't handle more than one field storage engine")); + } + } + if ($storage) { + // Use hook_field_storage_query() from the field storage. + return $storage . '_field_storage_query'; + } + else { + throw new EntityFieldQueryException(t("Field storage engine not found.")); + } + } + + /** + * Queries entity tables in SQL for property conditions and sorts. + * + * This method is only used if there are no field conditions and sorts. + * + * @return + * See EntityFieldQuery::execute(). + */ + protected function propertyQuery() { + if (empty($this->entityConditions['entity_type'])) { + throw new EntityFieldQueryException(t('For this query an entity type must be specified.')); + } + $entity_type = $this->entityConditions['entity_type']['value']; + $entity_info = entity_get_info($entity_type); + if (empty($entity_info['base table'])) { + throw new EntityFieldQueryException(t('Entity %entity has no base table.', array('%entity' => $entity_type))); + } + $base_table = $entity_info['base table']; + $base_table_schema = drupal_get_schema($base_table); + $select_query = db_select($base_table); + $select_query->addExpression(':entity_type', 'entity_type', array(':entity_type' => $entity_type)); + // Process the property conditions. + foreach ($this->propertyConditions as $property_condition) { + $this->addCondition($select_query, "$base_table." . $property_condition['column'], $property_condition); + } + // Process the four possible entity condition. + // The id field is always present in entity keys. + $sql_field = $entity_info['entity keys']['id']; + $id_map['entity_id'] = $sql_field; + $select_query->addField($base_table, $sql_field, 'entity_id'); + if (isset($this->entityConditions['entity_id'])) { + $this->addCondition($select_query, $sql_field, $this->entityConditions['entity_id']); + } + + // If there is a revision key defined, use it. + if (!empty($entity_info['entity keys']['revision'])) { + $sql_field = $entity_info['entity keys']['revision']; + $select_query->addField($base_table, $sql_field, 'revision_id'); + if (isset($this->entityConditions['revision_id'])) { + $this->addCondition($select_query, $sql_field, $this->entityConditions['revision_id']); + } + } + else { + $sql_field = 'revision_id'; + $select_query->addExpression('NULL', 'revision_id'); + } + $id_map['revision_id'] = $sql_field; + + // Handle bundles. + if (!empty($entity_info['entity keys']['bundle'])) { + $sql_field = $entity_info['entity keys']['bundle']; + $having = FALSE; + + if (!empty($base_table_schema['fields'][$sql_field])) { + $select_query->addField($base_table, $sql_field, 'bundle'); + } + } + else { + $sql_field = 'bundle'; + $select_query->addExpression(':bundle', 'bundle', array(':bundle' => $entity_type)); + $having = TRUE; + } + $id_map['bundle'] = $sql_field; + if (isset($this->entityConditions['bundle'])) { + $this->addCondition($select_query, $sql_field, $this->entityConditions['bundle'], $having); + } + + // Order the query. + foreach ($this->order as $order) { + if ($order['type'] == 'entity') { + $key = $order['specifier']; + if (!isset($id_map[$key])) { + throw new EntityFieldQueryException(t('Do not know how to order on @key for @entity_type', array('@key' => $key, '@entity_type' => $entity_type))); + } + $select_query->orderBy($id_map[$key], $order['direction']); + } + elseif ($order['type'] == 'property') { + $select_query->orderBy("$base_table." . $order['specifier'], $order['direction']); + } + } + + return $this->finishQuery($select_query); + } + + /** + * Gets the total number of results and initialize a pager for the query. + * + * This query can be detected by checking for ($this->pager && $this->count), + * which allows a driver to return 0 from the count query and disable + * the pager. + */ + function initializePager() { + if ($this->pager && !$this->count) { + $page = pager_find_page($this->pager['element']); + $count_query = clone $this; + $this->pager['total'] = $count_query->count()->execute(); + $this->pager['start'] = $page * $this->pager['limit']; + pager_default_initialize($this->pager['total'], $this->pager['limit'], $this->pager['element']); + $this->range($this->pager['start'], $this->pager['limit']); + } + } + + /** + * Finishes the query. + * + * Adds tags, metaData, range and returns the requested list or count. + * + * @param SelectQuery $select_query + * A SelectQuery which has entity_type, entity_id, revision_id and bundle + * fields added. + * @param $id_key + * Which field's values to use as the returned array keys. + * + * @return + * See EntityFieldQuery::execute(). + */ + function finishQuery($select_query, $id_key = 'entity_id') { + foreach ($this->tags as $tag) { + $select_query->addTag($tag); + } + foreach ($this->metaData as $key => $object) { + $select_query->addMetaData($key, $object); + } + $select_query->addMetaData('entity_field_query', $this); + if ($this->range) { + $select_query->range($this->range['start'], $this->range['length']); + } + if ($this->count) { + return $select_query->countQuery()->execute()->fetchField(); + } + $return = array(); + foreach ($select_query->execute() as $partial_entity) { + $bundle = isset($partial_entity->bundle) ? $partial_entity->bundle : NULL; + $entity = entity_create_stub_entity($partial_entity->entity_type, array($partial_entity->entity_id, $partial_entity->revision_id, $bundle)); + $return[$partial_entity->entity_type][$partial_entity->$id_key] = $entity; + $this->ordered_results[] = $partial_entity; + } + return $return; + } + + /** + * Adds a condition to an already built SelectQuery (internal function). + * + * This is a helper for hook_entity_query() and hook_field_storage_query(). + * + * @param SelectQuery $select_query + * A SelectQuery object. + * @param $sql_field + * The name of the field. + * @param $condition + * A condition as described in EntityFieldQuery::fieldCondition() and + * EntityFieldQuery::entityCondition(). + * @param $having + * HAVING or WHERE. This is necessary because SQL can't handle WHERE + * conditions on aliased columns. + */ + public function addCondition(SelectQuery $select_query, $sql_field, $condition, $having = FALSE) { + $method = $having ? 'havingCondition' : 'condition'; + $like_prefix = ''; + switch ($condition['operator']) { + case 'CONTAINS': + $like_prefix = '%'; + case 'STARTS_WITH': + $select_query->$method($sql_field, $like_prefix . db_like($condition['value']) . '%', 'LIKE'); + break; + default: + $select_query->$method($sql_field, $condition['value'], $condition['operator']); + } + } + +} + +/** + * Exception thrown when a malformed entity is passed. + */ +class EntityMalformedException extends Exception { } diff --git a/modules/entity/tests/entity_cache_test.info b/modules/entity/tests/entity_cache_test.info new file mode 100644 index 0000000..c13496e --- /dev/null +++ b/modules/entity/tests/entity_cache_test.info @@ -0,0 +1,7 @@ +name = "Entity cache test" +description = "Support module for testing entity cache." +package = Testing +version = VERSION +core = 8.x +dependencies[] = entity_cache_test_dependency +hidden = TRUE diff --git a/modules/entity/tests/entity_cache_test.module b/modules/entity/tests/entity_cache_test.module new file mode 100644 index 0000000..5ae9ecc --- /dev/null +++ b/modules/entity/tests/entity_cache_test.module @@ -0,0 +1,27 @@ + array( + 'label' => 'Entity Cache Test', + ), + ); +} diff --git a/modules/entity/tests/entity_crud_hook_test.info b/modules/entity/tests/entity_crud_hook_test.info new file mode 100644 index 0000000..28ce1b5 --- /dev/null +++ b/modules/entity/tests/entity_crud_hook_test.info @@ -0,0 +1,6 @@ +name = "Entity CRUD Hooks Test" +description = "Support module for CRUD hook tests." +core = 8.x +package = Testing +version = VERSION +hidden = TRUE diff --git a/modules/entity/tests/entity_crud_hook_test.module b/modules/entity/tests/entity_crud_hook_test.module new file mode 100644 index 0000000..873a162 --- /dev/null +++ b/modules/entity/tests/entity_crud_hook_test.module @@ -0,0 +1,266 @@ + 'Entity CRUD hooks', + 'description' => 'Tests the invocation of hooks when inserting, loading, updating or deleting an entity.', + 'group' => 'Entity API', + ); + } + + public function setUp() { + parent::setUp('entity_crud_hook_test', 'taxonomy', 'comment'); + } + + /** + * Pass if the message $text was set by one of the CRUD hooks in + * entity_crud_hook_test.module, i.e., if the $text is an element of + * $_SESSION['entity_crud_hook_test']. + * + * @param $text + * Plain text to look for. + * @param $message + * Message to display. + * @param $group + * The group this message belongs to, defaults to 'Other'. + * @return + * TRUE on pass, FALSE on fail. + */ + protected function assertHookMessage($text, $message = NULL, $group = 'Other') { + if (!isset($message)) { + $message = $text; + } + return $this->assertTrue(array_search($text, $_SESSION['entity_crud_hook_test']) !== FALSE, $message, $group); + } + + /** + * Test hook invocations for CRUD operations on comments. + */ + public function testCommentHooks() { + $node = (object) array( + 'uid' => 1, + 'type' => 'article', + 'title' => 'Test node', + 'status' => 1, + 'comment' => 2, + 'promote' => 0, + 'sticky' => 0, + 'language' => LANGUAGE_NONE, + 'created' => REQUEST_TIME, + 'changed' => REQUEST_TIME, + ); + node_save($node); + $nid = $node->nid; + + $comment = (object) array( + 'cid' => NULL, + 'pid' => 0, + 'nid' => $nid, + 'uid' => 1, + 'subject' => 'Test comment', + 'created' => REQUEST_TIME, + 'changed' => REQUEST_TIME, + 'status' => 1, + 'language' => LANGUAGE_NONE, + ); + $_SESSION['entity_crud_hook_test'] = array(); + comment_save($comment); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type comment'); + $this->assertHookMessage('entity_crud_hook_test_comment_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type comment'); + $this->assertHookMessage('entity_crud_hook_test_comment_insert called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $comment = comment_load($comment->cid); + + $this->assertHookMessage('entity_crud_hook_test_entity_load called for type comment'); + $this->assertHookMessage('entity_crud_hook_test_comment_load called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $comment->subject = 'New subject'; + comment_save($comment); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type comment'); + $this->assertHookMessage('entity_crud_hook_test_comment_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_update called for type comment'); + $this->assertHookMessage('entity_crud_hook_test_comment_update called'); + + $_SESSION['entity_crud_hook_test'] = array(); + comment_delete($comment->cid); + + $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type comment'); + $this->assertHookMessage('entity_crud_hook_test_comment_delete called'); + } + + /** + * Test hook invocations for CRUD operations on files. + */ + public function testFileHooks() { + $url = 'public://entity_crud_hook_test.file'; + file_put_contents($url, 'Test test test'); + $file = (object) array( + 'fid' => NULL, + 'uid' => 1, + 'filename' => 'entity_crud_hook_test.file', + 'uri' => $url, + 'filemime' => 'text/plain', + 'filesize' => filesize($url), + 'status' => 1, + 'timestamp' => REQUEST_TIME, + ); + $_SESSION['entity_crud_hook_test'] = array(); + file_save($file); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type file'); + $this->assertHookMessage('entity_crud_hook_test_file_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type file'); + $this->assertHookMessage('entity_crud_hook_test_file_insert called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $file = file_load($file->fid); + + $this->assertHookMessage('entity_crud_hook_test_entity_load called for type file'); + $this->assertHookMessage('entity_crud_hook_test_file_load called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $file->filename = 'new.entity_crud_hook_test.file'; + file_save($file); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type file'); + $this->assertHookMessage('entity_crud_hook_test_file_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_update called for type file'); + $this->assertHookMessage('entity_crud_hook_test_file_update called'); + + $_SESSION['entity_crud_hook_test'] = array(); + file_delete($file); + + $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type file'); + $this->assertHookMessage('entity_crud_hook_test_file_delete called'); + } + + /** + * Test hook invocations for CRUD operations on nodes. + */ + public function testNodeHooks() { + $node = (object) array( + 'uid' => 1, + 'type' => 'article', + 'title' => 'Test node', + 'status' => 1, + 'comment' => 2, + 'promote' => 0, + 'sticky' => 0, + 'language' => LANGUAGE_NONE, + 'created' => REQUEST_TIME, + 'changed' => REQUEST_TIME, + ); + $_SESSION['entity_crud_hook_test'] = array(); + node_save($node); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type node'); + $this->assertHookMessage('entity_crud_hook_test_node_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type node'); + $this->assertHookMessage('entity_crud_hook_test_node_insert called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $node = node_load($node->nid); + + $this->assertHookMessage('entity_crud_hook_test_entity_load called for type node'); + $this->assertHookMessage('entity_crud_hook_test_node_load called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $node->title = 'New title'; + node_save($node); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type node'); + $this->assertHookMessage('entity_crud_hook_test_node_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_update called for type node'); + $this->assertHookMessage('entity_crud_hook_test_node_update called'); + + $_SESSION['entity_crud_hook_test'] = array(); + node_delete($node->nid); + + $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type node'); + $this->assertHookMessage('entity_crud_hook_test_node_delete called'); + } + + /** + * Test hook invocations for CRUD operations on taxonomy terms. + */ + public function testTaxonomyTermHooks() { + $vocabulary = (object) array( + 'name' => 'Test vocabulary', + 'machine_name' => 'test', + 'description' => NULL, + 'module' => 'entity_crud_hook_test', + ); + taxonomy_vocabulary_save($vocabulary); + + $term = (object) array( + 'vid' => $vocabulary->vid, + 'name' => 'Test term', + 'description' => NULL, + 'format' => 1, + ); + $_SESSION['entity_crud_hook_test'] = array(); + taxonomy_term_save($term); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_term'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type taxonomy_term'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_insert called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $term = taxonomy_term_load($term->tid); + + $this->assertHookMessage('entity_crud_hook_test_entity_load called for type taxonomy_term'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_load called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $term->name = 'New name'; + taxonomy_term_save($term); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_term'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_update called for type taxonomy_term'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_update called'); + + $_SESSION['entity_crud_hook_test'] = array(); + taxonomy_term_delete($term->tid); + + $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type taxonomy_term'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_delete called'); + } + + /** + * Test hook invocations for CRUD operations on taxonomy vocabularies. + */ + public function testTaxonomyVocabularyHooks() { + $vocabulary = (object) array( + 'name' => 'Test vocabulary', + 'machine_name' => 'test', + 'description' => NULL, + 'module' => 'entity_crud_hook_test', + ); + $_SESSION['entity_crud_hook_test'] = array(); + taxonomy_vocabulary_save($vocabulary); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type taxonomy_vocabulary'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_insert called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $vocabulary = taxonomy_vocabulary_load($vocabulary->vid); + + $this->assertHookMessage('entity_crud_hook_test_entity_load called for type taxonomy_vocabulary'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_load called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $vocabulary->name = 'New name'; + taxonomy_vocabulary_save($vocabulary); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_update called for type taxonomy_vocabulary'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_update called'); + + $_SESSION['entity_crud_hook_test'] = array(); + taxonomy_vocabulary_delete($vocabulary->vid); + + $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type taxonomy_vocabulary'); + $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_delete called'); + } + + /** + * Test hook invocations for CRUD operations on users. + */ + public function testUserHooks() { + $edit = array( + 'name' => 'Test user', + 'mail' => 'test@example.com', + 'created' => REQUEST_TIME, + 'status' => 1, + 'language' => 'en', + ); + $account = (object) $edit; + $_SESSION['entity_crud_hook_test'] = array(); + $account = user_save($account, $edit); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type user'); + $this->assertHookMessage('entity_crud_hook_test_user_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type user'); + $this->assertHookMessage('entity_crud_hook_test_user_insert called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $account = user_load($account->uid); + + $this->assertHookMessage('entity_crud_hook_test_entity_load called for type user'); + $this->assertHookMessage('entity_crud_hook_test_user_load called'); + + $_SESSION['entity_crud_hook_test'] = array(); + $edit['name'] = 'New name'; + $account = user_save($account, $edit); + + $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type user'); + $this->assertHookMessage('entity_crud_hook_test_user_presave called'); + $this->assertHookMessage('entity_crud_hook_test_entity_update called for type user'); + $this->assertHookMessage('entity_crud_hook_test_user_update called'); + + $_SESSION['entity_crud_hook_test'] = array(); + user_delete($account->uid); + + $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type user'); + $this->assertHookMessage('entity_crud_hook_test_user_delete called'); + } + +} diff --git a/modules/entity/tests/entity_query.test b/modules/entity/tests/entity_query.test new file mode 100644 index 0000000..e540a90 --- /dev/null +++ b/modules/entity/tests/entity_query.test @@ -0,0 +1,1497 @@ + 'Entity query', + 'description' => 'Test the EntityFieldQuery class.', + 'group' => 'Entity API', + ); + } + + function setUp() { + parent::setUp(array('field_test')); + + field_attach_create_bundle('test_entity_bundle_key', 'bundle1'); + field_attach_create_bundle('test_entity_bundle_key', 'bundle2'); + field_attach_create_bundle('test_entity', 'test_bundles'); + field_attach_create_bundle('test_entity_bundle', 'test_entity_bundle'); + + $instances = array(); + $this->fields = array(); + $this->field_names[0] = $field_name = drupal_strtolower($this->randomName() . '_field_name'); + $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 4); + $field = field_create_field($field); + $this->fields[0] = $field; + $instance = array( + 'field_name' => $field_name, + 'entity_type' => '', + 'bundle' => '', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array( + 'test_instance_setting' => $this->randomName(), + ), + 'widget' => array( + 'type' => 'test_field_widget', + 'label' => 'Test Field', + 'settings' => array( + 'test_widget_setting' => $this->randomName(), + ) + ) + ); + + $instances[0] = $instance; + + // Add an instance to that bundle. + $instances[0]['bundle'] = 'bundle1'; + $instances[0]['entity_type'] = 'test_entity_bundle_key'; + field_create_instance($instances[0]); + $instances[0]['bundle'] = 'bundle2'; + field_create_instance($instances[0]); + $instances[0]['bundle'] = $instances[0]['entity_type'] = 'test_entity_bundle'; + field_create_instance($instances[0]); + + $this->field_names[1] = $field_name = drupal_strtolower($this->randomName() . '_field_name'); + $field = array('field_name' => $field_name, 'type' => 'shape', 'cardinality' => 4); + $field = field_create_field($field); + $this->fields[1] = $field; + $instance = array( + 'field_name' => $field_name, + 'entity_type' => '', + 'bundle' => '', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array( + 'test_instance_setting' => $this->randomName(), + ), + 'widget' => array( + 'type' => 'test_field_widget', + 'label' => 'Test Field', + 'settings' => array( + 'test_widget_setting' => $this->randomName(), + ) + ) + ); + + $instances[1] = $instance; + + // Add a field instance to the bundles. + $instances[1]['bundle'] = 'bundle1'; + $instances[1]['entity_type'] = 'test_entity_bundle_key'; + field_create_instance($instances[1]); + $instances[1]['bundle'] = $instances[1]['entity_type'] = 'test_entity_bundle'; + field_create_instance($instances[1]); + + $this->instances = $instances; + // Write entity base table if there is one. + $entities = array(); + + // Create entities which have a 'bundle key' defined. + for ($i = 1; $i < 7; $i++) { + $entity = new stdClass(); + $entity->ftid = $i; + $entity->fttype = ($i < 5) ? 'bundle1' : 'bundle2'; + + $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i; + drupal_write_record('test_entity_bundle_key', $entity); + field_attach_insert('test_entity_bundle_key', $entity); + } + + $entity = new stdClass(); + $entity->ftid = 5; + $entity->fttype = 'test_entity_bundle'; + $entity->{$this->field_names[1]}[LANGUAGE_NONE][0]['shape'] = 'square'; + $entity->{$this->field_names[1]}[LANGUAGE_NONE][0]['color'] = 'red'; + $entity->{$this->field_names[1]}[LANGUAGE_NONE][1]['shape'] = 'circle'; + $entity->{$this->field_names[1]}[LANGUAGE_NONE][1]['color'] = 'blue'; + drupal_write_record('test_entity_bundle', $entity); + field_attach_insert('test_entity_bundle', $entity); + + $instances[2] = $instance; + $instances[2]['bundle'] = 'test_bundle'; + $instances[2]['field_name'] = $this->field_names[0]; + $instances[2]['entity_type'] = 'test_entity'; + field_create_instance($instances[2]); + + // Create entities with support for revisions. + for ($i = 1; $i < 5; $i++) { + $entity = new stdClass(); + $entity->ftid = $i; + $entity->ftvid = $i; + $entity->fttype = 'test_bundle'; + $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i; + + drupal_write_record('test_entity', $entity); + field_attach_insert('test_entity', $entity); + drupal_write_record('test_entity_revision', $entity); + } + + // Add two revisions to an entity. + for ($i = 100; $i < 102; $i++) { + $entity = new stdClass(); + $entity->ftid = 4; + $entity->ftvid = $i; + $entity->fttype = 'test_bundle'; + $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i; + + drupal_write_record('test_entity', $entity, 'ftid'); + drupal_write_record('test_entity_revision', $entity); + + db_update('test_entity') + ->fields(array('ftvid' => $entity->ftvid)) + ->condition('ftid', $entity->ftid) + ->execute(); + + field_attach_update('test_entity', $entity); + } + } + + /** + * Tests EntityFieldQuery. + */ + function testEntityFieldQuery() { + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle') + ->entityCondition('entity_id', '5'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle', 5), + ), t('Test query on an entity type with a generated bundle.')); + + // Test entity_type condition. + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'test_entity_bundle_key'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test entity entity_type condition.')); + + // Test entity_id condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityCondition('entity_id', '3'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + ), t('Test entity entity_id condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', '3'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + ), t('Test entity entity_id condition and entity_id property condition.')); + + // Test bundle condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityCondition('bundle', 'bundle1'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test entity bundle condition: bundle1.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityCondition('bundle', 'bundle2'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test entity bundle condition: bundle2.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('fttype', 'bundle2'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test entity bundle condition and bundle property condition.')); + + // Test revision_id condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->entityCondition('revision_id', '3'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 3), + ), t('Test entity revision_id condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->propertyCondition('ftvid', '3'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 3), + ), t('Test entity revision_id condition and revision_id property condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->fieldCondition($this->fields[0], 'value', 100, '>=') + ->age(FIELD_LOAD_REVISION); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 100), + array('test_entity', 101), + ), t('Test revision age.')); + + // Test that fields attached to the non-revision supporting entity + // 'test_entity_bundle_key' are reachable in FIELD_LOAD_REVISION. + $query = new EntityFieldQuery(); + $query + ->fieldCondition($this->fields[0], 'value', 100, '<') + ->age(FIELD_LOAD_REVISION); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test that fields are reachable from FIELD_LOAD_REVISION even for non-revision entities.')); + + // Test entity sort by entity_id. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityOrderBy('entity_id', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test sort entity entity_id in ascending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityOrderBy('entity_id', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test sort entity entity_id in descending order.'), TRUE); + + // Test entity sort by entity_id, with a field condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->entityOrderBy('entity_id', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test sort entity entity_id in ascending order, with a field condition.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->propertyOrderBy('ftid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test sort entity entity_id property in descending order, with a field condition.'), TRUE); + + // Test property sort by entity id. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyOrderBy('ftid', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test sort entity entity_id property in ascending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyOrderBy('ftid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test sort entity entity_id property in descending order.'), TRUE); + + // Test property sort by entity id, with a field condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->propertyOrderBy('ftid', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test sort entity entity_id property in ascending order, with a field condition.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->propertyOrderBy('ftid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test sort entity entity_id property in descending order, with a field condition.'), TRUE); + + // Test entity sort by bundle. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityOrderBy('bundle', 'ASC') + ->propertyOrderBy('ftid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + ), t('Test sort entity bundle in ascending order, property in descending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityOrderBy('bundle', 'DESC') + ->propertyOrderBy('ftid', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test sort entity bundle in descending order, property in ascending order.'), TRUE); + + // Test entity sort by bundle, with a field condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->entityOrderBy('bundle', 'ASC') + ->propertyOrderBy('ftid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + ), t('Test sort entity bundle in ascending order, property in descending order, with a field condition.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->entityOrderBy('bundle', 'DESC') + ->propertyOrderBy('ftid', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test sort entity bundle in descending order, property in ascending order, with a field condition.'), TRUE); + + // Test entity sort by bundle, field. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityOrderBy('bundle', 'ASC') + ->fieldOrderBy($this->fields[0], 'value', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + ), t('Test sort entity bundle in ascending order, field in descending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityOrderBy('bundle', 'DESC') + ->fieldOrderBy($this->fields[0], 'value', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test sort entity bundle in descending order, field in ascending order.'), TRUE); + + // Test entity sort by revision_id. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->entityOrderBy('revision_id', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test sort entity revision_id in ascending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->entityOrderBy('revision_id', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 4), + array('test_entity', 3), + array('test_entity', 2), + array('test_entity', 1), + ), t('Test sort entity revision_id in descending order.'), TRUE); + + // Test entity sort by revision_id, with a field condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->entityOrderBy('revision_id', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test sort entity revision_id in ascending order, with a field condition.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->entityOrderBy('revision_id', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 4), + array('test_entity', 3), + array('test_entity', 2), + array('test_entity', 1), + ), t('Test sort entity revision_id in descending order, with a field condition.'), TRUE); + + // Test property sort by revision_id. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->propertyOrderBy('ftvid', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test sort entity revision_id property in ascending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->propertyOrderBy('ftvid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 4), + array('test_entity', 3), + array('test_entity', 2), + array('test_entity', 1), + ), t('Test sort entity revision_id property in descending order.'), TRUE); + + // Test property sort by revision_id, with a field condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->propertyOrderBy('ftvid', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test sort entity revision_id property in ascending order, with a field condition.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->propertyOrderBy('ftvid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 4), + array('test_entity', 3), + array('test_entity', 2), + array('test_entity', 1), + ), t('Test sort entity revision_id property in descending order, with a field condition.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldOrderBy($this->fields[0], 'value', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test sort field in ascending order without field condition.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldOrderBy($this->fields[0], 'value', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test sort field in descending order without field condition.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->fieldOrderBy($this->fields[0], 'value', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test sort field in ascending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->fieldOrderBy($this->fields[0], 'value', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test sort field in descending order.'), TRUE); + + // Test "in" operation with entity entity_type condition and entity_id + // property condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', array(1, 3, 4), 'IN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test "in" operation with entity entity_type condition and entity_id property condition.')); + + // Test "in" operation with entity entity_type condition and entity_id + // property condition. Sort in descending order by entity_id. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', array(1, 3, 4), 'IN') + ->propertyOrderBy('ftid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 1), + ), t('Test "in" operation with entity entity_type condition and entity_id property condition. Sort entity_id in descending order.'), TRUE); + + // Test query count + $query = new EntityFieldQuery(); + $query_count = $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->count() + ->execute(); + $this->assertEqual($query_count, 6, t('Test query count on entity condition.')); + + $query = new EntityFieldQuery(); + $query_count = $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', '1') + ->count() + ->execute(); + $this->assertEqual($query_count, 1, t('Test query count on entity and property condition.')); + + $query = new EntityFieldQuery(); + $query_count = $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', '4', '>') + ->count() + ->execute(); + $this->assertEqual($query_count, 2, t('Test query count on entity and property condition with operator.')); + + $query = new EntityFieldQuery(); + $query_count = $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 3, '=') + ->count() + ->execute(); + $this->assertEqual($query_count, 1, t('Test query count on field condition.')); + + // First, test without options. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('fttype', 'und', 'CONTAINS'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test the "contains" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[1], 'shape', 'uar', 'CONTAINS'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle', 5), + ), t('Test the "contains" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 1, '='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + ), t('Test the "equal to" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 3, '='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity', 3), + ), t('Test the "equal to" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 3, '<>'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test the "not equal to" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 3, '<>'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 4), + ), t('Test the "not equal to" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 2, '<'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + ), t('Test the "less than" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 2, '<'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity', 1), + ), t('Test the "less than" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 2, '<='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + ), t('Test the "less than or equal to" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 2, '<='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity', 1), + array('test_entity', 2), + ), t('Test the "less than or equal to" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 4, '>'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test the "greater than" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 2, '>'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test the "greater than" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 4, '>='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test the "greater than or equal to" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 3, '>='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test the "greater than or equal to" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', array(3, 4), 'NOT IN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test the "not in" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', array(3, 4, 100, 101), 'NOT IN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + array('test_entity', 1), + array('test_entity', 2), + ), t('Test the "not in" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', array(3, 4), 'IN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test the "in" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', array(2, 3), 'IN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity', 2), + array('test_entity', 3), + ), t('Test the "in" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', array(1, 3), 'BETWEEN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + ), t('Test the "between" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', array(1, 3), 'BETWEEN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 3), + ), t('Test the "between" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('fttype', 'bun', 'STARTS_WITH'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test the "starts_with" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[1], 'shape', 'squ', 'STARTS_WITH'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle', 5), + ), t('Test the "starts_with" operation on a field.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 3); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity', 3), + ), t('Test omission of an operator with a single item.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', array(2, 3)); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity', 2), + array('test_entity', 3), + ), t('Test omission of an operator with multiple items.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 1, '>') + ->fieldCondition($this->fields[0], 'value', 4, '<'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + ), t('Test entity, property and field conditions.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityCondition('bundle', 'bundle', 'STARTS_WITH') + ->propertyCondition('ftid', 4) + ->fieldCondition($this->fields[0], 'value', 4); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + ), t('Test entity condition with "starts_with" operation, and property and field conditions.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyOrderBy('ftid', 'ASC') + ->range(0, 2); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + ), t('Test limit on a property.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>=') + ->fieldOrderBy($this->fields[0], 'value', 'ASC') + ->range(0, 2); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + ), t('Test limit on a field.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyOrderBy('ftid', 'ASC') + ->range(4, 6); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test offset on a property.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->fieldOrderBy($this->fields[0], 'value', 'ASC') + ->range(2, 4); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test offset on a field.'), TRUE); + + for ($i = 6; $i < 10; $i++) { + $entity = new stdClass(); + $entity->ftid = $i; + $entity->fttype = 'test_entity_bundle'; + $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i - 5; + drupal_write_record('test_entity_bundle', $entity); + field_attach_insert('test_entity_bundle', $entity); + } + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 2, '>'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + array('test_entity', 3), + array('test_entity', 4), + array('test_entity_bundle', 8), + array('test_entity_bundle', 9), + ), t('Select a field across multiple entities.')); + + $query = new EntityFieldQuery(); + $query + ->fieldCondition($this->fields[1], 'shape', 'square') + ->fieldCondition($this->fields[1], 'color', 'blue'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle', 5), + ), t('Test without a delta group.')); + + $query = new EntityFieldQuery(); + $query + ->fieldCondition($this->fields[1], 'shape', 'square', '=', 'group') + ->fieldCondition($this->fields[1], 'color', 'blue', '=', 'group'); + $this->assertEntityFieldQuery($query, array(), t('Test with a delta group.')); + + // Test query on a deleted field. + field_attach_delete_bundle('test_entity_bundle_key', 'bundle1'); + field_attach_delete_bundle('test_entity', 'test_bundle'); + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', '3'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle', 8), + ), t('Test query on a field after deleting field from some entities.')); + + field_attach_delete_bundle('test_entity_bundle', 'test_entity_bundle'); + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', '3'); + $this->assertEntityFieldQuery($query, array(), t('Test query on a field after deleting field from all entities.')); + + $query = new EntityFieldQuery(); + $query + ->fieldCondition($this->fields[0], 'value', '3') + ->deleted(TRUE); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle', 8), + array('test_entity', 3), + ), t('Test query on a deleted field with deleted option set to TRUE.')); + + $pass = FALSE; + $query = new EntityFieldQuery(); + try { + $query->execute(); + } + catch (EntityFieldQueryException $exception) { + $pass = ($exception->getMessage() == t('For this query an entity type must be specified.')); + } + $this->assertTrue($pass, t("Can't query the universe.")); + } + + /** + * Tests field meta conditions. + */ + function testEntityFieldQueryMetaConditions() { + // Make a test field translatable. + $this->fields[0]['translatable'] = TRUE; + field_update_field($this->fields[0]); + field_test_entity_info_translatable('test_entity', TRUE); + drupal_static_reset('field_available_languages'); + + // Create more items with different languages. + $entity = new stdClass(); + $entity->ftid = 1; + $entity->ftvid = 1; + $entity->fttype = 'test_bundle'; + $j = 0; + + foreach (array(LANGUAGE_NONE, 'en') as $langcode) { + for ($i = 0; $i < 4; $i++) { + $entity->{$this->field_names[0]}[$langcode][$i]['value'] = $i + $j; + } + $j += 4; + } + + field_attach_update('test_entity', $entity); + + // Test delta field meta condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldDeltaCondition($this->fields[0], 0, '>'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + ), t('Test with a delta meta condition.')); + + // Test language field meta condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '<>'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + ), t('Test with a language meta condition.')); + + // Test delta grouping. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'group') + ->fieldDeltaCondition($this->fields[0], 1, '<', 'group'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + ), t('Test with a grouped delta meta condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'group') + ->fieldDeltaCondition($this->fields[0], 1, '>=', 'group'); + $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta meta condition (empty result set).')); + + // Test language grouping. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', NULL, 'group') + ->fieldLanguageCondition($this->fields[0], 'en', '<>', NULL, 'group'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + ), t('Test with a grouped language meta condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', NULL, 'group') + ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '<>', NULL, 'group'); + $this->assertEntityFieldQuery($query, array(), t('Test with a grouped language meta condition (empty result set).')); + + // Test delta and language grouping. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') + ->fieldDeltaCondition($this->fields[0], 1, '<', 'delta', 'language') + ->fieldLanguageCondition($this->fields[0], 'en', '<>', 'delta', 'language'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + ), t('Test with a grouped delta + language meta condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') + ->fieldDeltaCondition($this->fields[0], 1, '>=', 'delta', 'language') + ->fieldLanguageCondition($this->fields[0], 'en', '<>', 'delta', 'language'); + $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, delta condition unsatisifed).')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') + ->fieldDeltaCondition($this->fields[0], 1, '<', 'delta', 'language') + ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '<>', 'delta', 'language'); + $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, language condition unsatisifed).')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity', '=') + ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') + ->fieldDeltaCondition($this->fields[0], 1, '>=', 'delta', 'language') + ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '<>', 'delta', 'language'); + $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, both conditions unsatisifed).')); + + // Test grouping with another field to ensure that grouping cache is reset + // properly. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle', '=') + ->fieldCondition($this->fields[1], 'shape', 'circle', '=', 'delta', 'language') + ->fieldCondition($this->fields[1], 'color', 'blue', '=', 'delta', 'language') + ->fieldDeltaCondition($this->fields[1], 1, '=', 'delta', 'language') + ->fieldLanguageCondition($this->fields[1], LANGUAGE_NONE, '=', 'delta', 'language'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle', 5), + ), t('Test grouping cache.')); + } + + /** + * Tests the routing feature of EntityFieldQuery. + */ + function testEntityFieldQueryRouting() { + // Entity-only query. + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'test_entity_bundle_key'); + $this->assertIdentical($query->queryCallback(), array($query, 'propertyQuery'), t('Entity-only queries are handled by the propertyQuery handler.')); + + // Field-only query. + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', '3'); + $this->assertIdentical($query->queryCallback(), 'field_sql_storage_field_storage_query', t('Pure field queries are handled by the Field storage handler.')); + + // Mixed entity and field query. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', '3'); + $this->assertIdentical($query->queryCallback(), 'field_sql_storage_field_storage_query', t('Mixed queries are handled by the Field storage handler.')); + + // Overriding with $query->executeCallback. + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'test_entity_bundle_key'); + $query->executeCallback = 'field_test_dummy_field_storage_query'; + $this->assertEntityFieldQuery($query, array( + array('user', 1), + ), t('executeCallback can override the query handler.')); + + // Overriding with $query->executeCallback via hook_entity_query_alter(). + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'test_entity_bundle_key'); + // Add a flag that will be caught by field_test_entity_query_alter(). + $query->alterMyExecuteCallbackPlease = TRUE; + $this->assertEntityFieldQuery($query, array( + array('user', 1), + ), t('executeCallback can override the query handler when set in a hook_entity_query_alter().')); + + // Mixed-storage queries. + $query = new EntityFieldQuery(); + $query + ->fieldCondition($this->fields[0], 'value', '3') + ->fieldCondition($this->fields[1], 'shape', 'squ', 'STARTS_WITH'); + // Alter the storage of the field. + $query->fields[1]['storage']['module'] = 'dummy_storage'; + try { + $query->queryCallback(); + } + catch (EntityFieldQueryException $exception) { + $pass = ($exception->getMessage() == t("Can't handle more than one field storage engine")); + } + $this->assertTrue($pass, t('Cannot query across field storage engines.')); + } + + /** + * Tests the pager integration of EntityFieldQuery. + */ + function testEntityFieldQueryPager() { + // Test pager in propertyQuery + $_GET['page'] = '0,1'; + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyOrderBy('ftid', 'ASC') + ->pager(3, 0); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + ), t('Test pager integration in propertyQuery: page 1.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyOrderBy('ftid', 'ASC') + ->pager(3, 1); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test pager integration in propertyQuery: page 2.'), TRUE); + + // Test pager in field storage + $_GET['page'] = '0,1'; + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->propertyOrderBy('ftid', 'ASC') + ->pager(2, 0); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + ), t('Test pager integration in field storage: page 1.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->propertyOrderBy('ftid', 'ASC') + ->pager(2, 1); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test pager integration in field storage: page 2.'), TRUE); + + unset($_GET['page']); + } + + /** + * Tests the TableSort integration of EntityFieldQuery. + */ + function testEntityFieldQueryTableSort() { + // Test TableSort in propertyQuery + $_GET['sort'] = 'asc'; + $_GET['order'] = 'Id'; + $header = array( + 'id' => array('data' => 'Id', 'type' => 'property', 'specifier' => 'ftid'), + 'type' => array('data' => 'Type', 'type' => 'entity', 'specifier' => 'bundle'), + ); + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->tableSort($header); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test TableSort by property: ftid ASC in propertyQuery.'), TRUE); + + $_GET['sort'] = 'desc'; + $_GET['order'] = 'Id'; + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->tableSort($header); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test TableSort by property: ftid DESC in propertyQuery.'), TRUE); + + $_GET['sort'] = 'asc'; + $_GET['order'] = 'Type'; + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->tableSort($header); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test TableSort by entity: bundle ASC in propertyQuery.'), TRUE); + + $_GET['sort'] = 'desc'; + $_GET['order'] = 'Type'; + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->tableSort($header); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test TableSort by entity: bundle DESC in propertyQuery.'), TRUE); + + // Test TableSort in field storage + $_GET['sort'] = 'asc'; + $_GET['order'] = 'Id'; + $header = array( + 'id' => array('data' => 'Id', 'type' => 'property', 'specifier' => 'ftid'), + 'type' => array('data' => 'Type', 'type' => 'entity', 'specifier' => 'bundle'), + 'field' => array('data' => 'Field', 'type' => 'field', 'specifier' => array('field' => $this->field_names[0], 'column' => 'value')), + ); + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->tableSort($header); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test TableSort by property: ftid ASC in field storage.'), TRUE); + + $_GET['sort'] = 'desc'; + $_GET['order'] = 'Id'; + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->tableSort($header); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test TableSort by property: ftid DESC in field storage.'), TRUE); + + $_GET['sort'] = 'asc'; + $_GET['order'] = 'Type'; + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->tableSort($header) + ->entityOrderBy('entity_id', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + ), t('Test TableSort by entity: bundle ASC in field storage.'), TRUE); + + $_GET['sort'] = 'desc'; + $_GET['order'] = 'Type'; + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->tableSort($header) + ->entityOrderBy('entity_id', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test TableSort by entity: bundle DESC in field storage.'), TRUE); + + $_GET['sort'] = 'asc'; + $_GET['order'] = 'Field'; + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->tableSort($header); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test TableSort by field ASC.'), TRUE); + + $_GET['sort'] = 'desc'; + $_GET['order'] = 'Field'; + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->tableSort($header); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test TableSort by field DESC.'), TRUE); + + unset($_GET['sort']); + unset($_GET['order']); + } + + /** + * Fetches the results of an EntityFieldQuery and compares. + * + * @param $query + * An EntityFieldQuery to run. + * @param $intended_results + * A list of results, every entry is again a list, first being the entity + * type, the second being the entity_id. + * @param $message + * The message to be displayed as the result of this test. + * @param $ordered + * If FALSE then the result of EntityFieldQuery will match + * $intended_results even if the order is not the same. If TRUE then order + * should match too. + */ + function assertEntityFieldQuery($query, $intended_results, $message, $ordered = FALSE) { + $results = array(); + try { + foreach ($query->execute() as $entity_type => $entity_ids) { + foreach ($entity_ids as $entity_id => $stub_entity) { + $results[] = array($entity_type, $entity_id); + } + } + if (!isset($ordered) || !$ordered) { + sort($results); + sort($intended_results); + } + $this->assertEqual($results, $intended_results, $message); + } + catch (Exception $e) { + $this->fail('Exception thrown: '. $e->getMessage()); + } + } +} diff --git a/modules/field/field.info b/modules/field/field.info index bf419e3..c61c501 100644 --- a/modules/field/field.info +++ b/modules/field/field.info @@ -7,5 +7,6 @@ files[] = field.module files[] = field.attach.inc files[] = tests/field.test dependencies[] = field_sql_storage +dependencies[] = entity required = TRUE stylesheets[all][] = theme/field.css diff --git a/modules/node/node.info b/modules/node/node.info index 7f2c7ff..2e410ed 100644 --- a/modules/node/node.info +++ b/modules/node/node.info @@ -6,5 +6,6 @@ core = 8.x files[] = node.module files[] = node.test required = TRUE +dependencies[] = entity configure = admin/structure/types stylesheets[all][] = node.css diff --git a/modules/simpletest/tests/entity_cache_test.info b/modules/simpletest/tests/entity_cache_test.info deleted file mode 100644 index c13496e..0000000 --- a/modules/simpletest/tests/entity_cache_test.info +++ /dev/null @@ -1,7 +0,0 @@ -name = "Entity cache test" -description = "Support module for testing entity cache." -package = Testing -version = VERSION -core = 8.x -dependencies[] = entity_cache_test_dependency -hidden = TRUE diff --git a/modules/simpletest/tests/entity_cache_test.module b/modules/simpletest/tests/entity_cache_test.module deleted file mode 100644 index 5ae9ecc..0000000 --- a/modules/simpletest/tests/entity_cache_test.module +++ /dev/null @@ -1,27 +0,0 @@ - array( - 'label' => 'Entity Cache Test', - ), - ); -} diff --git a/modules/simpletest/tests/entity_crud_hook_test.info b/modules/simpletest/tests/entity_crud_hook_test.info deleted file mode 100644 index 28ce1b5..0000000 --- a/modules/simpletest/tests/entity_crud_hook_test.info +++ /dev/null @@ -1,6 +0,0 @@ -name = "Entity CRUD Hooks Test" -description = "Support module for CRUD hook tests." -core = 8.x -package = Testing -version = VERSION -hidden = TRUE diff --git a/modules/simpletest/tests/entity_crud_hook_test.module b/modules/simpletest/tests/entity_crud_hook_test.module deleted file mode 100644 index 873a162..0000000 --- a/modules/simpletest/tests/entity_crud_hook_test.module +++ /dev/null @@ -1,266 +0,0 @@ - 'Entity CRUD hooks', - 'description' => 'Tests the invocation of hooks when inserting, loading, updating or deleting an entity.', - 'group' => 'Entity API', - ); - } - - public function setUp() { - parent::setUp('entity_crud_hook_test', 'taxonomy', 'comment'); - } - - /** - * Pass if the message $text was set by one of the CRUD hooks in - * entity_crud_hook_test.module, i.e., if the $text is an element of - * $_SESSION['entity_crud_hook_test']. - * - * @param $text - * Plain text to look for. - * @param $message - * Message to display. - * @param $group - * The group this message belongs to, defaults to 'Other'. - * @return - * TRUE on pass, FALSE on fail. - */ - protected function assertHookMessage($text, $message = NULL, $group = 'Other') { - if (!isset($message)) { - $message = $text; - } - return $this->assertTrue(array_search($text, $_SESSION['entity_crud_hook_test']) !== FALSE, $message, $group); - } - - /** - * Test hook invocations for CRUD operations on comments. - */ - public function testCommentHooks() { - $node = (object) array( - 'uid' => 1, - 'type' => 'article', - 'title' => 'Test node', - 'status' => 1, - 'comment' => 2, - 'promote' => 0, - 'sticky' => 0, - 'language' => LANGUAGE_NONE, - 'created' => REQUEST_TIME, - 'changed' => REQUEST_TIME, - ); - node_save($node); - $nid = $node->nid; - - $comment = (object) array( - 'cid' => NULL, - 'pid' => 0, - 'nid' => $nid, - 'uid' => 1, - 'subject' => 'Test comment', - 'created' => REQUEST_TIME, - 'changed' => REQUEST_TIME, - 'status' => 1, - 'language' => LANGUAGE_NONE, - ); - $_SESSION['entity_crud_hook_test'] = array(); - comment_save($comment); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type comment'); - $this->assertHookMessage('entity_crud_hook_test_comment_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type comment'); - $this->assertHookMessage('entity_crud_hook_test_comment_insert called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $comment = comment_load($comment->cid); - - $this->assertHookMessage('entity_crud_hook_test_entity_load called for type comment'); - $this->assertHookMessage('entity_crud_hook_test_comment_load called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $comment->subject = 'New subject'; - comment_save($comment); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type comment'); - $this->assertHookMessage('entity_crud_hook_test_comment_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_update called for type comment'); - $this->assertHookMessage('entity_crud_hook_test_comment_update called'); - - $_SESSION['entity_crud_hook_test'] = array(); - comment_delete($comment->cid); - - $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type comment'); - $this->assertHookMessage('entity_crud_hook_test_comment_delete called'); - } - - /** - * Test hook invocations for CRUD operations on files. - */ - public function testFileHooks() { - $url = 'public://entity_crud_hook_test.file'; - file_put_contents($url, 'Test test test'); - $file = (object) array( - 'fid' => NULL, - 'uid' => 1, - 'filename' => 'entity_crud_hook_test.file', - 'uri' => $url, - 'filemime' => 'text/plain', - 'filesize' => filesize($url), - 'status' => 1, - 'timestamp' => REQUEST_TIME, - ); - $_SESSION['entity_crud_hook_test'] = array(); - file_save($file); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type file'); - $this->assertHookMessage('entity_crud_hook_test_file_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type file'); - $this->assertHookMessage('entity_crud_hook_test_file_insert called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $file = file_load($file->fid); - - $this->assertHookMessage('entity_crud_hook_test_entity_load called for type file'); - $this->assertHookMessage('entity_crud_hook_test_file_load called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $file->filename = 'new.entity_crud_hook_test.file'; - file_save($file); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type file'); - $this->assertHookMessage('entity_crud_hook_test_file_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_update called for type file'); - $this->assertHookMessage('entity_crud_hook_test_file_update called'); - - $_SESSION['entity_crud_hook_test'] = array(); - file_delete($file); - - $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type file'); - $this->assertHookMessage('entity_crud_hook_test_file_delete called'); - } - - /** - * Test hook invocations for CRUD operations on nodes. - */ - public function testNodeHooks() { - $node = (object) array( - 'uid' => 1, - 'type' => 'article', - 'title' => 'Test node', - 'status' => 1, - 'comment' => 2, - 'promote' => 0, - 'sticky' => 0, - 'language' => LANGUAGE_NONE, - 'created' => REQUEST_TIME, - 'changed' => REQUEST_TIME, - ); - $_SESSION['entity_crud_hook_test'] = array(); - node_save($node); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type node'); - $this->assertHookMessage('entity_crud_hook_test_node_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type node'); - $this->assertHookMessage('entity_crud_hook_test_node_insert called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $node = node_load($node->nid); - - $this->assertHookMessage('entity_crud_hook_test_entity_load called for type node'); - $this->assertHookMessage('entity_crud_hook_test_node_load called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $node->title = 'New title'; - node_save($node); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type node'); - $this->assertHookMessage('entity_crud_hook_test_node_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_update called for type node'); - $this->assertHookMessage('entity_crud_hook_test_node_update called'); - - $_SESSION['entity_crud_hook_test'] = array(); - node_delete($node->nid); - - $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type node'); - $this->assertHookMessage('entity_crud_hook_test_node_delete called'); - } - - /** - * Test hook invocations for CRUD operations on taxonomy terms. - */ - public function testTaxonomyTermHooks() { - $vocabulary = (object) array( - 'name' => 'Test vocabulary', - 'machine_name' => 'test', - 'description' => NULL, - 'module' => 'entity_crud_hook_test', - ); - taxonomy_vocabulary_save($vocabulary); - - $term = (object) array( - 'vid' => $vocabulary->vid, - 'name' => 'Test term', - 'description' => NULL, - 'format' => 1, - ); - $_SESSION['entity_crud_hook_test'] = array(); - taxonomy_term_save($term); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_term'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type taxonomy_term'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_insert called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $term = taxonomy_term_load($term->tid); - - $this->assertHookMessage('entity_crud_hook_test_entity_load called for type taxonomy_term'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_load called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $term->name = 'New name'; - taxonomy_term_save($term); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_term'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_update called for type taxonomy_term'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_update called'); - - $_SESSION['entity_crud_hook_test'] = array(); - taxonomy_term_delete($term->tid); - - $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type taxonomy_term'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_term_delete called'); - } - - /** - * Test hook invocations for CRUD operations on taxonomy vocabularies. - */ - public function testTaxonomyVocabularyHooks() { - $vocabulary = (object) array( - 'name' => 'Test vocabulary', - 'machine_name' => 'test', - 'description' => NULL, - 'module' => 'entity_crud_hook_test', - ); - $_SESSION['entity_crud_hook_test'] = array(); - taxonomy_vocabulary_save($vocabulary); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type taxonomy_vocabulary'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_insert called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $vocabulary = taxonomy_vocabulary_load($vocabulary->vid); - - $this->assertHookMessage('entity_crud_hook_test_entity_load called for type taxonomy_vocabulary'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_load called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $vocabulary->name = 'New name'; - taxonomy_vocabulary_save($vocabulary); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_update called for type taxonomy_vocabulary'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_update called'); - - $_SESSION['entity_crud_hook_test'] = array(); - taxonomy_vocabulary_delete($vocabulary->vid); - - $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type taxonomy_vocabulary'); - $this->assertHookMessage('entity_crud_hook_test_taxonomy_vocabulary_delete called'); - } - - /** - * Test hook invocations for CRUD operations on users. - */ - public function testUserHooks() { - $edit = array( - 'name' => 'Test user', - 'mail' => 'test@example.com', - 'created' => REQUEST_TIME, - 'status' => 1, - 'language' => 'en', - ); - $account = (object) $edit; - $_SESSION['entity_crud_hook_test'] = array(); - $account = user_save($account, $edit); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type user'); - $this->assertHookMessage('entity_crud_hook_test_user_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_insert called for type user'); - $this->assertHookMessage('entity_crud_hook_test_user_insert called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $account = user_load($account->uid); - - $this->assertHookMessage('entity_crud_hook_test_entity_load called for type user'); - $this->assertHookMessage('entity_crud_hook_test_user_load called'); - - $_SESSION['entity_crud_hook_test'] = array(); - $edit['name'] = 'New name'; - $account = user_save($account, $edit); - - $this->assertHookMessage('entity_crud_hook_test_entity_presave called for type user'); - $this->assertHookMessage('entity_crud_hook_test_user_presave called'); - $this->assertHookMessage('entity_crud_hook_test_entity_update called for type user'); - $this->assertHookMessage('entity_crud_hook_test_user_update called'); - - $_SESSION['entity_crud_hook_test'] = array(); - user_delete($account->uid); - - $this->assertHookMessage('entity_crud_hook_test_entity_delete called for type user'); - $this->assertHookMessage('entity_crud_hook_test_user_delete called'); - } - -} diff --git a/modules/simpletest/tests/entity_query.test b/modules/simpletest/tests/entity_query.test deleted file mode 100644 index fb95518..0000000 --- a/modules/simpletest/tests/entity_query.test +++ /dev/null @@ -1,1498 +0,0 @@ - 'Entity query', - 'description' => 'Test the EntityFieldQuery class.', - 'group' => 'Entity API', - ); - } - - function setUp() { - parent::setUp(array('field_test')); - - field_attach_create_bundle('test_entity_bundle_key', 'bundle1'); - field_attach_create_bundle('test_entity_bundle_key', 'bundle2'); - field_attach_create_bundle('test_entity', 'test_bundles'); - field_attach_create_bundle('test_entity_bundle', 'test_entity_bundle'); - - $instances = array(); - $this->fields = array(); - $this->field_names[0] = $field_name = drupal_strtolower($this->randomName() . '_field_name'); - $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 4); - $field = field_create_field($field); - $this->fields[0] = $field; - $instance = array( - 'field_name' => $field_name, - 'entity_type' => '', - 'bundle' => '', - 'label' => $this->randomName() . '_label', - 'description' => $this->randomName() . '_description', - 'weight' => mt_rand(0, 127), - 'settings' => array( - 'test_instance_setting' => $this->randomName(), - ), - 'widget' => array( - 'type' => 'test_field_widget', - 'label' => 'Test Field', - 'settings' => array( - 'test_widget_setting' => $this->randomName(), - ) - ) - ); - - $instances[0] = $instance; - - // Add an instance to that bundle. - $instances[0]['bundle'] = 'bundle1'; - $instances[0]['entity_type'] = 'test_entity_bundle_key'; - field_create_instance($instances[0]); - $instances[0]['bundle'] = 'bundle2'; - field_create_instance($instances[0]); - $instances[0]['bundle'] = $instances[0]['entity_type'] = 'test_entity_bundle'; - field_create_instance($instances[0]); - - $this->field_names[1] = $field_name = drupal_strtolower($this->randomName() . '_field_name'); - $field = array('field_name' => $field_name, 'type' => 'shape', 'cardinality' => 4); - $field = field_create_field($field); - $this->fields[1] = $field; - $instance = array( - 'field_name' => $field_name, - 'entity_type' => '', - 'bundle' => '', - 'label' => $this->randomName() . '_label', - 'description' => $this->randomName() . '_description', - 'weight' => mt_rand(0, 127), - 'settings' => array( - 'test_instance_setting' => $this->randomName(), - ), - 'widget' => array( - 'type' => 'test_field_widget', - 'label' => 'Test Field', - 'settings' => array( - 'test_widget_setting' => $this->randomName(), - ) - ) - ); - - $instances[1] = $instance; - - // Add a field instance to the bundles. - $instances[1]['bundle'] = 'bundle1'; - $instances[1]['entity_type'] = 'test_entity_bundle_key'; - field_create_instance($instances[1]); - $instances[1]['bundle'] = $instances[1]['entity_type'] = 'test_entity_bundle'; - field_create_instance($instances[1]); - - $this->instances = $instances; - // Write entity base table if there is one. - $entities = array(); - - // Create entities which have a 'bundle key' defined. - for ($i = 1; $i < 7; $i++) { - $entity = new stdClass(); - $entity->ftid = $i; - $entity->fttype = ($i < 5) ? 'bundle1' : 'bundle2'; - - $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i; - drupal_write_record('test_entity_bundle_key', $entity); - field_attach_insert('test_entity_bundle_key', $entity); - } - - $entity = new stdClass(); - $entity->ftid = 5; - $entity->fttype = 'test_entity_bundle'; - $entity->{$this->field_names[1]}[LANGUAGE_NONE][0]['shape'] = 'square'; - $entity->{$this->field_names[1]}[LANGUAGE_NONE][0]['color'] = 'red'; - $entity->{$this->field_names[1]}[LANGUAGE_NONE][1]['shape'] = 'circle'; - $entity->{$this->field_names[1]}[LANGUAGE_NONE][1]['color'] = 'blue'; - drupal_write_record('test_entity_bundle', $entity); - field_attach_insert('test_entity_bundle', $entity); - - $instances[2] = $instance; - $instances[2]['bundle'] = 'test_bundle'; - $instances[2]['field_name'] = $this->field_names[0]; - $instances[2]['entity_type'] = 'test_entity'; - field_create_instance($instances[2]); - - // Create entities with support for revisions. - for ($i = 1; $i < 5; $i++) { - $entity = new stdClass(); - $entity->ftid = $i; - $entity->ftvid = $i; - $entity->fttype = 'test_bundle'; - $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i; - - drupal_write_record('test_entity', $entity); - field_attach_insert('test_entity', $entity); - drupal_write_record('test_entity_revision', $entity); - } - - // Add two revisions to an entity. - for ($i = 100; $i < 102; $i++) { - $entity = new stdClass(); - $entity->ftid = 4; - $entity->ftvid = $i; - $entity->fttype = 'test_bundle'; - $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i; - - drupal_write_record('test_entity', $entity, 'ftid'); - drupal_write_record('test_entity_revision', $entity); - - db_update('test_entity') - ->fields(array('ftvid' => $entity->ftvid)) - ->condition('ftid', $entity->ftid) - ->execute(); - - field_attach_update('test_entity', $entity); - } - } - - /** - * Tests EntityFieldQuery. - */ - function testEntityFieldQuery() { - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle') - ->entityCondition('entity_id', '5'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle', 5), - ), t('Test query on an entity type with a generated bundle.')); - - // Test entity_type condition. - $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', 'test_entity_bundle_key'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test entity entity_type condition.')); - - // Test entity_id condition. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->entityCondition('entity_id', '3'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 3), - ), t('Test entity entity_id condition.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', '3'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 3), - ), t('Test entity entity_id condition and entity_id property condition.')); - - // Test bundle condition. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->entityCondition('bundle', 'bundle1'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - ), t('Test entity bundle condition: bundle1.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->entityCondition('bundle', 'bundle2'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test entity bundle condition: bundle2.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('fttype', 'bundle2'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test entity bundle condition and bundle property condition.')); - - // Test revision_id condition. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity') - ->entityCondition('revision_id', '3'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 3), - ), t('Test entity revision_id condition.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity') - ->propertyCondition('ftvid', '3'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 3), - ), t('Test entity revision_id condition and revision_id property condition.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity') - ->fieldCondition($this->fields[0], 'value', 100, '>=') - ->age(FIELD_LOAD_REVISION); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 100), - array('test_entity', 101), - ), t('Test revision age.')); - - // Test that fields attached to the non-revision supporting entity - // 'test_entity_bundle_key' are reachable in FIELD_LOAD_REVISION. - $query = new EntityFieldQuery(); - $query - ->fieldCondition($this->fields[0], 'value', 100, '<') - ->age(FIELD_LOAD_REVISION); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - array('test_entity', 1), - array('test_entity', 2), - array('test_entity', 3), - array('test_entity', 4), - ), t('Test that fields are reachable from FIELD_LOAD_REVISION even for non-revision entities.')); - - // Test entity sort by entity_id. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->entityOrderBy('entity_id', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test sort entity entity_id in ascending order.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->entityOrderBy('entity_id', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - ), t('Test sort entity entity_id in descending order.'), TRUE); - - // Test entity sort by entity_id, with a field condition. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->entityOrderBy('entity_id', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test sort entity entity_id in ascending order, with a field condition.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->propertyOrderBy('ftid', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - ), t('Test sort entity entity_id property in descending order, with a field condition.'), TRUE); - - // Test property sort by entity id. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyOrderBy('ftid', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test sort entity entity_id property in ascending order.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyOrderBy('ftid', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - ), t('Test sort entity entity_id property in descending order.'), TRUE); - - // Test property sort by entity id, with a field condition. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->propertyOrderBy('ftid', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test sort entity entity_id property in ascending order, with a field condition.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->propertyOrderBy('ftid', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - ), t('Test sort entity entity_id property in descending order, with a field condition.'), TRUE); - - // Test entity sort by bundle. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->entityOrderBy('bundle', 'ASC') - ->propertyOrderBy('ftid', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - ), t('Test sort entity bundle in ascending order, property in descending order.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->entityOrderBy('bundle', 'DESC') - ->propertyOrderBy('ftid', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - ), t('Test sort entity bundle in descending order, property in ascending order.'), TRUE); - - // Test entity sort by bundle, with a field condition. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->entityOrderBy('bundle', 'ASC') - ->propertyOrderBy('ftid', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - ), t('Test sort entity bundle in ascending order, property in descending order, with a field condition.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->entityOrderBy('bundle', 'DESC') - ->propertyOrderBy('ftid', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - ), t('Test sort entity bundle in descending order, property in ascending order, with a field condition.'), TRUE); - - // Test entity sort by bundle, field. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->entityOrderBy('bundle', 'ASC') - ->fieldOrderBy($this->fields[0], 'value', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - ), t('Test sort entity bundle in ascending order, field in descending order.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->entityOrderBy('bundle', 'DESC') - ->fieldOrderBy($this->fields[0], 'value', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - ), t('Test sort entity bundle in descending order, field in ascending order.'), TRUE); - - // Test entity sort by revision_id. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity') - ->entityOrderBy('revision_id', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 1), - array('test_entity', 2), - array('test_entity', 3), - array('test_entity', 4), - ), t('Test sort entity revision_id in ascending order.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity') - ->entityOrderBy('revision_id', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 4), - array('test_entity', 3), - array('test_entity', 2), - array('test_entity', 1), - ), t('Test sort entity revision_id in descending order.'), TRUE); - - // Test entity sort by revision_id, with a field condition. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->entityOrderBy('revision_id', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 1), - array('test_entity', 2), - array('test_entity', 3), - array('test_entity', 4), - ), t('Test sort entity revision_id in ascending order, with a field condition.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->entityOrderBy('revision_id', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 4), - array('test_entity', 3), - array('test_entity', 2), - array('test_entity', 1), - ), t('Test sort entity revision_id in descending order, with a field condition.'), TRUE); - - // Test property sort by revision_id. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity') - ->propertyOrderBy('ftvid', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 1), - array('test_entity', 2), - array('test_entity', 3), - array('test_entity', 4), - ), t('Test sort entity revision_id property in ascending order.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity') - ->propertyOrderBy('ftvid', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 4), - array('test_entity', 3), - array('test_entity', 2), - array('test_entity', 1), - ), t('Test sort entity revision_id property in descending order.'), TRUE); - - // Test property sort by revision_id, with a field condition. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->propertyOrderBy('ftvid', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 1), - array('test_entity', 2), - array('test_entity', 3), - array('test_entity', 4), - ), t('Test sort entity revision_id property in ascending order, with a field condition.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->propertyOrderBy('ftvid', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 4), - array('test_entity', 3), - array('test_entity', 2), - array('test_entity', 1), - ), t('Test sort entity revision_id property in descending order, with a field condition.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldOrderBy($this->fields[0], 'value', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test sort field in ascending order without field condition.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldOrderBy($this->fields[0], 'value', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - ), t('Test sort field in descending order without field condition.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->fieldOrderBy($this->fields[0], 'value', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test sort field in ascending order.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->fieldOrderBy($this->fields[0], 'value', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - ), t('Test sort field in descending order.'), TRUE); - - // Test "in" operation with entity entity_type condition and entity_id - // property condition. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', array(1, 3, 4), 'IN'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - ), t('Test "in" operation with entity entity_type condition and entity_id property condition.')); - - // Test "in" operation with entity entity_type condition and entity_id - // property condition. Sort in descending order by entity_id. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', array(1, 3, 4), 'IN') - ->propertyOrderBy('ftid', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 1), - ), t('Test "in" operation with entity entity_type condition and entity_id property condition. Sort entity_id in descending order.'), TRUE); - - // Test query count - $query = new EntityFieldQuery(); - $query_count = $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->count() - ->execute(); - $this->assertEqual($query_count, 6, t('Test query count on entity condition.')); - - $query = new EntityFieldQuery(); - $query_count = $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', '1') - ->count() - ->execute(); - $this->assertEqual($query_count, 1, t('Test query count on entity and property condition.')); - - $query = new EntityFieldQuery(); - $query_count = $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', '4', '>') - ->count() - ->execute(); - $this->assertEqual($query_count, 2, t('Test query count on entity and property condition with operator.')); - - $query = new EntityFieldQuery(); - $query_count = $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 3, '=') - ->count() - ->execute(); - $this->assertEqual($query_count, 1, t('Test query count on field condition.')); - - // First, test without options. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('fttype', 'und', 'CONTAINS'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test the "contains" operation on a property.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[1], 'shape', 'uar', 'CONTAINS'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle', 5), - ), t('Test the "contains" operation on a field.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', 1, '='); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - ), t('Test the "equal to" operation on a property.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', 3, '='); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 3), - array('test_entity', 3), - ), t('Test the "equal to" operation on a field.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', 3, '<>'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test the "not equal to" operation on a property.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', 3, '<>'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - array('test_entity', 1), - array('test_entity', 2), - array('test_entity', 4), - ), t('Test the "not equal to" operation on a field.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', 2, '<'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - ), t('Test the "less than" operation on a property.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', 2, '<'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity', 1), - ), t('Test the "less than" operation on a field.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', 2, '<='); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - ), t('Test the "less than or equal to" operation on a property.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', 2, '<='); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity', 1), - array('test_entity', 2), - ), t('Test the "less than or equal to" operation on a field.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', 4, '>'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test the "greater than" operation on a property.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', 2, '>'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - array('test_entity', 3), - array('test_entity', 4), - ), t('Test the "greater than" operation on a field.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', 4, '>='); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test the "greater than or equal to" operation on a property.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', 3, '>='); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - array('test_entity', 3), - array('test_entity', 4), - ), t('Test the "greater than or equal to" operation on a field.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', array(3, 4), 'NOT IN'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test the "not in" operation on a property.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', array(3, 4, 100, 101), 'NOT IN'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - array('test_entity', 1), - array('test_entity', 2), - ), t('Test the "not in" operation on a field.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', array(3, 4), 'IN'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - ), t('Test the "in" operation on a property.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', array(2, 3), 'IN'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity', 2), - array('test_entity', 3), - ), t('Test the "in" operation on a field.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', array(1, 3), 'BETWEEN'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - ), t('Test the "between" operation on a property.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', array(1, 3), 'BETWEEN'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity', 1), - array('test_entity', 2), - array('test_entity', 3), - ), t('Test the "between" operation on a field.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('fttype', 'bun', 'STARTS_WITH'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test the "starts_with" operation on a property.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[1], 'shape', 'squ', 'STARTS_WITH'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle', 5), - ), t('Test the "starts_with" operation on a field.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', 3); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 3), - array('test_entity', 3), - ), t('Test omission of an operator with a single item.')); - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', array(2, 3)); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity', 2), - array('test_entity', 3), - ), t('Test omission of an operator with multiple items.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyCondition('ftid', 1, '>') - ->fieldCondition($this->fields[0], 'value', 4, '<'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - ), t('Test entity, property and field conditions.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->entityCondition('bundle', 'bundle', 'STARTS_WITH') - ->propertyCondition('ftid', 4) - ->fieldCondition($this->fields[0], 'value', 4); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 4), - ), t('Test entity condition with "starts_with" operation, and property and field conditions.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyOrderBy('ftid', 'ASC') - ->range(0, 2); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - ), t('Test limit on a property.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>=') - ->fieldOrderBy($this->fields[0], 'value', 'ASC') - ->range(0, 2); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - ), t('Test limit on a field.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyOrderBy('ftid', 'ASC') - ->range(4, 6); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test offset on a property.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->fieldOrderBy($this->fields[0], 'value', 'ASC') - ->range(2, 4); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test offset on a field.'), TRUE); - - for ($i = 6; $i < 10; $i++) { - $entity = new stdClass(); - $entity->ftid = $i; - $entity->fttype = 'test_entity_bundle'; - $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i - 5; - drupal_write_record('test_entity_bundle', $entity); - field_attach_insert('test_entity_bundle', $entity); - } - - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', 2, '>'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - array('test_entity', 3), - array('test_entity', 4), - array('test_entity_bundle', 8), - array('test_entity_bundle', 9), - ), t('Select a field across multiple entities.')); - - $query = new EntityFieldQuery(); - $query - ->fieldCondition($this->fields[1], 'shape', 'square') - ->fieldCondition($this->fields[1], 'color', 'blue'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle', 5), - ), t('Test without a delta group.')); - - $query = new EntityFieldQuery(); - $query - ->fieldCondition($this->fields[1], 'shape', 'square', '=', 'group') - ->fieldCondition($this->fields[1], 'color', 'blue', '=', 'group'); - $this->assertEntityFieldQuery($query, array(), t('Test with a delta group.')); - - // Test query on a deleted field. - field_attach_delete_bundle('test_entity_bundle_key', 'bundle1'); - field_attach_delete_bundle('test_entity', 'test_bundle'); - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', '3'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle', 8), - ), t('Test query on a field after deleting field from some entities.')); - - field_attach_delete_bundle('test_entity_bundle', 'test_entity_bundle'); - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', '3'); - $this->assertEntityFieldQuery($query, array(), t('Test query on a field after deleting field from all entities.')); - - $query = new EntityFieldQuery(); - $query - ->fieldCondition($this->fields[0], 'value', '3') - ->deleted(TRUE); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 3), - array('test_entity_bundle', 8), - array('test_entity', 3), - ), t('Test query on a deleted field with deleted option set to TRUE.')); - - $pass = FALSE; - $query = new EntityFieldQuery(); - try { - $query->execute(); - } - catch (EntityFieldQueryException $exception) { - $pass = ($exception->getMessage() == t('For this query an entity type must be specified.')); - } - $this->assertTrue($pass, t("Can't query the universe.")); - } - - /** - * Tests field meta conditions. - */ - function testEntityFieldQueryMetaConditions() { - // Make a test field translatable. - $this->fields[0]['translatable'] = TRUE; - field_update_field($this->fields[0]); - field_test_entity_info_translatable('test_entity', TRUE); - drupal_static_reset('field_available_languages'); - - // Create more items with different languages. - $entity = new stdClass(); - $entity->ftid = 1; - $entity->ftvid = 1; - $entity->fttype = 'test_bundle'; - $j = 0; - - foreach (array(LANGUAGE_NONE, 'en') as $langcode) { - for ($i = 0; $i < 4; $i++) { - $entity->{$this->field_names[0]}[$langcode][$i]['value'] = $i + $j; - } - $j += 4; - } - - field_attach_update('test_entity', $entity); - - // Test delta field meta condition. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity', '=') - ->fieldDeltaCondition($this->fields[0], 0, '>'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 1), - ), t('Test with a delta meta condition.')); - - // Test language field meta condition. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity', '=') - ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '<>'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 1), - ), t('Test with a language meta condition.')); - - // Test delta grouping. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity', '=') - ->fieldCondition($this->fields[0], 'value', 0, '=', 'group') - ->fieldDeltaCondition($this->fields[0], 1, '<', 'group'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 1), - ), t('Test with a grouped delta meta condition.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity', '=') - ->fieldCondition($this->fields[0], 'value', 0, '=', 'group') - ->fieldDeltaCondition($this->fields[0], 1, '>=', 'group'); - $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta meta condition (empty result set).')); - - // Test language grouping. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity', '=') - ->fieldCondition($this->fields[0], 'value', 0, '=', NULL, 'group') - ->fieldLanguageCondition($this->fields[0], 'en', '<>', NULL, 'group'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 1), - ), t('Test with a grouped language meta condition.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity', '=') - ->fieldCondition($this->fields[0], 'value', 0, '=', NULL, 'group') - ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '<>', NULL, 'group'); - $this->assertEntityFieldQuery($query, array(), t('Test with a grouped language meta condition (empty result set).')); - - // Test delta and language grouping. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity', '=') - ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') - ->fieldDeltaCondition($this->fields[0], 1, '<', 'delta', 'language') - ->fieldLanguageCondition($this->fields[0], 'en', '<>', 'delta', 'language'); - $this->assertEntityFieldQuery($query, array( - array('test_entity', 1), - ), t('Test with a grouped delta + language meta condition.')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity', '=') - ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') - ->fieldDeltaCondition($this->fields[0], 1, '>=', 'delta', 'language') - ->fieldLanguageCondition($this->fields[0], 'en', '<>', 'delta', 'language'); - $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, delta condition unsatisifed).')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity', '=') - ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') - ->fieldDeltaCondition($this->fields[0], 1, '<', 'delta', 'language') - ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '<>', 'delta', 'language'); - $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, language condition unsatisifed).')); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity', '=') - ->fieldCondition($this->fields[0], 'value', 0, '=', 'delta', 'language') - ->fieldDeltaCondition($this->fields[0], 1, '>=', 'delta', 'language') - ->fieldLanguageCondition($this->fields[0], LANGUAGE_NONE, '<>', 'delta', 'language'); - $this->assertEntityFieldQuery($query, array(), t('Test with a grouped delta + language meta condition (empty result set, both conditions unsatisifed).')); - - // Test grouping with another field to ensure that grouping cache is reset - // properly. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle', '=') - ->fieldCondition($this->fields[1], 'shape', 'circle', '=', 'delta', 'language') - ->fieldCondition($this->fields[1], 'color', 'blue', '=', 'delta', 'language') - ->fieldDeltaCondition($this->fields[1], 1, '=', 'delta', 'language') - ->fieldLanguageCondition($this->fields[1], LANGUAGE_NONE, '=', 'delta', 'language'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle', 5), - ), t('Test grouping cache.')); - } - - /** - * Tests the routing feature of EntityFieldQuery. - */ - function testEntityFieldQueryRouting() { - // Entity-only query. - $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', 'test_entity_bundle_key'); - $this->assertIdentical($query->queryCallback(), array($query, 'propertyQuery'), t('Entity-only queries are handled by the propertyQuery handler.')); - - // Field-only query. - $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', '3'); - $this->assertIdentical($query->queryCallback(), 'field_sql_storage_field_storage_query', t('Pure field queries are handled by the Field storage handler.')); - - // Mixed entity and field query. - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', '3'); - $this->assertIdentical($query->queryCallback(), 'field_sql_storage_field_storage_query', t('Mixed queries are handled by the Field storage handler.')); - - // Overriding with $query->executeCallback. - $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', 'test_entity_bundle_key'); - $query->executeCallback = 'field_test_dummy_field_storage_query'; - $this->assertEntityFieldQuery($query, array( - array('user', 1), - ), t('executeCallback can override the query handler.')); - - // Overriding with $query->executeCallback via hook_entity_query_alter(). - $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', 'test_entity_bundle_key'); - // Add a flag that will be caught by field_test_entity_query_alter(). - $query->alterMyExecuteCallbackPlease = TRUE; - $this->assertEntityFieldQuery($query, array( - array('user', 1), - ), t('executeCallback can override the query handler when set in a hook_entity_query_alter().')); - - // Mixed-storage queries. - $query = new EntityFieldQuery(); - $query - ->fieldCondition($this->fields[0], 'value', '3') - ->fieldCondition($this->fields[1], 'shape', 'squ', 'STARTS_WITH'); - // Alter the storage of the field. - $query->fields[1]['storage']['module'] = 'dummy_storage'; - try { - $query->queryCallback(); - } - catch (EntityFieldQueryException $exception) { - $pass = ($exception->getMessage() == t("Can't handle more than one field storage engine")); - } - $this->assertTrue($pass, t('Cannot query across field storage engines.')); - } - - /** - * Tests the pager integration of EntityFieldQuery. - */ - function testEntityFieldQueryPager() { - // Test pager in propertyQuery - $_GET['page'] = '0,1'; - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyOrderBy('ftid', 'ASC') - ->pager(3, 0); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - ), t('Test pager integration in propertyQuery: page 1.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->propertyOrderBy('ftid', 'ASC') - ->pager(3, 1); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test pager integration in propertyQuery: page 2.'), TRUE); - - // Test pager in field storage - $_GET['page'] = '0,1'; - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->propertyOrderBy('ftid', 'ASC') - ->pager(2, 0); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - ), t('Test pager integration in field storage: page 1.'), TRUE); - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->propertyOrderBy('ftid', 'ASC') - ->pager(2, 1); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - ), t('Test pager integration in field storage: page 2.'), TRUE); - - unset($_GET['page']); - } - - /** - * Tests the TableSort integration of EntityFieldQuery. - */ - function testEntityFieldQueryTableSort() { - // Test TableSort in propertyQuery - $_GET['sort'] = 'asc'; - $_GET['order'] = 'Id'; - $header = array( - 'id' => array('data' => 'Id', 'type' => 'property', 'specifier' => 'ftid'), - 'type' => array('data' => 'Type', 'type' => 'entity', 'specifier' => 'bundle'), - ); - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->tableSort($header); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test TableSort by property: ftid ASC in propertyQuery.'), TRUE); - - $_GET['sort'] = 'desc'; - $_GET['order'] = 'Id'; - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->tableSort($header); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - ), t('Test TableSort by property: ftid DESC in propertyQuery.'), TRUE); - - $_GET['sort'] = 'asc'; - $_GET['order'] = 'Type'; - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->tableSort($header); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test TableSort by entity: bundle ASC in propertyQuery.'), TRUE); - - $_GET['sort'] = 'desc'; - $_GET['order'] = 'Type'; - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->tableSort($header); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - ), t('Test TableSort by entity: bundle DESC in propertyQuery.'), TRUE); - - // Test TableSort in field storage - $_GET['sort'] = 'asc'; - $_GET['order'] = 'Id'; - $header = array( - 'id' => array('data' => 'Id', 'type' => 'property', 'specifier' => 'ftid'), - 'type' => array('data' => 'Type', 'type' => 'entity', 'specifier' => 'bundle'), - 'field' => array('data' => 'Field', 'type' => 'field', 'specifier' => array('field' => $this->field_names[0], 'column' => 'value')), - ); - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->tableSort($header); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test TableSort by property: ftid ASC in field storage.'), TRUE); - - $_GET['sort'] = 'desc'; - $_GET['order'] = 'Id'; - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->tableSort($header); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - ), t('Test TableSort by property: ftid DESC in field storage.'), TRUE); - - $_GET['sort'] = 'asc'; - $_GET['order'] = 'Type'; - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->tableSort($header) - ->entityOrderBy('entity_id', 'DESC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - ), t('Test TableSort by entity: bundle ASC in field storage.'), TRUE); - - $_GET['sort'] = 'desc'; - $_GET['order'] = 'Type'; - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->tableSort($header) - ->entityOrderBy('entity_id', 'ASC'); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - ), t('Test TableSort by entity: bundle DESC in field storage.'), TRUE); - - $_GET['sort'] = 'asc'; - $_GET['order'] = 'Field'; - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->tableSort($header); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 1), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 6), - ), t('Test TableSort by field ASC.'), TRUE); - - $_GET['sort'] = 'desc'; - $_GET['order'] = 'Field'; - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($this->fields[0], 'value', 0, '>') - ->tableSort($header); - $this->assertEntityFieldQuery($query, array( - array('test_entity_bundle_key', 6), - array('test_entity_bundle_key', 5), - array('test_entity_bundle_key', 4), - array('test_entity_bundle_key', 3), - array('test_entity_bundle_key', 2), - array('test_entity_bundle_key', 1), - ), t('Test TableSort by field DESC.'), TRUE); - - unset($_GET['sort']); - unset($_GET['order']); - } - - /** - * Fetches the results of an EntityFieldQuery and compares. - * - * @param $query - * An EntityFieldQuery to run. - * @param $intended_results - * A list of results, every entry is again a list, first being the entity - * type, the second being the entity_id. - * @param $message - * The message to be displayed as the result of this test. - * @param $ordered - * If FALSE then the result of EntityFieldQuery will match - * $intended_results even if the order is not the same. If TRUE then order - * should match too. - */ - function assertEntityFieldQuery($query, $intended_results, $message, $ordered = FALSE) { - $results = array(); - try { - foreach ($query->execute() as $entity_type => $entity_ids) { - foreach ($entity_ids as $entity_id => $stub_entity) { - $results[] = array($entity_type, $entity_id); - } - } - if (!isset($ordered) || !$ordered) { - sort($results); - sort($intended_results); - } - $this->assertEqual($results, $intended_results, $message); - } - catch (Exception $e) { - $this->fail('Exception thrown: '. $e->getMessage()); - } - } -} diff --git a/modules/system/system.api.php b/modules/system/system.api.php index caa45c4..baa4dcf 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -60,392 +60,6 @@ function hook_hook_info_alter(&$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. - * - 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. * * Modules may specify whether or not the paths they define in hook_menu() are @@ -492,30 +106,6 @@ function hook_admin_paths_alter(&$paths) { } /** - * 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. * * Modules that require some commands to be executed periodically can @@ -2366,6 +1956,30 @@ function hook_flush_caches() { } /** + * 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. * * This function differs from hook_install() in that it gives all other modules diff --git a/modules/taxonomy/taxonomy.info b/modules/taxonomy/taxonomy.info index cba3869..6a13f81 100644 --- a/modules/taxonomy/taxonomy.info +++ b/modules/taxonomy/taxonomy.info @@ -4,6 +4,7 @@ package = Core version = VERSION core = 8.x dependencies[] = options +dependencies[] = entity files[] = taxonomy.module files[] = taxonomy.test configure = admin/structure/taxonomy diff --git a/modules/user/user.entity.inc b/modules/user/user.entity.inc new file mode 100644 index 0000000..5549c77 --- /dev/null +++ b/modules/user/user.entity.inc @@ -0,0 +1,52 @@ + $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); + } +} diff --git a/modules/user/user.info b/modules/user/user.info index a4d18d6..d887352 100644 --- a/modules/user/user.info +++ b/modules/user/user.info @@ -3,7 +3,7 @@ description = Manages the user registration and login system. package = Core version = VERSION core = 8.x -files[] = user.module +files[] = user.entity.inc files[] = user.test required = TRUE configure = admin/config/people diff --git a/modules/user/user.module b/modules/user/user.module index 044ad46..f3d6ea2 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -288,53 +288,6 @@ function user_load_multiple($uids = array(), $conditions = array(), $reset = FAL } /** - * 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. * * Drupal has a global $user object, which represents the currently-logged-in diff --git a/update.php b/update.php index 105bc9d..cf76ffc 100644 --- a/update.php +++ b/update.php @@ -345,7 +345,6 @@ require_once DRUPAL_ROOT . '/includes/bootstrap.inc'; require_once DRUPAL_ROOT . '/includes/update.inc'; require_once DRUPAL_ROOT . '/includes/common.inc'; require_once DRUPAL_ROOT . '/includes/file.inc'; -require_once DRUPAL_ROOT . '/includes/entity.inc'; require_once DRUPAL_ROOT . '/includes/unicode.inc'; update_prepare_d8_bootstrap();