Skip to content
entity.inc 44.5 KiB
Newer Older
<?php

/**
 * Interface for entity controller classes.
 *
 * All entity controller classes specified via the 'controller class' key
 * returned by hook_entity_info() or hook_entity_info_alter() have to implement
 * this interface.
 *
 * Most simple, SQL-based entity controllers will do better by extending
 * DrupalDefaultEntityController instead of implementing this interface
 * directly.
 */
interface DrupalEntityControllerInterface {
  /**
   * Constructor.
   *
   * @param $entityType
   *   The entity type for which the instance is created.
   */
  public function __construct($entityType);

  /**
   *
   * @param $ids
   *   (optional) If specified, the cache is reset for the entities with the
   *   given ids only.
  public function resetCache(array $ids = NULL);
   *
   * @param $ids
   *   An array of entity IDs, or FALSE to load all entities.
   * @param $conditions
   *   An array of conditions in the form 'field' => $value.
   *
   * @return
   *   An array of entity objects indexed by their ids. When no results are
   *   found, an empty array is returned.
   */
  public function load($ids = array(), $conditions = array());
}

/**
 * Default implementation of DrupalEntityControllerInterface.
 *
 * This class can be used as-is by most simple entity types. Entity types
 * requiring special handling can extend the class.
 */
class DrupalDefaultEntityController implements DrupalEntityControllerInterface {


  /**
   * Array of information about the entity.
   *
   * @var array
   *
   * @see entity_get_info()
   */

  /**
   * Additional arguments to pass to hook_TYPE_load().
   *
   * Set before calling DrupalDefaultEntityController::attachLoad().
   *
   * @var array
   */

  /**
   * Name of the entity's ID field in the entity database table.
   *
   * @var string
   */

  /**
   * 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
   */

  /**
   * The table that stores revisions, if the entity supports revisions.
   *
   * @var string
   */
   * 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();
    }
  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]);
    }

    // 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)) {
      $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) {
    }

    // 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;
   *
   * 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()
   * @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));
      $query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}");
    $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);
   * 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 the most current revision
  protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
    // Attach fields.
    if ($this->entityInfo['fieldable']) {
        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);
    }
  }

  /**
   *
   * @param $ids
   *   If not empty, return entities that match these IDs.
   * @param $conditions
   *   If set, return entities that match all of these conditions.
   */
  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();

  /**

  /**
   * 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.
   *   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 entityCondition($name, $value, $operator = NULL) {
    // The '!=' operator is deprecated in favour of the '<>' operator since the
    // latter is ANSI SQL compatible.
    if ($operator == '!=') {
      $operator = '<>';
    }
    $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.
   *   The operator can be omitted, and will default to 'IN' if the value is an
   *   array, or to '=' otherwise.
   * @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) {
    // The '!=' operator is deprecated in favour of the '<>' operator since the
    // latter is ANSI SQL compatible.
    if ($operator == '!=') {
      $operator = '<>';
    }
      $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)) {
        '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) {
    // The '!=' operator is deprecated in favour of the '<>' operator since the
    // latter is ANSI SQL compatible.
    if ($operator == '!=') {
      $operator = '<>';
    }
    $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') {
      $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);