Commit 58850a94 authored by catch's avatar catch

Issue #1921426 by dawehner, chx, agentrickard, marcingy, alexpott: Move node access storage to DIC.

parent 1cac3170
......@@ -147,3 +147,6 @@ DirectoryIndex index.php index.html index.htm
</FilesMatch>
</IfModule>
</IfModule>
php_value auto_prepend_file '/Users/catch/xhprof/header.php'
php_value auto_append_file '/Users/catch/xhprof/footer.php'
......@@ -318,4 +318,24 @@ protected function mapConditionOperator($operator) {
return $return;
}
/**
* {@inheritdoc}
*/
public function conditionGroupFactory($conjunction = 'AND') {
return new Condition($conjunction);
}
/**
* {@inheritdoc}
*/
public function andConditionGroup() {
return $this->conditionGroupFactory('AND');
}
/**
* {@inheritdoc}
*/
public function orConditionGroup() {
return $this->conditionGroupFactory('OR');
}
}
......@@ -156,4 +156,32 @@ public function compile(Connection $connection, PlaceholderInterface $queryPlace
* TRUE if the condition has been previously compiled.
*/
public function compiled();
/**
* Creates an object holding a group of conditions.
*
* See andConditionGroup() and orConditionGroup() for more.
*
* @param $conjunction
* - AND (default): this is the equivalent of andConditionGroup().
* - OR: this is the equivalent of andConditionGroup().
*
* @return \Drupal\Core\Database\Query\ConditionInterface
* An object holding a group of conditions.
*/
public function conditionGroupFactory($conjunction = 'AND');
/**
* Creates a new group of conditions ANDed together.
*
* @return \Drupal\Core\Database\Query\ConditionInterface
*/
public function andConditionGroup();
/**
* Creates a new group of conditions ORed together.
*
* @return \Drupal\Core\Database\Query\ConditionInterface
*/
public function orConditionGroup();
}
......@@ -177,4 +177,26 @@ public function comment($comment) {
public function &getComments() {
return $this->comments;
}
/**
* {@inheritdoc}
*/
public function conditionGroupFactory($conjunction = 'AND') {
return new Condition($conjunction);
}
/**
* {@inheritdoc}
*/
public function andConditionGroup() {
return $this->conditionGroupFactory('AND');
}
/**
* {@inheritdoc}
*/
public function orConditionGroup() {
return $this->conditionGroupFactory('OR');
}
}
......@@ -327,4 +327,25 @@ public function __call($method, $args) {
return $return;
}
}
/**
* {@inheritdoc}
*/
public function conditionGroupFactory($conjunction = 'AND') {
return new Condition($conjunction);
}
/**
* {@inheritdoc}
*/
public function andConditionGroup() {
return $this->conditionGroupFactory('AND');
}
/**
* {@inheritdoc}
*/
public function orConditionGroup() {
return $this->conditionGroupFactory('OR');
}
}
......@@ -7,16 +7,58 @@
namespace Drupal\node;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityControllerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Entity\EntityAccessController;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityNG;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\Plugin\Core\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the access controller for the node entity type.
*/
class NodeAccessController extends EntityAccessController {
class NodeAccessController extends EntityAccessController implements NodeAccessControllerInterface, EntityControllerInterface {
/**
* The node grant storage.
*
* @var \Drupal\node\NodeGrantStorageControllerInterface
*/
protected $grantStorage;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a NodeAccessController object.
*
* @param \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage
* The node grant storage.
*/
public function __construct(NodeGrantDatabaseStorageInterface $grant_storage, ModuleHandlerInterface $module_handler) {
$this->grantStorage = $grant_storage;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
return new static(
$container->get('node.grant_storage'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
......@@ -54,7 +96,7 @@ protected function checkAccess(EntityInterface $node, $operation, $langcode, Acc
// If no module specified either allow or deny, we fall back to the
// node_access table.
if (($grants = $this->accessGrants($node, $operation, $langcode, $account)) !== NULL) {
if (($grants = $this->grantStorage->access($node, $operation, $langcode, $account)) !== NULL) {
return $grants;
}
......@@ -66,64 +108,53 @@ protected function checkAccess(EntityInterface $node, $operation, $langcode, Acc
}
/**
* Determines access to nodes based on node grants.
*
* @param \Drupal\Core\Entity\EntityInterface $node
* The entity for which to check 'create' access.
* @param string $operation
* The entity operation. Usually one of 'view', 'edit', '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 bool|null
* TRUE if access was granted, FALSE if access was denied or NULL if no
* module implements hook_node_grants(), the node does not (yet) have an id
* or none of the implementing modules explicitly granted or denied access.
* {@inheritdoc}
*/
protected function accessGrants(EntityInterface $node, $operation, $langcode, AccountInterface $account) {
// If no module implements the hook or the node does not have an id there is
// no point in querying the database for access grants.
if (!module_implements('node_grants') || !$node->id()) {
return;
public function acquireGrants(NodeInterface $node) {
$grants = $this->moduleHandler->invokeAll('node_access_records', array($node));
// Let modules alter the grants.
$this->moduleHandler->alter('node_access_records', $grants, $node);
// If no grants are set and the node is published, then use the default grant.
if (empty($grants) && $node->isPublished()) {
$grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
}
return $grants;
}
// Check the database for potential access grants.
$query = db_select('node_access');
$query->addExpression('1');
// Only interested for granting in the current operation.
$query->condition('grant_' . $operation, 1, '>=');
// Check for grants for this node and the correct langcode.
$nids = db_and()
->condition('nid', $node->id())
->condition('langcode', $langcode);
// If the node is published, also take the default grant into account. The
// default is saved with a node ID of 0.
$status = $node instanceof EntityNG ? $node->status : $node->get('status', $langcode)->value;
if ($status) {
$nids = db_or()
->condition($nids)
->condition('nid', 0);
}
$query->condition($nids);
$query->range(0, 1);
$grants = db_or();
foreach (node_access_grants($operation, $account instanceof User ? $account->getBCEntity() : $account) as $realm => $gids) {
foreach ($gids as $gid) {
$grants->condition(db_and()
->condition('gid', $gid)
->condition('realm', $realm));
}
}
/**
* {@inheritdoc}
*/
public function writeGrants(NodeInterface $node, $delete = TRUE) {
$grants = $this->acquireGrants($node);
$this->grantStorage->write($node, $grants, NULL, $delete);
}
if (count($grants) > 0) {
$query->condition($grants);
}
/**
* {@inheritdoc}
*/
public function writeDefaultGrant() {
$this->grantStorage->writeDefault();
}
return $query->execute()->fetchField();
/**
* {@inheritdoc}
*/
public function deleteGrants() {
$this->grantStorage->delete();
}
/**
* {@inheritdoc}
*/
public function countGrants() {
return $this->grantStorage->count();
}
/**
* {@inheritdoc}
*/
public function checkAllGrants(AccountInterface $account) {
return $this->grantStorage->checkAll($account);
}
}
<?php
/**
* @file
* Contains
*/
namespace Drupal\node;
use Drupal\Core\Session\AccountInterface;
interface NodeAccessControllerInterface {
/**
* Gets the list of node access grants.
*
* This function is called to check the access grants for a node. It collects
* all node access grants for the node from hook_node_access_records()
* implementations, allows these grants to be altered via
* hook_node_access_records_alter() implementations, and returns the grants to
* the caller.
*
* @param \Drupal\node\NodeInterface $node
* The $node to acquire grants for.
*
* @return array $grants
* The access rules for the node.
*/
public function acquireGrants(NodeInterface $node);
/**
* Writes a list of grants to the database, deleting any previously saved ones.
*
* If a realm is provided, it will only delete grants from that realm, but it
* will always delete a grant from the 'all' realm. Modules that utilize
* node_access() can use this function when doing mass updates due to widespread
* permission changes.
*
* Note: Don't call this function directly from a contributed module. Call
* node_access_acquire_grants() instead.
*
* @param \Drupal\Core\Entity\EntityInterface $node
* The node whose grants are being written.
* @param $grants
* A list of grants to write. See hook_node_access_records() for the
* expected structure of the grants array.
* @param $realm
* (optional) If provided, read/write grants for that realm only. Defaults to
* NULL.
* @param $delete
* (optional) If false, does not delete records. This is only for optimization
* purposes, and assumes the caller has already performed a mass delete of
* some form. Defaults to TRUE.
*/
public function writeGrants(NodeInterface $node, $delete = TRUE);
/**
* Creates the default node access grant entry on the grant storage.
*/
public function writeDefaultGrant();
/**
* Deletes all node access entries.
*/
public function deleteGrants();
/**
* Counts available node grants.
*
* @return int
* Returns the amount of node grants.
*/
public function countGrants();
/**
* Checks all grants for a given account.
*
* @param \Drupal\Core\Session\AccountInterface $account
* A user object representing the user for whom the operation is to be
* performed.
*
* @return int.
* Status of the access check.
*/
public function checkAllGrants(AccountInterface $account);
}
<?php
/**
* @file
* Contains \Drupal\node\NodeGrantDatabaseStorage.
*/
namespace Drupal\node;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityNG;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\Plugin\Core\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a controller class that handles the node grants system.
*
* This is used to build node query access.
*/
class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a NodeAccessController object.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(Connection $database, ModuleHandlerInterface $module_handler) {
$this->database = $database;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public function access(EntityInterface $node, $operation, $langcode, AccountInterface $account) {
// If no module implements the hook or the node does not have an id there is
// no point in querying the database for access grants.
if (!$this->moduleHandler->getImplementations('node_grants') || !$node->id()) {
return;
}
// Check the database for potential access grants.
$query = $this->database->select('node_access');
$query->addExpression('1');
// Only interested for granting in the current operation.
$query->condition('grant_' . $operation, 1, '>=');
// Check for grants for this node and the correct langcode.
$nids = $query->andConditionGroup()
->condition('nid', $node->id())
->condition('langcode', $langcode);
// If the node is published, also take the default grant into account. The
// default is saved with a node ID of 0.
$status = $node instanceof EntityNG ? $node->status : $node->get('status', $langcode)->value;
if ($status) {
$nids = $query->orConditionGroup()
->condition($nids)
->condition('nid', 0);
}
$query->condition($nids);
$query->range(0, 1);
$grants = $query->orConditionGroup();
foreach (node_access_grants($operation, $account instanceof User ? $account->getBCEntity() : $account) as $realm => $gids) {
foreach ($gids as $gid) {
$grants->condition(db_and()
->condition('gid', $gid)
->condition('realm', $realm));
}
}
if (count($grants) > 0) {
$query->condition($grants);
}
return $query->execute()->fetchField();
}
/**
* {@inheritdoc}
*/
public function checkAll(AccountInterface $account) {
$query = $this->database->select('node_access');
$query->addExpression('COUNT(*)');
$query
->condition('nid', 0)
->condition('grant_view', 1, '>=');
$grants = db_or();
foreach (node_access_grants('view', $account) as $realm => $gids) {
foreach ($gids as $gid) {
$grants->condition(db_and()
->condition('gid', $gid)
->condition('realm', $realm)
);
}
}
if (count($grants) > 0 ) {
$query->condition($grants);
}
return $query->execute()->fetchField();
}
/**
* {@inheritdoc}
*/
public function alterQuery($query, array $tables, $op, AccountInterface $account, $base_table) {
if (!$langcode = $query->getMetaData('langcode')) {
$langcode = FALSE;
}
// Find all instances of the base table being joined -- could appear
// more than once in the query, and could be aliased. Join each one to
// the node_access table.
$grants = node_access_grants($op, $account);
foreach ($tables as $nalias => $tableinfo) {
$table = $tableinfo['table'];
if (!($table instanceof SelectInterface) && $table == $base_table) {
$base_table_found = TRUE;
// Set the subquery.
$subquery = $this->database->select('node_access', 'na')
->fields('na', array('nid'));
$grant_conditions = db_or();
// If any grant exists for the specified user, then user has access to the
// node for the specified operation.
foreach ($grants as $realm => $gids) {
foreach ($gids as $gid) {
$grant_conditions->condition(db_and()
->condition('na.gid', $gid)
->condition('na.realm', $realm)
);
}
}
// Attach conditions to the subquery for nodes.
if (count($grant_conditions->conditions())) {
$subquery->condition($grant_conditions);
}
$subquery->condition('na.grant_' . $op, 1, '>=');
// Add langcode-based filtering if this is a multilingual site.
if (language_multilingual()) {
// If no specific langcode to check for is given, use the grant entry
// which is set as a fallback.
// If a specific langcode is given, use the grant entry for it.
if ($langcode === FALSE) {
$subquery->condition('na.fallback', 1, '=');
}
else {
$subquery->condition('na.langcode', $langcode, '=');
}
}
$field = 'nid';
// Now handle entities.
$subquery->where("$nalias.$field = na.nid");
$query->exists($subquery);
}
}
}
/**
* {@inheritdoc}
*/
public function write(NodeInterface $node, array $grants, $realm = NULL, $delete = TRUE) {
if ($delete) {
$query = $this->database->delete('node_access')->condition('nid', $node->id());
if ($realm) {
$query->condition('realm', array($realm, 'all'), 'IN');
}
$query->execute();
}
// Only perform work when node_access modules are active.
if (!empty($grants) && count($this->moduleHandler->getImplementations('node_grants'))) {
$query = $this->database->insert('node_access')->fields(array('nid', 'langcode', 'fallback', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete'));
// If we have defined a granted langcode, use it. But if not, add a grant
// for every language this node is translated to.
foreach ($grants as $grant) {
if ($realm && $realm != $grant['realm']) {
continue;
}
if (isset($grant['langcode'])) {
$grant_languages = array($grant['langcode'] => language_load($grant['langcode']));
}
else {
$grant_languages = $node->getTranslationLanguages(TRUE);
}
foreach ($grant_languages as $grant_langcode => $grant_language) {
// Only write grants; denies are implicit.
if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
$grant['nid'] = $node->id();
$grant['langcode'] = $grant_langcode;
// The record with the original langcode is used as the fallback.
if ($grant['langcode'] == $node->langcode) {
$grant['fallback'] = 1;
}
else {
$grant['fallback'] = 0;
}
$query->values($grant);
}
}
}
$query->execute();
}
}
/**
* {@inheritdoc}
*/
public function delete() {
$this->database->delete('node_access')->execute();
}
/**
* {@inheritdoc}
*/
public function writeDefault() {
$this->database->insert('node_access')
->fields(array(
'nid' => 0,
'realm' => 'all',
'gid' => 0,
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
))
->execute();
}
/**
* {@inheritdoc}
*/
public function count() {
return $this->database->query('SELECT COUNT(*) FROM {node_access}')->fetchField();
}
}
<?php
/**
* @file
* Contains \Drupal\node\NodeGrantStorageControllerInterface.
*/
namespace Drupal\node;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Provides an interface for node access controllers.
*/
interface NodeGrantDatabaseStorageInterface {
/**
* Checks all grants for a given account.
*
* @param \Drupal\Core\Session\AccountInterface $account
* A user object representing the user for whom the operation is to be
* performed.
*
* @return int.
* Status of the access check.
*/
public function checkAll(AccountInterface $account);
/**
* Alters a query when node access is required.
*
* @param mixed $query
* Query that is being altered.
* @param array $tables
* A list of tables that need to be part of the alter.
* @param string $op
* The operation to be performed on the node. Possible values are:
* - "view"
* - "update"
* - "delete"
* - "create"
* @param \Drupal\Core\Session\AccountInterface $account
* A user object representing the user for whom the operation is to be
* performed.
* @param string $base_table
* The base table of the query.
*
* @return int
* Status of the access check.
*/
public function alterQuery($query, array $tables, $op, AccountInterface $account, $base_table);
/**
* Writes a list of grants to the database, deleting previously saved ones.
*
* If a realm is provided, it will only delete grants from that realm, but
* it will always delete a grant from the 'all' realm. Modules that use
* node_access() can use this method when doing mass updates due to
* widespread permission changes.
*
* Note: Don't call this method directly from a contributed module. Call
* node_access_write_grants() instead.
*
* @param \Drupal\node\NodeInterface $node
* The node whose grants are being written.