3 namespace Drupal\entity\QueryAccess;
5 use Drupal\Core\Entity\EntityHandlerInterface;
6 use Drupal\Core\Entity\EntityPublishedInterface;
7 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
8 use Drupal\Core\Entity\EntityTypeInterface;
9 use Drupal\Core\Session\AccountInterface;
10 use Drupal\user\EntityOwnerInterface;
11 use Symfony\Component\DependencyInjection\ContainerInterface;
12 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
15 * Provides common logic for query access handlers.
17 * @see \Drupal\entity\QueryAccess\QueryAccessHandler
18 * @see \Drupal\entity\QueryAccess\UncacheableQueryAccessHandler
20 abstract class QueryAccessHandlerBase implements EntityHandlerInterface, QueryAccessHandlerInterface {
25 * @var \Drupal\Core\Entity\EntityTypeInterface
27 protected $entityType;
30 * The entity type bundle info.
32 * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
34 protected $bundleInfo;
37 * The event dispatcher.
39 * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
41 protected $eventDispatcher;
46 * @var \Drupal\Core\Session\AccountInterface
48 protected $currentUser;
51 * Constructs a new QueryAccessHandlerBase object.
53 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
55 * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
56 * The entity type bundle info.
57 * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
58 * The event dispatcher.
59 * @param \Drupal\Core\Session\AccountInterface $current_user
62 public function __construct(EntityTypeInterface $entity_type, EntityTypeBundleInfoInterface $bundle_info, EventDispatcherInterface $event_dispatcher, AccountInterface $current_user) {
63 $this->entityType = $entity_type;
64 $this->bundleInfo = $bundle_info;
65 $this->eventDispatcher = $event_dispatcher;
66 $this->currentUser = $current_user;
72 public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
75 $container->get('entity_type.bundle.info'),
76 $container->get('event_dispatcher'),
77 $container->get('current_user')
84 public function getConditions($operation, AccountInterface $account = NULL) {
85 $account = $account ?: $this->currentUser;
86 $entity_type_id = $this->entityType->id();
87 $conditions = $this->buildConditions($operation, $account);
89 // Allow other modules to modify the conditions before they are used.
90 $event = new QueryAccessEvent($conditions, $operation, $account);
91 $this->eventDispatcher->dispatch("entity.query_access.{$entity_type_id}", $event);
97 * Builds the conditions for the given operation and user.
99 * @param string $operation
100 * The access operation. Usually one of "view", "update" or "delete".
101 * @param \Drupal\Core\Session\AccountInterface $account
102 * The user for which to restrict access.
104 * @return \Drupal\entity\QueryAccess\ConditionGroup
107 public function buildConditions($operation, AccountInterface $account) {
108 $entity_type_id = $this->entityType->id();
109 $has_owner = $this->entityType->entityClassImplements(EntityOwnerInterface::class);
111 if ($account->hasPermission("administer {$entity_type_id}")) {
112 // The user has full access to all operations, no conditions needed.
113 $conditions = new ConditionGroup('OR');
114 $conditions->addCacheContexts(['user.permissions']);
119 $entity_conditions = $this->buildEntityOwnerConditions($operation, $account);
122 $entity_conditions = $this->buildEntityConditions($operation, $account);
126 if ($operation == 'view' && $this->entityType->entityClassImplements(EntityPublishedInterface::class)) {
127 $uid_key = $this->entityType->getKey('uid');
128 $published_key = $this->entityType->getKey('published');
129 $published_conditions = NULL;
130 $unpublished_conditions = NULL;
132 if ($entity_conditions) {
133 // Restrict the existing conditions to published entities only.
134 $published_conditions = new ConditionGroup('AND');
135 $published_conditions->addCacheContexts(['user.permissions']);
136 $published_conditions->addCondition($entity_conditions);
137 $published_conditions->addCondition($published_key, '1');
139 if ($has_owner && $account->hasPermission("view own unpublished $entity_type_id")) {
140 $unpublished_conditions = new ConditionGroup('AND');
141 $unpublished_conditions->addCacheContexts(['user']);
142 $unpublished_conditions->addCondition($uid_key, $account->id());
143 $unpublished_conditions->addCondition($published_key, '0');
146 if ($published_conditions && $unpublished_conditions) {
147 $conditions = new ConditionGroup('OR');
148 $conditions->addCondition($published_conditions);
149 $conditions->addCondition($unpublished_conditions);
151 elseif ($published_conditions) {
152 $conditions = $published_conditions;
154 elseif ($unpublished_conditions) {
155 $conditions = $unpublished_conditions;
159 $conditions = $entity_conditions;
163 // The user doesn't have access to any entities.
164 // Falsify the query to ensure no results are returned.
165 $conditions = new ConditionGroup('OR');
166 $conditions->addCacheContexts(['user.permissions']);
167 $conditions->alwaysFalse();
174 * Builds the conditions for entities that have an owner.
176 * @param string $operation
177 * The access operation. Usually one of "view", "update" or "delete".
178 * @param \Drupal\Core\Session\AccountInterface $account
179 * The user for which to restrict access.
181 * @return \Drupal\entity\QueryAccess\ConditionGroup|null
182 * The conditions, or NULL if the user doesn't have access to any entity.
184 protected function buildEntityOwnerConditions($operation, AccountInterface $account) {
185 $entity_type_id = $this->entityType->id();
186 $uid_key = $this->entityType->getKey('uid');
187 $bundle_key = $this->entityType->getKey('bundle');
189 $conditions = new ConditionGroup('OR');
190 $conditions->addCacheContexts(['user.permissions']);
191 // Any $entity_type permission.
192 if ($account->hasPermission("$operation any $entity_type_id")) {
193 // The user has full access, no conditions needed.
197 // Own $entity_type permission.
198 if ($account->hasPermission("$operation own $entity_type_id")) {
199 $conditions->addCacheContexts(['user']);
200 $conditions->addCondition($uid_key, $account->id());
203 $bundles = array_keys($this->bundleInfo->getBundleInfo($entity_type_id));
204 $bundles_with_any_permission = [];
205 $bundles_with_own_permission = [];
206 foreach ($bundles as $bundle) {
207 if ($account->hasPermission("$operation any $bundle $entity_type_id")) {
208 $bundles_with_any_permission[] = $bundle;
210 if ($account->hasPermission("$operation own $bundle $entity_type_id")) {
211 $bundles_with_own_permission[] = $bundle;
214 // Any $bundle permission.
215 if ($bundles_with_any_permission) {
216 $conditions->addCondition($bundle_key, $bundles_with_any_permission);
218 // Own $bundle permission.
219 if ($bundles_with_own_permission) {
220 $conditions->addCacheContexts(['user']);
221 $conditions->addCondition((new ConditionGroup('AND'))
222 ->addCondition($uid_key, $account->id())
223 ->addCondition($bundle_key, $bundles_with_own_permission)
227 return $conditions->count() ? $conditions : NULL;
231 * Builds the conditions for entities that do not have an owner.
233 * @param string $operation
234 * The access operation. Usually one of "view", "update" or "delete".
235 * @param \Drupal\Core\Session\AccountInterface $account
236 * The user for which to restrict access.
238 * @return \Drupal\entity\QueryAccess\ConditionGroup|null
239 * The conditions, or NULL if the user doesn't have access to any entity.
241 protected function buildEntityConditions($operation, AccountInterface $account) {
242 $entity_type_id = $this->entityType->id();
243 $bundle_key = $this->entityType->getKey('bundle');
245 $conditions = new ConditionGroup('OR');
246 $conditions->addCacheContexts(['user.permissions']);
247 // The $entity_type permission.
248 if ($account->hasPermission("$operation $entity_type_id")) {
249 // The user has full access, no conditions needed.
253 $bundles = array_keys($this->bundleInfo->getBundleInfo($entity_type_id));
254 $bundles_with_any_permission = [];
255 foreach ($bundles as $bundle) {
256 if ($account->hasPermission("$operation $bundle $entity_type_id")) {
257 $bundles_with_any_permission[] = $bundle;
260 // The $bundle permission.
261 if ($bundles_with_any_permission) {
262 $conditions->addCondition($bundle_key, $bundles_with_any_permission);
265 return $conditions->count() ? $conditions : NULL;