--- /dev/null
+<?php
+
+namespace Drupal\entity\QueryAccess;
+
+use Drupal\Core\Entity\EntityHandlerInterface;
+use Drupal\Core\Entity\EntityPublishedInterface;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\user\EntityOwnerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * Provides common logic for query access handlers.
+ *
+ * @see \Drupal\entity\QueryAccess\QueryAccessHandler
+ * @see \Drupal\entity\QueryAccess\UncacheableQueryAccessHandler
+ */
+abstract class QueryAccessHandlerBase implements EntityHandlerInterface, QueryAccessHandlerInterface {
+
+ /**
+ * The entity type.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeInterface
+ */
+ protected $entityType;
+
+ /**
+ * The entity type bundle info.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+ */
+ protected $bundleInfo;
+
+ /**
+ * The event dispatcher.
+ *
+ * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+ */
+ protected $eventDispatcher;
+
+ /**
+ * The current user.
+ *
+ * @var \Drupal\Core\Session\AccountInterface
+ */
+ protected $currentUser;
+
+ /**
+ * Constructs a new QueryAccessHandlerBase object.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The entity type.
+ * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
+ * The entity type bundle info.
+ * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
+ * The event dispatcher.
+ * @param \Drupal\Core\Session\AccountInterface $current_user
+ * The current user.
+ */
+ public function __construct(EntityTypeInterface $entity_type, EntityTypeBundleInfoInterface $bundle_info, EventDispatcherInterface $event_dispatcher, AccountInterface $current_user) {
+ $this->entityType = $entity_type;
+ $this->bundleInfo = $bundle_info;
+ $this->eventDispatcher = $event_dispatcher;
+ $this->currentUser = $current_user;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+ return new static(
+ $entity_type,
+ $container->get('entity_type.bundle.info'),
+ $container->get('event_dispatcher'),
+ $container->get('current_user')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConditions($operation, AccountInterface $account = NULL) {
+ $account = $account ?: $this->currentUser;
+ $entity_type_id = $this->entityType->id();
+ $conditions = $this->buildConditions($operation, $account);
+
+ // Allow other modules to modify the conditions before they are used.
+ $event = new QueryAccessEvent($conditions, $operation, $account);
+ $this->eventDispatcher->dispatch("entity.query_access.{$entity_type_id}", $event);
+
+ return $conditions;
+ }
+
+ /**
+ * Builds the conditions for the given operation and user.
+ *
+ * @param string $operation
+ * The access operation. Usually one of "view", "update" or "delete".
+ * @param \Drupal\Core\Session\AccountInterface $account
+ * The user for which to restrict access.
+ *
+ * @return \Drupal\entity\QueryAccess\ConditionGroup
+ * The conditions.
+ */
+ public function buildConditions($operation, AccountInterface $account) {
+ $entity_type_id = $this->entityType->id();
+ $has_owner = $this->entityType->entityClassImplements(EntityOwnerInterface::class);
+
+ if ($account->hasPermission("administer {$entity_type_id}")) {
+ // The user has full access to all operations, no conditions needed.
+ $conditions = new ConditionGroup('OR');
+ $conditions->addCacheContexts(['user.permissions']);
+ return $conditions;
+ }
+
+ if ($has_owner) {
+ $entity_conditions = $this->buildEntityOwnerConditions($operation, $account);
+ }
+ else {
+ $entity_conditions = $this->buildEntityConditions($operation, $account);
+ }
+
+ $conditions = NULL;
+ if ($operation == 'view' && $this->entityType->entityClassImplements(EntityPublishedInterface::class)) {
+ $uid_key = $this->entityType->getKey('uid');
+ $published_key = $this->entityType->getKey('published');
+ $published_conditions = NULL;
+ $unpublished_conditions = NULL;
+
+ if ($entity_conditions) {
+ // Restrict the existing conditions to published entities only.
+ $published_conditions = new ConditionGroup('AND');
+ $published_conditions->addCacheContexts(['user.permissions']);
+ $published_conditions->addCondition($entity_conditions);
+ $published_conditions->addCondition($published_key, '1');
+ }
+ if ($has_owner && $account->hasPermission("view own unpublished $entity_type_id")) {
+ $unpublished_conditions = new ConditionGroup('AND');
+ $unpublished_conditions->addCacheContexts(['user']);
+ $unpublished_conditions->addCondition($uid_key, $account->id());
+ $unpublished_conditions->addCondition($published_key, '0');
+ }
+
+ if ($published_conditions && $unpublished_conditions) {
+ $conditions = new ConditionGroup('OR');
+ $conditions->addCondition($published_conditions);
+ $conditions->addCondition($unpublished_conditions);
+ }
+ elseif ($published_conditions) {
+ $conditions = $published_conditions;
+ }
+ elseif ($unpublished_conditions) {
+ $conditions = $unpublished_conditions;
+ }
+ }
+ else {
+ $conditions = $entity_conditions;
+ }
+
+ if (!$conditions) {
+ // The user doesn't have access to any entities.
+ // Falsify the query to ensure no results are returned.
+ $conditions = new ConditionGroup('OR');
+ $conditions->addCacheContexts(['user.permissions']);
+ $conditions->alwaysFalse();
+ }
+
+ return $conditions;
+ }
+
+ /**
+ * Builds the conditions for entities that have an owner.
+ *
+ * @param string $operation
+ * The access operation. Usually one of "view", "update" or "delete".
+ * @param \Drupal\Core\Session\AccountInterface $account
+ * The user for which to restrict access.
+ *
+ * @return \Drupal\entity\QueryAccess\ConditionGroup|null
+ * The conditions, or NULL if the user doesn't have access to any entity.
+ */
+ protected function buildEntityOwnerConditions($operation, AccountInterface $account) {
+ $entity_type_id = $this->entityType->id();
+ $uid_key = $this->entityType->getKey('uid');
+ $bundle_key = $this->entityType->getKey('bundle');
+
+ $conditions = new ConditionGroup('OR');
+ $conditions->addCacheContexts(['user.permissions']);
+ // Any $entity_type permission.
+ if ($account->hasPermission("$operation any $entity_type_id")) {
+ // The user has full access, no conditions needed.
+ return $conditions;
+ }
+
+ // Own $entity_type permission.
+ if ($account->hasPermission("$operation own $entity_type_id")) {
+ $conditions->addCacheContexts(['user']);
+ $conditions->addCondition($uid_key, $account->id());
+ }
+
+ $bundles = array_keys($this->bundleInfo->getBundleInfo($entity_type_id));
+ $bundles_with_any_permission = [];
+ $bundles_with_own_permission = [];
+ foreach ($bundles as $bundle) {
+ if ($account->hasPermission("$operation any $bundle $entity_type_id")) {
+ $bundles_with_any_permission[] = $bundle;
+ }
+ if ($account->hasPermission("$operation own $bundle $entity_type_id")) {
+ $bundles_with_own_permission[] = $bundle;
+ }
+ }
+ // Any $bundle permission.
+ if ($bundles_with_any_permission) {
+ $conditions->addCondition($bundle_key, $bundles_with_any_permission);
+ }
+ // Own $bundle permission.
+ if ($bundles_with_own_permission) {
+ $conditions->addCacheContexts(['user']);
+ $conditions->addCondition((new ConditionGroup('AND'))
+ ->addCondition($uid_key, $account->id())
+ ->addCondition($bundle_key, $bundles_with_own_permission)
+ );
+ }
+
+ return $conditions->count() ? $conditions : NULL;
+ }
+
+ /**
+ * Builds the conditions for entities that do not have an owner.
+ *
+ * @param string $operation
+ * The access operation. Usually one of "view", "update" or "delete".
+ * @param \Drupal\Core\Session\AccountInterface $account
+ * The user for which to restrict access.
+ *
+ * @return \Drupal\entity\QueryAccess\ConditionGroup|null
+ * The conditions, or NULL if the user doesn't have access to any entity.
+ */
+ protected function buildEntityConditions($operation, AccountInterface $account) {
+ $entity_type_id = $this->entityType->id();
+ $bundle_key = $this->entityType->getKey('bundle');
+
+ $conditions = new ConditionGroup('OR');
+ $conditions->addCacheContexts(['user.permissions']);
+ // The $entity_type permission.
+ if ($account->hasPermission("$operation $entity_type_id")) {
+ // The user has full access, no conditions needed.
+ return $conditions;
+ }
+
+ $bundles = array_keys($this->bundleInfo->getBundleInfo($entity_type_id));
+ $bundles_with_any_permission = [];
+ foreach ($bundles as $bundle) {
+ if ($account->hasPermission("$operation $bundle $entity_type_id")) {
+ $bundles_with_any_permission[] = $bundle;
+ }
+ }
+ // The $bundle permission.
+ if ($bundles_with_any_permission) {
+ $conditions->addCondition($bundle_key, $bundles_with_any_permission);
+ }
+
+ return $conditions->count() ? $conditions : NULL;
+ }
+
+}