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..31eb720 --- /dev/null +++ b/modules/entity/entity.info @@ -0,0 +1,8 @@ +name = Entity +description = API for managing entities like nodes and users. +package = Core +version = VERSION +core = 8.x +required = TRUE +files[] = entity.query.inc +files[] = entity.controller.inc diff --git a/modules/entity/entity.module b/modules/entity/entity.module new file mode 100644 index 0000000..cbde1fe --- /dev/null +++ b/modules/entity/entity.module @@ -0,0 +1,456 @@ +' . t('About') . ''; + $output .= '

' . t('The Entity module provides an API for managing entities like nodes and users, i.e. an API for loading and identifying entities. For more information, see the online handbook entry for Entity module', array('!url' => 'http://drupal.org/handbook/modules/entity')) . '

'; + return $output; + } +} + +/** + * Implements hook_modules_preenable(). + */ +function entity_modules_preenable() { + entity_info_cache_clear(); +} + +/** + * Implements hook_modules_disabled(). + */ +function entity_modules_disabled() { + entity_info_cache_clear(); +} + +/** + * Gets the entity info array of an entity type. + * + * @see hook_entity_info() + * @see hook_entity_info_alter() + * + * @param $entity_type + * The entity type, e.g. node, for which the info shall be returned, or NULL + * to return an array with info about all types. + */ +function entity_get_info($entity_type = NULL) { + global $language; + + // Use the advanced drupal_static() pattern, since this is called very often. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['entity_info'] = &drupal_static(__FUNCTION__); + } + $entity_info = &$drupal_static_fast['entity_info']; + + // hook_entity_info() includes translated strings, so each language is cached + // separately. + $langcode = $language->language; + + if (empty($entity_info)) { + if ($cache = cache_get("entity_info:$langcode")) { + $entity_info = $cache->data; + } + else { + $entity_info = module_invoke_all('entity_info'); + // Merge in default values. + foreach ($entity_info as $name => $data) { + $entity_info[$name] += array( + 'fieldable' => FALSE, + 'controller class' => 'DrupalDefaultEntityController', + 'static cache' => TRUE, + 'field cache' => TRUE, + 'load hook' => $name . '_load', + 'bundles' => array(), + 'view modes' => array(), + 'entity keys' => array(), + 'translation' => array(), + ); + $entity_info[$name]['entity keys'] += array( + 'revision' => '', + 'bundle' => '', + ); + foreach ($entity_info[$name]['view modes'] as $view_mode => $view_mode_info) { + $entity_info[$name]['view modes'][$view_mode] += array( + 'custom settings' => FALSE, + ); + } + // If no bundle key is provided, assume a single bundle, named after + // the entity type. + if (empty($entity_info[$name]['entity keys']['bundle']) && empty($entity_info[$name]['bundles'])) { + $entity_info[$name]['bundles'] = array($name => array('label' => $entity_info[$name]['label'])); + } + // Prepare entity schema fields SQL info for + // DrupalEntityControllerInterface::buildQuery(). + if (isset($entity_info[$name]['base table'])) { + $entity_info[$name]['schema_fields_sql']['base table'] = drupal_schema_fields_sql($entity_info[$name]['base table']); + if (isset($entity_info[$name]['revision table'])) { + $entity_info[$name]['schema_fields_sql']['revision table'] = drupal_schema_fields_sql($entity_info[$name]['revision table']); + } + } + } + // Let other modules alter the entity info. + drupal_alter('entity_info', $entity_info); + cache_set("entity_info:$langcode", $entity_info); + } + } + + if (empty($entity_type)) { + return $entity_info; + } + elseif (isset($entity_info[$entity_type])) { + return $entity_info[$entity_type]; + } +} + +/** + * Resets the cached information about entity types. + */ +function entity_info_cache_clear() { + drupal_static_reset('entity_get_info'); + // Clear all languages. + cache_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..f66b8a7 --- /dev/null +++ b/modules/entity/entity.query.inc @@ -0,0 +1,949 @@ +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']); + } + } + +} + 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();