NodeAccessJoinTest.php
Same filename in other branches
Namespace
Drupal\Tests\node\FunctionalFile
-
core/
modules/ node/ tests/ src/ Functional/ NodeAccessJoinTest.php
View source
<?php
declare (strict_types=1);
namespace Drupal\Tests\node\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\node\Entity\NodeType;
use Drupal\user\UserInterface;
use Drupal\views\Tests\ViewTestData;
/**
* Tests Node Access on join.
*
* @group views
*/
class NodeAccessJoinTest extends NodeTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'node_access_test',
'node_test_views',
'views',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The user that will create the articles.
*
* @var \Drupal\user\UserInterface
*/
protected UserInterface $authorUser;
/**
* Another user that will create articles.
*
* @var \Drupal\user\UserInterface
*/
protected UserInterface $otherUser;
/**
* A user with just access content permissions.
*
* @var \Drupal\user\UserInterface
*/
protected UserInterface $regularUser;
/**
* A user with access to private articles.
*
* @var \Drupal\user\UserInterface
*/
protected UserInterface $accessUser;
/**
* Articles.
*
* @var array
*/
protected array $articles;
/**
* Views used by this test.
*
* @var array
*/
public static array $testViews = [
'test_node_access_join',
];
/**
* {@inheritdoc}
*/
protected function setUp() : void {
parent::setUp();
node_access_test_add_field(NodeType::load('article'));
$field_storage = FieldStorageConfig::create([
'field_name' => 'related_article',
'entity_type' => 'node',
'translatable' => FALSE,
'entity_types' => [],
'settings' => [
'target_type' => 'node',
],
'type' => 'entity_reference',
]);
$field_storage->save();
$field = FieldConfig::create([
'field_name' => 'related_article',
'entity_type' => 'node',
'bundle' => 'page',
'label' => 'Related Article',
'settings' => [
'handler' => 'default',
'handler_settings' => [
// Reference a single vocabulary.
'target_bundles' => [
'article',
],
],
],
]);
$field->save();
$entity_display = \Drupal::service('entity_display.repository');
$entity_display->getViewDisplay('node', 'page', 'default')
->setComponent('related_article')
->save();
$entity_display->getFormDisplay('node', 'page', 'default')
->setComponent('related_article', [
'type' => 'entity_reference_autocomplete',
])
->save();
$field = FieldConfig::create([
'field_name' => 'related_article',
'entity_type' => 'node',
'bundle' => 'article',
'label' => 'Related Article',
'settings' => [
'handler' => 'default',
'handler_settings' => [
// Reference a single vocabulary.
'target_bundles' => [
'article',
],
],
],
]);
$field->save();
$entity_display->getViewDisplay('node', 'article', 'default')
->setComponent('related_article')
->save();
$entity_display->getFormDisplay('node', 'article', 'default')
->setComponent('related_article', [
'type' => 'entity_reference_autocomplete',
])
->save();
node_access_rebuild();
\Drupal::state()->set('node_access_test.private', TRUE);
}
/**
* Tests the accessibility of joined nodes.
*
* - Create two users with "access content" and "create article" permissions
* who can each access their own private articles but not others'.
* - Create article-type nodes with and without references to other articles.
* The articles and references represent all possible combinations of the
* tested access types.
* - Create page-type nodes referencing each of the articles, as well as a
* page with no reference.
* - Use a custom view that creates two joins between nodes and has a
* node_access tag. The view lists the page nodes, the article
* referenced by each page node, and the article referenced by each
* article.
*
* - Login with the author user and check that user does not have access to
* private nodes created by other users. Test access using total row
* count as well as checking for presence of individual page titles.
* - Repeat tests using a user with only the "access content" permission,
* confirming this user does not have access to any private nodes.
* - Repeat tests using a user with "access content" and "node test view"
* permissions, confirming this user sees the complete view.
*/
public function testNodeAccessJoin() : void {
$permissions = [
'access content',
'create article content',
];
// User to add articles and test author access.
$this->authorUser = $this->drupalCreateUser($permissions);
// Another user to add articles whose private articles can not be accessed
// by authorUser.
$this->otherUser = $this->drupalCreateUser($permissions);
// Create the articles. The articles are stored in an array keyed by
// $article and $reference2, where $article is the access type of the
// article itself, and $reference2 is the access type of the reference
// linked to by the article. 'public' articles are created by otherUser with
// private=0. 'private' articles are created by otherUser with private=1.
// 'author_public' articles are created by authorUser with private=0.
// 'author_private' articles are created by authorUser with private=1.
// 'no_reference' is used for references when there is no related article.
$access_type = [
'public',
'private',
'author_public',
'author_private',
];
$reference_access_type = array_merge([
'no_reference',
], $access_type);
foreach ($reference_access_type as $reference2) {
foreach ($access_type as $article) {
$is_author = str_starts_with($article, 'author');
$is_private = str_ends_with($article, 'private');
$edit = [
'type' => 'article',
'uid' => $is_author ? $this->authorUser
->id() : $this->otherUser
->id(),
];
$edit['private'][0]['value'] = $is_private;
// The article names provide the access status of the article and the
// access status of the related article, if any. The naming system
// ensures that the text 'Article $article' will only appear in the view
// if an article with that access type is displayed in the view. The
// text '$article' alone will appear in the titles of other nodes that
// reference an article.
$edit['title'] = "Article {$article} - {$reference2}";
if ($reference2 !== 'no_reference') {
$edit['related_article'][0]['target_id'] = $this->articles[$reference2]['no_reference'];
}
$node = $this->drupalCreateNode($edit);
$this->articles[$article][$reference2] = $node->id();
$this->assertEquals((int) $is_private, (int) $node->private->value, 'The private status of the article node was properly set in the node_access_test table.' . $node->uid->target_id);
if ($reference2 !== 'no_reference') {
$this->assertEquals((int) $this->articles[$reference2]['no_reference'], (int) $node->related_article->target_id, 'Proper article attached to article.');
}
}
}
// Add a blank 'no_reference' entry to the article list, so that a page with
// no reference gets created.
$this->articles['no_reference']['no_reference'] = NULL;
$total = 0;
$count_s_total = $count_s2_total = 0;
$count_s_public = $count_s2_public = 0;
$count_s_author = $count_s2_author = 0;
$total_public = $total_author = 0;
// Create page nodes referencing each article, as a page without reference.
foreach ($this->articles as $reference => $list) {
foreach ($list as $reference2 => $article_nid) {
$title = "Page - {$reference}";
if ($reference !== 'no_reference') {
$title .= " - {$reference2}";
}
$edit = [
'type' => 'page',
'title' => $title,
];
if ($article_nid) {
$edit['related_article'][0]['target_id'] = $article_nid;
}
$node = $this->drupalCreateNode($edit);
if ($article_nid) {
$this->assertEquals((int) $article_nid, (int) $node->related_article->target_id, 'Proper article attached to page.');
}
// Calculate totals expected for each user type.
$total++;
// Total number of primary and secondary references.
if ($reference !== 'no_reference') {
$count_s_total++;
if ($reference2 !== 'no_reference') {
$count_s2_total++;
}
}
// Public users only see 'public' and 'author_public' articles.
if (str_ends_with($reference, 'public')) {
$count_s_public++;
if (str_ends_with($reference2, 'public')) {
$count_s2_public++;
}
}
// authorUser sees 'public','author_public', 'author_private' articles.
if (str_ends_with($reference, 'public') || str_starts_with($reference, 'author')) {
$count_s_author++;
if (str_ends_with($reference2, 'public') || str_starts_with($reference2, 'author')) {
$count_s2_author++;
}
}
// $total_public and $total_author are not currently in use -- but
// represent the totals when joins are handled by adding an is-null
// check (i.e., if inaccessible references caused the entire row to be
// hidden from view, instead of hiding just one cell of the table).
// Count of pages where all related articles are accessible by
// public users.
if (!str_ends_with($reference, 'private') && !str_ends_with($reference2, 'private')) {
$total_public++;
}
// Count of pages where all related articles are accessible by
// authorUser.
if ($reference !== 'private' && $reference2 !== 'private') {
$total_author++;
}
}
}
// Generate a view listing all the pages, and check the view's content for
// users with three different access levels.
ViewTestData::createTestViews(get_class($this), [
'node_test_views',
]);
// Check the author of the 'author' articles.
$this->drupalLogin($this->authorUser);
$this->drupalGet('test-node-access-join');
$chk_total = count($this->xpath("//td[@headers='view-title-table-column']"));
$this->assertEquals($chk_total, $total, 'Author should see ' . $total . ' rows. Actual: ' . $chk_total);
$chk_total = count($this->xpath("//td[@headers='view-title-1-table-column']/a"));
$this->assertEquals($chk_total, $count_s_author, 'Author should see ' . $count_s_author . ' primary references. Actual: ' . $chk_total);
$chk_total = count($this->xpath("//td[@headers='view-title-2-table-column']/a"));
$this->assertEquals($chk_total, $count_s2_author, 'Author should see ' . $count_s2_author . ' secondary references. Actual: ' . $chk_total);
$session = $this->assertSession();
$session->pageTextContains('Page - no_reference');
$session->pageTextContains('Page - public - no_reference');
$session->pageTextContains('Page - public - public');
$session->pageTextContains('Page - author_private - no_reference');
$session->pageTextContains('Article public');
$session->pageTextNotContains('Article private');
$session->pageTextContains('Article author_public');
$session->pageTextContains('Article author_private');
// Check a regular user who did not author any articles.
$this->regularUser = $this->drupalCreateUser([
'access content',
]);
$this->drupalLogin($this->regularUser);
$this->drupalGet('test-node-access-join');
$chk_total = count($this->xpath("//td[@headers='view-title-table-column']"));
$this->assertEquals($chk_total, $total, 'Public user should see ' . $total . ' rows. Actual: ' . $chk_total);
$chk_total = count($this->xpath("//td[@headers='view-title-1-table-column']/a"));
$this->assertEquals($chk_total, $count_s_public, 'Public user should see ' . $count_s_public . ' primary references. Actual: ' . $chk_total);
$chk_total = count($this->xpath("//td[@headers='view-title-2-table-column']/a"));
$this->assertEquals($chk_total, $count_s2_public, 'Public user should see ' . $count_s2_public . ' secondary references. Actual: ' . $chk_total);
$session->pageTextContains('Page - no_reference');
$session->pageTextContains('Page - public - no_reference');
$session->pageTextContains('Page - public - public');
$session->pageTextContains('Article public');
$session->pageTextNotContains('Article private');
$session->pageTextContains('Article author_public');
$session->pageTextNotContains('Article author_private');
// Check that a user with 'node test view' permission, can view all pages
// and articles.
$this->accessUser = $this->drupalCreateUser([
'access content',
'node test view',
]);
$this->drupalLogin($this->accessUser);
$this->drupalGet('test-node-access-join');
$chk_total = count($this->xpath("//td[@headers='view-title-table-column']"));
$this->assertEquals($chk_total, $total, 'Full-access user should see ' . $total . ' rows. Actual: ' . $chk_total);
$chk_total = count($this->xpath("//td[@headers='view-title-1-table-column']/a"));
$this->assertEquals($chk_total, $count_s_total, 'Full-access user should see ' . $count_s_total . ' primary references. Actual: ' . $chk_total);
$chk_total = count($this->xpath("//td[@headers='view-title-2-table-column']/a"));
$this->assertEquals($chk_total, $count_s2_total, 'Full-access user should see ' . $count_s2_total . ' secondary references. Actual: ' . $chk_total);
$session->pageTextContains('Page - no_reference');
$session->pageTextContains('Page - public - no_reference');
$session->pageTextContains('Page - public - public');
$session->pageTextContains('Page - author_private - no_reference');
$session->pageTextContains('Article public');
$session->pageTextContains('Article private');
$session->pageTextContains('Article author_public');
$session->pageTextContains('Article author_private');
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
NodeAccessJoinTest | Tests Node Access on join. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.