3 namespace Drupal\entity\QueryAccess;
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;
21 * Defines a class for altering views queries.
25 class ViewsQueryAlter implements ContainerInjectionInterface {
28 * The database connection.
30 * @var \Drupal\Core\Database\Connection
32 protected $connection;
35 * The entity field manager.
37 * @var \Drupal\Core\Entity\EntityFieldManagerInterface
39 protected $entityFieldManager;
42 * The entity type manager.
44 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
46 protected $entityTypeManager;
51 * @var \Drupal\Core\Render\RendererInterface
58 * @var \Symfony\Component\HttpFoundation\RequestStack
60 protected $requestStack;
63 * Constructs a new ViewsQueryAlter object.
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
73 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
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;
87 public static function create(ContainerInterface $container) {
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')
98 * Alters the given views query.
100 * @param \Drupal\views\Plugin\views\query\Sql $query
102 * @param \Drupal\views\ViewExecutable $view
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') {
111 $entity_type_id = $base_table['entity_type'];
112 $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
113 if (!$entity_type->hasHandlerClass('query_access')) {
116 $storage = $this->entityTypeManager->getStorage($entity_type_id);
117 if (!$storage instanceof SqlContentEntityStorage) {
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');
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);
137 $this->applyCacheability(CacheableMetadata::createFromObject($conditions));
141 * Maps an entity type's access conditions to views SQL conditions.
143 * @param \Drupal\entity\QueryAccess\ConditionGroup $conditions
144 * The access conditions.
145 * @param \Drupal\views\Plugin\views\query\Sql $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
154 * @return \Drupal\Core\Database\Query\ConditionInterface
155 * The SQL conditions.
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);
165 $field = $condition->getField();
166 $property_name = NULL;
167 if (strpos($field, '.') !== FALSE) {
168 list($field, $property_name) = explode('.', $field);
170 // Skip unknown fields.
171 if (!isset($field_storage_definitions[$field])) {
174 $field_storage_definition = $field_storage_definitions[$field];
175 if (!$property_name) {
176 $property_name = $field_storage_definition->getMainPropertyName();
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);
185 $dedicated_table = $table_mapping->getDedicatedDataTableName($field_storage_definition);
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);
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'];
200 'table' => $base_table['data_table'],
202 'left_table' => $base_table['alias'],
203 'left_field' => 'id',
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);
211 $alias = $base_table['alias'];
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');
224 if (!$case_sensitive && isset($operator_map[$operator])) {
225 $operator = $operator_map[$operator];
226 $value = $this->connection->escapeLike($value);
229 $sql_condition->condition("$alias.$column", $value, $operator);
233 return $sql_condition;
237 * Applies the cacheablity metadata to the current request.
239 * @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata
240 * The cacheability metadata.
242 protected function applyCacheability(CacheableMetadata $cacheable_metadata) {
243 $request = $this->requestStack->getCurrentRequest();
244 if ($request->isMethodCacheable() && $this->renderer->hasRenderContext()) {
246 $cacheable_metadata->applyTo($build);
247 $this->renderer->render($build);