diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php index e9fb855..fec3fc8 100644 --- a/core/includes/entity.api.php +++ b/core/includes/entity.api.php @@ -347,20 +347,9 @@ function hook_entity_delete(Drupal\Core\Entity\EntityInterface $entity) { } /** - * Alter or execute an Drupal\Core\Entity\EntityFieldQuery. - * - * @param Drupal\Core\Entity\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. + * Alter or execute an Drupal\Core\Entity\Query\EntityQueryInterface. * + * @param \Drupal\Core\Entity\Query\QueryInterface $query * 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 @@ -368,8 +357,8 @@ function hook_entity_delete(Drupal\Core\Entity\EntityInterface $entity) { * ($query->pager && $query->count), allowing the driver to return 0 from * the count query and disable the pager. */ -function hook_entity_query_alter(Drupal\Core\Entity\EntityFieldQuery $query) { - $query->executeCallback = 'my_module_query_callback'; +function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query) { + // @todo: code example. } /** diff --git a/core/includes/entity.inc b/core/includes/entity.inc index 48ee728..d87793f 100644 --- a/core/includes/entity.inc +++ b/core/includes/entity.inc @@ -5,10 +5,7 @@ * Entity API for handling entities like nodes or users. */ -use \InvalidArgumentException; use Drupal\Core\Cache\CacheBackendInterface; -use Drupal\Core\Entity\EntityFieldQuery; -use Drupal\Core\Entity\EntityMalformedException; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Entity\EntityInterface; @@ -123,7 +120,7 @@ function entity_info_cache_clear() { * @see entity_load_multiple() * @see Drupal\Core\Entity\EntityStorageControllerInterface * @see Drupal\Core\Entity\DatabaseStorageController - * @see Drupal\Core\Entity\EntityFieldQuery + * @see Drupal\Core\Entity\Query\QueryInterface */ function entity_load($entity_type, $id, $reset = FALSE) { $entities = entity_load_multiple($entity_type, array($id), $reset); @@ -227,7 +224,7 @@ function entity_load_by_uuid($entity_type, $uuid, $reset = FALSE) { * @see hook_entity_info() * @see Drupal\Core\Entity\EntityStorageControllerInterface * @see Drupal\Core\Entity\DatabaseStorageController - * @see Drupal\Core\Entity\EntityFieldQuery + * @see Drupal\Core\Entity\Query\QueryInterface */ function entity_load_multiple($entity_type, array $ids = NULL, $reset = FALSE) { if ($reset) { @@ -576,3 +573,17 @@ function entity_view(EntityInterface $entity, $view_mode, $langcode = NULL) { function entity_view_multiple(array $entities, $view_mode, $langcode = NULL) { return entity_render_controller(reset($entities)->entityType())->viewMultiple($entities, $view_mode, $langcode); } + +/** + * Returns the entity query object for this entity type. + * + * @param $entity_type + * The entity type, e.g. node, for which the query object should be + * returned. + * @param $conjunction + * AND if all conditions in the query need to apply, OR if any of them is + * enough. Optional, defaults to AND. + */ +function entity_query($entity_type, $conjunction = 'AND') { + return drupal_container()->get('entity.query')->get($entity_type, $conjunction); +} diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index 07849c6..d5d6e8b 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -374,4 +374,11 @@ protected function invokeHook($hook, EntityInterface $entity) { // Invoke the respective entity-level hook. module_invoke_all('entity_' . $hook, $entity, $this->entityType); } + + /** + * Implements Drupal\Core\Entity\EntityStorageControllerInterface::getQueryServicename(). + */ + public function getQueryServicename() { + throw new \LogicException('Querying config entities is not supported.'); + } } diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index f1e43d6..c652306 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -66,6 +66,9 @@ public function build(ContainerBuilder $container) { $container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder') ->addArgument(new Reference('router.dumper')); + $container->register('entity.query', 'Drupal\Core\Entity\Query\QueryFactory') + ->addArgument(new Reference('service_container')); + // @todo Replace below lines with the commented out block below it when it's // performant to do so: http://drupal.org/node/1706064. $dispatcher = $container->get('dispatcher'); diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 1960874..462699b 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -631,7 +631,7 @@ public function getDriverClass($class) { * @param $options * An array of options on the query. * - * @return Drupal\Core\Database\Query\SelectInterface + * @return \Drupal\Core\Database\Query\SelectInterface * An appropriate SelectQuery object for this database connection. Note that * it may be a driver-specific subclass of SelectQuery, depending on the * driver. diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php index 22aca8f..1bfe9a0 100644 --- a/core/lib/Drupal/Core/Database/Query/Select.php +++ b/core/lib/Drupal/Core/Database/Query/Select.php @@ -124,8 +124,9 @@ class Select extends Query implements SelectInterface { public function __construct($table, $alias = NULL, Connection $connection, $options = array()) { $options['return'] = Database::RETURN_STATEMENT; parent::__construct($connection, $options); - $this->where = new Condition('AND'); - $this->having = new Condition('AND'); + $conjunction = isset($options['conjunction']) ? $options['conjunction'] : 'AND'; + $this->where = new Condition($conjunction); + $this->having = new Condition($conjunction); $this->addJoin(NULL, $table, $alias); } diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index 6342502..d496ea0 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Entity; use PDO; +use Drupal\Core\Entity\Query\QueryInterface; use Exception; use Drupal\Component\Uuid\Uuid; @@ -264,30 +265,24 @@ public function deleteRevision($revision_id) { */ public function loadByProperties(array $values = array()) { // Build a query to fetch the entity IDs. - $entity_query = new EntityFieldQuery(); - $entity_query->entityCondition('entity_type', $this->entityType); + $entity_query = entity_query($this->entityType); $this->buildPropertyQuery($entity_query, $values); $result = $entity_query->execute(); - - if (empty($result[$this->entityType])) { - return array(); - } - // Load and return the found entities. - return $this->load(array_keys($result[$this->entityType])); + return $result ? $this->load($result) : array(); } /** * Builds an entity query. * - * @param Drupal\Core\Entity\EntityFieldQuery $entity_query - * EntityFieldQuery instance. + * @param \Drupal\Core\Entity\Query\QueryInterface $entity_query + * EntityQuery instance. * @param array $values * An associative array of properties of the entity, where the keys are the * property names and the values are the values those properties must have. */ - protected function buildPropertyQuery(EntityFieldQuery $entity_query, array $values) { + protected function buildPropertyQuery(QueryInterface $entity_query, array $values) { foreach ($values as $name => $value) { - $entity_query->propertyCondition($name, $value); + $entity_query->condition($name, $value); } } @@ -353,6 +348,7 @@ protected function buildQuery($ids, $revision_id = FALSE) { if ($ids) { $query->condition("base.{$this->idKey}", $ids, 'IN'); } + return $query; } @@ -699,4 +695,11 @@ public function getFieldDefinitions(array $constraints) { public function baseFieldDefinitions() { return array(); } + + /** + * Implements Drupal\Core\Entity\EntityStorageControllerInterface::getQueryServiceName(). + */ + public function getQueryServiceName() { + return 'entity.query.field_sql_storage'; + } } diff --git a/core/lib/Drupal/Core/Entity/EntityFieldQuery.php b/core/lib/Drupal/Core/Entity/EntityFieldQuery.php index 0c054ce..d97e3d2 100644 --- a/core/lib/Drupal/Core/Entity/EntityFieldQuery.php +++ b/core/lib/Drupal/Core/Entity/EntityFieldQuery.php @@ -39,7 +39,7 @@ * restrictions. Node access control is performed by the SQL storage engine but * other storage engines might not do this. */ -class EntityFieldQuery { +class xEntityFieldQuery { /** * Indicates that both deleted and non-deleted fields should be returned. diff --git a/core/lib/Drupal/Core/Entity/EntityFieldQueryException.php b/core/lib/Drupal/Core/Entity/EntityFieldQueryException.php deleted file mode 100644 index bf792a5..0000000 --- a/core/lib/Drupal/Core/Entity/EntityFieldQueryException.php +++ /dev/null @@ -1,19 +0,0 @@ -create() the follow keys are supported: - * - queryable: Whether the field is queryable via EntityFieldQuery. + * - queryable: Whether the field is queryable via QueryInterface. * Defaults to TRUE if 'computed' is FALSE or not set, to FALSE otherwise. * - translatable: Whether the field is translatable. Defaults to FALSE. * - configurable: A boolean indicating whether the field is configurable @@ -152,4 +152,12 @@ public function save(EntityInterface $entity); * @see typed_data() */ public function getFieldDefinitions(array $constraints); + + /** + * Gets the name of the service for the query for this entity storage. + * + * @return string + */ + public function getQueryServicename(); + } diff --git a/core/lib/Drupal/Core/Entity/Query/ConditionBase.php b/core/lib/Drupal/Core/Entity/Query/ConditionBase.php new file mode 100644 index 0000000..7a77580 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Query/ConditionBase.php @@ -0,0 +1,78 @@ +conjunction = $conjunction; + } + + /** + * Implements Drupal\Core\Entity\Query\ConditionInterface::getConjunction(). + */ + public function getConjunction() { + return $this->conjunction; + } + + /** + * Implements Countable::count(). + */ + public function count() { + return count($this->conditions) - 1; + } + + /** + * Implements Drupal\Core\Entity\Query\ConditionInterface::compile(). + */ + public function condition($field, $value = NULL, $operator = NULL, $langcode = NULL) { + $this->conditions[] = array( + 'field' => $field, + 'value' => $value, + 'operator' => $operator, + 'langcode' => $langcode, + ); + + return $this; + } + + /** + * Implements Drupal\Core\Entity\Query\ConditionInterface::conditions(). + */ + public function &conditions() { + return $this->conditions; + } + + /** + * Makes sure condition groups are cloned as well. + */ + function __clone() { + foreach ($this->conditions as $key => $condition) { + if ($condition['field'] instanceOf ConditionInterface) { + $this->conditions[$key]['field'] = clone($condition['field']); + } + } + } + +} diff --git a/core/lib/Drupal/Core/Entity/Query/ConditionInterface.php b/core/lib/Drupal/Core/Entity/Query/ConditionInterface.php new file mode 100644 index 0000000..936cfe2 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Query/ConditionInterface.php @@ -0,0 +1,85 @@ +entityType = $entity_type; + $this->conjunction = $conjunction; + $this->condition = $this->conditionGroupFactory($conjunction); + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::getEntityType(). + */ + public function getEntityType() { + return $this->entityType; + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::condition(). + */ + public function condition($property, $value = NULL, $operator = NULL, $langcode = NULL) { + $this->condition->condition($property, $value, $operator, $langcode); + return $this; + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::exists(). + */ + public function exists($property, $langcode = NULL) { + $this->condition->exists($property, $langcode); + return $this; + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::notExists(). + */ + public function notExists($property, $langcode = NULL) { + $this->condition->notExists($property, $langcode); + return $this; + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::range(). + */ + public function range($start = NULL, $length = NULL) { + $this->range = array( + 'start' => $start, + 'length' => $length, + ); + return $this; + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::andConditionGroup(). + */ + public function andConditionGroup() { + return $this->conditionGroupFactory('and'); + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::orConditionGroup(). + */ + public function orConditionGroup() { + return $this->conditionGroupFactory('or'); + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::sort(). + */ + public function sort($property, $direction = 'ASC') { + $this->sort[$property] = $direction; + return $this; + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::count(). + */ + public function count() { + $this->count = TRUE; + return $this; + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::setAccessCheck(). + */ + public function setAccessCheck($access_check = TRUE) { + $this->accessCheck = $access_check; + return $this; + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::age(). + */ + public function age($age = FIELD_LOAD_CURRENT) { + $this->age = $age; + return $this; + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::pager(). + */ + public function pager($limit = 10, $element = NULL) { + // Even when not using SQL, storing the element PagerSelectExtender is as + // good as anywhere else. + if (!isset($element)) { + $element = PagerSelectExtender::$maxElement++; + } + elseif ($element >= PagerSelectExtender::$maxElement) { + PagerSelectExtender::$maxElement = $element + 1; + } + + $this->pager = array( + 'limit' => $limit, + 'element' => $element, + ); + return $this; + } + + /** + * Gets the total number of results and initialize a pager for the query. + * + * The pager can be disabled by either setting the pager limit to 0, or by + * setting this query to be a count query. + */ + protected function initializePager() { + if ($this->pager && !empty($this->pager['limit']) && !$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']); + } + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::tableSort(). + */ + 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'])) { + $this->sort($header['specifier'], $direction); + } + } + + return $this; + } + +} diff --git a/core/lib/Drupal/Core/Entity/Query/QueryException.php b/core/lib/Drupal/Core/Entity/Query/QueryException.php new file mode 100644 index 0000000..a1d54d6 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Query/QueryException.php @@ -0,0 +1,17 @@ +container = $container; + } + + /** + * @param string $entity_type + * @param string $conjunction + * @return QueryInterface + */ + public function get($entity_type, $conjunction = "AND") { + $service_name = entity_get_controller($entity_type)->getQueryServicename(); + return $this->container->get($service_name)->get($entity_type, $conjunction); + } + +} diff --git a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php new file mode 100644 index 0000000..a45e349 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php @@ -0,0 +1,240 @@ +condition('greetings', 'merhaba', '=', 'tr'); + * ->condition('greetings.value', 'siema', '=', 'pl'); + * ->execute(); + * $entity_ids = $query->execute(); + * @endcode + * + * @param $value + * The value for $field. 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', + * 'ENDS_WITH': 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 $langcode + * Language code (optional). + * + * @return \Drupal\Core\Entity\Query\QueryInterface + * @see Drupal\Core\Entity\Query\andConditionGroup + * @see Drupal\Core\Entity\Query\orConditionGroup + */ + public function condition($field, $value = NULL, $operator = NULL, $langcode = NULL); + + /** + * Queries for the existence of a field. + * + * @param $field + * Name of a field. + * @param $langcode + * Language code (optional). + * @return \Drupal\Core\Entity\Query\QueryInterface + */ + public function exists($field, $langcode = NULL); + + /** + * Queries for the nonexistence of a field. + * + * @param $field. + * Name of a field. + * @param $langcode + * Language code (optional). + * @return \Drupal\Core\Entity\Query\QueryInterface + */ + public function notExists($field, $langcode = NULL); + + /** + * 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 \Drupal\Core\Entity\Query\QueryInterface + * The called object. + */ + public function pager($limit = 10, $element = NULL); + + /** + * @param null $start + * @param null $length + * @return \Drupal\Core\Entity\Query\QueryInterface + * The called object. + */ + public function range($start = NULL, $length = NULL); + + /** + * @param $property + * @param string $direction + * @return \Drupal\Core\Entity\Query\QueryInterface + * The called object. + */ + public function sort($property, $direction = 'ASC'); + + /** + * Makes this a count query. + * + * For count queries, execute() returns the number entities found. + * + * @return \Drupal\Core\Entity\Query\QueryInterface + * The called object. + */ + public function count(); + + /** + * Enables sortable tables for this query. + * + * @param $headers + * An array of headers of the same struucture as described in + * theme_table(). Use a 'specifier' in place of a 'field' to specify what + * to sort on. This can be an entity or a field as described in + * condition(). + * @return \Drupal\Core\Entity\Query\QueryInterface + * The called object. + */ + public function tableSort(&$headers); + + /** + * @return \Drupal\Core\Entity\Query\QueryInterface + * The called object. + */ + public function setAccessCheck($access_check = TRUE); + + /** + * 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 only, + * - FIELD_LOAD_REVISION: Query all revisions. + * + * @return \Drupal\Core\Entity\Query\QueryInterface + * The called object. + */ + public function age($age = FIELD_LOAD_CURRENT); + + /** + * Execute the query. + * + * @return int|array + * Returns an integer for count queries or an array of ids. The values of + * the array are always entity ids. The keys will be revision ids if the + * entity supports revision and entity ids if not. + */ + public function execute(); + + /** + * Creates an object holding a group of conditions. + * + * See andConditionGroup() and orConditionGroup() for more. + * + * @param $conjunction + * - AND (default): this is the equivalent of andConditionGroup(). + * - OR: this is the equivalent of andConditionGroup(). + * + * return ConditionInterface + * An object holding a group of conditions. + */ + public function conditionGroupFactory($conjunction = 'AND'); + + /** + * Creates a new group of conditions ANDed together. + * + * For example, consider a drawing entity type with a 'figures' multi-value + * field containing 'shape' and 'color' columns. To find all drawings + * containing both a red triangle and a blue circle: + * @code + * $query = entity_query('drawing'); + * $group = $query->andConditionGroup() + * ->condition('figures.color', 'red') + * ->condition('figures.shape', 'triangle'); + * $query->condition($group); + * $group = $query->andConditionGroup() + * ->condition('figures.color', 'blue') + * ->condition('figures.shape', 'circle'); + * $query->condition($group); + * $entity_ids = $query->execute(); + * @endcode + * + * @return \Drupal\Core\Entity\Query\ConditionInterface + */ + public function andConditionGroup(); + + /** + * Creates a new group of conditions ORed together. + * + * For example, consider a map entity with an 'attributes' field + * containing 'building_type' and 'color' columns. To find all green and + * red bikesheds: + * @code + * $query = entity_query('map'); + * $group = $query->orConditionGroup() + * ->condition('attributes.color', 'red') + * ->condition('attributes.color', 'green'); + * $entity_ids = $query + * ->condition('attributes.building_type', 'bikeshed') + * ->condition($group) + * ->execute(); + * @endcode + * Note that this particular example can be simplified: + * @code + * $entity_ids = $query + * ->condition('attributes.color', array('red', 'green')) + * ->condition('attributes.building_type', 'bikeshed') + * ->execute(); + * @endcode + * + * @return \Drupal\Core\Entity\Query\ConditionInterface + */ + public function orConditionGroup(); + +} diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index df0a33a..57cc799 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -1432,7 +1432,7 @@ function comment_delete_multiple($cids) { * An array of comment objects, indexed by comment ID. * * @see entity_load() - * @see Drupal\Core\Entity\EntityFieldQuery + * @see Drupal\Core\Entity\Query\QueryInterface */ function comment_load_multiple(array $cids = NULL, $reset = FALSE) { return entity_load_multiple('comment', $cids, $reset); diff --git a/core/modules/field/field.crud.inc b/core/modules/field/field.crud.inc index ba2f09e..45389ca 100644 --- a/core/modules/field/field.crud.inc +++ b/core/modules/field/field.crud.inc @@ -6,7 +6,6 @@ */ use Drupal\field\FieldException; -use Drupal\Core\Entity\EntityFieldQuery; /** * @defgroup field_crud Field CRUD API @@ -137,6 +136,9 @@ function field_create_field($field) { $schema += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array()); // 'columns' are hardcoded in the field type. $field['columns'] = $schema['columns']; + if (array_intersect(array_keys($field['columns']), field_reserved_columns())) { + throw new FieldException(t('Illegal field type columns.')); + } // 'foreign keys' are hardcoded in the field type. $field['foreign keys'] = $schema['foreign keys']; // 'indexes' can be both hardcoded in the field type, and specified in the @@ -873,31 +875,37 @@ function field_purge_batch($batch_size) { // Retrieve all deleted field instances. We cannot use field_info_instances() // because that function does not return deleted instances. $instances = field_read_instances(array('deleted' => 1), array('include_deleted' => 1)); - + $factory = drupal_container()->get('entity.query'); + $info = entity_get_info(); foreach ($instances as $instance) { + $entity_type = $instance['entity_type']; + $ids = (object) array( + 'entity_type' => $entity_type, + 'bundle' => $instance['bundle'], + ); // field_purge_data() will need the field array. $field = field_info_field_by_id($instance['field_id']); // Retrieve some entities. - $query = new EntityFieldQuery(); - $results = $query - ->fieldCondition($field) - ->entityCondition('bundle', $instance['bundle']) - ->deleted(TRUE) + $results = $factory->get($entity_type) + ->condition('id:' . $field['id'] . '.deleted', 1) + ->condition($info[$entity_type]['entity keys']['bundle'], $ids->bundle) ->range(0, $batch_size) ->execute(); if ($results) { - foreach ($results as $entity_type => $ids_array) { - $entities = array(); - foreach ($ids_array as $ids) { - $entities[$ids->entity_id] = _field_create_entity_from_ids($ids); - } - - field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); - foreach ($entities as $entity) { - // Purge the data for the entity. - field_purge_data($entity_type, $entity, $field, $instance); - } + $entities = array(); + foreach ($results as $revision_id => $entity_id) { + // This might not be the revision id if the entity type does not support + // revisions but _field_create_entity_from_ids() checks that and + // disregards this value so there is no harm setting it. + $ids->revision_id = $revision_id; + $ids->entity_id = $entity_id; + $entities[$entity_id] = _field_create_entity_from_ids($ids); + } + field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); + foreach ($entities as $entity) { + // Purge the data for the entity. + field_purge_data($entity_type, $entity, $field, $instance); } } else { diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 7fac3d2..96c258e 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -4,8 +4,8 @@ * Attach custom data fields to Drupal entities. */ -use Drupal\Core\Entity\EntityFieldQuery; use Drupal\Core\Template\Attribute; +use Drupal\Core\Entity\Query\QueryFactory; /* * Load all public Field API functions. Drupal currently has no @@ -1045,16 +1045,27 @@ function field_get_items($entity_type, $entity, $field_name, $langcode = NULL) { * TRUE if the field has data for any entity; FALSE otherwise. */ function field_has_data($field) { - $query = new EntityFieldQuery(); - return (bool) $query - ->fieldCondition($field) - ->range(0, 1) - ->count() - // Neutralize the 'entity_field_access' query tag added by - // field_sql_storage_field_storage_query(). The result cannot depend on the - // access grants of the current user. - ->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT') - ->execute(); + $field = field_info_field_by_id($field['id']); + $columns = array_keys($field['columns']); + $factory = drupal_container()->get('entity.query'); + foreach ($field['bundles'] as $entity_type => $bundle) { + $query = $factory->get($entity_type); + $group = $query->orConditionGroup(); + foreach ($columns as $column) { + $group->exists($field['field_name'] . '.' . $column); + } + $result = $query + ->condition($group) + ->count() + ->setAccessCheck(FALSE) + ->range(0, 1) + ->execute(); + if ($result) { + return TRUE; + } + } + + return FALSE; } /** @@ -1287,14 +1298,11 @@ function theme_field($variables) { /** * Assembles a partial entity structure with initial IDs. * - * This can be used to create an entity based on the the ids object returned by - * EntityFieldQuery. - * * @param stdClass $ids * An object with the properties entity_type (required), entity_id (required), * revision_id (optional) and bundle (optional). * - * @return + * @return \Drupal\Core\Entity\EntityInterface * An entity, initialized with the provided IDs. */ function _field_create_entity_from_ids($ids) { @@ -1308,4 +1316,13 @@ function _field_create_entity_from_ids($ids) { $id_properties[$info['entity keys']['bundle']] = $ids->bundle; } return entity_create($ids->entity_type, $id_properties); - } +} + +/** + * A list of columns that can not be used as field type columns. + * + * @return array + */ +function field_reserved_columns() { + return array('deleted'); +} diff --git a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php index 8211ce8..cc47e59 100644 --- a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php @@ -7,8 +7,6 @@ namespace Drupal\field\Tests; -use Drupal\Core\Entity\EntityFieldQuery; - /** * Unit test class for field bulk delete and batch purge functionality. */ @@ -115,7 +113,7 @@ function setUp() { // For each bundle, create an instance of each field, and 10 // entities with values for each field. - $id = 0; + $id = 1; $this->entity_type = 'test_entity'; foreach ($this->bundles as $bundle) { foreach ($this->fields as $field) { @@ -135,14 +133,15 @@ function setUp() { foreach ($this->fields as $field) { $entity->{$field['field_name']}[LANGUAGE_NOT_SPECIFIED] = $this->_generateTestFieldValues($field['cardinality']); } - - $this->entities[$id] = $entity; - // Also keep track of the entities per bundle. - $this->entities_by_bundles[$bundle][$id] = $entity; - field_attach_insert($this->entity_type, $entity); + $entity->save(); $id++; } } + $this->entities = entity_load_multiple($this->entity_type, range(1, $id)); + foreach ($this->entities as $entity) { + // Also keep track of the entities per bundle. + $this->entities_by_bundles[$entity->fttype][$entity->ftid] = $entity; + } } /** @@ -157,14 +156,14 @@ function setUp() { function testDeleteFieldInstance() { $bundle = reset($this->bundles); $field = reset($this->fields); + $field_name = $field['field_name']; + $factory = drupal_container()->get('entity.query'); // There are 10 entities of this bundle. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) + $found = $factory->get('test_entity') + ->condition('fttype', $bundle) ->execute(); - $this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found before deleting'); + $this->assertEqual(count($found), 10, 'Correct number of entities found before deleting'); // Delete the instance. $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); @@ -176,27 +175,30 @@ function testDeleteFieldInstance() { $this->assertEqual($instances[0]['bundle'], $bundle, 'The deleted instance is for the correct bundle'); // There are 0 entities of this bundle with non-deleted data. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) + $found = $factory->get('test_entity') + ->condition('fttype', $bundle) + ->condition("$field_name.deleted", 0) ->execute(); - $this->assertTrue(!isset($found['test_entity']), 'No entities found after deleting'); + $this->assertFalse($found, 'No entities found after deleting'); // There are 10 entities of this bundle when deleted fields are allowed, and // their values are correct. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) - ->deleted(TRUE) + $found = $factory->get('test_entity') + ->condition('fttype', $bundle) + ->condition("$field_name.deleted", 1) + ->sort('ftid') ->execute(); + $ids = (object) array( + 'entity_type' => 'test_entity', + 'bundle' => $bundle, + ); $entities = array(); - foreach ($found[$this->entity_type] as $ids) { - $entities[$ids->entity_id] = _field_create_entity_from_ids($ids); + foreach ($found as $entity_id) { + $ids->entity_id = $entity_id; + $entities[$entity_id] = _field_create_entity_from_ids($ids); } field_attach_load($this->entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); - $this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found after deleting'); + $this->assertEqual(count($found), 10, 'Correct number of entities found after deleting'); foreach ($entities as $id => $entity) { $this->assertEqual($this->entities[$id]->{$field['field_name']}, $entity->{$field['field_name']}, "Entity $id with deleted data loaded correctly"); } @@ -227,13 +229,11 @@ function testPurgeInstance() { field_purge_batch($batch_size); // There are $count deleted entities left. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) - ->deleted(TRUE) + $found = entity_query('test_entity') + ->condition('fttype', $bundle) + ->condition($field['field_name'] . '.deleted', 1) ->execute(); - $this->assertEqual($count ? count($found['test_entity']) : count($found), $count, 'Correct number of entities found after purging 2'); + $this->assertEqual(count($found), $count, 'Correct number of entities found after purging 2'); } // Check hooks invocations. diff --git a/core/modules/field/modules/field_sql_storage/field_sql_storage.module b/core/modules/field/modules/field_sql_storage/field_sql_storage.module index dcd6ba1..d354f51 100644 --- a/core/modules/field/modules/field_sql_storage/field_sql_storage.module +++ b/core/modules/field/modules/field_sql_storage/field_sql_storage.module @@ -6,9 +6,6 @@ */ use Drupal\Core\Database\Database; -use Drupal\Core\Database\Query\Select; -use Drupal\Core\Entity\EntityFieldQuery; -use Drupal\Core\Entity\EntityFieldQueryException; use Drupal\field\FieldUpdateForbiddenException; /** @@ -85,7 +82,7 @@ function _field_sql_storage_revision_tablename($field) { * unique among all other fields. */ function _field_sql_storage_columnname($name, $column) { - return $name . '_' . $column; + return in_array($column, field_reserved_columns()) ? $column : $name . '_' . $column; } /** @@ -525,191 +522,6 @@ function field_sql_storage_field_storage_purge($entity_type, $entity, $field, $i } /** - * Implements hook_field_storage_query(). - */ -function field_sql_storage_field_storage_query(EntityFieldQuery $query) { - if ($query->age == FIELD_LOAD_CURRENT) { - $tablename_function = '_field_sql_storage_tablename'; - $id_key = 'entity_id'; - } - else { - $tablename_function = '_field_sql_storage_revision_tablename'; - $id_key = 'revision_id'; - } - $table_aliases = array(); - // Add tables for the fields used. - foreach ($query->fields as $key => $field) { - $tablename = $tablename_function($field); - // Every field needs a new table. - $table_alias = $tablename . $key; - $table_aliases[$key] = $table_alias; - if ($key) { - $select_query->join($tablename, $table_alias, "$table_alias.entity_type = $field_base_table.entity_type AND $table_alias.$id_key = $field_base_table.$id_key"); - } - else { - $select_query = db_select($tablename, $table_alias); - // Allow queries internal to the Field API to opt out of the access - // check, for situations where the query's results should not depend on - // the access grants for the current user. - if (!isset($query->tags['DANGEROUS_ACCESS_CHECK_OPT_OUT'])) { - $select_query->addTag('entity_field_access'); - } - $select_query->addMetaData('base_table', $tablename); - $select_query->fields($table_alias, array('entity_type', 'entity_id', 'revision_id', 'bundle')); - $field_base_table = $table_alias; - } - if ($field['cardinality'] != 1 || $field['translatable']) { - $select_query->distinct(); - } - } - - // Add field conditions. We need a fresh grouping cache. - drupal_static_reset('_field_sql_storage_query_field_conditions'); - _field_sql_storage_query_field_conditions($query, $select_query, $query->fieldConditions, $table_aliases, '_field_sql_storage_columnname'); - - // Add field meta conditions. - _field_sql_storage_query_field_conditions($query, $select_query, $query->fieldMetaConditions, $table_aliases, function ($field_name, $column) { return $column; }); - - if (isset($query->deleted)) { - $select_query->condition("$field_base_table.deleted", (int) $query->deleted); - } - - // Is there a need to sort the query by property? - $has_property_order = FALSE; - foreach ($query->order as $order) { - if ($order['type'] == 'property') { - $has_property_order = TRUE; - } - } - - $entity_type = FALSE; - $entity_base_table = FALSE; - if ($query->propertyConditions || $has_property_order) { - if (empty($query->entityConditions['entity_type']['value'])) { - throw new EntityFieldQueryException('Property conditions and orders must have an entity type defined.'); - } - $entity_type = $query->entityConditions['entity_type']['value']; - $entity_base_table = _field_sql_storage_query_join_entity($select_query, $entity_type, $field_base_table); - $query->entityConditions['entity_type']['operator'] = '='; - $query->joinPropertyData($select_query, $entity_type, $field_base_table, 'entity_id'); - $query->addPropertyConditions($select_query, $entity_type); - } - - // The entity base keys require joining the entity base table. - $entity_base_keys = array('langcode', 'uuid'); - foreach ($query->entityConditions as $key => $condition) { - $table = FALSE; - if (!in_array($key, $entity_base_keys)) { - $table = $field_base_table; - } - else { - // Join only if we did not already before. - if (empty($entity_base_table)) { - if (empty($query->entityConditions['entity_type']['value'])) { - throw new EntityFieldQueryException('Property conditions and orders must have an entity type defined.'); - } - $entity_type = $query->entityConditions['entity_type']['value']; - $entity_base_table = _field_sql_storage_query_join_entity($select_query, $entity_type, $field_base_table); - } - if (empty($entity_schema)) { - $entity_schema = drupal_get_schema($entity_base_table); - $entity_info = entity_get_info($entity_type); - } - $key = !empty($entity_info['entity keys'][$key]) ? $entity_info['entity keys'][$key] : $key; - if (!empty($entity_schema['fields'][$key])) { - $table = $entity_base_table; - } - } - // Apply the condition only if we have a valid table: entity conditions may - // refer to non existing columns. - if ($table) { - $query->addCondition($select_query, "$table.$key", $condition); - } - } - - // Order the query. - foreach ($query->order as $order) { - if ($order['type'] == 'entity') { - $key = $order['specifier']; - $select_query->orderBy("$field_base_table.$key", $order['direction']); - } - elseif ($order['type'] == 'field') { - $specifier = $order['specifier']; - $field = $specifier['field']; - $table_alias = $table_aliases[$specifier['index']]; - $sql_field = "$table_alias." . _field_sql_storage_columnname($field['field_name'], $specifier['column']); - $select_query->orderBy($sql_field, $order['direction']); - } - elseif ($order['type'] == 'property') { - $query->addPropertyOrderBy($select_query, $entity_type, $order); - } - } - - return $query->finishQuery($select_query, $id_key); -} - -/** - * Adds the base entity table to a field query object. - * - * @param SelectQuery $select_query - * A SelectQuery containing at least one table as specified by - * _field_sql_storage_tablename(). - * @param $entity_type - * The entity type for which the base table should be joined. - * @param $field_base_table - * Name of a table in $select_query. As only INNER JOINs are used, it does not - * matter which. - * - * @return - * The name of the entity base table that was joined in. - */ -function _field_sql_storage_query_join_entity(Select $select_query, $entity_type, $field_base_table) { - $entity_info = entity_get_info($entity_type); - $entity_base_table = $entity_info['base table']; - $entity_field = $entity_info['entity keys']['id']; - $select_query->join($entity_base_table, $entity_base_table, "$entity_base_table.$entity_field = $field_base_table.entity_id"); - return $entity_base_table; -} - -/** - * Adds field (meta) conditions to the given query objects respecting groupings. - * - * @param Drupal\Core\Entity\EntityFieldQuery $query - * The field query object to be processed. - * @param SelectQuery $select_query - * The SelectQuery that should get grouping conditions. - * @param condtions - * The conditions to be added. - * @param $table_aliases - * An associative array of table aliases keyed by field index. - * @param $column_callback - * A callback that should return the column name to be used for the field - * conditions. Accepts a field name and a field column name as parameters. - */ -function _field_sql_storage_query_field_conditions(EntityFieldQuery $query, Select $select_query, $conditions, $table_aliases, $column_callback) { - $groups = &drupal_static(__FUNCTION__, array()); - foreach ($conditions as $key => $condition) { - $table_alias = $table_aliases[$key]; - $field = $condition['field']; - // Add the specified condition. - $sql_field = "$table_alias." . $column_callback($field['field_name'], $condition['column']); - $query->addCondition($select_query, $sql_field, $condition); - // Add delta / langcode group conditions. - foreach (array('delta', 'langcode') as $column) { - if (isset($condition[$column . '_group'])) { - $group_name = $condition[$column . '_group']; - if (!isset($groups[$column][$group_name])) { - $groups[$column][$group_name] = $table_alias; - } - else { - $select_query->where("$table_alias.$column = " . $groups[$column][$group_name] . ".$column"); - } - } - } - } -} - -/** * Implements hook_field_storage_delete_revision(). * * This function actually deletes the data from the database. diff --git a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Condition.php b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Condition.php new file mode 100644 index 0000000..8e4b73d --- /dev/null +++ b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Condition.php @@ -0,0 +1,91 @@ +sqlQuery; + $tables = new Tables($sqlQuery); + foreach ($this->conditions as $condition) { + if ($condition['field'] instanceOf ConditionInterface) { + $sqlCondition = new SqlCondition($condition['field']->getConjunction()); + // Add the SQL query to the object before calling this method again. + $sqlCondition->sqlQuery = $sqlQuery; + $condition['field']->compile($sqlCondition); + $sqlQuery->condition($sqlCondition); + } + else { + $type = strtoupper($this->conjunction) == 'OR' || $condition['operator'] == 'IS NULL' ? 'LEFT' : 'INNER'; + $this->translateCondition($condition); + $field = $tables->addField($condition['field'], $type, $table); + if ($condition['langcode']) { + // Even inside an OR condition group the language code and the + // actual condition need to be ANDed together. For AND conditions + // this is not necessary but it simplifies the code. + $and = new SqlCondition('AND'); + $and + ->condition("$table.langcode", $condition['langcode']) + ->condition($field, $condition['value'], $condition['operator']); + $conditionContainer->condition($and); + } + else { + $conditionContainer->condition($field, $condition['value'], $condition['operator']); + } + } + } + } + + /** + * Implements Drupal\Core\Entity\Query\ConditionInterface::exists(). + */ + public function exists($field, $langcode = NULL) { + return $this->condition($field, NULL, 'IS NOT NULL', $langcode); + } + + /** + * Implements Drupal\Core\Entity\Query\ConditionInterface::notExists(). + */ + public function notExists($field, $langcode = NULL) { + return $this->condition($field, NULL, 'IS NULL', $langcode); + } + + protected function translateCondition(&$condition) { + switch ($condition['operator']) { + case 'STARTS_WITH': + $condition['value'] .= '%'; + $condition['operator'] = 'LIKE'; + break; + + case 'CONTAINS': + $condition['value'] = '%' . $condition['value'] . '%'; + $condition['operator'] = 'LIKE'; + break; + + case 'ENDS_WITH': + $condition['value'] = '%' . $condition['value']; + $condition['operator'] = 'LIKE'; + break; + + } + } + +} diff --git a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php new file mode 100644 index 0000000..c96720a --- /dev/null +++ b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php @@ -0,0 +1,168 @@ +connection = $connection; + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::conditionGroupFactory(). + */ + public function conditionGroupFactory($conjunction = 'AND') { + return new Condition($conjunction); + } + + /** + * Implements Drupal\Core\Entity\Query\QueryInterface::execute(). + */ + public function execute() { + $entity_info = entity_get_info($this->entityType); + if (!isset($entity_info['base table'])) { + throw new QueryException("No base table, nothing to query."); + } + $configurable_fields = array(); + foreach (field_info_instances($this->entityType) as $instances) { + foreach ($instances as $field_name => $instance) { + $configurable_fields[$field_name] = TRUE; + } + } + $base_table = $entity_info['base table']; + // Assemble a list of entity tables, primarily for use in + // \Drupal\field_sql_storage\Entity\Tables::ensureEntityTable(). + $entity_tables = array(); + $simple_query = TRUE; + // ensureEntityTable() decides whether an entity property will be queried + // from the data table or the base table based on where it finds the + // property first. The data table is prefered, which is why it gets added + // before the base table. + if (isset($entity_info['data table'])) { + $entity_tables[$entity_info['data table']] = drupal_get_schema($entity_info['data table']); + $simple_query = FALSE; + } + $entity_tables[$base_table] = drupal_get_schema($base_table); + $sqlQuery = $this->connection->select($base_table, 'base_table', array('conjunction' => $this->conjunction)); + $sqlQuery->addMetaData('configurable_fields', $configurable_fields); + // Determines the key of the column to join on. This is either the entity + // id key or the revision id key, depending on whether the entity type + // supports revisions. + $id_key = 'id'; + $id_field = $entity_info['entity keys']['id']; + $fields[$id_field] = TRUE; + if (empty($entity_info['entity keys']['revision'])) { + // Add the key field for fetchAllKeyed(). When there is no revision + // support, this is the entity key. + $sqlQuery->addField('base_table', $entity_info['entity keys']['id']); + } + else { + // Add the key field for fetchAllKeyed(). When there is revision + // support, this is the revision key. + $revision_field = $entity_info['entity keys']['revision']; + $fields[$revision_field] = TRUE; + $sqlQuery->addField('base_table', $revision_field); + // Now revision id is column 0 and the value column is 1. + if ($this->age == FIELD_LOAD_CURRENT) { + $id_key = 'revision'; + } + } + // Now add the value column for fetchAllKeyed(). This is always the + // entity id. + $sqlQuery->addField('base_table', $id_field); + if ($this->accessCheck) { + $sqlQuery->addTag($this->entityType . '_access'); + } + $sqlQuery->addTag('entity_query'); + $sqlQuery->addTag('entity_query_' . $this->entityType); + // This now contains first the table containing entity properties and + // last the entity base table. They might be the same. + $sqlQuery->addMetaData('entity_tables', $entity_tables); + $sqlQuery->addMetaData('age', $this->age); + // This contains the relevant SQL field to be used when joining entity + // tables. + $sqlQuery->addMetaData('entity_id_field', $entity_info['entity keys'][$id_key]); + // This contains the relevant SQL field to be used when joining field + // tables. + $sqlQuery->addMetaData('field_id_field', $id_key == 'id' ? 'entity_id' : 'revision_id'); + $sqlQuery->addMetaData('simple_query', $simple_query); + $this->condition->compile($sqlQuery); + if ($this->sort) { + $tables = new Tables($sqlQuery); + // Gather the SQL field aliases first to make sure every field table + // necessary is added. + foreach ($this->sort as $property => $direction) { + $sort[$property] = isset($fields[$property]) ? $property : $tables->addField($property, 'LEFT'); + } + // If the query is set up for paging, then the correct amount of rows + // returned is important. If the entity has a data table or multiple + // value fields are involved then each revision might appear in several + // rows and this needs a significantly more complex query. + $simple_query = (!$this->pager && !$this->range && !$this->count) || $sqlQuery->getMetaData('simple_query'); + if (!$simple_query) { + // First, GROUP BY revision id (if it has been added) and entity id. + // Now each group contains a single revision of an entity. + foreach (array_keys($fields) as $field) { + $sqlQuery->groupBy($field); + } + } + if (!$this->count) { + foreach ($sort as $property => $sql_alias) { + $direction = $this->sort[$property]; + if ($simple_query || isset($fields[$property])) { + // Simple queries, and the grouped columns of complicated queries + // can be ordered normally, without the aggregation function. + $sqlQuery->orderBy($sql_alias, $direction); + } + else { + // Order based on the smallest element of each group if the + // direction is ascending, or on the largest element of each group + // if the direction is descending. + $function = $direction == 'ASC' ? 'min' : 'max'; + $expression = "$function($sql_alias)"; + $sqlQuery->addExpression($expression, "order_by_{$property}_$direction"); + $sqlQuery->orderBy($expression, $direction); + } + } + } + } + $this->initializePager(); + if ($this->range) { + $sqlQuery->range($this->range['start'], $this->range['length']); + } + if ($this->count) { + return $sqlQuery->countQuery()->execute()->fetchField(); + } + // Return a keyed array of results. The key is either the revision_id or + // the entity_id depending on whether the entity type supports revisions. + // The value is always the entity id. + return $sqlQuery->execute()->fetchAllKeyed(); + } + +} diff --git a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/QueryFactory.php b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/QueryFactory.php new file mode 100644 index 0000000..22739df --- /dev/null +++ b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/QueryFactory.php @@ -0,0 +1,24 @@ +connection = $connection; + } + + function get($entity_type, $conjunction) { + return new Query($entity_type, $conjunction, $this->connection); + } +} diff --git a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php new file mode 100644 index 0000000..1c1b8c5 --- /dev/null +++ b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php @@ -0,0 +1,137 @@ +sqlQuery = $sql_query; + } + + /** + * @param string $field + * If it contains a dot, then field name dot field column. If it doesn't + * then entity property name. + * @param string $type + * Join type, can either be INNER or LEFT. + * @return string + * The return value is a string containing the alias of the table, a dot + * and the appropriate SQL column as passed in. This allows the direct use + * of this in a query for a condition or sort. + */ + function addField($field, $type, &$table = NULL) { + $parts = explode('.', $field); + $property = $parts[0]; + $configurable_fields = $this->sqlQuery->getMetaData('configurable_fields'); + if (!empty($configurable_fields[$property]) || substr($property, 0, 3) == 'id:') { + $field_name = $property; + $table = $this->ensureFieldTable($field_name, $type); + // Default to .value. + $column = isset($parts[1]) ? $parts[1] : 'value'; + $sql_column = _field_sql_storage_columnname($field_name, $column); + } + else { + $sql_column = $property; + $table = $this->ensureEntityTable($property, $type); + } + return "$table.$sql_column"; + } + + /** + * Join entity table if necessary and return the alias for it. + * + * @param string $property + * @return string + * @throws \Drupal\Core\Entity\Query\QueryException + */ + protected function ensureEntityTable($property, $type = 'INNER') { + $entity_tables = $this->sqlQuery->getMetaData('entity_tables'); + if (!$entity_tables) { + throw new QueryException('Can not query entity properties without an entity type.'); + } + foreach ($entity_tables as $table => $schema) { + if (isset($schema['fields'][$property])) { + if (!isset($this->entityTables[$table])) { + $id_field = $this->sqlQuery->getMetaData('entity_id_field'); + $this->entityTables[$table] = $this->sqlQuery->addJoin($type, $table, NULL, "%alias.$id_field = base_table.$id_field"); + } + return $this->entityTables[$table]; + } + } + throw new QueryException(format_string('@property not found', array('@property' => $property))); + } + + /** + * Join field table if necessary. + * + * @param $field_name + * Name of the field. + * @return string + * @throws \Drupal\Core\Entity\Query\QueryException + */ + protected function ensureFieldTable(&$field_name, $type = 'INNER') { + if (!isset($this->fieldTables[$field_name])) { + // This is field_purge_batch() passing in a field id. + if (substr($field_name, 0, 3) == 'id:') { + $field = field_info_field_by_id(substr($field_name, 3)); + } + else { + $field = field_info_field($field_name); + } + if (!$field) { + throw new QueryException(format_string('field @field_name not found', array('@field_name' => $field_name))); + } + // This is really necessary only for the id: case but it can't be run + // before throwing the exception. + $field_name = $field['field_name']; + $table = $this->sqlQuery->getMetaData('age') == FIELD_LOAD_CURRENT ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); + $field_id_field = $this->sqlQuery->getMetaData('field_id_field'); + $entity_id_field = $this->sqlQuery->getMetaData('entity_id_field'); + if ($field['cardinality'] != 1) { + $this->sqlQuery->addMetaData('simple_query', FALSE); + } + $this->fieldTables[$field_name] = $this->sqlQuery->addJoin($type, $table, NULL, "%alias.$field_id_field = base_table.$entity_id_field"); + } + return $this->fieldTables[$field_name]; + } + +} diff --git a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/FieldSqlStorageBundle.php b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/FieldSqlStorageBundle.php new file mode 100644 index 0000000..2d5f996 --- /dev/null +++ b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/FieldSqlStorageBundle.php @@ -0,0 +1,22 @@ +register('entity.query.field_sql_storage', 'Drupal\field_sql_storage\Entity\QueryFactory') + ->addArgument(new Reference('database')); + } + +} diff --git a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php index 3cabd58..5681f59 100644 --- a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php +++ b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php @@ -309,7 +309,7 @@ function testUpdateFieldSchemaWithData() { $instance = field_create_instance($instance); $entity = field_test_create_entity(0, 0, $instance['bundle']); $entity->decimal52[LANGUAGE_NOT_SPECIFIED][0]['value'] = '1.235'; - field_attach_insert('test_entity', $entity); + $entity->save(); // Attempt to update the field in a way that would work without data. $field['settings']['scale'] = 3; @@ -367,9 +367,9 @@ function testFieldUpdateIndexesWithData() { } // Add data so the table cannot be dropped. - $entity = field_test_create_entity(0, 0, $instance['bundle']); + $entity = field_test_create_entity(1, 1, $instance['bundle']); $entity->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['value'] = 'field data'; - field_attach_insert('test_entity', $entity); + $entity->save(); // Add an index $field = array('field_name' => $field_name, 'indexes' => array('value' => array(array('value', 255)))); @@ -387,8 +387,8 @@ function testFieldUpdateIndexesWithData() { } // Verify that the tables were not dropped. - $entity = field_test_create_entity(0, 0, $instance['bundle']); - field_attach_load('test_entity', array(0 => $entity)); + $entity = field_test_create_entity(1, 1, $instance['bundle']); + field_attach_load('test_entity', array(1 => $entity)); $this->assertEqual($entity->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['value'], 'field data', t("Index changes performed without dropping the tables")); } diff --git a/core/modules/field/modules/options/options.module b/core/modules/field/modules/options/options.module index 31652e3..9c8d679 100644 --- a/core/modules/field/modules/options/options.module +++ b/core/modules/field/modules/options/options.module @@ -6,7 +6,7 @@ */ use Drupal\field\FieldUpdateForbiddenException; -use Drupal\Core\Entity\EntityFieldQuery; +use Drupal\Core\Entity\Query\QueryFactory; /** * Implements hook_help(). @@ -384,12 +384,19 @@ function options_field_update_forbid($field, $prior_field, $has_data) { */ function _options_values_in_use($field, $values) { if ($values) { - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field['field_name'], 'value', $values) - ->range(0, 1) - ->execute(); - return !empty($found); + $field = field_info_field_by_id($field['id']); + $factory = drupal_container()->get('entity.query'); + foreach ($field['bundles'] as $entity_type => $bundle) { + $result = $factory->get($entity_type) + ->condition($field['field_name'] . '.value', $values) + ->count() + ->setAccessCheck(FALSE) + ->range(0, 1) + ->execute(); + if ($result) { + return TRUE; + } + } } return FALSE; diff --git a/core/modules/field/tests/modules/field_test/field_test.field.inc b/core/modules/field/tests/modules/field_test/field_test.field.inc index f47ccad..2538b91 100644 --- a/core/modules/field/tests/modules/field_test/field_test.field.inc +++ b/core/modules/field/tests/modules/field_test/field_test.field.inc @@ -66,7 +66,7 @@ function field_test_field_load($entity_type, $entities, $field, $instances, $lan foreach ($items as $id => $item) { // To keep the test non-intrusive, only act for instances with the // test_hook_field_load setting explicitly set to TRUE. - if ($instances[$id]['settings']['test_hook_field_load']) { + if (!empty($instances[$id]['settings']['test_hook_field_load'])) { foreach ($item as $delta => $value) { // Don't add anything on empty values. if ($value) { diff --git a/core/modules/field/tests/modules/field_test/field_test.module b/core/modules/field/tests/modules/field_test/field_test.module index 6d3195c..832f21c 100644 --- a/core/modules/field/tests/modules/field_test/field_test.module +++ b/core/modules/field/tests/modules/field_test/field_test.module @@ -13,8 +13,6 @@ * test helper functions */ -use Drupal\Core\Entity\EntityFieldQuery; - require_once DRUPAL_ROOT . '/core/modules/field/tests/modules/field_test/field_test.entity.inc'; require_once DRUPAL_ROOT . '/core/modules/field/tests/modules/field_test/field_test.field.inc'; require_once DRUPAL_ROOT . '/core/modules/field/tests/modules/field_test/field_test.storage.inc'; @@ -188,27 +186,6 @@ function field_test_field_create_field($field) { } /** - * Implements hook_entity_query_alter(). - */ -function field_test_entity_query_alter(&$query) { - if (!empty($query->alterMyExecuteCallbackPlease)) { - $query->executeCallback = 'field_test_dummy_field_storage_query'; - } -} - -/** - * Pseudo-implements hook_field_storage_query(). - */ -function field_test_dummy_field_storage_query(EntityFieldQuery $query) { - // Return dummy values that will be checked by the test. - return array( - 'user' => array( - 1 => (object) array('entity_id' => 1, 'revision_id' => NULL, 'bundle' => NULL), - ), - ); -} - -/** * Entity label callback. * * @param $entity_type diff --git a/core/modules/file/file.field.inc b/core/modules/file/file.field.inc index 28a32a9..4688926 100644 --- a/core/modules/file/file.field.inc +++ b/core/modules/file/file.field.inc @@ -930,6 +930,29 @@ function file_field_formatter_view($entity_type, $entity, $field, $instance, $la } /** + * Determine whether a field references files stored in {file_managed}. + * + * @param array $field + * A field array. + * + * @return + * The field column if the field references {file_managed}.fid, typically + * fid, FALSE if it doesn't. + */ +function file_field_find_file_reference_column($field) { + foreach ($field['foreign keys'] as $data) { + if ($data['table'] == 'file_managed') { + foreach ($data['columns'] as $field_column => $column) { + if ($column == 'fid') { + return $field_column; + } + } + } + } + return FALSE; +} + +/** * Returns HTML for a file attachments table. * * @param $variables diff --git a/core/modules/file/file.module b/core/modules/file/file.module index 4e6c8eb..5578848 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -5,7 +5,6 @@ * Defines a "managed_file" Form API field and a "file" field for Field module. */ -use Drupal\Core\Entity\EntityFieldQuery; use Drupal\file\File; use Drupal\Core\Template\Attribute; use Symfony\Component\HttpFoundation\JsonResponse; @@ -124,7 +123,7 @@ function file_entity_info() { * @see hook_file_load() * @see file_load() * @see entity_load() - * @see Drupal\Core\Entity\EntityFieldQuery + * @see Drupal\Core\Entity\Query\EntityQueryInterface */ function file_load_multiple(array $fids = NULL) { return entity_load_multiple('file', $fids); @@ -668,7 +667,7 @@ function file_file_download($uri, $field_type = 'file') { // an image preview on a node/add form) in which case, allow download by the // file's owner. if (empty($references) && ($file->status == FILE_STATUS_PERMANENT || $file->uid != $user->uid)) { - return; + return; } // Default to allow access. @@ -680,34 +679,13 @@ function file_file_download($uri, $field_type = 'file') { // and no reference denied access, access is granted as well. If at least one // reference denied access, access is denied. foreach ($references as $field_name => $field_references) { - foreach ($field_references as $entity_type => $type_references) { - foreach ($type_references as $id => $reference) { - // Try to load $entity and $field. - $entity = entity_load($entity_type, $id); + foreach ($field_references as $entity_type => $entities) { + foreach ($entities as $entity) { $field = field_info_field($field_name); - - // Load the field item that references the file. - $match = FALSE; - if ($entity) { - // Load all fields items for that entity. - $field_items = field_get_items($entity_type, $entity, $field_name); - - // Try to find the field item that references the given file. - foreach ($field_items as $item) { - if ($file->fid == $item['fid']) { - $match = TRUE; - break; - } - } - } - - // Check that $entity and $field were loaded successfully, a field - // item that references the file exists and check if access to that - // field is not disallowed. If any of these checks fail, stop checking - // access for this reference. - if (empty($entity) || empty($field) || !$match || !field_access('view', $field, $entity_type, $entity)) { + // Check if access to this field is not disallowed. + if (!field_access('view', $field, $entity_type, $entity)) { $denied = TRUE; - break; + continue; } // Invoke hook and collect grants/denies for download access. @@ -1555,27 +1533,83 @@ function file_icon_map(File $file) { * FIELD_LOAD_CURRENT to retrieve references only in the current revisions. * @param $field_type * (optional) The name of a field type. If given, limits the reference check - * to fields of the given type. + * to fields of the given type. If both $field and $field_type is given but + * $field is not the same type as $field_type, an empty array will be + * returned. * * @return - * An integer value. + * A multidimensional array. The keys are field_name, entity_type, + * entity_id and the value is an entity referencing this file. */ function file_get_file_references(File $file, $field = NULL, $age = FIELD_LOAD_REVISION, $field_type = 'file') { - $references = drupal_static(__FUNCTION__, array()); - $fields = isset($field) ? array($field['field_name'] => $field) : field_info_fields(); - - foreach ($fields as $field_name => $file_field) { - if ((empty($field_type) || $file_field['type'] == $field_type) && !isset($references[$field_name])) { - // Get each time this file is used within a field. - $query = new EntityFieldQuery(); - $query - ->fieldCondition($file_field, 'fid', $file->fid) - ->age($age); - $references[$field_name] = $query->execute(); + $references = &drupal_static(__FUNCTION__, array()); + $field_columns = &drupal_static(__FUNCTION__ . ':field_columns', array()); + + // Fill the static cache, disregard $field and $field_type for now. + if (!isset($references[$file->fid][$age])) { + $references[$file->fid][$age] = array(); + $usage_list = file_usage()->listUsage($file); + $file_usage_list = isset($usage_list['file']) ? $usage_list['file'] : array(); + foreach ($file_usage_list as $entity_type => $entity_ids) { + $entity_info = entity_get_info($entity_type); + // The usage table contains usage of every revision. If we are looking + // for every revision or the entity does not support revisions then + // every usage is already a match. + $match_entity_type = $age == FIELD_LOAD_REVISION || !isset($entity_info['entity keys']['revision']); + $entities = entity_load_multiple($entity_type, $entity_ids); + foreach ($entities as $entity) { + $bundle = $entity->bundle(); + // We need to find file fields for this entity type and bundle. + if (!isset($file_fields[$entity_type][$bundle])) { + $file_fields[$entity_type][$bundle] = array(); + // This contains the possible field names. + $instances = field_info_instances($entity_type, $bundle); + foreach ($instances as $field_name => $instance) { + $current_field = field_info_field($field_name); + // If this is the first time this field type is seen, check + // whether it references files. + if (!isset($field_columns[$current_field['type']])) { + $field_columns[$current_field['type']] = file_field_find_file_reference_column($current_field); + } + // If the field type does reference files then record it. + if ($field_columns[$current_field['type']]) { + $file_fields[$entity_type][$bundle][$field_name] = $field_columns[$current_field['type']]; + } + } + } + foreach ($file_fields[$entity_type][$bundle] as $field_name => $field_column) { + $match = $match_entity_type; + // If we didn't match yet then iterate over the field items to find + // the referenced file. This will fail if the usage checked is in a + // non-current revision because field items are from the current + // revision. + if (!$match && ($field_items = field_get_items($entity_type, $entity, $field_name))) { + foreach ($field_items as $item) { + if ($file->fid == $item[$field_column]) { + $match = TRUE; + break; + } + } + } + if ($match) { + $references[$file->fid][$age][$field_name][$entity_type][$entity->id()] = $entity; + } + } + } } } - - return isset($field) ? $references[$field['field_name']] : array_filter($references); + $return = $references[$file->fid][$age]; + // Filter the static cache down to the requested entries. The usual static + // cache is very small so this will be very fast. + if ($field || $field_type) { + foreach ($return as $field_name => $data) { + $current_field = field_info_field($field_name); + if (($field_type && $current_field['type'] != $field_type) || ($field && $field['id'] != $current_field['id'])) { + unset($return[$field_name]); + } + } + } + return $return; } /** diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 55989de..dd08f84 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -1007,7 +1007,7 @@ function node_invoke($node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) { * An array of node entities indexed by nid. * * @see entity_load_multiple() - * @see Drupal\Core\Entity\EntityFieldQuery + * @see Drupal\Core\Entity\Query\EntityQueryInterface */ function node_load_multiple(array $nids = NULL, $reset = FALSE) { return entity_load_multiple('node', $nids, $reset); @@ -3081,34 +3081,6 @@ function node_access_view_all_nodes($account = NULL) { * 'update' and 'delete'). */ function node_query_node_access_alter(AlterableInterface $query) { - _node_query_node_access_alter($query, 'node'); -} - -/** - * Implements hook_query_TAG_alter(). - * - * This function implements the same functionality as - * node_query_node_access_alter() for the SQL field storage engine. Node access - * conditions are added for field values belonging to nodes only. - */ -function node_query_entity_field_access_alter(AlterableInterface $query) { - _node_query_node_access_alter($query, 'entity'); -} - -/** - * Helper for node access functions. - * - * @param $query - * The query to add conditions to. - * @param $type - * Either 'node' or 'entity' depending on what sort of query it is. See - * node_query_node_access_alter() and node_query_entity_field_access_alter() - * for more. - * - * @see node_query_node_access_alter() - * @see node_query_entity_field_access_alter() - */ -function _node_query_node_access_alter($query, $type) { global $user; // Read meta-data from query, if provided. @@ -3157,27 +3129,6 @@ function _node_query_node_access_alter($query, $type) { // the node_access table. $grants = node_access_grants($op, $account); - if ($type == 'entity') { - // The original query looked something like: - // @code - // SELECT nid FROM sometable s - // INNER JOIN node_access na ON na.nid = s.nid - // WHERE ($node_access_conditions) - // @endcode - // - // Our query will look like: - // @code - // SELECT entity_type, entity_id - // FROM field_data_something s - // LEFT JOIN node_access na ON s.entity_id = na.nid - // WHERE (entity_type = 'node' AND $node_access_conditions) OR (entity_type <> 'node') - // @endcode - // - // So instead of directly adding to the query object, we need to collect - // all of the node access conditions in a separate db_and() object and - // then add it to the query at the end. - $node_conditions = db_and(); - } foreach ($tables as $nalias => $tableinfo) { $table = $tableinfo['table']; if (!($table instanceof SelectInterface) && $table == $base_table) { @@ -3204,37 +3155,11 @@ function _node_query_node_access_alter($query, $type) { $subquery->condition('na.grant_' . $op, 1, '>='); $field = 'nid'; // Now handle entities. - if ($type == 'entity') { - // Set a common alias for entities. - $base_alias = $nalias; - $field = 'entity_id'; - } $subquery->where("$nalias.$field = na.nid"); - // For an entity query, attach the subquery to entity conditions. - if ($type == 'entity') { - $node_conditions->exists($subquery); - } - // Otherwise attach it to the node query itself. - else { - $query->exists($subquery); - } + $query->exists($subquery); } } - - if ($type == 'entity' && count($subquery->conditions())) { - // All the node access conditions are only for field values belonging to - // nodes. - $node_conditions->condition("$base_alias.entity_type", 'node'); - $or = db_or(); - $or->condition($node_conditions); - // If the field value belongs to a non-node entity type then this function - // does not do anything with it. - $or->condition("$base_alias.entity_type", 'node', '<>'); - // Add the compiled set of rules to the query. - $query->condition($or); - } - } /** diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.module b/core/modules/node/tests/modules/node_access_test/node_access_test.module index a503217..f5fc00f 100644 --- a/core/modules/node/tests/modules/node_access_test/node_access_test.module +++ b/core/modules/node/tests/modules/node_access_test/node_access_test.module @@ -7,8 +7,6 @@ * a special 'node test view' permission. */ -use Drupal\Core\Entity\EntityFieldQuery; - use Drupal\node\Node; /** @@ -152,12 +150,13 @@ function node_access_test_page() { function node_access_entity_test_page() { $output = ''; try { - $query = new EntityFieldQuery; - $result = $query->fieldCondition('body', 'value', 'A', 'STARTS_WITH')->execute(); - if (!empty($result['node'])) { - $output .= '

Yes, ' . count($result['node']) . ' nodes

'; + $result = entity_query('node') + ->condition('body.value', 'A', 'STARTS_WITH') + ->execute(); + if (!empty($result)) { + $output .= '

Yes, ' . count($result) . ' nodes

'; $output .= ''; diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldQueryTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldQueryTest.php index fa0f4b2..fdc1099 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldQueryTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldQueryTest.php @@ -25,7 +25,7 @@ class EntityFieldQueryTest extends WebTestBase { */ public static $modules = array('node', 'field_test', 'entity_query_access_test', 'node_access_test'); - public static function getInfo() { + public static function xgetInfo() { return array( 'name' => 'Entity query', 'description' => 'Test the EntityFieldQuery class.', diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryTest.php new file mode 100644 index 0000000..c6510fc --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryTest.php @@ -0,0 +1,433 @@ + 'Entity Query', + 'description' => 'Tests Entity Query functionality.', + 'group' => 'Entity API', + ); + } + + function setUp() { + parent::setUp(); + drupal_install_system(); + module_enable(self::$modules); + $this->rebuildContainer(); + $figures = drupal_strtolower($this->randomName()); + $greetings = drupal_strtolower($this->randomName()); + foreach (array($figures => 'shape', $greetings => 'text') as $field_name => $field_type) { + $field = array( + 'field_name' => $field_name, + 'type' => $field_type, + 'cardinality' => 2, + ); + $fields[] = field_create_field($field); + } + $bundles = array(); + for ($i = 0; $i < 2; $i++) { + // For the sake of tablesort, make sure the second bundle is higher than + // the first one. + do { + $bundle = $this->randomName(); + } while ($bundles && $bundles[0] >= $bundle); + field_test_create_bundle($bundle); + foreach ($fields as $field) { + $instance = array( + 'field_name' => $field['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => $bundle, + ); + field_create_instance($instance); + } + $bundles[] = $bundle; + } + $GLOBALS['debug'] = TRUE; + unset($GLOBALS['debug']); + // Each unit is a list of field name, langcode and a column-value array. + $units[] = array($figures, LANGUAGE_NOT_SPECIFIED, array( + 'color' => 'red', + 'shape' => 'triangle', + )); + $units[] = array($figures, LANGUAGE_NOT_SPECIFIED, array( + 'color' => 'blue', + 'shape' => 'circle', + )); + // To make it easier to test sorting, the greetings get formats according + // to their langcode. + $units[] = array($greetings, 'tr', array( + 'value' => 'merhaba', + 'format' => 'format-tr' + )); + $units[] = array($greetings, 'pl', array( + 'value' => 'siema', + 'format' => 'format-pl' + )); + // Make these languages available to the greetings field. + $field_langcodes = &drupal_static('field_available_languages'); + $field_langcodes['test_entity'][$greetings] = array('tr', 'pl'); + // Calculate the cartesian product of the unit array by looking at the + // bits of $i and add the unit at the bits that are 1. For example, + // decimal 13 is binary 1101 so unit 3,2 and 0 will be added to the + // entity. + for ($i = 1; $i <= 15; $i++) { + $entity = entity_create('test_entity', array( + 'ftid' => $i, + 'ftvid' => $i, + 'fttype' => $bundles[$i & 1], + )); + $entity->enforceIsNew(); + $entity->setNewRevision(); + foreach (array_reverse(str_split(decbin($i))) as $key => $bit) { + if ($bit) { + $unit = $units[$key]; + $entity->{$unit[0]}[$unit[1]][] = $unit[2]; + } + } + $entity->save(); + } + $this->figures = $figures; + $this->greetings = $greetings; + $this->factory = drupal_container()->get('entity.query'); + } + + function tearDown() { + $modules = self::$modules; + $modules[] = 'system'; + foreach (self::$modules as $module) { + module_load_install($module); + $function = $module . '_schema'; + if (function_exists($function)) { + foreach ($function() as $table => $schema) { + db_drop_table($table); + } + } + } + parent::tearDown(); + } + + /** + * Test basic functionality. + */ + function testEntityQuery() { + $greetings = $this->greetings; + $figures = $this->figures; + $this->queryResults = $this->factory->get('test_entity') + ->exists($greetings, 'tr') + ->condition("$figures.color", 'red') + ->sort('ftid') + ->execute(); + // As unit 0 was the red triangle and unit 2 was the turkish greeting, + // bit 0 and bit 2 needs to be set. + $this->assertResult(5, 7, 13, 15); + + $this->queryResults = $this->factory->get('test_entity', 'OR') + ->exists($greetings, 'tr') + ->condition("$figures.color", 'red') + ->sort('ftid') + ->execute(); + // Now bit 0 (1, 3, 5, 7, 9, 11, 13, 15) or bit 2 (4, 5, 6, 7, 12, 13, 14, + // 15) needs to be set. + $this->assertResult(1, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14, 15); + + $query = $this->factory->get('test_entity'); + $group = $query->orConditionGroup() + ->exists($greetings, 'tr') + ->condition("$figures.color", 'red'); + $this->queryResults = $query + ->condition($group) + ->condition("$greetings.value", 'sie', 'STARTS_WITH') + ->sort('ftvid') + ->execute(); + // Bit 3 and (bit 0 or 2) -- the above 8 part of the above. + $this->assertResult(9, 11, 12, 13, 14, 15); + + // No figure has both the colors blue and red at the same time. + $this->queryResults = $this->factory->get('test_entity') + ->condition("$figures.color", 'blue') + ->condition("$figures.color", 'red') + ->sort('ftid') + ->execute(); + $this->assertResult(); + + // But an entity might have a red and a blue figure both. + $query = $this->factory->get('test_entity'); + $group_blue = $query->andConditionGroup()->condition("$figures.color", 'blue'); + $group_red = $query->andConditionGroup()->condition("$figures.color", 'red'); + $this->queryResults = $query + ->condition($group_blue) + ->condition($group_red) + ->sort('ftvid') + ->execute(); + // Unit 0 and unit 1, so bits 0 1. + $this->assertResult(3, 7, 11, 15); + + $this->queryResults = $this->factory->get('test_entity') + ->exists("$figures.color") + ->notExists("$greetings.value") + ->sort('ftid') + ->execute(); + // Bit 0 or 1 is on but 2 and 3 are not. + $this->assertResult(1, 2, 3); + // Now update the 'merhaba' string to xsiemax which is not a meaningful + // word but allows us to test revisions and string operations. + $ids = $this->factory->get('test_entity') + ->condition("$greetings.value", 'merhaba') + ->execute(); + $GLOBALS['debug'] = TRUE; + $entities = entity_load_multiple('test_entity', $ids); + unset($GLOBALS['debug']); + foreach ($entities as $entity) { + $entity->setNewRevision(); + $entity->{$greetings}['tr'][0]['value'] = 'xsiemax'; + $entity->save(); + } + // When querying current revisions, this string is no longer found. + $this->queryResults = $this->factory->get('test_entity') + ->condition("$greetings.value", 'merhaba') + ->execute(); + $this->assertResult(); + $this->queryResults = $this->factory->get('test_entity') + ->condition("$greetings.value", 'merhaba') + ->age(FIELD_LOAD_REVISION) + ->sort('ftvid') + ->execute(); + // Bit 2 needs to be set. + // The keys must be 16-23 because the first batch stopped at 15 so the + // second started at 16 and eight entities were saved. + $assert = $this->assertRevisionResult(range(16, 23), array(4, 5, 6, 7, 12, 13, 14, 15)); + $results = $this->factory->get('test_entity') + ->condition("$greetings.value", 'siema', 'CONTAINS') + ->sort('ftid') + ->execute(); + // This is the same as the previous one because xsiemax replaced merhaba + // but also it contains the entities that siema originally but not + // merhaba. + $assert = array_slice($assert, 0, 4, TRUE) + array(8 => '8', 9 => '9', 10 => '10', 11 => '11') + array_slice($assert, 4, 4, TRUE); + $this->assertIdentical($results, $assert); + $results = $this->factory->get('test_entity') + ->condition("$greetings.value", 'siema', 'STARTS_WITH') + ->execute(); + // Now we only get the ones that originally were siema, entity id 8 and + // above. + $this->assertIdentical($results, array_slice($assert, 4, 8, TRUE)); + $results = $this->factory->get('test_entity') + ->condition("$greetings.value", 'a', 'ENDS_WITH') + ->execute(); + // It is very important that we do not get the ones which only have + // xsiemax despite originally they were merhaba, ie. ended with a. + $this->assertIdentical($results, array_slice($assert, 4, 8, TRUE)); + $results = $this->factory->get('test_entity') + ->condition("$greetings.value", 'a', 'ENDS_WITH') + ->age(FIELD_LOAD_REVISION) + ->sort('ftid') + ->execute(); + // Now we get everything. + $this->assertIdentical($results, $assert); + } + + /** + * Test sort(). + * + * Warning: this is complicated. + */ + function testSort() { + $greetings = $this->greetings; + $figures = $this->figures; + // Order up and down on a number. + $this->queryResults = $this->factory->get('test_entity') + ->sort('ftid') + ->execute(); + $this->assertResult(range(1, 15)); + $this->queryResults = $this->factory->get('test_entity') + ->sort('ftid', 'DESC') + ->execute(); + $this->assertResult(range(15, 1)); + $query = $this->factory->get('test_entity') + ->sort("$figures.color") + ->sort("$greetings.format") + ->sort('ftid'); + // As we do not have any conditions, here are the possible colors and + // language codes, already in order, with the first occurence of the + // entity id marked with *: + // 8 NULL pl * + // 12 NULL pl * + + // 4 NULL tr * + // 12 NULL tr + + // 2 blue NULL * + // 3 blue NULL * + + // 10 blue pl * + // 11 blue pl * + // 14 blue pl * + // 15 blue pl * + + // 6 blue tr * + // 7 blue tr * + // 14 blue tr + // 15 blue tr + + // 1 red NULL + // 3 red NULL + + // 9 red pl * + // 11 red pl + // 13 red pl * + // 15 red pl + + // 5 red tr * + // 7 red tr + // 13 red tr + // 15 red tr + $count_query = clone $query; + $this->assertEqual(15, $count_query->count()->execute()); + $this->queryResults = $query->execute(); + $this->assertResult(8, 12, 4, 2, 3, 10, 11, 14, 15, 6, 7, 1, 9, 13, 5); + + // Test the pager by setting element #1 to page 2 with a page size of 4. + // Results will be #8-12 from above. + $_GET['page'] = '0,2'; + $this->queryResults = $this->factory->get('test_entity') + ->sort("$figures.color") + ->sort("$greetings.format") + ->sort('ftid') + ->pager(4, 1) + ->execute(); + $this->assertResult(15, 6, 7, 1); + + // Now test the reversed order. + $query = $this->factory->get('test_entity') + ->sort("$figures.color", 'DESC') + ->sort("$greetings.format", 'DESC') + ->sort('ftid', 'DESC'); + $count_query = clone $query; + $this->assertEqual(15, $count_query->count()->execute()); + $this->queryResults = $query->execute(); + $this->assertResult(15, 13, 7, 5, 11, 9, 3, 1, 14, 6, 10, 2, 12, 4, 8); + } + + /** + * Test tablesort(). + */ + protected function testTableSort() { + // While ordering on bundles do not give us a definite order, we can still + // assert that all entities from one bundle are after the other as the + // order dictates. + $_GET['sort'] = 'asc'; + $_GET['order'] = 'Type'; + $header = array( + 'id' => array('data' => 'Id', 'specifier' => 'ftid'), + 'type' => array('data' => 'Type', 'specifier' => 'fttype'), + ); + + $this->queryResults = array_values($this->factory->get('test_entity') + ->tableSort($header) + ->execute()); + $this->assertBundleOrder('asc'); + $_GET['sort'] = 'desc'; + $header = array( + 'id' => array('data' => 'Id', 'specifier' => 'ftid'), + 'type' => array('data' => 'Type', 'specifier' => 'fttype'), + ); + $this->queryResults = array_values($this->factory->get('test_entity') + ->tableSort($header) + ->execute()); + $this->assertBundleOrder('desc'); + // Ordering on ID is definite, however. + $_GET['order'] = 'Id'; + $this->queryResults = $this->factory->get('test_entity') + ->tableSort($header) + ->execute(); + $this->assertResult(range(15, 1)); + } + + protected function assertResult() { + $assert = array(); + $expected = func_get_args(); + if ($expected && is_array($expected[0])) { + $expected = $expected[0]; + } + foreach ($expected as $binary) { + $assert[$binary] = strval($binary); + } + $this->assertIdentical($this->queryResults, $assert); + } + + protected function assertRevisionResult($keys, $expected) { + $assert = array(); + foreach ($expected as $key => $binary) { + $assert[$keys[$key]] = strval($binary); + } + $this->assertIdentical($this->queryResults, $assert); + return $assert; + } + + protected function assertBundleOrder($order) { + // This loop is for bundle1 entities. + for ($i = 1; $i <= 15; $i +=2) { + $ok = TRUE; + $index1 = array_search($i, $this->queryResults); + $this->assertNotIdentical($index1, FALSE, "$i found at $index1."); + // This loop is for bundle2 entities. + for ($j = 2; $j <= 15; $j += 2) { + if ($ok) { + if ($order == 'asc') { + $ok = $index1 > array_search($j, $this->queryResults); + } + else { + $ok = $index1 < array_search($j, $this->queryResults); + } + } + } + $this->assertTrue($ok, format_string("$i is after all entities in bundle2")); + } + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php index e0b25fb..733c80f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php @@ -7,10 +7,8 @@ namespace Drupal\system\Tests\Entity; -use Exception; use InvalidArgumentException; -use Drupal\Core\Entity\EntityFieldQuery; use Drupal\Core\Language\Language; use Drupal\simpletest\WebTestBase; @@ -271,35 +269,34 @@ function testMultilingualProperties() { // Test property conditions and orders with multiple languages in the same // query. - $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', 'entity_test'); - $query->entityCondition('langcode', $default_langcode); - $query->propertyCondition('user_id', $properties[$default_langcode]['user_id'], NULL, 'original'); - $query->propertyCondition('name', $properties[$default_langcode]['name'], NULL, 'original'); - $query->propertyLanguageCondition($default_langcode, NULL, 'original'); - $query->propertyCondition('name', $properties[$langcode]['name'], NULL, 'translation'); - $query->propertyLanguageCondition($langcode, NULL, 'translation'); - $query->propertyOrderBy('name', 'ASC', 'original'); - $result = $query->execute(); + $query = entity_query('entity_test'); + $group = $query->andConditionGroup() + ->condition('user_id', $properties[$default_langcode]['user_id'], '=', $default_langcode) + ->condition('name', $properties[$default_langcode]['name'], '=', $default_langcode); + $result = $query + ->condition($group) + ->condition('name', $properties[$langcode]['name'], '=', $langcode) + ->execute(); $this->assertEqual(count($result), 1, 'One entity loaded by name and uid using different language meta conditions.'); // Test mixed property and field conditions. - $entity = entity_load('entity_test', key($result['entity_test']), TRUE); + $entity = entity_load('entity_test', reset($result), TRUE); $field_value = $this->randomString(); $entity->getTranslation($langcode)->set($this->field_name, array(array('value' => $field_value))); $entity->save(); - $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', 'entity_test'); - $query->entityCondition('langcode', $default_langcode); - $query->propertyCondition('user_id', $properties[$default_langcode]['user_id'], NULL, 'original'); - $query->propertyCondition('name', $properties[$default_langcode]['name'], NULL, 'original'); - $query->propertyLanguageCondition($default_langcode, NULL, 'original'); - $query->propertyCondition('name', $properties[$langcode]['name'], NULL, 'translation'); - $query->propertyLanguageCondition($langcode, NULL, 'translation'); - $query->fieldCondition($this->field_name, 'value', $field_value, NULL, NULL, 'translation'); - $query->fieldLanguageCondition($this->field_name, $langcode, NULL, NULL, 'translation'); - $query->propertyOrderBy('name', 'ASC', 'original'); - $result = $query->execute(); + $query = entity_query('entity_test'); + $default_langcode_group = $query->andConditionGroup() + ->condition('user_id', $properties[$default_langcode]['user_id'], '=', $default_langcode) + ->condition('name', $properties[$default_langcode]['name'], '=', $default_langcode); + $langcode_group = $query->andConditionGroup() + ->condition('name', $properties[$langcode]['name'], '=', $langcode) + ->condition("$this->field_name.value", $field_value, '=', $langcode); + $result = $query + ->condition('langcode', $default_langcode) + ->condition($default_langcode_group) + ->condition($langcode_group) + ->execute(); $this->assertEqual(count($result), 1, 'One entity loaded by name, uid and field value using different language meta conditions.'); } + } diff --git a/core/modules/system/tests/modules/entity_query_access_test/entity_query_access_test.info b/core/modules/system/tests/modules/entity_query_access_test/entity_query_access_test.info deleted file mode 100644 index 369b204..0000000 --- a/core/modules/system/tests/modules/entity_query_access_test/entity_query_access_test.info +++ /dev/null @@ -1,7 +0,0 @@ -name = "Entity query access test" -description = "Support module for checking entity query results." -package = Testing -version = VERSION -core = 8.x -hidden = TRUE - diff --git a/core/modules/system/tests/modules/entity_query_access_test/entity_query_access_test.module b/core/modules/system/tests/modules/entity_query_access_test/entity_query_access_test.module deleted file mode 100644 index 2a4fd75..0000000 --- a/core/modules/system/tests/modules/entity_query_access_test/entity_query_access_test.module +++ /dev/null @@ -1,57 +0,0 @@ - "Retrieve a sample of entity query access data", - 'page callback' => 'entity_query_access_test_sample_query', - 'page arguments' => array(2), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, - ); - - return $items; -} - -/** - * Returns the results from an example EntityFieldQuery. - */ -function entity_query_access_test_sample_query($field_name) { - global $user; - - // Simulate user does not have access to view all nodes. - $access = &drupal_static('node_access_view_all_nodes'); - $access[$user->uid] = FALSE; - - $query = new EntityFieldQuery(); - $query - ->entityCondition('entity_type', 'test_entity_bundle_key') - ->fieldCondition($field_name, 'value', 0, '>') - ->entityOrderBy('entity_id', 'ASC'); - $results = array( - 'items' => array(), - 'title' => t('EntityFieldQuery results'), - ); - foreach ($query->execute() as $entity_type => $entity_ids) { - foreach ($entity_ids as $entity_id => $entity_stub) { - $results['items'][] = format_string('Found entity of type @entity_type with id @entity_id', array('@entity_type' => $entity_type, '@entity_id' => $entity_id)); - } - } - if (count($results['items']) > 0) { - $output = theme('item_list', $results); - } - else { - $output = 'No results found with EntityFieldQuery.'; - } - return $output; -} diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php index 67e4b9b..fbb735d 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php @@ -8,10 +8,9 @@ namespace Drupal\entity_test; use PDO; - +use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\DatabaseStorageControllerNG; -use Drupal\Core\Entity\EntityFieldQuery; /** * Defines the controller class for the test entity. @@ -24,7 +23,7 @@ class EntityTestStorageController extends DatabaseStorageControllerNG { /** * Overrides Drupal\Core\Entity\DatabaseStorageController::buildPropertyQuery(). */ - protected function buildPropertyQuery(EntityFieldQuery $entity_query, array $values) { + protected function buildPropertyQuery(QueryInterface $entity_query, array $values) { // @todo We should not be using a condition to specify whether conditions // apply to the default language or not. We need to move this to a // separate parameter during the following API refactoring. diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php index c77cad5..64b7a06 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php @@ -8,6 +8,7 @@ namespace Drupal\taxonomy; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Entity\DatabaseStorageController; /** @@ -57,9 +58,9 @@ protected function buildQuery($ids, $revision_id = FALSE) { /** * Overrides Drupal\Core\Entity\DatabaseStorageController::buildPropertyQuery(). */ - protected function buildPropertyQuery(\Drupal\Core\Entity\EntityFieldQuery $entity_query, array $values) { + protected function buildPropertyQuery(QueryInterface $entity_query, array $values) { if (isset($values['name'])) { - $entity_query->propertyCondition('name', $values['name'], 'LIKE'); + $entity_query->condition('name', $values['name'], 'LIKE'); unset($values['name']); } parent::buildPropertyQuery($entity_query, $values); diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/EfqTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/EfqTest.php index 7726dbf..d8a3ca5 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/EfqTest.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/EfqTest.php @@ -7,16 +7,16 @@ namespace Drupal\taxonomy\Tests; -use Drupal\Core\Entity\EntityFieldQuery; +use Drupal\Core\Entity\Query\QueryFactory; /** - * Tests the functionality of EntityFieldQuery for taxonomy entities. + * Tests the functionality of EntityQueryInterface for taxonomy entities. */ class EfqTest extends TaxonomyTestBase { public static function getInfo() { return array( - 'name' => 'Taxonomy EntityFieldQuery', - 'description' => 'Verifies operation of a taxonomy-based EntityFieldQuery.', + 'name' => 'Taxonomy entity query', + 'description' => 'Verifies operation of a taxonomy-based Entity Query.', 'group' => 'Taxonomy', ); } @@ -29,7 +29,7 @@ function setUp() { } /** - * Tests that a basic taxonomy EntityFieldQuery works. + * Tests that a basic taxonomy entity query works. */ function testTaxonomyEfq() { $terms = array(); @@ -37,16 +37,17 @@ function testTaxonomyEfq() { $term = $this->createTerm($this->vocabulary); $terms[$term->tid] = $term; } - $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', 'taxonomy_term'); - $result = $query->execute(); - $result = $result['taxonomy_term']; - asort($result); - $this->assertEqual(array_keys($terms), array_keys($result), 'Taxonomy terms were retrieved by EntityFieldQuery.'); - - $first_result = reset($result); - $term = _field_create_entity_from_ids($first_result); - $this->assertEqual($term->tid, $first_result->entity_id, 'Taxonomy term can be created based on the IDs'); + $result = entity_query('taxonomy_term')->execute(); + sort($result); + $this->assertEqual(array_keys($terms), $result, 'Taxonomy terms were retrieved by entity query.'); + $tid = reset($result); + $ids = (object) array( + 'entity_type' => 'taxonomy_term', + 'entity_id' => $tid, + 'bundle' => $this->vocabulary->machine_name, + ); + $term = _field_create_entity_from_ids($ids); + $this->assertEqual($term->tid, $tid, 'Taxonomy term can be created based on the IDs'); // Create a second vocabulary and five more terms. $vocabulary2 = $this->createVocabulary(); @@ -56,12 +57,18 @@ function testTaxonomyEfq() { $terms2[$term->tid] = $term; } - $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', 'taxonomy_term'); - $query->entityCondition('bundle', $vocabulary2->machine_name); - $result = $query->execute(); - $result = $result['taxonomy_term']; - asort($result); - $this->assertEqual(array_keys($terms2), array_keys($result), format_string('Taxonomy terms from the %name vocabulary were retrieved by EntityFieldQuery.', array('%name' => $vocabulary2->name))); + $result = entity_query('taxonomy_term') + ->condition('vid', $vocabulary2->vid) + ->execute(); + sort($result); + $this->assertEqual(array_keys($terms2), $result, format_string('Taxonomy terms from the %name vocabulary were retrieved by entity query.', array('%name' => $vocabulary2->name))); + $tid = reset($result); + $ids = (object) array( + 'entity_type' => 'taxonomy_term', + 'entity_id' => $tid, + 'bundle' => $vocabulary2->machine_name, + ); + $term = _field_create_entity_from_ids($ids); + $this->assertEqual($term->tid, $tid, 'Taxonomy term can be created based on the IDs'); } } diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index 874c78a..4d32458 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -934,7 +934,7 @@ function taxonomy_term_load_multiple_by_name($name, $vocabulary = NULL) { * database access if loaded again during the same page request. * * @see entity_load_multiple() - * @see Drupal\Core\Entity\EntityFieldQuery + * @see Drupal\Core\Entity\Query\EntityQueryInterface * * @param array $tids * (optional) An array of entity IDs. If omitted, all entities are loaded. @@ -1581,38 +1581,6 @@ function taxonomy_taxonomy_term_delete(Term $term) { */ /** - * Implements hook_entity_query_alter(). - * - * Converts EntityFieldQuery instances on taxonomy terms that have an entity - * condition on term bundles (vocabulary machine names). Since the vocabulary - * machine name is not present in the {taxonomy_term_data} table itself, we have - * to convert the bundle condition into a property condition of vocabulary IDs - * to match against {taxonomy_term_data}.vid. - */ -function taxonomy_entity_query_alter($query) { - $conditions = &$query->entityConditions; - - // Alter only taxonomy term queries with bundle conditions. - if (isset($conditions['entity_type']) && $conditions['entity_type']['value'] == 'taxonomy_term' && isset($conditions['bundle'])) { - // Convert vocabulary machine names to vocabulary IDs. - $vocabulary_data = taxonomy_vocabulary_get_names(); - $vids = array(); - if (is_array($conditions['bundle']['value'])) { - foreach ($conditions['bundle']['value'] as $vocabulary_machine_name) { - $vids[] = $vocabulary_data[$vocabulary_machine_name]->vid; - } - } - else { - $vocabulary_machine_name = $conditions['bundle']['value']; - $vids = $vocabulary_data[$vocabulary_machine_name]->vid; - } - - $query->propertyCondition('vid', $vids, $conditions['bundle']['operator']); - unset($conditions['bundle']); - } -} - -/** * Implements hook_library_info(). */ function taxonomy_library_info() { diff --git a/core/modules/user/user.module b/core/modules/user/user.module index c064427..423723c 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -290,7 +290,7 @@ function user_external_load($authname) { * @see user_load() * @see user_load_by_mail() * @see user_load_by_name() - * @see Drupal\Core\Entity\EntityFieldQuery + * @see \Drupal\Core\Entity\Query\QueryInterface */ function user_load_multiple(array $uids = NULL, $reset = FALSE) { return entity_load_multiple('user', $uids, $reset);