Change record status: 
Project: 
Introduced in branch: 
8.x
Introduced in version: 
8.0
Description: 

Drupal 7 core supports a node-copy translation model, where each translation is a separate node and the items are related in a translation set. The node access system supported this level of access specification because each node got its own record for access. Drupal 7 core also includes field translation and our target for Drupal 8 core is to migrate to the field translation system only. This was not yet compatible with node access, because it is not possible to add language variance to node access with the existing tools.

A simple example

Therefore we added language support to node access grants and records, making it possible to specify access grants per language for each node. hook_node_access_records() was modified so that the return value can also include an optional language code:

function example_node_access_records(Drupal\node\Node $node) {
  // ...
  $grants[] = array(
    'realm' => 'example',
    'gid' => 1,
    'grant_view' => 1,
    'grant_update' => 0,
    'grant_delete' => 0,
    // Only grant view for the Italian variant of this node.
    'langcode' => 'it',
  );
  // ..
  return $grants;
}

If a langcode is not provided than the node's original submission language ($node->langcode) is assumed. If the langcode equals the node's original submission language, a fallback = 1 value is automatically added, otherwise the fallback flag is 0.

Node access restricted queries

The fallback flag is needed for node access queries where a language code is not provided. In this case, because there are multiple records for each node, we need to pick one to serve as the definite information for the node access query. We pick the fallback items in that case (which are for the original submission language versions of the node). Examples for a language specified as part of a node access controlled query and lack of languages specified.

// Query with Italian (it) specified. Node access records for 'it' will be
// considered.
$select = db_select('node', 'n')
  ->fields('n', array('nid'))
  ->addMetaData('account', $account)
  ->addMetaData('langcode', 'it')
  ->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');

// Query with no language specified. Node access records for fallbacks
// will be considered (the original language for each node respectively).
$select = db_select('node', 'n')
  ->fields('n', array('nid'))
  ->addMetaData('account', $account)
  ->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');

Language grants are only considered in the query if the site is already multilingual (language_multilingual()), single language sites have no difference. Node access grant providers should use the same logic to return multilingual grants only on multilingual sites.

Node access hook writing tips

1. If your node access module does not want to deal with language variance, you can just ignore this new feature altogether. If you don't include a langcode as part of your grant, the grant will be saved for all langcodes that the node is available in / translated to. If on a single language site, the node can only have a single language, so it will just work. If on a multilingual site, the grant will be saved for all language versions of all nodes as appropriate.

// No langcode returned in grants makes them save for all langcodes appropriate for the node.
function example_node_access_records(Drupal\node\Node $node) {
  // ...
  $grants[] = array(
    'realm' => 'example',
    'gid' => 1,
    'grant_view' => 1,
    'grant_update' => 0,
    'grant_delete' => 0,
  );
  // ..
  return $grants;
}

2a. If you do want to have variance per language for the node access grants returned, that is use this new feature at all, you should make sure not to return multilingual grants if the site is not multilingual. Just return the grant for the node's language or return grants without language.

// Make sure to only generate grants for langcodes appropriate for the node.
// On single language sites, the node will never have multiple languages (the
// array returned by getTranslationLanguages() will always have one element 
// only), so the node access records will stay unique.
function example_node_access_records(Drupal\node\Node $node) {
  // ...
  // ... Retrieve $langcode_dependent_view_value some way from config ...
  // ...
  foreach ($node->getTranslationLanguages(TRUE) as $langcode => $language) {
    $grants[] = array(
      'realm' => 'example',
      'gid' => 1,
      'grant_view' => $langcode_dependent_view_value[$langcode],
      'grant_update' => 0,
      'grant_delete' => 0,
      'langcode' => $langcode,
    );
  }
  // ..
  return $grants;
}

2b. Alternatively you can use different logic for single language and multilingual scenarios:

function example_node_access_records(Drupal\node\Node $node) {
  // ...
  if (!language_multilingual()) {
    // Only assemble $grants arrays here *without* a langcode (or only use $node->langcode).
  }
  else {
    // Assemble $grants arrays here with langcode as appropriate for your logic.
  }
  return $grants;
}

Note that you never need to specify the 'fallback' key in the grants, that is always automatically added to the grant (or overwritten with the right value if provided improperly).

Comparison to node-copy translation node access support

A visual comparison of node-copy translations in relation to node access as well as node access with language support:

More information

See also the up to date documentation for http://api.drupal.org/api/drupal/core%21modules%21node%21node.api.php/fu...

The runtime node access system (hook_node_access()) got language support earlier, see http://drupal.org/node/1667616 for documentation on that.

Impacts: 
Module developers