Added Entity and Entity Reference Revisions which got dropped somewhere along the...
[yaffs-website] / web / modules / contrib / entity / src / QueryAccess / ViewsQueryAlter.php
1 <?php
2
3 namespace Drupal\entity\QueryAccess;
4
5 use Drupal\Core\Cache\CacheableMetadata;
6 use Drupal\Core\Database\Connection;
7 use Drupal\Core\Database\Query\Condition as SqlCondition;
8 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
9 use Drupal\Core\Entity\EntityFieldManagerInterface;
10 use Drupal\Core\Entity\EntityTypeManagerInterface;
11 use Drupal\Core\Entity\Sql\DefaultTableMapping;
12 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
13 use Drupal\Core\Render\RendererInterface;
14 use Drupal\views\Plugin\views\query\Sql;
15 use Drupal\views\ViewExecutable;
16 use Drupal\views\Views;
17 use Symfony\Component\DependencyInjection\ContainerInterface;
18 use Symfony\Component\HttpFoundation\RequestStack;
19
20 /**
21  * Defines a class for altering views queries.
22  *
23  * @internal
24  */
25 class ViewsQueryAlter implements ContainerInjectionInterface {
26
27   /**
28    * The database connection.
29    *
30    * @var \Drupal\Core\Database\Connection
31    */
32   protected $connection;
33
34   /**
35    * The entity field manager.
36    *
37    * @var \Drupal\Core\Entity\EntityFieldManagerInterface
38    */
39   protected $entityFieldManager;
40
41   /**
42    * The entity type manager.
43    *
44    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
45    */
46   protected $entityTypeManager;
47
48   /**
49    * The renderer.
50    *
51    * @var \Drupal\Core\Render\RendererInterface
52    */
53   protected $renderer;
54
55   /**
56    * The request stack.
57    *
58    * @var \Symfony\Component\HttpFoundation\RequestStack
59    */
60   protected $requestStack;
61
62   /**
63    * Constructs a new ViewsQueryAlter object.
64    *
65    * @param \Drupal\Core\Database\Connection $connection
66    *   The database connection.
67    * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
68    *   The entity field manager.
69    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
70    *   The entity type manager.
71    * @param \Drupal\Core\Render\RendererInterface $renderer
72    *   The renderer.
73    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
74    *   The request stack.
75    */
76   public function __construct(Connection $connection, EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, RequestStack $request_stack) {
77     $this->connection = $connection;
78     $this->entityFieldManager = $entity_field_manager;
79     $this->entityTypeManager = $entity_type_manager;
80     $this->renderer = $renderer;
81     $this->requestStack = $request_stack;
82   }
83
84   /**
85    * {@inheritdoc}
86    */
87   public static function create(ContainerInterface $container) {
88     return new static(
89       $container->get('database'),
90       $container->get('entity_field.manager'),
91       $container->get('entity_type.manager'),
92       $container->get('renderer'),
93       $container->get('request_stack')
94     );
95   }
96
97   /**
98    * Alters the given views query.
99    *
100    * @param \Drupal\views\Plugin\views\query\Sql $query
101    *   The views query.
102    * @param \Drupal\views\ViewExecutable $view
103    *   The view.
104    */
105   public function alter(Sql $query, ViewExecutable $view) {
106     $table_info = $query->getEntityTableInfo();
107     $base_table = reset($table_info);
108     if (empty($base_table['entity_type']) || $base_table['relationship_id'] != 'none') {
109       return;
110     }
111     $entity_type_id = $base_table['entity_type'];
112     $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
113     if (!$entity_type->hasHandlerClass('query_access')) {
114       return;
115     }
116     $storage = $this->entityTypeManager->getStorage($entity_type_id);
117     if (!$storage instanceof SqlContentEntityStorage) {
118       return;
119     }
120
121     /** @var \Drupal\entity\QueryAccess\QueryAccessHandlerInterface $query_access */
122     $query_access = $this->entityTypeManager->getHandler($entity_type_id, 'query_access');
123     $conditions = $query_access->getConditions('view');
124     if ($conditions->isAlwaysFalse()) {
125       $query->addWhereExpression(0, '1 = 0');
126     }
127     elseif (count($conditions)) {
128       // Store the data table, in case mapConditions() needs to join it in.
129       $base_table['data_table'] = $entity_type->getDataTable();
130       $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id);
131       /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
132       $table_mapping = $storage->getTableMapping();
133       $sql_conditions = $this->mapConditions($conditions, $query, $base_table, $field_storage_definitions, $table_mapping);
134       $query->addWhere(0, $sql_conditions);
135     }
136
137     $this->applyCacheability(CacheableMetadata::createFromObject($conditions));
138   }
139
140   /**
141    * Maps an entity type's access conditions to views SQL conditions.
142    *
143    * @param \Drupal\entity\QueryAccess\ConditionGroup $conditions
144    *   The access conditions.
145    * @param \Drupal\views\Plugin\views\query\Sql $query
146    *   The views query.
147    * @param array $base_table
148    *   The base table information.
149    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_storage_definitions
150    *   The field storage definitions.
151    * @param \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping
152    *   The table mapping.
153    *
154    * @return \Drupal\Core\Database\Query\ConditionInterface
155    *   The SQL conditions.
156    */
157   protected function mapConditions(ConditionGroup $conditions, Sql $query, array $base_table, array $field_storage_definitions, DefaultTableMapping $table_mapping) {
158     $sql_condition = new SqlCondition($conditions->getConjunction());
159     foreach ($conditions->getConditions() as $condition) {
160       if ($condition instanceof ConditionGroup) {
161         $nested_sql_conditions = $this->mapConditions($condition, $query, $base_table, $field_storage_definitions, $table_mapping);
162         $sql_condition->condition($nested_sql_conditions);
163       }
164       else {
165         $field = $condition->getField();
166         $property_name = NULL;
167         if (strpos($field, '.') !== FALSE) {
168           list($field, $property_name) = explode('.', $field);
169         }
170         // Skip unknown fields.
171         if (!isset($field_storage_definitions[$field])) {
172           continue;
173         }
174         $field_storage_definition = $field_storage_definitions[$field];
175         if (!$property_name) {
176           $property_name = $field_storage_definition->getMainPropertyName();
177         }
178
179         $column = $table_mapping->getFieldColumnName($field_storage_definition, $property_name);
180         if ($table_mapping->requiresDedicatedTableStorage($field_storage_definitions[$field])) {
181           if ($base_table['revision']) {
182             $dedicated_table = $table_mapping->getDedicatedRevisionTableName($field_storage_definition);
183           }
184           else {
185             $dedicated_table = $table_mapping->getDedicatedDataTableName($field_storage_definition);
186           }
187           // Views defaults to LEFT JOIN. For simplicity, we don't try to
188           // use an INNER JOIN when it's safe to do so (AND conjunctions).
189           $alias = $query->ensureTable($dedicated_table);
190         }
191         elseif ($base_table['revision'] && !$field_storage_definition->isRevisionable()) {
192           // Workaround for #2652652, which causes $query->ensureTable()
193           // to not work in this case, due to a missing relationship.
194           if ($data_table = $query->getTableInfo($base_table['data_table'])) {
195             $alias = $data_table['alias'];
196           }
197           else {
198             $configuration = [
199               'type' => 'INNER',
200               'table' => $base_table['data_table'],
201               'field' => 'id',
202               'left_table' => $base_table['alias'],
203               'left_field' => 'id',
204             ];
205             /** @var \Drupal\Views\Plugin\views\join\JoinPluginBase $join */
206             $join = Views::pluginManager('join')->createInstance('standard', $configuration);
207             $alias = $query->addRelationship($base_table['data_table'], $join, $data_table);
208           }
209         }
210         else {
211           $alias = $base_table['alias'];
212         }
213
214         $value = $condition->getValue();
215         $operator = $condition->getOperator();
216         // Using LIKE/NOT LIKE ensures a case insensitive comparison.
217         // @see \Drupal\Core\Entity\Query\Sql\Condition::translateCondition().
218         $property_definitions = $field_storage_definition->getPropertyDefinitions();
219         $case_sensitive = $property_definitions[$property_name]->getSetting('case_sensitive');
220         $operator_map = [
221           '=' => 'LIKE',
222           '<>' => 'NOT LIKE',
223         ];
224         if (!$case_sensitive && isset($operator_map[$operator])) {
225           $operator = $operator_map[$operator];
226           $value = $this->connection->escapeLike($value);
227         }
228
229         $sql_condition->condition("$alias.$column", $value, $operator);
230       }
231     }
232
233     return $sql_condition;
234   }
235
236   /**
237    * Applies the cacheablity metadata to the current request.
238    *
239    * @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata
240    *   The cacheability metadata.
241    */
242   protected function applyCacheability(CacheableMetadata $cacheable_metadata) {
243     $request = $this->requestStack->getCurrentRequest();
244     if ($request->isMethodCacheable() && $this->renderer->hasRenderContext()) {
245       $build = [];
246       $cacheable_metadata->applyTo($build);
247       $this->renderer->render($build);
248     }
249   }
250
251 }