diff --git a/core/lib/Drupal/Core/Entity/EntityAccessController.php b/core/lib/Drupal/Core/Entity/EntityAccessController.php index c747cca..e1b6565 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessController.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessController.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Entity; +use Drupal\Core\Access\AccessInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Field\FieldDefinitionInterface; @@ -16,7 +17,7 @@ /** * Defines a default implementation for entity access controllers. */ -class EntityAccessController implements EntityAccessControllerInterface { +class EntityAccessController implements AccessInterface, EntityAccessControllerInterface { /** * Stores calculcated access check results. @@ -84,10 +85,15 @@ public function access(EntityInterface $entity, $operation, $langcode = Language $this->moduleHandler->invokeAll($entity->entityType() . '_access', array($entity, $operation, $account, $langcode)) ); - if (($return = $this->processAccessHookResults($access)) === NULL) { - // No module had an opinion about the access, so let's the access + $return = $this->processAccessHookResults($access); + if ($return == self::DENY) { + // No module had an opinion about the access, so let the access // controller check create access. - $return = (bool) $this->checkAccess($entity, $operation, $langcode, $account); + $return = $this->checkAccess($entity, $operation, $langcode, $account); + } + else { + // Convert to a boolean. + $return = $return == self::ALLOW; } return $this->setCache($return, $entity->uuid(), $operation, $langcode, $account); } @@ -100,19 +106,18 @@ public function access(EntityInterface $entity, $operation, $langcode = Language * @param array $access * An array of access results of the fired access hook. * - * @return bool|null - * Returns FALSE if access should be denied, TRUE if access should be - * granted and NULL if no module denied access. + * @return string + * self::ALLOW, self::DENY, or self::KILL. */ protected function processAccessHookResults(array $access) { - if (in_array(FALSE, $access, TRUE)) { - return FALSE; + if (in_array(self::KILL, $access)) { + return self::KILL; } - elseif (in_array(TRUE, $access, TRUE)) { - return TRUE; + elseif (in_array(self::ALLOW, $access)) { + return self::ALLOW; } else { - return; + return self::DENY; } } @@ -132,17 +137,10 @@ protected function processAccessHookResults(array $access) { * @param \Drupal\Core\Session\AccountInterface $account * The user for which to check access. * - * @return bool|null - * TRUE if access was granted, FALSE if access was denied and NULL if access - * could not be determined. + * @return bool */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { - if (!empty($this->entityInfo['admin_permission'])) { - return $account->hasPermission($this->entityInfo['admin_permission']); - } - else { - return NULL; - } + return !empty($this->entityInfo['admin_permission']) && $account->hasPermission($this->entityInfo['admin_permission']); } /** @@ -231,10 +229,15 @@ public function createAccess($entity_bundle = NULL, AccountInterface $account = $this->moduleHandler->invokeAll($this->entityType . '_create_access', array($account, $context['langcode'])) ); - if (($return = $this->processAccessHookResults($access)) === NULL) { - // No module had an opinion about the access, so let's the access + $return = $this->processAccessHookResults($access); + if ($return == self::DENY) { + // No module had an opinion about the access, so let the access // controller check create access. - $return = (bool) $this->checkCreateAccess($account, $context, $entity_bundle); + $return = $this->checkCreateAccess($account, $context, $entity_bundle); + } + else { + // Convert to a boolean. + $return = $return == self::ALLOW; } return $this->setCache($return, $cid, 'create', $context['langcode'], $account); } @@ -253,17 +256,10 @@ public function createAccess($entity_bundle = NULL, AccountInterface $account = * (optional) The bundle of the entity. Required if the entity supports * bundles, defaults to NULL otherwise. * - * @return bool|null - * TRUE if access was granted, FALSE if access was denied and NULL if access - * could not be determined. + * @return bool */ protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { - if (!empty($this->entityInfo['admin_permission'])) { - return $account->hasPermission($this->entityInfo['admin_permission']); - } - else { - return NULL; - } + return !empty($this->entityInfo['admin_permission']) && $account->hasPermission($this->entityInfo['admin_permission']); } /** @@ -315,17 +311,10 @@ public function fieldAccess($operation, FieldDefinitionInterface $field_definiti 'account' => $account, ); $this->moduleHandler->alter('entity_field_access', $grants, $context); + $return = $this->processAccessHookResults($grants); - // One grant being FALSE is enough to deny access immediately. - if (in_array(FALSE, $grants, TRUE)) { - return FALSE; - } - // At least one grant has the explicit opinion to allow access. - if (in_array(TRUE, $grants, TRUE)) { - return TRUE; - } - // All grants are NULL and have no opinion - deny access in that case. - return FALSE; + // Convert to a boolean. + return $return == self::ALLOW; } } diff --git a/core/modules/field/tests/modules/field_test/field_test.field.inc b/core/modules/field/tests/modules/field_test/field_test.field.inc index f889d85..e7bbae6 100644 --- a/core/modules/field/tests/modules/field_test/field_test.field.inc +++ b/core/modules/field/tests/modules/field_test/field_test.field.inc @@ -5,6 +5,7 @@ * Defines a field type and its formatters and widgets. */ +use Drupal\Core\Access\AccessInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; @@ -39,14 +40,14 @@ function field_test_default_value(EntityInterface $entity, $field, $instance) { */ function field_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) { if ($field_definition->getFieldName() == "field_no_{$operation}_access") { - return FALSE; + return AccessInterface::KILL; } // Only grant view access to test_view_field fields when the user has // 'view test_view_field content' permission. if ($field_definition->getFieldName() == 'test_view_field' && $operation == 'view' && !$account->hasPermission('view test_view_field content')) { - return FALSE; + return AccessInterface::KILL; } - return TRUE; + return AccessInterface::ALLOW; } diff --git a/core/modules/file/file.api.php b/core/modules/file/file.api.php index 9ebbc95..a462ed4 100644 --- a/core/modules/file/file.api.php +++ b/core/modules/file/file.api.php @@ -220,7 +220,7 @@ function hook_file_delete(Drupal\file\FileInterface $file) { */ function hook_file_download_access($field, Drupal\Core\Entity\EntityInterface $entity, Drupal\file\FileInterface $file) { if ($entity->entityType() == 'node') { - return $entity->access('view'); + return $entity->access('view') ? $entity::ALLOW : $entity::DENY; } } diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index ba67aca..2be139e 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -522,73 +522,6 @@ function hook_node_load($nodes, $types) { } } -/** - * Controls access to a node. - * - * Modules may implement this hook if they want to have a say in whether or not - * a given user has access to perform a given operation on a node. - * - * The administrative account (user ID #1) always passes any access check, so - * this hook is not called in that case. Users with the "bypass node access" - * permission may always view and edit content through the administrative - * interface. - * - * Note that not all modules will want to influence access on all node types. If - * your module does not want to actively grant or block access, return - * NODE_ACCESS_IGNORE or simply return nothing. Blindly returning FALSE will - * break other node access modules. - * - * Also note that this function isn't called for node listings (e.g., RSS feeds, - * the default home page at path 'node', a recent content block, etc.) See - * @link node_access Node access rights @endlink for a full explanation. - * - * @param \Drupal\Core\Entity\EntityInterface|string $node - * Either a node entity or the machine name of the content type on which to - * perform the access check. - * @param string $op - * The operation to be performed. Possible values: - * - "create" - * - "delete" - * - "update" - * - "view" - * @param object $account - * The user object to perform the access check operation on. - * @param object $langcode - * The language code to perform the access check operation on. - * - * @return string - * - NODE_ACCESS_ALLOW: if the operation is to be allowed. - * - NODE_ACCESS_DENY: if the operation is to be denied. - * - NODE_ACCESS_IGNORE: to not affect this operation at all. - * - * @ingroup node_access - */ -function hook_node_access(\Drupal\node\NodeInterface $node, $op, $account, $langcode) { - $type = is_string($node) ? $node : $node->getType(); - - $configured_types = node_permissions_get_configured_types(); - if (isset($configured_types[$type])) { - if ($op == 'create' && user_access('create ' . $type . ' content', $account)) { - return NODE_ACCESS_ALLOW; - } - - if ($op == 'update') { - if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->id() == $node->getAuthorId()))) { - return NODE_ACCESS_ALLOW; - } - } - - if ($op == 'delete') { - if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->id() == $node->getAuthorId()))) { - return NODE_ACCESS_ALLOW; - } - } - } - - // Returning nothing from this function would have the same effect. - return NODE_ACCESS_IGNORE; -} - /** * Act on a node object about to be shown on the add/edit form. diff --git a/core/modules/node/node.module b/core/modules/node/node.module index faf8217..3fee222 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -54,30 +54,6 @@ const NODE_STICKY = 1; /** - * Denotes that access is allowed for a node. - * - * Modules should return this value from hook_node_access() to allow access to a - * node. - */ -const NODE_ACCESS_ALLOW = TRUE; - -/** - * Denotes that access is denied for a node. - * - * Modules should return this value from hook_node_access() to deny access to a - * node. - */ -const NODE_ACCESS_DENY = FALSE; - -/** - * Denotes that access is unaffected for a node. - * - * Modules should return this value from hook_node_access() to indicate no - * effect on node access. - */ -const NODE_ACCESS_IGNORE = NULL; - -/** * Implements hook_help(). */ function node_help($path, $arg) { @@ -1496,14 +1472,11 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) { * users have unrestricted access to all nodes. user 1 will always pass this * check. * - * Next, all implementations of hook_node_access() will be called. Each - * implementation may explicitly allow, explicitly deny, or ignore the access - * request. If at least one module says to deny the request, it will be rejected. - * If no modules deny the request and at least one says to allow it, the request - * will be permitted. + * Next, all implementations of hook_ENTITY_TYPE_access() (effectively + * hook_node_access() will be called. * - * If all modules ignore the access request, then the node_access table is used - * to determine access. All node access modules are queried using + * If no module allows or kills the access request, then the {node_access} table + * is used to determine access. All node access modules are queried using * hook_node_grants() to assemble a list of "grant IDs" for the user. This list * is compared against the table. If any row contains the node ID in question * (or 0, which stands for "all nodes"), one of the grant IDs returned, and a @@ -1513,25 +1486,16 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) { * * In node listings (lists of nodes generated from a select query, such as the * default home page at path 'node', an RSS feed, a recent content block, etc.), - * the process above is followed except that hook_node_access() is not called on - * each node for performance reasons and for proper functioning of the pager - * system. When adding a node listing to your module, be sure to use a dynamic - * query created by db_select() and add a tag of "node_access". This will allow - * modules dealing with node access to ensure only nodes to which the user has - * access are retrieved, through the use of hook_query_TAG_alter(). - * - * Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access() - * will block access to the node. Therefore, implementers should take care to - * not deny access unless they really intend to. Unless a module wishes to - * actively deny access it should return NODE_ACCESS_IGNORE (or simply return - * nothing) to allow other modules or the node_access table to control access. - * - * To see how to write a node access module of your own, see - * node_access_example.module. + * the process above is followed except that hook_ENTITY_TYPE_access() is not + * called on each node for performance reasons and for proper functioning of the + * pager system. When adding a node listing to your module, be sure to use a + * dynamic query created by db_select() and add a tag of "node_access". This + * will allow modules dealing with node access to ensure only nodes to which the + * user has access are retrieved, through the use of hook_query_TAG_alter(). */ /** - * Implements hook_node_access(). + * Implements hook_ENTITY_TYPE_access(). */ function node_node_access($node, $op, $account) { $type = $node->bundle(); @@ -1539,23 +1503,23 @@ function node_node_access($node, $op, $account) { $configured_types = node_permissions_get_configured_types(); if (isset($configured_types[$type])) { if ($op == 'create' && user_access('create ' . $type . ' content', $account)) { - return NODE_ACCESS_ALLOW; + return $node::ALLOW; } if ($op == 'update') { if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->id() == $node->getAuthorId()))) { - return NODE_ACCESS_ALLOW; + return $node::ALLOW; } } if ($op == 'delete') { if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->id() == $node->getAuthorId()))) { - return NODE_ACCESS_ALLOW; + return $node::ALLOW; } } } - return NODE_ACCESS_IGNORE; + return $node::DENY; } /** @@ -1608,8 +1572,8 @@ function node_list_permissions($type) { * node_permissions_$type variable to 0. Core does not provide an interface for * doing so. However, contrib modules may exclude their own nodes in * hook_install(). Alternatively, contrib modules may configure all node types - * at once, or decide to apply some other hook_node_access() implementation to - * some or all node types. + * at once, or decide to apply some other hook_ENTITY_TYPE_access() + * implementation to some or all node types. * * @return * An array of node types managed by this module. diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.module b/core/modules/node/tests/modules/node_access_test/node_access_test.module index 12800b1..a3f821d 100644 --- a/core/modules/node/tests/modules/node_access_test/node_access_test.module +++ b/core/modules/node/tests/modules/node_access_test/node_access_test.module @@ -150,13 +150,13 @@ function _node_access_test_node_write(EntityInterface $node) { } /** - * Implements hook_node_access(). + * Implements hook_ENTITY_TYPE_access(). */ function node_access_test_node_access($node, $op, $account, $langcode) { $secret_catalan = \Drupal::state()->get('node_access_test_secret_catalan') ?: 0; if ($secret_catalan && $langcode == 'ca') { // Make all Catalan content secret. - return NODE_ACCESS_DENY; + return $node::KILL; } - return NODE_ACCESS_IGNORE; + return $node::DENY; } diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php index 8414b72..12458a6 100644 --- a/core/modules/system/entity.api.php +++ b/core/modules/system/entity.api.php @@ -5,6 +5,7 @@ * Hooks provided the Entity module. */ +use Drupal\Core\Access\AccessInterface; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Field\FieldDefinition; @@ -25,14 +26,13 @@ * @param string $langcode * The code of the language $entity is accessed in. * - * @return bool|null - * A boolean to explicitly allow or deny access, or NULL to neither allow nor - * deny access. + * @return string + * $entity::ALLOW, $entity::DENY, or $entity::KILL. * * @see \Drupal\Core\Entity\EntityAccessController */ function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) { - return NULL; + return $entity::DENY; } /** @@ -47,14 +47,13 @@ function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operat * @param string $langcode * The code of the language $entity is accessed in. * - * @return bool|null - * A boolean to explicitly allow or deny access, or NULL to neither allow nor - * deny access. + * @return string + * $entity::ALLOW, $entity::DENY, or $entity::KILL. * * @see \Drupal\Core\Entity\EntityAccessController */ function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) { - return NULL; + return $entity::DENY; } /** @@ -65,14 +64,13 @@ function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $o * @param string $langcode * The code of the language $entity is accessed in. * - * @return bool|null - * A boolean to explicitly allow or deny access, or NULL to neither allow nor - * deny access. + * @return string + * $entity::ALLOW, $entity::DENY, or $entity::KILL. * * @see \Drupal\Core\Entity\EntityAccessController */ function hook_entity_create_access(\Drupal\Core\Session\AccountInterface $account, $langcode) { - return NULL; + return $entity::DENY; } /** @@ -83,14 +81,13 @@ function hook_entity_create_access(\Drupal\Core\Session\AccountInterface $accoun * @param string $langcode * The code of the language $entity is accessed in. * - * @return bool|null - * A boolean to explicitly allow or deny access, or NULL to neither allow nor - * deny access. + * @return string + * $entity::ALLOW, $entity::DENY, or $entity::KILL. * * @see \Drupal\Core\Entity\EntityAccessController */ function hook_ENTITY_TYPE_create_access(\Drupal\Core\Session\AccountInterface $account, $langcode) { - return NULL; + return $entity::DENY; } /** @@ -715,13 +712,12 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent * (optional) The entity field object on which the operation is to be * performed. * - * @return bool|null - * TRUE if access should be allowed, FALSE if access should be denied and NULL - * if the implementation has no opinion. + * @return string + * One of the \Drupal\Core\Access\AccessInterface constants. */ function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinitionInterface $field_definition, \Drupal\Core\Session\AccountInterface $account, \Drupal\Core\Field\FieldItemListInterface $items = NULL) { if ($field_definition->getFieldName() == 'field_of_interest' && $operation == 'edit') { - return user_access('update field of interest', $account); + return $account->hasPermission('update field of interest') ? AccessInterface::ALLOW : AccessInterface::DENY; } } @@ -734,7 +730,8 @@ function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinition * @param array $grants * An array of grants gathered by hook_entity_field_access(). The array is * keyed by the module that defines the field's access control; the values are - * grant responses for each module (Boolean or NULL). + * grant responses for each module (\Drupal\Core\Access\AccessInterface + * constants). * @param array $context * Context array on the performed operation with the following keys: * - operation: The operation to be performed (string). @@ -747,12 +744,12 @@ function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinition */ function hook_entity_field_access_alter(array &$grants, array $context) { $field_definition = $context['field_definition']; - if ($field_definition->getFieldName() == 'field_of_interest' && $grants['node'] === FALSE) { + if ($field_definition->getFieldName() == 'field_of_interest' && $grants['node'] === AccessInterface::KILL) { // Override node module's restriction to no opinion. We don't want to // provide our own access hook, we only want to take out node module's part // in the access handling of this field. We also don't want to switch node // module's grant to TRUE, because the grants of other modules should still // decide on their own if this field is accessible or not. - $grants['node'] = NULL; + $grants['node'] = AccessInterface::DENY; } } diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index 9c2b0b9..d2e0d8f 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -5,6 +5,7 @@ * Test module for the entity API providing several entity types for testing. */ +use Drupal\Core\Access\AccessInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; @@ -400,13 +401,14 @@ function entity_test_entity_field_access($operation, FieldDefinitionInterface $f if ($field_definition->getFieldName() == 'field_test_text') { if ($items) { if ($items[0]->value == 'no access value') { - return FALSE; + return AccessInterface::KILL; } elseif ($operation == 'delete' && $items[0]->value == 'no delete access value') { - return FALSE; + return AccessInterface::KILL; } } } + return AccessInterface::DENY; } /**