diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php index 8a6ce99..359ebc7 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php @@ -22,7 +22,7 @@ class NodeAccessLanguageTest extends NodeTestBase { public static function getInfo() { return array( 'name' => 'Node access language', - 'description' => 'Test node_access functionality with multiple languages.', + 'description' => 'Test node_access and db_select with node_access tag functionality with multiple languages.', 'group' => 'Node', ); } @@ -40,10 +40,15 @@ class NodeAccessLanguageTest extends NodeTestBase { function setUp() { parent::setUp(); + node_access_rebuild(); + // Clear permissions for authenticated users. db_delete('role_permission') ->condition('rid', DRUPAL_AUTHENTICATED_RID) ->execute(); + + // Enable the private node feature of node_access_test module. + variable_set('node_access_test_private', TRUE); } /** @@ -60,30 +65,110 @@ class NodeAccessLanguageTest extends NodeTestBase { ); language_save($language); - // Tests the default access provided for a published Hungarian node. $web_user = $this->drupalCreateUser(array('access content')); - $node = $this->drupalCreateNode(array('body' => array('hu' => array(array())), 'langcode' => 'hu')); - $this->assertTrue($node->langcode == 'hu', t('Node created as Hungarian.')); + + // Creating a public Node with langcode Hungarian, will be saved as + // the fallback in node access table. + $node_public = $this->drupalCreateNode(array('body' => array('hu' => array(array())), 'langcode' => 'hu', 'private' => FALSE)); + $this->assertTrue($node_public->langcode == 'hu', t('Node created as Hungarian.')); + + // Tests the default access provided for the public Hungarian node. $expected_node_access = array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE); - $this->assertNodeAccess($expected_node_access, $node, $web_user); + $expected_node_access_no_access = array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE); + $this->assertNodeAccess($expected_node_access, $node_public, $web_user); // Tests that Hungarian provided specifically results in the same. - $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hu'); + $this->assertNodeAccess($expected_node_access, $node_public, $web_user, 'hu'); // There is no specific Catalan version of this node and Croatian is not - // even set up on the system in this scenario, so these languages will not - // play a role in the node's permissions. - $this->assertNodeAccess($expected_node_access, $node, $web_user, 'ca'); - $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hr'); + // even set up on the system in this scenario, so the user will not get + // access to these nodes. + $this->assertNodeAccess($expected_node_access_no_access, $node_public, $web_user, 'ca'); + $this->assertNodeAccess($expected_node_access_no_access, $node_public, $web_user, 'hr'); // Reset the node access cache and turn on our test node_access() code. drupal_static_reset('node_access'); variable_set('node_access_test_secret_catalan', 1); // Tests that Hungarian is still accessible. - $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hu'); + $this->assertNodeAccess($expected_node_access, $node_public, $web_user, 'hu'); // Tests that Catalan is not accessible anymore. - $this->assertNodeAccess(array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE), $node, $web_user, 'ca'); + $this->assertNodeAccess(array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE), $node_public, $web_user, 'ca'); + } + + /** + * Runs tests for db_select with node_access tag and langcode. + */ + function testNodeAccessQueryTag() { + // Add Hungarian and Catalan. + $language = (object) array( + 'langcode' => 'hu', + ); + language_save($language); + $language = (object) array( + 'langcode' => 'ca', + ); + language_save($language); + + $web_user = $this->drupalCreateUser(array('access content')); + + // Creating a private Node with langcode Hungarian, will be saved as + // the fallback in node access table. + $node_private = $this->drupalCreateNode(array('body' => array('hu' => array(array())), 'langcode' => 'hu', 'private' => TRUE)); + $this->assertTrue($node_private->langcode == 'hu', t('Node created as Hungarian.')); + + // Creating a public Node with langcode Hungarian, will be saved as + // the fallback in node access table. + $node_public = $this->drupalCreateNode(array('body' => array('hu' => array(array())), 'langcode' => 'hu', 'private' => FALSE)); + $this->assertTrue($node_public->langcode == 'hu', t('Node created as Hungarian.')); + + // Query the nodes table as readonly user with Node Access Tag and no + // specific langcode. + $select = db_select('node', 'n') + ->fields('n', array('nid')) + ->addMetaData('account', $web_user) + ->addTag('node_access'); + $nids = $select->execute()->fetchAllAssoc('nid'); + + // Only the public node should be returned. Because no langcode is given it + // will use the fallback node (which is the hungarian node). + $this->assertEqual(count($nids), 1, t('db_select returns only 1 node')); + $this->assertTrue(array_key_exists($node_public->nid, $nids), t('Returned node id is public node')); + + // Query the nodes table as readonly User with Node Access Tag and + // langcode de. + $select = db_select('node', 'n') + ->fields('n', array('nid')) + ->addMetaData('account', $web_user) + ->addMetaData('langcode', 'de') + ->addTag('node_access'); + $nids = $select->execute()->fetchAllAssoc('nid'); + + // Because both created nodes are in Hungarian, no Nodes are returned. + $this->assertTrue(empty($nids), t('db_select returns empty result')); + + // Query the nodes table as User 1 (full access) with Node Access Tag and no + // specific langcode. + $select = db_select('node', 'n') + ->fields('n', array('nid')) + ->addTag('node_access'); + $nids = $select->execute()->fetchAllAssoc('nid'); + + // Both nodes are returned. + $this->assertEqual(count($nids), 2, t('db_select returns both nodes')); + + // Query the nodes table as User 1 (full access) with Node Access Tag and + // langcode de. + $select = db_select('node', 'n') + ->fields('n', array('nid')) + ->addMetaData('langcode', 'de') + ->addTag('node_access'); + $nids = $select->execute()->fetchAllAssoc('nid'); + + // Both nodes are returned because node access tag is not invoked when + // the user is user 1. + $this->assertEqual(count($nids), 2, t('db_select returns both nodes')); + } } diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index a73b851..5484dd5 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -233,10 +233,13 @@ 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 but want to deny access to - * everyone, it may return a "deny all" grant: + * 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: * * @code * $grants[] = array( @@ -246,6 +249,7 @@ function hook_node_grants($account, $op) { * 'grant_update' => 0, * 'grant_delete' => 0, * 'priority' => 1, + * 'langcode' => 'ca' * ); * @endcode * @@ -269,7 +273,7 @@ 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 nodes should be viewable to all users. If we allow access + // Only published Catalan nodes should be viewable to all users. If we allow access // blindly here, then all users could view an unpublished node. if ($node->status) { $grants[] = array( @@ -278,6 +282,7 @@ function hook_node_access_records(Drupal\node\Node $node) { 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0, + 'langcode' => 'ca' ); } // For the example_author array, the GID is equivalent to a UID, which @@ -290,6 +295,7 @@ function hook_node_access_records(Drupal\node\Node $node) { 'grant_view' => 1, 'grant_update' => 1, 'grant_delete' => 1, + 'langcode' => 'ca' ); return $grants; diff --git a/core/modules/node/node.install b/core/modules/node/node.install index 5727129..5a6d3bd 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -148,6 +148,20 @@ function node_schema() { 'not null' => TRUE, 'default' => 0, ), + 'langcode' => array( + 'description' => 'The {language}.langcode of this node.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), + 'fallback' => array( + 'description' => 'Boolean indicating whether this record should be used as a fallback if a language condition is not provided.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 1, + ), 'gid' => array( 'description' => "The grant ID a user must possess in the specified realm to gain this row's privileges on the node.", 'type' => 'int', @@ -601,6 +615,32 @@ function node_update_8004() { } /** + * Add language.langcode and fallback field to node_access table. + */ +function node_update_8005() { + // Add the langcode field. + $langcode_field = array( + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The {language}.langcode of this node.', + ); + db_add_field('node_access', 'langcode', $langcode_field); + + // Add the fallback field. + $fallback_field = array( + 'description' => 'Boolean indicating whether this record should be used as a fallback if a language condition is not provided.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 1, + ); + db_add_field('node_access', 'fallback', $fallback_field); +} + + +/** * @} End of "addtogroup updates-7.x-to-8.x" * The next series of updates should start at 9000. */ diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 349aab5..c226f5a 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -2946,9 +2946,13 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) { $query = db_select('node_access'); $query->addExpression('1'); $query->condition('grant_' . $op, 1, '>='); - $nids = db_or()->condition('nid', $node->nid); + $nids = db_and() + ->condition('nid', $node->nid) + ->condition('langcode', $langcode); if ($node->status) { - $nids->condition('nid', 0); + $nids = db_or() + ->condition($nids) + ->condition('nid', 0); } $query->condition($nids); $query->range(0, 1); @@ -3210,6 +3214,9 @@ function _node_query_node_access_alter($query, $type) { if (!$op = $query->getMetaData('op')) { $op = 'view'; } + if (!$langcode = $query->getMetaData('langcode')) { + $langcode = FALSE; + } // If $account can bypass node access, or there are no node access modules, // or the operation is 'view' and the $acount has a global view grant (i.e., @@ -3294,6 +3301,13 @@ function _node_query_node_access_alter($query, $type) { $subquery->condition($grant_conditions); } $subquery->condition('na.grant_' . $op, 1, '>='); + if ($langcode === FALSE) { + $subquery->condition('na.fallback', 1, '='); + } + else { + $subquery->condition('na.langcode', $langcode, '='); + } + $field = 'nid'; // Now handle entities. if ($type == 'entity') { @@ -3372,7 +3386,9 @@ function node_access_acquire_grants(Node $node, $delete = TRUE) { * nid. * @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. + * 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. @@ -3394,7 +3410,7 @@ function _node_access_write_grants(Node $node, $grants, $realm = NULL, $delete = // Only perform work when node_access modules are active. if (!empty($grants) && count(module_implements('node_grants'))) { - $query = db_insert('node_access')->fields(array('nid', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete')); + $query = db_insert('node_access')->fields(array('nid', 'langcode', 'fallback', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete')); foreach ($grants as $grant) { if ($realm && $realm != $grant['realm']) { continue; @@ -3402,6 +3418,16 @@ function _node_access_write_grants(Node $node, $grants, $realm = NULL, $delete = // Only write grants; denies are implicit. if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) { $grant['nid'] = $node->nid; + if (!isset($grant['langcode'])) { + $grant['langcode'] = $node->langcode; + } + // The record with the original langcode is used as fallback. + if (empty($node->tnid) || $node->nid == $node->tnid) { + $grant['fallback'] = 1; + } + else { + $grant['fallback'] = 0; + } $query->values($grant); } }