3 namespace Drupal\entity\QueryAccess;
5 use Drupal\Core\Cache\CacheableMetadata;
6 use Drupal\Core\Database\Query\SelectInterface;
7 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
8 use Drupal\Core\Entity\EntityFieldManagerInterface;
9 use Drupal\Core\Entity\EntityTypeInterface;
10 use Drupal\Core\Entity\EntityTypeManagerInterface;
11 use Drupal\Core\Entity\Query\Sql\Tables;
12 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
13 use Drupal\Core\Render\RendererInterface;
14 use Symfony\Component\DependencyInjection\ContainerInterface;
15 use Symfony\Component\HttpFoundation\RequestStack;
18 * Defines a class for altering entity queries.
20 * EntityQuery doesn't have an alter hook, forcing this class to operate
21 * on the underlying SQL query, duplicating the EntityQuery condition logic.
25 class EntityQueryAlter implements ContainerInjectionInterface {
28 * The entity field manager.
30 * @var \Drupal\Core\Entity\EntityFieldManagerInterface
32 protected $entityFieldManager;
35 * The entity type manager.
37 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
39 protected $entityTypeManager;
44 * @var \Drupal\Core\Render\RendererInterface
51 * @var \Symfony\Component\HttpFoundation\RequestStack
53 protected $requestStack;
56 * Constructs a new EntityQueryAlter object.
58 * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
59 * The entity field manager.
60 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
61 * The entity type manager.
62 * @param \Drupal\Core\Render\RendererInterface $renderer
64 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
67 public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, RequestStack $request_stack) {
68 $this->entityFieldManager = $entity_field_manager;
69 $this->entityTypeManager = $entity_type_manager;
70 $this->renderer = $renderer;
71 $this->requestStack = $request_stack;
77 public static function create(ContainerInterface $container) {
79 $container->get('entity_field.manager'),
80 $container->get('entity_type.manager'),
81 $container->get('renderer'),
82 $container->get('request_stack')
87 * Alters the select query for the given entity type.
89 * @param \Drupal\Core\Database\Query\SelectInterface $query
91 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
94 public function alter(SelectInterface $query, EntityTypeInterface $entity_type) {
95 if (!$entity_type->hasHandlerClass('query_access')) {
98 $entity_type_id = $entity_type->id();
99 $storage = $this->entityTypeManager->getStorage($entity_type_id);
100 if (!$storage instanceof SqlContentEntityStorage) {
104 /** @var \Drupal\entity\QueryAccess\QueryAccessHandlerInterface $query_access */
105 $query_access = $this->entityTypeManager->getHandler($entity_type_id, 'query_access');
106 $conditions = $query_access->getConditions('view');
107 if ($conditions->isAlwaysFalse()) {
108 $query->where('1 = 0');
110 elseif (count($conditions)) {
111 $sql_conditions = $this->mapConditions($conditions, $query);
112 $query->condition($sql_conditions);
115 $this->applyCacheability(CacheableMetadata::createFromObject($conditions));
119 * Maps an entity type's access conditions to SQL conditions.
121 * @param \Drupal\entity\QueryAccess\ConditionGroup $conditions
122 * The access conditions.
123 * @param \Drupal\Core\Database\Query\SelectInterface $query
125 * @param bool $nested_inside_or
126 * Whether the access conditions are nested inside an OR condition.
128 * @return \Drupal\Core\Database\Query\ConditionInterface
129 * The SQL conditions.
131 protected function mapConditions(ConditionGroup $conditions, SelectInterface $query, $nested_inside_or = FALSE) {
132 $sql_condition = $query->conditionGroupFactory($conditions->getConjunction());
133 $tables = new Tables($query);
134 $nested_inside_or = $nested_inside_or || $conditions->getConjunction() == 'OR';
135 foreach ($conditions->getConditions() as $condition) {
136 if ($condition instanceof ConditionGroup) {
137 $nested_sql_conditions = $this->mapConditions($condition, $query, $nested_inside_or);
138 $sql_condition->condition($nested_sql_conditions);
141 // Access conditions don't specify a langcode.
143 $type = $nested_inside_or || $condition->getOperator() === 'IS NULL' ? 'LEFT' : 'INNER';
144 $sql_field = $tables->addField($condition->getField(), $type, $langcode);
145 $value = $condition->getValue();
146 $operator = $condition->getOperator();
147 // Using LIKE/NOT LIKE ensures a case insensitive comparison.
148 // @see \Drupal\Core\Entity\Query\Sql\Condition::translateCondition().
149 $case_sensitive = $tables->isFieldCaseSensitive($condition->getField());
154 if (!$case_sensitive && isset($operator_map[$operator])) {
155 $operator = $operator_map[$operator];
156 $value = $query->escapeLike($value);
159 $sql_condition->condition($sql_field, $value, $operator);
163 return $sql_condition;
167 * Applies the cacheablity metadata to the current request.
169 * @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata
170 * The cacheability metadata.
172 protected function applyCacheability(CacheableMetadata $cacheable_metadata) {
173 $request = $this->requestStack->getCurrentRequest();
174 if ($request->isMethodCacheable() && $this->renderer->hasRenderContext()) {
176 $cacheable_metadata->applyTo($build);
177 $this->renderer->render($build);