You are here

class ViewsQueryAlter in Entity API 8

Defines a class for altering views queries.

@internal

Hierarchy

Expanded class hierarchy of ViewsQueryAlter

1 file declares its use of ViewsQueryAlter
entity.module in ./entity.module
Provides expanded entity APIs.

File

src/QueryAccess/ViewsQueryAlter.php, line 25

Namespace

Drupal\entity\QueryAccess
View source
class ViewsQueryAlter implements ContainerInjectionInterface {

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $connection;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The renderer.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * Constructs a new ViewsQueryAlter object.
   *
   * @param \Drupal\Core\Database\Connection $connection
   *   The database connection.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The entity field manager.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   */
  public function __construct(Connection $connection, EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, RequestStack $request_stack) {
    $this->connection = $connection;
    $this->entityFieldManager = $entity_field_manager;
    $this->entityTypeManager = $entity_type_manager;
    $this->renderer = $renderer;
    $this->requestStack = $request_stack;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('database'), $container
      ->get('entity_field.manager'), $container
      ->get('entity_type.manager'), $container
      ->get('renderer'), $container
      ->get('request_stack'));
  }

  /**
   * Alters the given views query.
   *
   * @param \Drupal\views\Plugin\views\query\Sql $query
   *   The views query.
   * @param \Drupal\views\ViewExecutable $view
   *   The view.
   */
  public function alter(Sql $query, ViewExecutable $view) {
    $table_info = $query
      ->getEntityTableInfo();
    $base_table = reset($table_info);
    if (empty($base_table['entity_type']) || $base_table['relationship_id'] != 'none') {
      return;
    }
    $entity_type_id = $base_table['entity_type'];
    $entity_type = $this->entityTypeManager
      ->getDefinition($entity_type_id);
    if (!$entity_type
      ->hasHandlerClass('query_access')) {
      return;
    }
    $storage = $this->entityTypeManager
      ->getStorage($entity_type_id);
    if (!$storage instanceof SqlContentEntityStorage) {
      return;
    }

    /** @var \Drupal\entity\QueryAccess\QueryAccessHandlerInterface $query_access */
    $query_access = $this->entityTypeManager
      ->getHandler($entity_type_id, 'query_access');
    $conditions = $query_access
      ->getConditions('view');
    if ($conditions
      ->isAlwaysFalse()) {
      $query
        ->addWhereExpression(0, '1 = 0');
    }
    elseif (count($conditions)) {

      // Store the data table, in case mapConditions() needs to join it in.
      $base_table['data_table'] = $entity_type
        ->getDataTable();
      $field_storage_definitions = $this->entityFieldManager
        ->getFieldStorageDefinitions($entity_type_id);

      /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
      $table_mapping = $storage
        ->getTableMapping();
      $sql_conditions = $this
        ->mapConditions($conditions, $query, $base_table, $field_storage_definitions, $table_mapping);
      $query
        ->addWhere(0, $sql_conditions);
    }
    $this
      ->applyCacheability(CacheableMetadata::createFromObject($conditions));
  }

  /**
   * Maps an entity type's access conditions to views SQL conditions.
   *
   * @param \Drupal\entity\QueryAccess\ConditionGroup $conditions
   *   The access conditions.
   * @param \Drupal\views\Plugin\views\query\Sql $query
   *   The views query.
   * @param array $base_table
   *   The base table information.
   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_storage_definitions
   *   The field storage definitions.
   * @param \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping
   *   The table mapping.
   *
   * @return \Drupal\Core\Database\Query\ConditionInterface
   *   The SQL conditions.
   */
  protected function mapConditions(ConditionGroup $conditions, Sql $query, array $base_table, array $field_storage_definitions, DefaultTableMapping $table_mapping) {
    $sql_condition = new SqlCondition($conditions
      ->getConjunction());
    foreach ($conditions
      ->getConditions() as $condition) {
      if ($condition instanceof ConditionGroup) {
        $nested_sql_conditions = $this
          ->mapConditions($condition, $query, $base_table, $field_storage_definitions, $table_mapping);
        $sql_condition
          ->condition($nested_sql_conditions);
      }
      else {
        $field = $condition
          ->getField();
        $property_name = NULL;
        if (strpos($field, '.') !== FALSE) {
          list($field, $property_name) = explode('.', $field);
        }

        // Skip unknown fields.
        if (!isset($field_storage_definitions[$field])) {
          continue;
        }
        $field_storage_definition = $field_storage_definitions[$field];
        if (!$property_name) {
          $property_name = $field_storage_definition
            ->getMainPropertyName();
        }
        $column = $table_mapping
          ->getFieldColumnName($field_storage_definition, $property_name);
        if ($table_mapping
          ->requiresDedicatedTableStorage($field_storage_definitions[$field])) {
          if ($base_table['revision']) {
            $dedicated_table = $table_mapping
              ->getDedicatedRevisionTableName($field_storage_definition);
          }
          else {
            $dedicated_table = $table_mapping
              ->getDedicatedDataTableName($field_storage_definition);
          }

          // Views defaults to LEFT JOIN. For simplicity, we don't try to
          // use an INNER JOIN when it's safe to do so (AND conjunctions).
          $alias = $query
            ->ensureTable($dedicated_table);
        }
        elseif ($base_table['revision'] && !$field_storage_definition
          ->isRevisionable()) {

          // Workaround for #2652652, which causes $query->ensureTable()
          // to not work in this case, due to a missing relationship.
          if ($data_table = $query
            ->getTableInfo($base_table['data_table'])) {
            $alias = $data_table['alias'];
          }
          else {
            $configuration = [
              'type' => 'INNER',
              'table' => $base_table['data_table'],
              'field' => 'id',
              'left_table' => $base_table['alias'],
              'left_field' => 'id',
            ];

            /** @var \Drupal\Views\Plugin\views\join\JoinPluginBase $join */
            $join = Views::pluginManager('join')
              ->createInstance('standard', $configuration);
            $alias = $query
              ->addRelationship($base_table['data_table'], $join, $data_table);
          }
        }
        else {
          $alias = $base_table['alias'];
        }
        $value = $condition
          ->getValue();
        $operator = $condition
          ->getOperator();

        // Using LIKE/NOT LIKE ensures a case insensitive comparison.
        // @see \Drupal\Core\Entity\Query\Sql\Condition::translateCondition().
        $property_definitions = $field_storage_definition
          ->getPropertyDefinitions();
        $case_sensitive = $property_definitions[$property_name]
          ->getSetting('case_sensitive');
        $operator_map = [
          '=' => 'LIKE',
          '<>' => 'NOT LIKE',
        ];
        if ($case_sensitive === FALSE && isset($operator_map[$operator])) {
          $operator = $operator_map[$operator];
          $value = $this->connection
            ->escapeLike($value);
        }
        $sql_condition
          ->condition("{$alias}.{$column}", $value, $operator);
      }
    }
    return $sql_condition;
  }

  /**
   * Applies the cacheablity metadata to the current request.
   *
   * @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata
   *   The cacheability metadata.
   */
  protected function applyCacheability(CacheableMetadata $cacheable_metadata) {
    $request = $this->requestStack
      ->getCurrentRequest();
    if ($request
      ->isMethodCacheable() && $this->renderer
      ->hasRenderContext()) {
      $build = [];
      $cacheable_metadata
        ->applyTo($build);
      $this->renderer
        ->render($build);
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ViewsQueryAlter::$connection protected property The database connection.
ViewsQueryAlter::$entityFieldManager protected property The entity field manager.
ViewsQueryAlter::$entityTypeManager protected property The entity type manager.
ViewsQueryAlter::$renderer protected property The renderer.
ViewsQueryAlter::$requestStack protected property The request stack.
ViewsQueryAlter::alter public function Alters the given views query.
ViewsQueryAlter::applyCacheability protected function Applies the cacheablity metadata to the current request.
ViewsQueryAlter::create public static function Instantiates a new instance of this class. Overrides ContainerInjectionInterface::create
ViewsQueryAlter::mapConditions protected function Maps an entity type's access conditions to views SQL conditions.
ViewsQueryAlter::__construct public function Constructs a new ViewsQueryAlter object.