1<?php
2
3namespace Drupal\Tests\node\Kernel;
4
5use Drupal\Core\Database\Database;
6use Drupal\Core\Language\LanguageInterface;
7use Drupal\field\Entity\FieldConfig;
8use Drupal\language\Entity\ConfigurableLanguage;
9use Drupal\user\Entity\User;
10use Drupal\field\Entity\FieldStorageConfig;
11
12/**
13 * Tests node_access and select queries with node_access tag functionality with
14 * multiple languages with node_access_test_language which is language-aware.
15 *
16 * @group node
17 */
18class NodeAccessLanguageAwareTest extends NodeAccessTestBase {
19
20  /**
21   * Enable language and a language-aware node access module.
22   *
23   * @var array
24   */
25  public static $modules = ['language', 'node_access_test_language'];
26
27  /**
28   * A set of nodes to use in testing.
29   *
30   * @var \Drupal\node\NodeInterface[]
31   */
32  protected $nodes = [];
33
34  /**
35   * A user with permission to bypass access content.
36   *
37   * @var \Drupal\user\UserInterface
38   */
39  protected $adminUser;
40
41  /**
42   * A normal authenticated user.
43   *
44   * @var \Drupal\user\UserInterface
45   */
46  protected $webUser;
47
48  protected function setUp() {
49    parent::setUp();
50
51    // Create the 'private' field, which allows the node to be marked as private
52    // (restricted access) in a given translation.
53    $field_storage = FieldStorageConfig::create([
54      'field_name' => 'field_private',
55      'entity_type' => 'node',
56      'type' => 'boolean',
57      'cardinality' => 1,
58    ]);
59    $field_storage->save();
60
61    FieldConfig::create([
62      'field_storage' => $field_storage,
63      'bundle' => 'page',
64      'widget' => [
65        'type' => 'options_buttons',
66      ],
67      'settings' => [
68        'on_label' => 'Private',
69        'off_label' => 'Not private',
70      ],
71    ])->save();
72
73    // After enabling a node access module, the access table has to be rebuild.
74    node_access_rebuild();
75
76    // Create a normal authenticated user.
77    $this->webUser = $this->drupalCreateUser(['access content']);
78
79    // Load the user 1 user for later use as an admin user with permission to
80    // see everything.
81    $this->adminUser = User::load(1);
82
83    // Add Hungarian and Catalan.
84    ConfigurableLanguage::createFromLangcode('hu')->save();
85    ConfigurableLanguage::createFromLangcode('ca')->save();
86
87    // The node_access_test_language module allows individual translations of a
88    // node to be marked private (not viewable by normal users).
89
90    // Create six nodes:
91    // 1. Four Hungarian nodes with Catalan translations
92    //   - One with neither language marked as private.
93    //   - One with only the Hungarian translation private.
94    //   - One with only the Catalan translation private.
95    //   - One with both the Hungarian and Catalan translations private.
96    // 2. Two nodes with no language specified.
97    //   - One public.
98    //   - One private.
99    $this->nodes['both_public'] = $node = $this->drupalCreateNode([
100      'body' => [[]],
101      'langcode' => 'hu',
102      'field_private' => [['value' => 0]],
103    ]);
104    $translation = $node->addTranslation('ca');
105    $translation->title->value = $this->randomString();
106    $translation->field_private->value = 0;
107    $node->save();
108
109    $this->nodes['ca_private'] = $node = $this->drupalCreateNode([
110      'body' => [[]],
111      'langcode' => 'hu',
112      'field_private' => [['value' => 0]],
113    ]);
114    $translation = $node->addTranslation('ca');
115    $translation->title->value = $this->randomString();
116    $translation->field_private->value = 1;
117    $node->save();
118
119    $this->nodes['hu_private'] = $node = $this->drupalCreateNode([
120      'body' => [[]],
121      'langcode' => 'hu',
122      'field_private' => [['value' => 1]],
123    ]);
124    $translation = $node->addTranslation('ca');
125    $translation->title->value = $this->randomString();
126    $translation->field_private->value = 0;
127    $node->save();
128
129    $this->nodes['both_private'] = $node = $this->drupalCreateNode([
130      'body' => [[]],
131      'langcode' => 'hu',
132      'field_private' => [['value' => 1]],
133    ]);
134    $translation = $node->addTranslation('ca');
135    $translation->title->value = $this->randomString();
136    $translation->field_private->value = 1;
137    $node->save();
138
139    $this->nodes['no_language_public'] = $this->drupalCreateNode([
140      'field_private' => [['value' => 0]],
141      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
142    ]);
143    $this->nodes['no_language_private'] = $this->drupalCreateNode([
144      'field_private' => [['value' => 1]],
145      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
146    ]);
147  }
148
149  /**
150   * Tests node access and node access queries with multiple node languages.
151   */
152  public function testNodeAccessLanguageAware() {
153    // The node_access_test_language module only grants view access.
154    $expected_node_access = ['view' => TRUE, 'update' => FALSE, 'delete' => FALSE];
155    $expected_node_access_no_access = ['view' => FALSE, 'update' => FALSE, 'delete' => FALSE];
156
157    // When both Hungarian and Catalan are marked as public, access to the
158    // Hungarian translation should be granted with the default entity object or
159    // when the Hungarian translation is specified explicitly.
160    $this->assertNodeAccess($expected_node_access, $this->nodes['both_public'], $this->webUser);
161    $this->assertNodeAccess($expected_node_access, $this->nodes['both_public']->getTranslation('hu'), $this->webUser);
162    // Access to the Catalan translation should also be granted.
163    $this->assertNodeAccess($expected_node_access, $this->nodes['both_public']->getTranslation('ca'), $this->webUser);
164
165    // When Hungarian is marked as private, access to the Hungarian translation
166    // should be denied with the default entity object or when the Hungarian
167    // translation is specified explicitly.
168    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['hu_private'], $this->webUser);
169    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['hu_private']->getTranslation('hu'), $this->webUser);
170    // Access to the Catalan translation should be granted.
171    $this->assertNodeAccess($expected_node_access, $this->nodes['hu_private']->getTranslation('ca'), $this->webUser);
172
173    // When Catalan is marked as private, access to the Hungarian translation
174    // should be granted with the default entity object or when the Hungarian
175    // translation is specified explicitly.
176    $this->assertNodeAccess($expected_node_access, $this->nodes['ca_private'], $this->webUser);
177    $this->assertNodeAccess($expected_node_access, $this->nodes['ca_private']->getTranslation('hu'), $this->webUser);
178    // Access to the Catalan translation should be granted.
179    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['ca_private']->getTranslation('ca'), $this->webUser);
180
181    // When both translations are marked as private, access should be denied
182    // regardless of the entity object specified.
183    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private'], $this->webUser);
184    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private']->getTranslation('hu'), $this->webUser);
185    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private']->getTranslation('ca'), $this->webUser);
186
187    // When no language is specified for a private node, access to every node
188    // translation is denied.
189    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['no_language_private'], $this->webUser);
190
191    // When no language is specified for a public node, access should be
192    // granted.
193    $this->assertNodeAccess($expected_node_access, $this->nodes['no_language_public'], $this->webUser);
194
195    // Query the node table with the node access tag in several languages.
196    $connection = Database::getConnection();
197    // Query with no language specified. The fallback (hu) will be used.
198    $select = $connection->select('node', 'n')
199      ->fields('n', ['nid'])
200      ->addMetaData('account', $this->webUser)
201      ->addTag('node_access');
202    $nids = $select->execute()->fetchAllAssoc('nid');
203
204    // Three nodes should be returned:
205    // - Node with both translations public.
206    // - Node with only the Catalan translation marked as private.
207    // - No language node marked as public.
208    $this->assertCount(3, $nids, 'db_select() returns 3 nodes when no langcode is specified.');
209    $this->assertArrayHasKey($this->nodes['both_public']->id(), $nids);
210    $this->assertArrayHasKey($this->nodes['ca_private']->id(), $nids);
211    $this->assertArrayHasKey($this->nodes['no_language_public']->id(), $nids);
212
213    // Query with Hungarian (hu) specified.
214    $select = $connection->select('node', 'n')
215      ->fields('n', ['nid'])
216      ->addMetaData('account', $this->webUser)
217      ->addMetaData('langcode', 'hu')
218      ->addTag('node_access');
219    $nids = $select->execute()->fetchAllAssoc('nid');
220
221    // Two nodes should be returned: the node with both translations public, and
222    // the node with only the Catalan translation marked as private.
223    $this->assertCount(2, $nids, 'Query returns 2 nodes when the hu langcode is specified.');
224    $this->assertArrayHasKey($this->nodes['both_public']->id(), $nids);
225    $this->assertArrayHasKey($this->nodes['ca_private']->id(), $nids);
226
227    // Query with Catalan (ca) specified.
228    $select = $connection->select('node', 'n')
229      ->fields('n', ['nid'])
230      ->addMetaData('account', $this->webUser)
231      ->addMetaData('langcode', 'ca')
232      ->addTag('node_access');
233    $nids = $select->execute()->fetchAllAssoc('nid');
234
235    // Two nodes should be returned: the node with both translations public, and
236    // the node with only the Hungarian translation marked as private.
237    $this->assertCount(2, $nids, 'Query returns 2 nodes when the hu langcode is specified.');
238    $this->assertArrayHasKey($this->nodes['both_public']->id(), $nids);
239    $this->assertArrayHasKey($this->nodes['hu_private']->id(), $nids);
240
241    // Query with German (de) specified.
242    $select = $connection->select('node', 'n')
243      ->fields('n', ['nid'])
244      ->addMetaData('account', $this->webUser)
245      ->addMetaData('langcode', 'de')
246      ->addTag('node_access');
247    $nids = $select->execute()->fetchAllAssoc('nid');
248
249    // There are no nodes with German translations, so no results are returned.
250    $this->assertTrue(empty($nids), 'Query returns an empty result when the de langcode is specified.');
251
252    // Query the nodes table as admin user (full access) with the node access
253    // tag and no specific langcode.
254    $select = $connection->select('node', 'n')
255      ->fields('n', ['nid'])
256      ->addMetaData('account', $this->adminUser)
257      ->addTag('node_access');
258    $nids = $select->execute()->fetchAllAssoc('nid');
259
260    // All nodes are returned.
261    $this->assertCount(6, $nids, 'Query returns all nodes.');
262
263    // Query the nodes table as admin user (full access) with the node access
264    // tag and langcode de.
265    $select = $connection->select('node', 'n')
266      ->fields('n', ['nid'])
267      ->addMetaData('account', $this->adminUser)
268      ->addMetaData('langcode', 'de')
269      ->addTag('node_access');
270    $nids = $select->execute()->fetchAllAssoc('nid');
271
272    // Even though there is no German translation, all nodes are returned
273    // because node access filtering does not occur when the user is user 1.
274    $this->assertCount(6, $nids, 'Query returns all nodes.');
275  }
276
277}
278