Added Entity and Entity Reference Revisions which got dropped somewhere along the...
[yaffs-website] / web / modules / contrib / entity / src / QueryAccess / EntityQueryAlter.php
1 <?php
2
3 namespace Drupal\entity\QueryAccess;
4
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;
16
17 /**
18  * Defines a class for altering entity queries.
19  *
20  * EntityQuery doesn't have an alter hook, forcing this class to operate
21  * on the underlying SQL query, duplicating the EntityQuery condition logic.
22  *
23  * @internal
24  */
25 class EntityQueryAlter implements ContainerInjectionInterface {
26
27   /**
28    * The entity field manager.
29    *
30    * @var \Drupal\Core\Entity\EntityFieldManagerInterface
31    */
32   protected $entityFieldManager;
33
34   /**
35    * The entity type manager.
36    *
37    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
38    */
39   protected $entityTypeManager;
40
41   /**
42    * The renderer.
43    *
44    * @var \Drupal\Core\Render\RendererInterface
45    */
46   protected $renderer;
47
48   /**
49    * The request stack.
50    *
51    * @var \Symfony\Component\HttpFoundation\RequestStack
52    */
53   protected $requestStack;
54
55   /**
56    * Constructs a new EntityQueryAlter object.
57    *
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
63    *   The renderer.
64    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
65    *   The request stack.
66    */
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;
72   }
73
74   /**
75    * {@inheritdoc}
76    */
77   public static function create(ContainerInterface $container) {
78     return new static(
79       $container->get('entity_field.manager'),
80       $container->get('entity_type.manager'),
81       $container->get('renderer'),
82       $container->get('request_stack')
83     );
84   }
85
86   /**
87    * Alters the select query for the given entity type.
88    *
89    * @param \Drupal\Core\Database\Query\SelectInterface $query
90    *   The select query.
91    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
92    *   The entity type.
93    */
94   public function alter(SelectInterface $query, EntityTypeInterface $entity_type) {
95     if (!$entity_type->hasHandlerClass('query_access')) {
96       return;
97     }
98     $entity_type_id = $entity_type->id();
99     $storage = $this->entityTypeManager->getStorage($entity_type_id);
100     if (!$storage instanceof SqlContentEntityStorage) {
101       return;
102     }
103
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');
109     }
110     elseif (count($conditions)) {
111       $sql_conditions = $this->mapConditions($conditions, $query);
112       $query->condition($sql_conditions);
113     }
114
115     $this->applyCacheability(CacheableMetadata::createFromObject($conditions));
116   }
117
118   /**
119    * Maps an entity type's access conditions to SQL conditions.
120    *
121    * @param \Drupal\entity\QueryAccess\ConditionGroup $conditions
122    *   The access conditions.
123    * @param \Drupal\Core\Database\Query\SelectInterface $query
124    *   The SQL query.
125    * @param bool $nested_inside_or
126    *   Whether the access conditions are nested inside an OR condition.
127    *
128    * @return \Drupal\Core\Database\Query\ConditionInterface
129    *   The SQL conditions.
130    */
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);
139       }
140       else {
141         // Access conditions don't specify a langcode.
142         $langcode = NULL;
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());
150         $operator_map = [
151           '=' => 'LIKE',
152           '<>' => 'NOT LIKE',
153         ];
154         if (!$case_sensitive && isset($operator_map[$operator])) {
155           $operator = $operator_map[$operator];
156           $value = $query->escapeLike($value);
157         }
158
159         $sql_condition->condition($sql_field, $value, $operator);
160       }
161     }
162
163     return $sql_condition;
164   }
165
166   /**
167    * Applies the cacheablity metadata to the current request.
168    *
169    * @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata
170    *   The cacheability metadata.
171    */
172   protected function applyCacheability(CacheableMetadata $cacheable_metadata) {
173     $request = $this->requestStack->getCurrentRequest();
174     if ($request->isMethodCacheable() && $this->renderer->hasRenderContext()) {
175       $build = [];
176       $cacheable_metadata->applyTo($build);
177       $this->renderer->render($build);
178     }
179   }
180
181 }