diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.php index 2566443..beb97bf 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.php @@ -10,12 +10,12 @@ use Drupal\Core\Language\Language; /** - * Test case to verify node_access functionality for multiple languages. + * Tests node access with multiple languages and access control modules. */ class NodeAccessLanguageAwareCombinationTest extends NodeTestBase { /** - * Modules to enable. + * Enable language and two node access modules. * * @var array */ @@ -24,21 +24,11 @@ class NodeAccessLanguageAwareCombinationTest extends NodeTestBase { public static function getInfo() { return array( 'name' => 'Node access language aware combination', - 'description' => 'Test node_access and db_select with node_access tag functionality with multiple languages with node_access_test_language and node_access_test module.', + 'description' => 'Tests node access functionality with multiple languages and two node access modules.', 'group' => 'Node', ); } - /** - * Asserts node_access correctly grants or denies access. - */ - function assertNodeAccess($ops, $node, $account, $langcode = NULL) { - foreach ($ops as $op => $result) { - $msg = t("node_access returns @result with operation '@op', language code @langcode.", array('@result' => $result ? 'true' : 'false', '@op' => $op, '@langcode' => !empty($langcode) ? "'$langcode'" : 'empty')); - $this->assertEqual($result, node_access($op, $node, $account, $langcode), $msg); - } - } - function setUp() { parent::setUp(); @@ -61,7 +51,7 @@ function setUp() { } /** - * Runs tests for node_access function with multiple languages. + * Tests the node_access() function with multiple node languages. */ function testNodeAccessLanguageAwareCombination() { $web_user = $this->drupalCreateUser(array('access content')); @@ -192,7 +182,7 @@ function testNodeAccessLanguageAwareCombination() { } /** - * Runs tests for db_select with node_access tag and langcode. + * Tests db_select() with a 'node_access' tag and langcode metadata. */ function testNodeAccessLanguageAwareQueryTag() { $web_user = $this->drupalCreateUser(array('access content')); diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.php index 7042a14..46d200f 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.php @@ -10,12 +10,12 @@ use Drupal\Core\Language\Language; /** - * Test case to verify node_access functionality for multiple languages. + * Tests node access functionality for multiple languages. */ class NodeAccessLanguageAwareTest extends NodeTestBase { /** - * Modules to enable. + * Enable language and a language-aware node access module. * * @var array */ @@ -29,16 +29,6 @@ public static function getInfo() { ); } - /** - * Asserts node_access correctly grants or denies access. - */ - function assertNodeAccess($ops, $node, $account, $langcode = NULL) { - foreach ($ops as $op => $result) { - $msg = t("node_access returns @result with operation '@op', language code @langcode.", array('@result' => $result ? 'true' : 'false', '@op' => $op, '@langcode' => !empty($langcode) ? "'$langcode'" : 'empty')); - $this->assertEqual($result, node_access($op, $node, $account, $langcode), $msg); - } - } - function setUp() { parent::setUp(); @@ -61,7 +51,7 @@ function setUp() { } /** - * Runs tests for node_access function with multiple languages. + * Tests node_access() with multiple node languages. */ function testNodeAccessLanguageAware() { $web_user = $this->drupalCreateUser(array('access content')); @@ -168,7 +158,7 @@ function testNodeAccessLanguageAware() { } /** - * Runs tests for db_select with node_access tag and langcode. + * Tests db_select() with a 'node_access' tag and langcode metadata. */ function testNodeAccessLanguageAwareQueryTag() { $web_user = $this->drupalCreateUser(array('access content')); diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php index 2f82341..9fc802c 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php @@ -24,21 +24,11 @@ class NodeAccessLanguageTest extends NodeTestBase { public static function getInfo() { return array( 'name' => 'Node access language', - 'description' => 'Test node_access and db_select with node_access tag functionality with multiple languages with node_access_test_module which is not language aware.', + 'description' => 'Test node_access and db_select with node_access tag functionality with multiple languages with a test node access module that is not language-aware.', 'group' => 'Node', ); } - /** - * Asserts node_access correctly grants or denies access. - */ - function assertNodeAccess($ops, $node, $account, $langcode = NULL) { - foreach ($ops as $op => $result) { - $msg = t("node_access returns @result with operation '@op', language code @langcode.", array('@result' => $result ? 'true' : 'false', '@op' => $op, '@langcode' => !empty($langcode) ? "'$langcode'" : 'empty')); - $this->assertEqual($result, node_access($op, $node, $account, $langcode), $msg); - } - } - function setUp() { parent::setUp(); @@ -64,8 +54,7 @@ function setUp() { } /** - * Runs tests for node_access function with multiple languages and no private - * nodes. + * Tests node_access() with multiple node languages and no private nodes. */ function testNodeAccess() { $web_user = $this->drupalCreateUser(array('access content')); @@ -122,8 +111,7 @@ function testNodeAccess() { } /** - * Runs tests for node_access function with multiple languages and private - * nodes. + * Tests node_access() with multiple node languages and private nodes. */ function testNodeAccessPrivate() { $web_user = $this->drupalCreateUser(array('access content')); @@ -180,7 +168,7 @@ function testNodeAccessPrivate() { } /** - * Runs tests for db_select with node_access tag and langcode. + * Tests db_select() with a 'node_access' tag and langcode metadata. */ function testNodeAccessQueryTag() { $web_user = $this->drupalCreateUser(array('access content')); @@ -248,4 +236,5 @@ function testNodeAccessQueryTag() { // the user is user 1. $this->assertEqual(count($nids), 3, 'db_select returns all three nodes.'); } + } diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php b/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php index 1bba558..40121eb 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php @@ -30,4 +30,35 @@ function setUp() { $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); } } + + /** + * Asserts that node_access() correctly grants or denies access. + * + * @param array $ops + * An associative array of the expected node access grants for the node + * and account, with each key as the name of an operation (e.g. 'view', + * 'delete') and each value a Boolean indicating whether access to that + * operation should be granted. + * @param \Drupal\node\Plugin\Core\Entity\Node $node + * The node object to check. + * @param \Drupal\user\Plugin\Core\Entity\User $account + * The user account for which to check access. + * @param string|null $langcode + * (optional) The language code indicating which translation of the node + * to check. If NULL, the untranslated (fallback) access is checked. + */ + function assertNodeAccess(array $ops, $node, $account, $langcode = NULL) { + foreach ($ops as $op => $result) { + $msg = format_string( + "node_access() returns @result with operation %op, language code %langcode.", + array( + '@result' => $result ? 'true' : 'false', + '%op' => $op, + '%langcode' => !empty($langcode) ? $langcode : 'empty' + ) + ); + $this->assertEqual($result, node_access($op, $node, $account, $langcode), $msg); + } + } + } diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index ff9507e..9624dd8 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -231,14 +231,16 @@ function hook_node_grants($account, $op) { * of this gid within this realm can edit this node. * - 'grant_delete': If set to 1 a user that has been identified as a member * of this gid within this realm can delete this node. - * - 'langcode': Optional key. The language code of the grant version. This - * value is set automatically from the $node parameter during database - * storage. - * - * - * When an implementation is interested in a node in Catalan language, but want - * to deny access to everyone, it may return a "deny all" grant: - * + * - langcode: (optional) The language code of a specific translation of the + * node, if any. Modules may add this key to grant different access to + * different translations of a node, such that (e.g.) a particular group + * is granted access to edit the Catalan version of the node, but not the + * Hungarian version. If no value is provided, the langcode is set + * set automatically from the $node parameter and the node's original + * language (if specified) is used as a fallback. + * + * A "deny all" grant may be used to deny all access to a particular node or + * node translation: * @code * $grants[] = array( * 'realm' => 'all', @@ -246,16 +248,14 @@ function hook_node_grants($account, $op) { * 'grant_view' => 0, * 'grant_update' => 0, * 'grant_delete' => 0, - * 'priority' => 1, - * 'langcode' => 'ca' + * 'langcode' => 'ca', * ); * @endcode - * - * Setting the priority should cancel out other grants. In the case of a - * conflict between modules, it is safer to use hook_node_access_records_alter() - * to return only the deny grant. - * - * Note: a deny all grant is not written to the database; denies are implicit. + * Note that another module node access module could override this by granting + * access to one or more nodes, since grants are additive. To enforce that + * access is denied in a particular case, use hook_node_access_records_alter(). + * Also node that a deny all grant is not written to the database; denies are + * implicit. * * @param Drupal\node\Node $node * The node that has just been saved. @@ -272,8 +272,9 @@ function hook_node_access_records(Drupal\node\Node $node) { // treated just like any other node and we completely ignore it. if ($node->private) { $grants = array(); - // Only published Catalan nodes should be viewable to all users. If we allow access - // blindly here, then all users could view an unpublished node. + // Only published Catalan translations of private nodes should be viewable + // to all users. If we fail to check $node->status, all users would be able + // to view an unpublished node. if ($node->status) { $grants[] = array( 'realm' => 'example', diff --git a/core/modules/node/node.module b/core/modules/node/node.module index ab64edd..22f4931 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -2780,24 +2780,25 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) { // If no language code was provided, default to the node's langcode or // to an empty langcode if a node type was requested. The latter is purely // for caching purposes. - if (empty($langcode) && (is_object($node) && isset($node->nid))) { - if (module_exists('language')) { - // Load language from content negotiation. - $content_negotiation_langcode = language(LANGUAGE_TYPE_CONTENT)->langcode; - // Load languages the node exists in. - $node_translations = $node->getTranslationLanguages(); - // If the node does not exist in the language from content negotiation - // return the default language of the node. - if (isset($node_translations[$content_negotiation_langcode])) { - $langcode = $content_negotiation_langcode; - } else { - $langcode = $node->langcode; - } - } else { + if (empty($langcode)) { + // Initialize the langcode as an empty string. + $langcode = ''; + if (is_object($node) && isset($node->nid)) { + // Default to the node's default langcode. $langcode = $node->langcode; + // If the Language module is enabled, try to use the language from + // content negotiation. + if (module_exists('language')) { + // Load languages the node exists in. + $node_translations = $node->getTranslationLanguages(); + // Load the language from content negotiation. + $content_negotiation_langcode = language(LANGUAGE_TYPE_CONTENT)->langcode; + // If there is a translation available, use it. + if (isset($node_translations[$content_negotiation_langcode])) { + $langcode = $content_negotiation_langcode; + } + } } - } else if (empty($langcode)){ - $langcode = ''; } // If we've already checked access for this node, user and op, return from @@ -2848,8 +2849,8 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) { ->condition('langcode', $langcode); if ($node->status) { $nids = db_or() - ->condition($nids) - ->condition('nid', 0); + ->condition($nids) + ->condition('nid', 0); } $query->condition($nids); $query->range(0, 1); @@ -3208,13 +3209,8 @@ function node_access_acquire_grants(Node $node, $delete = TRUE) { * @param Drupal\node\Node $node * The node whose grants are being written. * @param $grants - * A list of grants to write. Each grant is an array that must contain the - * following keys: realm, gid, grant_view, grant_update, grant_delete and - * langcode is an optional key which is set automatically from $node - * parameter. - * The realm is specified by a particular module; the gid is as well, and - * is a module-defined id to define grant privileges. each grant_* field - * is a boolean value. + * 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. @@ -3247,7 +3243,7 @@ function _node_access_write_grants(Node $node, $grants, $realm = NULL, $delete = if (!isset($grant['langcode'])) { $grant['langcode'] = $node->langcode; } - // The record with the original langcode is used as fallback. + // The record with the original langcode is used as the fallback. if ($grant['langcode'] == $node->langcode) { $grant['fallback'] = 1; } diff --git a/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.info b/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.info index d5a9c7c..e8f72c5 100644 --- a/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.info +++ b/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.info @@ -1,5 +1,5 @@ name = "Node module access tests language" -description = "Support module for language aware node permission testing." +description = "Support module for language-aware node access testing." package = Testing version = VERSION core = 8.x diff --git a/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.module b/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.module index 41c6682..b5c7880 100644 --- a/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.module +++ b/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.module @@ -2,9 +2,11 @@ /** * @file - * Dummy module implementing language aware node access related hooks to - * test API interaction with the Node module. This module restricts - * view permission to those with a special 'node test view' permission. + * Test module with a language-aware node access implementation. + * + * This module implements language-aware node access hooks to test the node + * access API. This module restricts view permission to those with a special + * 'node test view' permission. */ use Drupal\node\Plugin\Core\Entity\Node; @@ -81,7 +83,10 @@ function node_access_test_language_permission() { return array('node access language test view' => array('title' => 'View content')); } -function node_access_test_language_enable(){ +/** + * Implements hook_disable(). + */ +function node_access_test_language_enable() { $field_private = array( 'field_name' => 'field_private', 'type' => 'list_boolean', @@ -104,6 +109,9 @@ function node_access_test_language_enable(){ $instance = field_create_instance($instance); } -function node_access_test_language_disable(){ +/** + * Implements hook_disable(). + */ +function node_access_test_language_disable() { field_delete_instance(field_read_instance('node', 'field_private', 'page')); }