diff --git a/core/lib/Drupal/Core/Entity/EntityAccessController.php b/core/lib/Drupal/Core/Entity/EntityAccessController.php index 2c9d6fd..15e7ca2 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessController.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessController.php @@ -23,12 +23,25 @@ class EntityAccessController implements EntityAccessControllerInterface { protected $accessCache = array(); /** + * The entity type of the access controller instance. + * + * @var string + */ + protected $entity_type; + + /** + * @param string $entity_type + * The entity type of the access controller instance. + */ + public function __construct($entity_type) { + $this->entity_type = $entity_type; + } + + /** * {@inheritdoc} */ public function access(EntityInterface $entity, $operation, $langcode = Language::LANGUAGE_DEFAULT, AccountInterface $account = NULL) { - if (!$account) { - $account = $GLOBALS['user']; - } + $account = $this->prepareUser($account); if (($access = $this->getCache($entity, $operation, $langcode, $account)) !== NULL) { // Cache hit, no work necessary. @@ -45,17 +58,35 @@ public function access(EntityInterface $entity, $operation, $langcode = Language // - At least one module says to grant access. $access = module_invoke_all($entity->entityType() . '_access', $entity->getBCEntity(), $operation, $account, $langcode); + if ($return = $this->processAccessHookResults($access) === NULL) { + // No result from hook, so entity checks are done. + $return = (bool) $this->checkAccess($entity, $operation, $langcode, $account); + } + return $this->setCache($return, $entity, $operation, $langcode, $account); + } + + /** + * 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. + */ + protected function processAccessHookResults($access) { if (in_array(FALSE, $access, TRUE)) { - $return = FALSE; + return FALSE; } elseif (in_array(TRUE, $access, TRUE)) { - $return = TRUE; + return TRUE; } else { - // No result from hook, so entity checks are done. - $return = (bool) $this->checkAccess($entity, $operation, $langcode, $account); + return; } - return $this->setCache($return, $entity, $operation, $langcode, $account); + } + + protected function prepareUser($account) { + if (!$account) { + $account = $GLOBALS['user']; + } + return $account; } /** @@ -85,8 +116,8 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A /** * Tries to retrieve a previously cached access value from the static cache. * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity for which to check 'create' access. + * @param \Drupal\Core\Entity\EntityInterface|string $entity + * The entity for which to check access or a custom string. * @param string $operation * The entity operation. Usually one of 'view', 'update', 'create' or * 'delete'. @@ -100,13 +131,21 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A * is no record for the given user, operation, langcode and entity in the * cache. */ - protected function getCache(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { + protected function getCache($entity, $operation, $langcode, AccountInterface $account) { $uid = $account ? $account->id() : 0; - $uuid = $entity->uuid(); + + // The cache key might be either the UUID of an existing entity or a custom + // string, for example used by the createAccess method. + if ($entity instanceof EntityInterface) { + $cid = $entity->uuid(); + } + else { + $cid = $entity; + } // Return from cache if a value has been set for it previously. - if (isset($this->accessCache[$uid][$uuid][$langcode][$operation])) { - return $this->accessCache[$uid][$uuid][$langcode][$operation]; + if (isset($this->accessCache[$uid][$cid][$langcode][$operation])) { + return $this->accessCache[$uid][$cid][$langcode][$operation]; } } @@ -115,8 +154,8 @@ protected function getCache(EntityInterface $entity, $operation, $langcode, Acco * * @param bool $access * TRUE if the user has access, FALSE otherwise. - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity for which to check 'create' access. + * @param \Drupal\Core\Entity\EntityInterface|string $entity + * The entity for which to check access or a custom string. * @param string $operation * The entity operation. Usually one of 'view', 'update', 'create' or * 'delete'. @@ -128,12 +167,21 @@ protected function getCache(EntityInterface $entity, $operation, $langcode, Acco * @return bool * TRUE if access was granted, FALSE otherwise. */ - protected function setCache($access, EntityInterface $entity, $operation, $langcode, AccountInterface $account) { + protected function setCache($access, $entity, $operation, $langcode, AccountInterface $account) { $uid = $account ? $account->id() : 0; - $uuid = $entity->uuid(); + + + // The cache key might be either the UUID of an existing entity or a custom + // string, for example used by the createAccess method. + if ($entity instanceof EntityInterface) { + $cid = $entity->uuid(); + } + else { + $cid = $entity; + } // Save the given value in the static cache and directly return it. - return $this->accessCache[$uid][$uuid][$langcode][$operation] = (bool) $access; + return $this->accessCache[$uid][$cid][$langcode][$operation] = (bool) $access; } /** @@ -143,4 +191,55 @@ public function resetCache() { $this->accessCache = array(); } + /** + * {@inheritdoc} + */ + public function createAccess($type, AccountInterface $account = NULL, $context = array()) { + $account = $this->prepareUser($account); + $context += array( + 'langcode' => Language::LANGCODE_DEFAULT, + ); + + if (($access = $this->getCache('create', 'create', $context['langcode'], $account)) !== NULL) { + // Cache hit, no work necessary. + return $access; + } + + // Invoke hook_entity_access(), hook results take precedence over overridden + // implementations of EntityAccessController::checkAccess(). Entities + // that have checks that need to be done before the hook is invoked should + // do so by overridding 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 = module_invoke_all($this->entity_type . '_create_access', $account, $context['langcode']); + + if ($return = $this->processAccessHookResults($access) === NULL) { + // No result from hook, so entity checks are done. + $return = (bool) $this->checkCreateAccess($access, $context); + } + return $this->setCache($return, 'create', 'create', $context['langcode'], $account); + } + + /** + * 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. + * + * @return bool|null + * TRUE if access was granted, FALSE if access was denied and NULL if access + * could not be determined. + */ + protected function checkCreateAccess(AccountInterface $account, array $context) { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php index f7b4348..9054f98 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php @@ -36,6 +36,19 @@ public function access(EntityInterface $entity, $operation, $langcode = Language::LANGCODE_DEFAULT, AccountInterface $account = NULL); /** + * Checks access to create an entity. + * + * @param string $type + * The node type to check access for. + * @param \Drupal\Core\Session\AccountInterface $account + * (optional) The user session for which to check access, or NULL to check + * access for the current user. Defaults to NULL. + * @param array $context + * An array of key-value pairs to pass additional context when needed. + */ + public function createAccess($type, AccountInterface $account = NULL, $context = array()); + + /** * Clears all cached access checks. */ public function resetCache(); diff --git a/core/modules/node/lib/Drupal/node/NodeAccessController.php b/core/modules/node/lib/Drupal/node/NodeAccessController.php index d7cfd2c..deae4b9 100644 --- a/core/modules/node/lib/Drupal/node/NodeAccessController.php +++ b/core/modules/node/lib/Drupal/node/NodeAccessController.php @@ -32,6 +32,41 @@ public function access(EntityInterface $entity, $operation, $langcode = Language } /** + * @param string $type + * The node type to check access for. + * @param \Drupal\Core\Session\AccountInterface $account + * (optional) The user session for which to check access, or NULL to check + * access for the current user. Defaults to NULL. + * @param array $context + * An array of key-value pairs to pass additional context when needed. + */ + public function createAccess($type, AccountInterface $account = NULL, $context = array()) { + $account = $this->prepareUser($account); + + if (user_access('bypass node access', $account)) { + return TRUE; + } + if (!user_access('access content', $account)) { + return FALSE; + } + + $access = module_invoke_all('node_create_access', $account, $context); + + $configured_types = node_permissions_get_configured_types(); + if (isset($configured_types[$type])) { + if (user_access('create ' . $type . ' content', $account)) { + $access[] = NODE_ACCESS_ALLOW; + } + } + + if ($return = $this->processAccessHookResults($access) !== NULL) { + return $return; + } + + return NODE_ACCESS_DENY; + } + + /** * {@inheritdoc} */ protected function checkAccess(EntityInterface $node, $operation, $langcode, AccountInterface $account) { diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 7057db2..7cb5d6b 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -2155,8 +2155,12 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) { * @see node_menu() */ function node_access($op, $node, $account = NULL, $langcode = NULL) { + $access_controller = Drupal::entityManager()->getAccessController('node'); + if (!$node instanceof EntityInterface) { $node = entity_create('node', array('type' => $node)); + $access_controller->createAccess($node, ) + $access_controller = entity_access_controller('node'); } elseif ($node instanceof NodeTypeInterface) { $node = entity_create('node', array('type' => $node->id())); @@ -2185,7 +2189,7 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) { $account = user_load($account->uid); } - return entity_access_controller('node')->access($node, $op, $langcode, $account); + return $access_controller->access($node, $op, $langcode, $account); } /** diff --git a/core/modules/translation/translation.module b/core/modules/translation/translation.module index 9c88847..95dbd9f 100644 --- a/core/modules/translation/translation.module +++ b/core/modules/translation/translation.module @@ -121,14 +121,14 @@ function translation_permission() { } /** - * Implements hook_node_access(). + * Implements hook_node_create_access(). */ -function translation_node_access($node, $op, $account, $langcode) { +function translation_node_create_access($account) { $query = Drupal::request()->query; $translation = $query->get('translation'); $target = $query->get('target'); $request_has_translation_arg = !empty($translation) && !empty($target) && is_numeric($translation); - if ($op == 'create' && $request_has_translation_arg) { + if ($request_has_translation_arg) { $source_node = node_load($translation); if (empty($source_node) || !translation_user_can_translate_node($source_node, $account)){ return NODE_ACCESS_DENY;