entityTypeId = $entity_type->id(); $this->entityType = $entity_type; } /** * {@inheritdoc} */ public function access(EntityInterface $entity, $operation, $langcode = LanguageInterface::LANGCODE_DEFAULT, AccountInterface $account = NULL, $return_as_object = FALSE) { $account = $this->prepareUser($account); if (($return = $this->getCache($entity->uuid(), $operation, $langcode, $account)) !== NULL) { // Cache hit, no work necessary. return $return_as_object ? $return : $return->isAllowed(); } // Invoke hook_entity_access() and hook_ENTITY_TYPE_access(). Hook results // take precedence over overridden implementations of // EntityAccessControlHandler::checkAccess(). Entities that have checks that // need to be done before the hook is invoked should do so by overriding // this method. // We grant access to the entity if both of these conditions are met: // - No modules say to deny access. // - At least one module says to grant access. $access = array_merge( $this->moduleHandler()->invokeAll('entity_access', array($entity, $operation, $account, $langcode)), $this->moduleHandler()->invokeAll($entity->getEntityTypeId() . '_access', array($entity, $operation, $account, $langcode)) ); $return = $this->processAccessHookResults($access); // Also execute the default access check except when the access result is // already forbidden, as in that case, it can not be anything else. if (!$return->isForbidden()) { $return = $return->orIf($this->checkAccess($entity, $operation, $langcode, $account)); } $result = $this->setCache($return, $entity->uuid(), $operation, $langcode, $account); return $return_as_object ? $result : $result->isAllowed(); } /** * We grant access to the entity if both of these conditions are met: * - No modules say to deny access. * - At least one module says to grant access. * * @param \Drupal\Core\Access\AccessResultInterface[] $access * An array of access results of the fired access hook. * * @return \Drupal\Core\Access\AccessResultInterface * The combined result of the various access checks' results. All their * cacheability metadata is merged as well. * * @see \Drupal\Core\Access\AccessResultInterface::orIf() */ protected function processAccessHookResults(array $access) { // No results means no opinion. if (empty($access)) { return AccessResult::neutral(); } /** @var \Drupal\Core\Access\AccessResultInterface $result */ $result = array_shift($access); foreach ($access as $other) { $result = $result->orIf($other); } return $result; } /** * Performs access checks. * * This method is supposed to be overwritten by extending classes that * do their own custom access checking. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity for which to check access. * @param string $operation * The entity operation. Usually one of 'view', 'update' or 'delete'. * @param string $langcode * The language code for which to check access. * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { if ($operation == 'delete' && $entity->isNew()) { return AccessResult::forbidden()->cacheUntilEntityChanges($entity); } if ($admin_permission = $this->entityType->getAdminPermission()) { return AccessResult::allowedIfHasPermission($account, $this->entityType->getAdminPermission()); } else { // No opinion. return AccessResult::neutral(); } } /** * Tries to retrieve a previously cached access value from the static cache. * * @param string $cid * Unique string identifier for the entity/operation, for example the * entity UUID or a custom string. * @param string $operation * The entity operation. Usually one of 'view', 'update', 'create' or * 'delete'. * @param string $langcode * The language code for which to check access. * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * * @return \Drupal\Core\Access\AccessResultInterface|null * The cached AccessResult, or NULL if there is no record for the given * user, operation, langcode and entity in the cache. */ protected function getCache($cid, $operation, $langcode, AccountInterface $account) { // Return from cache if a value has been set for it previously. if (isset($this->accessCache[$account->id()][$cid][$langcode][$operation])) { return $this->accessCache[$account->id()][$cid][$langcode][$operation]; } } /** * Statically caches whether the given user has access. * * @param \Drupal\Core\Access\AccessResultInterface $access * The access result. * @param string $cid * Unique string identifier for the entity/operation, for example the * entity UUID or a custom string. * @param string $operation * The entity operation. Usually one of 'view', 'update', 'create' or * 'delete'. * @param string $langcode * The language code for which to check access. * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * * @return \Drupal\Core\Access\AccessResultInterface * Whether the user has access, plus cacheability metadata. */ protected function setCache($access, $cid, $operation, $langcode, AccountInterface $account) { // Save the given value in the static cache and directly return it. return $this->accessCache[$account->id()][$cid][$langcode][$operation] = $access; } /** * {@inheritdoc} */ public function resetCache() { $this->accessCache = array(); } /** * {@inheritdoc} */ public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = array(), $return_as_object = FALSE) { $account = $this->prepareUser($account); $context += array( 'langcode' => LanguageInterface::LANGCODE_DEFAULT, ); $cid = $entity_bundle ? 'create:' . $entity_bundle : 'create'; if (($access = $this->getCache($cid, 'create', $context['langcode'], $account)) !== NULL) { // Cache hit, no work necessary. return $return_as_object ? $access : $access->isAllowed(); } // Invoke hook_entity_create_access() and hook_ENTITY_TYPE_create_access(). // Hook results take precedence over overridden implementations of // EntityAccessControlHandler::checkAccess(). Entities that have checks that // need to be done before the hook is invoked should do so by overriding // this method. // We grant access to the entity if both of these conditions are met: // - No modules say to deny access. // - At least one module says to grant access. $access = array_merge( $this->moduleHandler()->invokeAll('entity_create_access', array($account, $context, $entity_bundle)), $this->moduleHandler()->invokeAll($this->entityTypeId . '_create_access', array($account, $context, $entity_bundle)) ); $return = $this->processAccessHookResults($access); // Also execute the default access check except when the access result is // already forbidden, as in that case, it can not be anything else. if (!$return->isForbidden()) { $return = $return->orIf($this->checkCreateAccess($account, $context, $entity_bundle)); } $result = $this->setCache($return, $cid, 'create', $context['langcode'], $account); return $return_as_object ? $result : $result->isAllowed(); } /** * Performs create access checks. * * This method is supposed to be overwritten by extending classes that * do their own custom access checking. * * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * @param array $context * An array of key-value pairs to pass additional context when needed. * @param string|null $entity_bundle * (optional) The bundle of the entity. Required if the entity supports * bundles, defaults to NULL otherwise. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { if ($admin_permission = $this->entityType->getAdminPermission()) { return AccessResult::allowedIfHasPermission($account, $admin_permission); } else { // No opinion. return AccessResult::neutral(); } } /** * Loads the current account object, if it does not exist yet. * * @param \Drupal\Core\Session\AccountInterface $account * The account interface instance. * * @return \Drupal\Core\Session\AccountInterface * Returns the current account object. */ protected function prepareUser(AccountInterface $account = NULL) { if (!$account) { $account = \Drupal::currentUser(); } return $account; } /** * {@inheritdoc} */ public function fieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account = NULL, FieldItemListInterface $items = NULL, $return_as_object = FALSE) { $account = $this->prepareUser($account); // Get the default access restriction that lives within this field. $default = $items ? $items->defaultAccess($operation, $account) : AccessResult::allowed(); // Get the default access restriction as specified by the access control // handler. $entity_default = $this->checkFieldAccess($operation, $field_definition, $account, $items); // Combine default access, denying access wins. $default = $default->andIf($entity_default); // Invoke hook and collect grants/denies for field access from other // modules. Our default access flag is masked under the ':default' key. $grants = array(':default' => $default); $hook_implementations = $this->moduleHandler()->getImplementations('entity_field_access'); foreach ($hook_implementations as $module) { $grants = array_merge($grants, array($module => $this->moduleHandler()->invoke($module, 'entity_field_access', array($operation, $field_definition, $account, $items)))); } // Also allow modules to alter the returned grants/denies. $context = array( 'operation' => $operation, 'field_definition' => $field_definition, 'items' => $items, 'account' => $account, ); $this->moduleHandler()->alter('entity_field_access', $grants, $context); $result = $this->processAccessHookResults($grants); return $return_as_object ? $result : $result->isAllowed(); } /** * Default field access as determined by this access control handler. * * @param string $operation * The operation access should be checked for. * Usually one of "view" or "edit". * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition * The field definition. * @param \Drupal\Core\Session\AccountInterface $account * The user session for which to check access. * @param \Drupal\Core\Field\FieldItemListInterface $items * (optional) The field values for which to check access, or NULL if access * is checked for the field definition, without any specific value * available. Defaults to NULL. * * @return bool * TRUE if access is allowed, FALSE otherwise. */ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) { return AccessResult::allowed(); } }