Commit 7dd31494 authored by catch's avatar catch

Issue #1979094 by Berdir, dawehner, msonnabaum, tim.plunkett, effulgentsia:...

Issue #1979094 by Berdir, dawehner, msonnabaum, tim.plunkett, effulgentsia: Fixed Separate create access operation entity access controllers to avoid costly EntityNG instantiation.
parent 780bc62e
......@@ -865,21 +865,11 @@ function entity_page_access(EntityInterface $entity, $operation = 'view') {
* The entity type.
* @param string $bundle
* (optional) The bundle of the entity. Required if the entity supports
* bundles, defaults to the entity type otherwise.
* bundles, defaults to NULL otherwise.
*
* @return bool
* TRUE if the access is granted. FALSE if access is denied.
*/
function entity_page_create_access($entity_type, $bundle = NULL) {
$definition = Drupal::entityManager()->getDefinition($entity_type);
// Pass in the entity bundle if given and required.
$values = array();
if ($bundle && isset($definition['entity_keys']['bundle'])) {
$values[$definition['entity_keys']['bundle']] = $bundle;
}
$entity = Drupal::entityManager()
->getStorageController($entity_type)
->create($values);
return $entity->access('create');
return Drupal::entityManager()->getAccessController($entity_type)->createAccess($bundle);
}
......@@ -273,6 +273,11 @@ public function getIterator() {
* Implements \Drupal\Core\TypedData\AccessibleInterface::access().
*/
public function access($operation = 'view', AccountInterface $account = NULL) {
if ($operation == 'create') {
return \Drupal::entityManager()
->getAccessController($this->entityType)
->createAccess($this->bundle(), $account);
}
return \Drupal::entityManager()
->getAccessController($this->entityType)
->access($this, $operation, Language::LANGCODE_DEFAULT, $account);
......
......@@ -22,15 +22,30 @@ class EntityAccessController implements EntityAccessControllerInterface {
*/
protected $accessCache = array();
/**
* The entity type of the access controller instance.
*
* @var string
*/
protected $entityType;
/**
* Constructs an access controller instance.
*
* @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::LANGCODE_DEFAULT, AccountInterface $account = NULL) {
if (!$account) {
$account = $GLOBALS['user'];
}
$account = $this->prepareUser($account);
if (($access = $this->getCache($entity, $operation, $langcode, $account)) !== NULL) {
if (($access = $this->getCache($entity->uuid(), $operation, $langcode, $account)) !== NULL) {
// Cache hit, no work necessary.
return $access;
}
......@@ -45,17 +60,36 @@ 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 module had an opinion about the access, so let's the access
// controller check create access.
$return = (bool) $this->checkAccess($entity, $operation, $langcode, $account);
}
return $this->setCache($return, $entity->uuid(), $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.
*
* @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.
*/
protected function processAccessHookResults(array $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);
}
/**
......@@ -85,8 +119,9 @@ 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 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'.
......@@ -100,13 +135,10 @@ 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) {
$uid = $account ? $account->id() : 0;
$uuid = $entity->uuid();
protected function getCache($cid, $operation, $langcode, AccountInterface $account) {
// 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[$account->id()][$cid][$langcode][$operation])) {
return $this->accessCache[$account->id()][$cid][$langcode][$operation];
}
}
......@@ -115,8 +147,9 @@ 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 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'.
......@@ -128,12 +161,9 @@ 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) {
$uid = $account ? $account->id() : 0;
$uuid = $entity->uuid();
protected function setCache($access, $cid, $operation, $langcode, AccountInterface $account) {
// Save the given value in the static cache and directly return it.
return $this->accessCache[$uid][$uuid][$langcode][$operation] = (bool) $access;
return $this->accessCache[$account->id()][$cid][$langcode][$operation] = (bool) $access;
}
/**
......@@ -143,4 +173,75 @@ public function resetCache() {
$this->accessCache = array();
}
/**
* {@inheritdoc}
*/
public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = array()) {
$account = $this->prepareUser($account);
$context += array(
'langcode' => Language::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 $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 module had an opinion about the access, so let's the access
// controller check create access.
$return = (bool) $this->checkCreateAccess($account, $context, $entity_bundle);
}
return $this->setCache($return, $cid, '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.
* @param string|null $entity_bundle
* (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.
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return NULL;
}
/**
* 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 = $GLOBALS['user'];
}
return $account;
}
}
......@@ -18,11 +18,14 @@ interface EntityAccessControllerInterface {
/**
* Checks access to an operation on a given entity or entity translation.
*
* Use \Drupal\Core\Entity\EntityAccessControllerInterface::createAccess()
* to check access to create an entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which to check access.
* @param string $operation
* The operation access should be checked for.
* Usually one of "view", "create", "update" or "delete".
* Usually one of "view", "update" or "delete".
* @param string $langcode
* (optional) The language code for which to check access. Defaults to
* Language::LANGCODE_DEFAULT.
......@@ -36,6 +39,21 @@ interface EntityAccessControllerInterface {
public function access(EntityInterface $entity, $operation, $langcode = Language::LANGCODE_DEFAULT, AccountInterface $account = NULL);
/**
* Checks access to create an entity.
*
* @param string $entity_bundle
* (optional) The bundle of the entity. Required if the entity supports
* bundles, defaults to NULL otherwise.
* @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
* (optional) An array of key-value pairs to pass additional context when
* needed.
*/
public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = array());
/**
* Clears all cached access checks.
*/
public function resetCache();
......
......@@ -52,35 +52,7 @@ public function appliesTo() {
*/
public function access(Route $route, Request $request) {
list($entity_type, $bundle) = explode(':', $route->getRequirement($this->requirementsKey) . ':');
$definition = $this->entityManager->getDefinition($entity_type);
$values = $this->prepareEntityValues($definition, $request, $bundle);
$entity = $this->entityManager->getStorageController($entity_type)
->create($values);
return $this->entityManager->getAccessController($entity_type)->access($entity, 'create');
}
/**
* Prepare the values passed into the storage controller.
*
* @param array $definition
* The entity type definition.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param string $bundle
* (optional) The bundle to check access for.
*
* @return array
* An array of values to be used when creating the entity.
*/
protected function prepareEntityValues(array $definition, Request $request, $bundle = NULL) {
$values = array();
if ($bundle && isset($definition['entity_keys']['bundle'])) {
$values[$definition['entity_keys']['bundle']] = $bundle;
}
return $values;
return $this->entityManager->getAccessController($entity_type)->createAccess($bundle);
}
}
......@@ -413,6 +413,11 @@ public function isEmpty() {
* {@inheritdoc}
*/
public function access($operation = 'view', AccountInterface $account = NULL) {
if ($operation == 'create') {
return \Drupal::entityManager()
->getAccessController($this->entityType)
->createAccess($this->bundle(), $account);
}
return \Drupal::entityManager()
->getAccessController($this->entityType)
->access($this, $operation, $this->activeLangcode, $account);
......
......@@ -23,9 +23,16 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
if ($operation === 'view') {
return TRUE;
}
elseif (in_array($operation, array('create', 'update', 'delete'))) {
elseif (in_array($operation, array('update', 'delete'))) {
return user_access('administer blocks', $account);
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return user_access('administer blocks', $account);
}
}
......@@ -23,9 +23,16 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
if ($operation === 'view') {
return TRUE;
}
elseif (in_array($operation, array('create', 'update', 'delete'))) {
elseif (in_array($operation, array('update', 'delete'))) {
return user_access('administer blocks', $account);
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return user_access('administer blocks', $account);
}
}
......@@ -27,10 +27,6 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
return user_access('access comments', $account);
break;
case 'create':
return user_access('post comments', $account);
break;
case 'update':
return ($account->id() && $account->id() == $entity->uid->value && $entity->status->value == COMMENT_PUBLISHED && user_access('edit own comments', $account)) || user_access('administer comments', $account);
break;
......@@ -45,4 +41,11 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return user_access('post comments', $account);
}
}
......@@ -24,4 +24,11 @@ public function access(EntityInterface $entity, $operation, $langcode = Language
return TRUE;
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return TRUE;
}
}
......@@ -36,4 +36,11 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
return !empty($permission) && user_access($permission, $account);
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return user_access('administer filters', $account);
}
}
......@@ -31,9 +31,16 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
}
}
if (in_array($operation, array('create', 'update', 'delete'))) {
if (in_array($operation, array('update', 'delete'))) {
return user_access('administer menu', $account);
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return user_access('administer menu', $account);
}
}
......@@ -41,12 +41,17 @@ class NodeAccessController extends EntityAccessController implements NodeAccessC
/**
* Constructs a NodeAccessController object.
*
* @param string $entity_type
* The entity type of the access controller instance.
* @param \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage
* The node grant storage.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
public function __construct(NodeGrantDatabaseStorageInterface $grant_storage, ModuleHandlerInterface $module_handler) {
$this->grantStorage = $grant_storage;
$this->moduleHandler = $module_handler;
public function __construct($entity_type, NodeGrantDatabaseStorageInterface $grant_storage, ModuleHandlerInterface $module_handler) {
parent::__construct($entity_type);
$this->grantStorage = $grant_storage;
$this->moduleHandler = $module_handler;
}
/**
......@@ -54,6 +59,7 @@ public function __construct(NodeGrantDatabaseStorageInterface $grant_storage, Mo
*/
public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
return new static(
$entity_type,
$container->get('node.grant_storage'),
$container->get('module_handler')
);
......@@ -73,6 +79,22 @@ public function access(EntityInterface $entity, $operation, $langcode = Language
return parent::access($entity, $operation, $langcode, $account);
}
/**
* {@inheritdoc}
*/
public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = array()) {
$account = $this->prepareUser($account);
if (user_access('bypass node access', $account)) {
return TRUE;
}
if (!user_access('access content', $account)) {
return FALSE;
}
return parent::createAccess($entity_bundle, $account, $context);
}
/**
* {@inheritdoc}
*/
......@@ -107,6 +129,16 @@ protected function checkAccess(EntityInterface $node, $operation, $langcode, Acc
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
$configured_types = node_permissions_get_configured_types();
if (isset($configured_types[$entity_bundle])) {
return user_access('create ' . $entity_bundle . ' content', $account);
}
}
/**
* {@inheritdoc}
*/
......
......@@ -28,4 +28,11 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
return user_access('administer content types', $account);
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return user_access('administer content types', $account);
}
}
......@@ -2028,11 +2028,19 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) {
* @see node_menu()
*/
function node_access($op, $node, $account = NULL, $langcode = NULL) {
if (!$node instanceof EntityInterface) {
$node = entity_create('node', array('type' => $node));
}
elseif ($node instanceof NodeTypeInterface) {
$node = entity_create('node', array('type' => $node->id()));
$access_controller = Drupal::entityManager()->getAccessController('node');
if ($op == 'create') {
if (!$node instanceof EntityInterface) {
$bundle = $node;
}
elseif ($node instanceof NodeTypeInterface) {
$bundle = $node->id();
}
else {
$bundle = $node->bundle();
}
return $access_controller->createAccess($bundle, $account, array('langcode' => $langcode));
}
// If no language code was provided, default to the node's langcode.
......@@ -2051,14 +2059,7 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
}
}
}
// Make sure that if an account is passed, that it is a fully loaded user
// object.
if ($account && !($account instanceof UserInterface)) {
$account = user_load($account->id());
}
return Drupal::entityManager()->getAccessController('node')->access($node, $op, $langcode, $account);
return $access_controller->access($node, $op, $langcode, $account);
}
/**
......
......@@ -23,9 +23,16 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
if ($operation === 'view') {
return TRUE;
}
elseif (in_array($operation, array('create', 'update', 'delete'))) {
elseif (in_array($operation, array('update', 'delete'))) {
return user_access('administer pictures', $account);
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return user_access('administer pictures', $account);
}
}
......@@ -27,9 +27,16 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
}
return user_access('view test entity', $account);
}
elseif (in_array($operation, array('create', 'update', 'delete'))) {
elseif (in_array($operation, array('update', 'delete'))) {
return user_access('administer entity_test content', $account);
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return user_access('administer entity_test content', $account);
}
}
......@@ -9,6 +9,7 @@
use Drupal\Core\Entity\EntityCreateAccessCheck;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Provides an access check for taxonomy term creation.
......@@ -23,12 +24,12 @@ class TaxonomyTermCreateAccess extends EntityCreateAccessCheck {
/**
* {@inheritdoc}
*/
protected function prepareEntityValues(array $definition, Request $request, $bundle = NULL) {
$values = array();
public function access(Route $route, Request $request) {
$entity_type = $route->getRequirement($this->requirementsKey);
if ($vocabulary = $request->attributes->get('taxonomy_vocabulary')) {
$values = parent::prepareEntityValues($definition, $request, $vocabulary->id());
return $this->entityManager->getAccessController($entity_type)->createAccess($vocabulary->id());
}
return $values;
return parent::access($route, $request);
}
}
......@@ -27,10 +27,6 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
return user_access('access content', $account);
break;
case 'create':
return user_access('administer taxonomy', $account);
break;
case 'update':
return user_access("edit terms in {$entity->bundle()}", $account) || user_access('administer taxonomy', $account);
break;
......@@ -41,4 +37,11 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return user_access('administer taxonomy', $account);
}
}
......@@ -25,4 +25,11 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
return user_access('administer taxonomy', $account);
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return user_access('administer taxonomy', $account);
}
}
......@@ -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;
......
......@@ -31,4 +31,11 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return user_access('administer permissions', $account);
}
}
......@@ -25,10 +25,6 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
return $this->viewAccess($entity, $langcode, $account);
break;
case 'create':
return user_access('administer users', $account);
break;