updated core and modules

This commit is contained in:
Bachir Soussi Chiadmi
2017-09-09 16:27:51 +02:00
parent 027aa99b32
commit 41863f872c
7810 changed files with 318922 additions and 83631 deletions
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2016-08-03
version: '8.1.8'
# Information added by Drupal.org packaging script on 2017-08-16
version: '8.3.7'
core: '8.x'
project: 'drupal'
datestamp: 1470233790
datestamp: 1502903957
@@ -50,15 +50,15 @@ use Drupal\node\NodeInterface;
* @see node_access_test_node_access_records()
*/
function node_access_test_node_grants($account, $op) {
$grants = array();
$grants['node_access_test_author'] = array($account->id());
$grants = [];
$grants['node_access_test_author'] = [$account->id()];
if ($op == 'view' && $account->hasPermission('node test view', $account)) {
$grants['node_access_test'] = array(8888, 8889);
$grants['node_access_test'] = [8888, 8889];
}
$no_access_uid = \Drupal::state()->get('node_access_test.no_access_uid') ?: 0;
if ($op == 'view' && $account->id() == $no_access_uid) {
$grants['node_access_all'] = array(0);
$grants['node_access_all'] = [0];
}
return $grants;
}
@@ -77,37 +77,37 @@ function node_access_test_node_grants($account, $op) {
* @see node_access_test.permissions.yml
*/
function node_access_test_node_access_records(NodeInterface $node) {
$grants = array();
$grants = [];
// For NodeAccessBaseTableTestCase, only set records for private nodes.
if (!\Drupal::state()->get('node_access_test.private') || $node->private->value) {
// Groups 8888 and 8889 for the node_access_test realm both receive a view
// grant for all controlled nodes. See node_access_test_node_grants().
$grants[] = array(
$grants[] = [
'realm' => 'node_access_test',
'gid' => 8888,
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
'priority' => 0,
);
$grants[] = array(
];
$grants[] = [
'realm' => 'node_access_test',
'gid' => 8889,
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
'priority' => 0,
);
];
// For the author realm, the group ID is equivalent to a user ID, which
// means there are many many groups of just 1 user.
$grants[] = array(
$grants[] = [
'realm' => 'node_access_test_author',
'gid' => $node->getOwnerId(),
'grant_view' => 1,
'grant_update' => 1,
'grant_delete' => 1,
'priority' => 0,
);
];
}
return $grants;
@@ -120,25 +120,25 @@ function node_access_test_node_access_records(NodeInterface $node) {
* A node type entity.
*/
function node_access_test_add_field(NodeTypeInterface $type) {
$field_storage = FieldStorageConfig::create(array(
$field_storage = FieldStorageConfig::create([
'field_name' => 'private',
'entity_type' => 'node',
'type' => 'integer',
));
]);
$field_storage->save();
$field = FieldConfig::create(array(
$field = FieldConfig::create([
'field_name' => 'private',
'entity_type' => 'node',
'bundle' => $type->id(),
'label' => 'Private',
));
]);
$field->save();
// Assign widget settings for the 'default' form mode.
entity_get_form_display('node', $type->id(), 'default')
->setComponent('private', array(
->setComponent('private', [
'type' => 'number',
))
])
->save();
}
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2016-08-03
version: '8.1.8'
# Information added by Drupal.org packaging script on 2017-08-16
version: '8.3.7'
core: '8.x'
project: 'drupal'
datestamp: 1470233790
datestamp: 1502903957
@@ -11,12 +11,12 @@ use Drupal\node\NodeInterface;
* Implements hook_node_grants().
*/
function node_access_test_empty_node_grants($account, $op) {
return array();
return [];
}
/**
* Implements hook_node_access_records().
*/
function node_access_test_empty_node_access_records(NodeInterface $node) {
return array();
return [];
}
@@ -7,8 +7,8 @@ package: Testing
dependencies:
- options
# Information added by Drupal.org packaging script on 2016-08-03
version: '8.1.8'
# Information added by Drupal.org packaging script on 2017-08-16
version: '8.3.7'
core: '8.x'
project: 'drupal'
datestamp: 1470233790
datestamp: 1502903957
@@ -16,7 +16,7 @@ use Drupal\node\NodeInterface;
* This module defines a single grant realm. All users belong to this group.
*/
function node_access_test_language_node_grants($account, $op) {
$grants['node_access_language_test'] = array(7888);
$grants['node_access_language_test'] = [7888];
return $grants;
}
@@ -24,13 +24,13 @@ function node_access_test_language_node_grants($account, $op) {
* Implements hook_node_access_records().
*/
function node_access_test_language_node_access_records(NodeInterface $node) {
$grants = array();
$grants = [];
// Create grants for each translation of the node.
foreach ($node->getTranslationLanguages() as $langcode => $language) {
// If the translation is not marked as private, grant access.
$translation = $node->getTranslation($langcode);
$grants[] = array(
$grants[] = [
'realm' => 'node_access_language_test',
'gid' => 7888,
'grant_view' => empty($translation->field_private->value) ? 1 : 0,
@@ -38,7 +38,7 @@ function node_access_test_language_node_access_records(NodeInterface $node) {
'grant_delete' => 0,
'priority' => 0,
'langcode' => $langcode,
);
];
}
return $grants;
}
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2016-08-03
version: '8.1.8'
# Information added by Drupal.org packaging script on 2017-08-16
version: '8.3.7'
core: '8.x'
project: 'drupal'
datestamp: 1470233790
datestamp: 1502903957
@@ -10,32 +10,34 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
/**
* Implements hook_ENTITY_TYPE_view() for node entities.
*/
function node_test_node_view(array &$build, NodeInterface $node, EntityViewDisplayInterface $display, $view_mode) {
if ($view_mode == 'rss') {
// Add RSS elements and namespaces when building the RSS feed.
$node->rss_elements[] = array(
$node->rss_elements[] = [
'key' => 'testElement',
'value' => t('Value of testElement RSS element for node @nid.', array('@nid' => $node->id())),
);
'value' => t('Value of testElement RSS element for node @nid.', ['@nid' => $node->id()]),
];
// Add content that should be displayed only in the RSS feed.
$build['extra_feed_content'] = array(
'#markup' => '<p>' . t('Extra data that should appear only in the RSS feed for node @nid.', array('@nid' => $node->id())) . '</p>',
$build['extra_feed_content'] = [
'#markup' => '<p>' . t('Extra data that should appear only in the RSS feed for node @nid.', ['@nid' => $node->id()]) . '</p>',
'#weight' => 10,
);
];
}
if ($view_mode != 'rss') {
// Add content that should NOT be displayed in the RSS feed.
$build['extra_non_feed_content'] = array(
'#markup' => '<p>' . t('Extra data that should appear everywhere except the RSS feed for node @nid.', array('@nid' => $node->id())) . '</p>',
);
$build['extra_non_feed_content'] = [
'#markup' => '<p>' . t('Extra data that should appear everywhere except the RSS feed for node @nid.', ['@nid' => $node->id()]) . '</p>',
];
}
}
@@ -55,11 +57,11 @@ function node_test_node_grants(AccountInterface $account, $op) {
// Give everyone full grants so we don't break other node tests.
// Our node access tests asserts three realms of access.
// See testGrantAlter().
return array(
'test_article_realm' => array(1),
'test_page_realm' => array(1),
'test_alter_realm' => array(2),
);
return [
'test_article_realm' => [1],
'test_page_realm' => [1],
'test_alter_realm' => [2],
];
}
/**
@@ -70,28 +72,28 @@ function node_test_node_access_records(NodeInterface $node) {
if (!empty($node->disable_node_access)) {
return;
}
$grants = array();
$grants = [];
if ($node->getType() == 'article') {
// Create grant in arbitrary article_realm for article nodes.
$grants[] = array(
$grants[] = [
'realm' => 'test_article_realm',
'gid' => 1,
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
'priority' => 0,
);
];
}
elseif ($node->getType() == 'page') {
// Create grant in arbitrary page_realm for page nodes.
$grants[] = array(
$grants[] = [
'realm' => 'test_page_realm',
'gid' => 1,
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
'priority' => 0,
);
];
}
return $grants;
}
@@ -116,7 +118,7 @@ function node_test_node_access_records_alter(&$grants, NodeInterface $node) {
*/
function node_test_node_grants_alter(&$grants, AccountInterface $account, $op) {
// Return an empty array of grants to prove that we can alter by reference.
$grants = array();
$grants = [];
}
/**
@@ -175,3 +177,16 @@ function node_test_node_insert(NodeInterface $node) {
$node->save();
}
}
/**
* Implements hook_form_alter().
*/
function node_test_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if (!$form_state->get('node_test_form_alter')) {
drupal_set_message('Storage is not set');
$form_state->set('node_test_form_alter', TRUE);
}
else {
drupal_set_message('Storage is set');
}
}
@@ -2,7 +2,7 @@ type: default
name: Default
description: 'Default description.'
help: ''
new_revision: false
new_revision: true
display_submitted: true
preview_mode: 1
status: true
@@ -5,8 +5,8 @@ description: 'Support module for node configuration tests.'
package: Testing
# version: VERSION
# Information added by Drupal.org packaging script on 2016-08-03
version: '8.1.8'
# Information added by Drupal.org packaging script on 2017-08-16
version: '8.3.7'
core: '8.x'
project: 'drupal'
datestamp: 1470233790
datestamp: 1502903957
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2016-08-03
version: '8.1.8'
# Information added by Drupal.org packaging script on 2017-08-16
version: '8.3.7'
core: '8.x'
project: 'drupal'
datestamp: 1470233790
datestamp: 1502903957
@@ -9,8 +9,8 @@ dependencies:
- views
- language
# Information added by Drupal.org packaging script on 2016-08-03
version: '8.1.8'
# Information added by Drupal.org packaging script on 2017-08-16
version: '8.3.7'
core: '8.x'
project: 'drupal'
datestamp: 1470233790
datestamp: 1502903957
@@ -0,0 +1,106 @@
langcode: en
status: true
dependencies:
module:
- node
- user
id: test_argument_node_uid_revision
label: test_argument_node_uid_revision
module: views
description: ''
tag: default
base_table: node_field_data
base_field: nid
core: 8.0-dev
display:
default:
display_options:
access:
type: perm
cache:
type: tag
exposed_form:
type: basic
fields:
nid:
id: nid
table: node_field_data
field: nid
plugin_id: field
entity_type: node
entity_field: nid
filter_groups:
groups:
1: AND
operator: AND
filters: { }
sorts:
nid:
id: nid
table: node_field_data
field: nid
order: ASC
plugin_id: standard
relationship: none
entity_type: node
entity_field: nid
pager:
type: full
query:
type: views_query
style:
type: default
row:
type: fields
display_extenders: { }
arguments:
uid_revision:
id: uid_revision
table: node_field_data
field: uid_revision
relationship: none
group_type: group
admin_label: ''
default_action: empty
exception:
value: all
title_enable: false
title: All
title_enable: false
title: ''
default_argument_type: fixed
default_argument_options:
argument: ''
default_argument_skip_url: false
summary_options:
base_path: ''
count: true
items_per_page: 25
override: false
summary:
sort_order: asc
number_of_records: 0
format: default_summary
specify_validation: false
validate:
type: none
fail: 'not found'
validate_options: { }
break_phrase: false
not: false
entity_type: node
plugin_id: node_uid_revision
display_plugin: default
display_title: Master
id: default
position: 0
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
@@ -91,7 +91,7 @@ display:
plugin_id: field
filters:
status:
value: true
value: '1'
table: node_field_data
field: status
id: status
@@ -177,7 +177,7 @@ display:
filter_groups: false
filters:
status:
value: true
value: '1'
table: node_field_data
field: status
id: status
@@ -244,7 +244,7 @@ display:
filter_groups: false
filters:
status:
value: true
value: '1'
table: node_field_data
field: status
id: status
@@ -311,7 +311,7 @@ display:
filter_groups: false
filters:
status:
value: true
value: '1'
table: node_field_data
field: status
id: status
@@ -0,0 +1,203 @@
langcode: en
status: true
dependencies:
module:
- node
- user
id: test_filter_node_access
label: test_filter_node_access
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: true
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: some
options:
items_per_page: 10
offset: 0
style:
type: default
row:
type: fields
options:
default_field_elements: true
inline: { }
separator: ''
hide_empty: false
fields:
title:
id: title
table: node_field_data
field: title
entity_type: node
entity_field: title
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
settings:
link_to_entity: true
plugin_id: field
relationship: none
group_type: group
admin_label: ''
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
click_sort_column: value
type: string
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
filters:
status:
value: '1'
table: node_field_data
field: status
plugin_id: boolean
entity_type: node
entity_field: status
id: status
expose:
operator: ''
group: 1
nid:
id: nid
table: node_access
field: nid
relationship: none
group_type: group
admin_label: ''
operator: '='
value: ''
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: node_access
sorts:
created:
id: created
table: node_field_data
field: created
order: DESC
entity_type: node
entity_field: created
plugin_id: date
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
granularity: second
title: test_filter_node_access
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- 'user.node_grants:view'
- user.permissions
tags: { }
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: test_filter_node_access
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- 'user.node_grants:view'
- user.permissions
tags: { }
@@ -166,7 +166,7 @@ display:
type: language
filters:
status:
value: true
value: '1'
table: node_field_data
field: status
id: status
@@ -123,7 +123,7 @@ display:
plugin_id: node_path
filters:
status:
value: true
value: '1'
table: node_field_data
field: status
plugin_id: boolean
@@ -29,7 +29,7 @@ display:
group: 1
id: status
table: node_field_data
value: true
value: '1'
plugin_id: boolean
entity_type: node
entity_field: status
@@ -123,7 +123,7 @@ display:
entity_field: nid
filters:
status:
value: true
value: '1'
table: node_field_data
field: status
id: status
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2016-08-03
version: '8.1.8'
# Information added by Drupal.org packaging script on 2017-08-16
version: '8.3.7'
core: '8.x'
project: 'drupal'
datestamp: 1470233790
datestamp: 1502903957
@@ -4,40 +4,13 @@ namespace Drupal\node_access_test_auto_bubbling\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\Query\QueryFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\node\NodeInterface;
/**
* Returns a node ID listing.
*/
class NodeAccessTestAutoBubblingController extends ControllerBase implements ContainerInjectionInterface {
/**
* The entity query factory service.
*
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $entityQuery;
/**
* Constructs a new NodeAccessTestAutoBubblingController.
*
* @param \Drupal\Core\Entity\Query\QueryFactory $entity_query
* The entity query factory.
*/
public function __construct(QueryFactory $entity_query) {
$this->entityQuery = $entity_query;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.query')
);
}
/**
* Lists the three latest published node IDs.
*
@@ -45,8 +18,8 @@ class NodeAccessTestAutoBubblingController extends ControllerBase implements Con
* A render array.
*/
public function latest() {
$nids = $this->entityQuery->get('node')
->condition('status', NODE_PUBLISHED)
$nids = $this->entityTypeManager()->getStorage('node')->getQuery()
->condition('status', NodeInterface::PUBLISHED)
->sort('created', 'DESC')
->range(0, 3)
->execute();
@@ -0,0 +1,49 @@
<?php
namespace Drupal\Tests\node\Functional;
/**
* Asserts that buttons are present on a page.
*/
trait AssertButtonsTrait {
/**
* Assert method to verify the buttons in the dropdown element.
*
* @param array $buttons
* A collection of buttons to assert for on the page.
* @param bool $dropbutton
* Whether to check if the buttons are in a dropbutton widget or not.
*/
public function assertButtons(array $buttons, $dropbutton = TRUE) {
// Try to find a Save button.
$save_button = $this->xpath('//input[@type="submit"][@value="Save"]');
// Verify that the number of buttons passed as parameters is
// available in the dropbutton widget.
if ($dropbutton) {
$i = 0;
$count = count($buttons);
// Assert there is no save button.
$this->assertTrue(empty($save_button));
// Dropbutton elements.
/** @var \Behat\Mink\Element\NodeElement[] $elements */
$elements = $this->xpath('//div[@class="dropbutton-wrapper"]//input[@type="submit"]');
$this->assertEqual($count, count($elements));
foreach ($elements as $element) {
$value = $element->getValue() ?: '';
$this->assertEqual($buttons[$i], $value);
$i++;
}
}
else {
// Assert there is a save button.
$this->assertTrue(!empty($save_button));
$this->assertNoRaw('dropbutton-wrapper');
}
}
}
@@ -0,0 +1,50 @@
<?php
namespace Drupal\Tests\node\Functional\Migrate\d6;
use Drupal\Tests\node\Kernel\Migrate\d6\MigrateNodeTestBase;
/**
* Node content revisions migration.
*
* @group migrate_drupal_6
*/
class MigrateNodeRevisionTest extends MigrateNodeTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['language', 'content_translation', 'menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->executeMigrations(['d6_node', 'd6_node_revision']);
}
/**
* Test node revisions migration from Drupal 6 to 8.
*/
public function testNodeRevision() {
$node = \Drupal::entityManager()->getStorage('node')->loadRevision(2);
/** @var \Drupal\node\NodeInterface $node */
$this->assertIdentical('1', $node->id());
$this->assertIdentical('2', $node->getRevisionId());
$this->assertIdentical('und', $node->langcode->value);
$this->assertIdentical('Test title rev 2', $node->getTitle());
$this->assertIdentical('body test rev 2', $node->body->value);
$this->assertIdentical('teaser test rev 2', $node->body->summary);
$this->assertIdentical('2', $node->getRevisionUser()->id());
$this->assertIdentical('modified rev 2', $node->revision_log->value);
$this->assertIdentical('1390095702', $node->getRevisionCreationTime());
$node = \Drupal::entityManager()->getStorage('node')->loadRevision(5);
$this->assertIdentical('1', $node->id());
$this->assertIdentical('body test rev 3', $node->body->value);
$this->assertIdentical('1', $node->getRevisionUser()->id());
$this->assertIdentical('modified rev 3', $node->revision_log->value);
$this->assertIdentical('1390095703', $node->getRevisionCreationTime());
}
}
@@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests\node\Functional\Migrate\d7;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests the d7_node node deriver.
*
* @group node
*/
class NodeMigrateDeriverTest extends MigrateDrupal7TestBase {
public static $modules = ['node'];
public function testBuilder() {
$process = $this->getMigration('d7_node:test_content_type')->getProcess();
$this->assertIdentical('field_boolean', $process['field_boolean'][0]['source']);
$this->assertIdentical('field_email', $process['field_email'][0]['source']);
$this->assertIdentical('field_phone', $process['field_phone'][0]['source']);
$this->assertIdentical('field_date', $process['field_date'][0]['source']);
$this->assertIdentical('field_date_with_end_time', $process['field_date_with_end_time'][0]['source']);
$this->assertIdentical('field_file', $process['field_file'][0]['source']);
$this->assertIdentical('field_float', $process['field_float'][0]['source']);
$this->assertIdentical('field_images', $process['field_images'][0]['source']);
$this->assertIdentical('field_integer', $process['field_integer'][0]['source']);
$this->assertIdentical('field_link', $process['field_link'][0]['source']);
$this->assertIdentical('field_text_list', $process['field_text_list'][0]['source']);
$this->assertIdentical('field_integer_list', $process['field_integer_list'][0]['source']);
$this->assertIdentical('field_long_text', $process['field_long_text'][0]['source']);
$this->assertIdentical('field_term_reference', $process['field_term_reference'][0]['source']);
$this->assertIdentical('field_text', $process['field_text'][0]['source']);
}
}
@@ -0,0 +1,63 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Component\Utility\Unicode;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests the persistence of basic options through multiple steps.
*
* @group node
*/
class MultiStepNodeFormBasicOptionsTest extends NodeTestBase {
/**
* The field name to create.
*
* @var string
*/
protected $fieldName;
/**
* Tests changing the default values of basic options to ensure they persist.
*/
public function testMultiStepNodeFormBasicOptions() {
// Prepare a user to create the node.
$web_user = $this->drupalCreateUser(['administer nodes', 'create page content']);
$this->drupalLogin($web_user);
// Create an unlimited cardinality field.
$this->fieldName = Unicode::strtolower($this->randomMachineName());
FieldStorageConfig::create([
'field_name' => $this->fieldName,
'entity_type' => 'node',
'type' => 'text',
'cardinality' => -1,
])->save();
// Attach an instance of the field to the page content type.
FieldConfig::create([
'field_name' => $this->fieldName,
'entity_type' => 'node',
'bundle' => 'page',
'label' => $this->randomMachineName() . '_label',
])->save();
entity_get_form_display('node', 'page', 'default')
->setComponent($this->fieldName, [
'type' => 'text_textfield',
])
->save();
$edit = [
'title[0][value]' => 'a',
'promote[value]' => FALSE,
'sticky[value]' => 1,
"{$this->fieldName}[0][value]" => $this->randomString(32),
];
$this->drupalPostForm('node/add/page', $edit, t('Add another item'));
$this->assertNoFieldChecked('edit-promote-value', 'Promote stayed unchecked');
$this->assertFieldChecked('edit-sticky-value', 'Sticky stayed checked');
}
}
@@ -0,0 +1,70 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Core\Url;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
/**
* Tests the node access automatic cacheability bubbling logic.
*
* @group node
* @group Cache
* @group cacheability_safeguards
*/
class NodeAccessAutoBubblingTest extends NodeTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node_access_test', 'node_access_test_auto_bubbling'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
node_access_rebuild();
// Create some content.
$this->drupalCreateNode();
$this->drupalCreateNode();
$this->drupalCreateNode();
$this->drupalCreateNode();
}
/**
* Tests that the node grants cache context is auto-added, only when needed.
*
* @see node_query_node_access_alter()
*/
public function testNodeAccessCacheabilitySafeguard() {
$this->dumpHeaders = TRUE;
// The node grants cache context should be added automatically.
$this->drupalGet(new Url('node_access_test_auto_bubbling'));
$this->assertCacheContext('user.node_grants:view');
// The root user has the 'bypass node access' permission, which means the
// node grants cache context is not necessary.
$this->drupalLogin($this->rootUser);
$this->drupalGet(new Url('node_access_test_auto_bubbling'));
$this->assertNoCacheContext('user.node_grants:view');
$this->drupalLogout();
// Uninstall the module with the only hook_node_grants() implementation.
$this->container->get('module_installer')->uninstall(['node_access_test']);
$this->rebuildContainer();
// Because there are no node grants defined, there also is no need for the
// node grants cache context to be bubbled.
$this->drupalGet(new Url('node_access_test_auto_bubbling'));
$this->assertNoCacheContext('user.node_grants:view');
}
}
@@ -0,0 +1,226 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\node\Entity\NodeType;
/**
* Tests behavior of the node access subsystem if the base table is not node.
*
* @group node
*/
class NodeAccessBaseTableTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node_access_test', 'views'];
/**
* The installation profile to use with this test.
*
* This test class requires the "tags" taxonomy field.
*
* @var string
*/
protected $profile = 'standard';
/**
* Nodes by user.
*
* @var array
*/
protected $nodesByUser;
/**
* A public tid.
*
* @var \Drupal\Core\Database\StatementInterface
*/
protected $publicTid;
/**
* A private tid.
*
* @var \Drupal\Core\Database\StatementInterface
*/
protected $privateTid;
/**
* A web user.
*/
protected $webUser;
/**
* The nids visible.
*
* @var array
*/
protected $nidsVisible;
protected function setUp() {
parent::setUp();
node_access_test_add_field(NodeType::load('article'));
node_access_rebuild();
\Drupal::state()->set('node_access_test.private', TRUE);
}
/**
* Tests the "private" node access functionality.
*
* - Create 2 users with "access content" and "create article" permissions.
* - Each user creates one private and one not private article.
*
* - Test that each user can view the other user's non-private article.
* - Test that each user cannot view the other user's private article.
* - Test that each user finds only appropriate (non-private + own private)
* in taxonomy listing.
* - Create another user with 'view any private content'.
* - Test that user 4 can view all content created above.
* - Test that user 4 can view all content on taxonomy listing.
*/
public function testNodeAccessBasic() {
$num_simple_users = 2;
$simple_users = [];
// Nodes keyed by uid and nid: $nodes[$uid][$nid] = $is_private;
$this->nodesByUser = [];
// Titles keyed by nid.
$titles = [];
// Array of nids marked private.
$private_nodes = [];
for ($i = 0; $i < $num_simple_users; $i++) {
$simple_users[$i] = $this->drupalCreateUser(['access content', 'create article content']);
}
foreach ($simple_users as $this->webUser) {
$this->drupalLogin($this->webUser);
foreach ([0 => 'Public', 1 => 'Private'] as $is_private => $type) {
$edit = [
'title[0][value]' => t('@private_public Article created by @user', ['@private_public' => $type, '@user' => $this->webUser->getUsername()]),
];
if ($is_private) {
$edit['private[0][value]'] = TRUE;
$edit['body[0][value]'] = 'private node';
$edit['field_tags[target_id]'] = 'private';
}
else {
$edit['body[0][value]'] = 'public node';
$edit['field_tags[target_id]'] = 'public';
}
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertEqual($is_private, (int)$node->private->value, 'The private status of the node was properly set in the node_access_test table.');
if ($is_private) {
$private_nodes[] = $node->id();
}
$titles[$node->id()] = $edit['title[0][value]'];
$this->nodesByUser[$this->webUser->id()][$node->id()] = $is_private;
}
}
$this->publicTid = db_query('SELECT tid FROM {taxonomy_term_field_data} WHERE name = :name AND default_langcode = 1', [':name' => 'public'])->fetchField();
$this->privateTid = db_query('SELECT tid FROM {taxonomy_term_field_data} WHERE name = :name AND default_langcode = 1', [':name' => 'private'])->fetchField();
$this->assertTrue($this->publicTid, 'Public tid was found');
$this->assertTrue($this->privateTid, 'Private tid was found');
foreach ($simple_users as $this->webUser) {
$this->drupalLogin($this->webUser);
// Check own nodes to see that all are readable.
foreach ($this->nodesByUser as $uid => $data) {
foreach ($data as $nid => $is_private) {
$this->drupalGet('node/' . $nid);
if ($is_private) {
$should_be_visible = $uid == $this->webUser->id();
}
else {
$should_be_visible = TRUE;
}
$this->assertResponse($should_be_visible ? 200 : 403, strtr('A %private node by user %uid is %visible for user %current_uid.', [
'%private' => $is_private ? 'private' : 'public',
'%uid' => $uid,
'%visible' => $should_be_visible ? 'visible' : 'not visible',
'%current_uid' => $this->webUser->id(),
]));
}
}
// Check to see that the correct nodes are shown on taxonomy/private
// and taxonomy/public.
$this->assertTaxonomyPage(FALSE);
}
// Now test that a user with 'node test view' permissions can view content.
$access_user = $this->drupalCreateUser(['access content', 'create article content', 'node test view', 'search content']);
$this->drupalLogin($access_user);
foreach ($this->nodesByUser as $private_status) {
foreach ($private_status as $nid => $is_private) {
$this->drupalGet('node/' . $nid);
$this->assertResponse(200);
}
}
// This user should be able to see all of the nodes on the relevant
// taxonomy pages.
$this->assertTaxonomyPage(TRUE);
// Rebuild the node access permissions, repeat the test. This is done to
// ensure that node access is rebuilt correctly even if the current user
// does not have the bypass node access permission.
node_access_rebuild();
foreach ($this->nodesByUser as $private_status) {
foreach ($private_status as $nid => $is_private) {
$this->drupalGet('node/' . $nid);
$this->assertResponse(200);
}
}
// This user should be able to see all of the nodes on the relevant
// taxonomy pages.
$this->assertTaxonomyPage(TRUE);
}
/**
* Checks taxonomy/term listings to ensure only accessible nodes are listed.
*
* @param $is_admin
* A boolean indicating whether the current user is an administrator. If
* TRUE, all nodes should be listed. If FALSE, only public nodes and the
* user's own private nodes should be listed.
*/
protected function assertTaxonomyPage($is_admin) {
foreach ([$this->publicTid, $this->privateTid] as $tid_is_private => $tid) {
$this->drupalGet("taxonomy/term/$tid");
$this->nidsVisible = [];
foreach ($this->xpath("//a[text()='Read more']") as $link) {
// See also testTranslationRendering() in NodeTranslationUITest.
$this->assertTrue(preg_match('|node/(\d+)$|', $link->getAttribute('href'), $matches), 'Read more points to a node');
$this->nidsVisible[$matches[1]] = TRUE;
}
foreach ($this->nodesByUser as $uid => $data) {
foreach ($data as $nid => $is_private) {
// Private nodes should be visible on the private term page,
// public nodes should be visible on the public term page.
$should_be_visible = $tid_is_private == $is_private;
// Non-administrators can only see their own nodes on the private
// term page.
if (!$is_admin && $tid_is_private) {
$should_be_visible = $should_be_visible && $uid == $this->webUser->id();
}
$this->assertIdentical(isset($this->nidsVisible[$nid]), $should_be_visible, strtr('A %private node by user %uid is %visible for user %current_uid on the %tid_is_private page.', [
'%private' => $is_private ? 'private' : 'public',
'%uid' => $uid,
'%visible' => isset($this->nidsVisible[$nid]) ? 'visible' : 'not visible',
'%current_uid' => $this->webUser->id(),
'%tid_is_private' => $tid_is_private ? 'private' : 'public',
]));
}
}
}
}
}
@@ -0,0 +1,113 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Component\Utility\Unicode;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests the interaction of the node access system with fields.
*
* @group node
*/
class NodeAccessFieldTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node_access_test', 'field_ui'];
/**
* A user with permission to bypass access content.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* A user with permission to manage content types and fields.
*
* @var \Drupal\user\UserInterface
*/
protected $contentAdminUser;
/**
* The name of the created field.
*
* @var string
*/
protected $fieldName;
protected function setUp() {
parent::setUp();
node_access_rebuild();
// Create some users.
$this->adminUser = $this->drupalCreateUser(['access content', 'bypass node access']);
$this->contentAdminUser = $this->drupalCreateUser(['access content', 'administer content types', 'administer node fields']);
// Add a custom field to the page content type.
$this->fieldName = Unicode::strtolower($this->randomMachineName() . '_field_name');
FieldStorageConfig::create([
'field_name' => $this->fieldName,
'entity_type' => 'node',
'type' => 'text'
])->save();
FieldConfig::create([
'field_name' => $this->fieldName,
'entity_type' => 'node',
'bundle' => 'page',
])->save();
entity_get_display('node', 'page', 'default')
->setComponent($this->fieldName)
->save();
entity_get_form_display('node', 'page', 'default')
->setComponent($this->fieldName)
->save();
}
/**
* Tests administering fields when node access is restricted.
*/
public function testNodeAccessAdministerField() {
// Create a page node.
$fieldData = [];
$value = $fieldData[0]['value'] = $this->randomMachineName();
$node = $this->drupalCreateNode([$this->fieldName => $fieldData]);
// Log in as the administrator and confirm that the field value is present.
$this->drupalLogin($this->adminUser);
$this->drupalGet('node/' . $node->id());
$this->assertText($value, 'The saved field value is visible to an administrator.');
// Log in as the content admin and try to view the node.
$this->drupalLogin($this->contentAdminUser);
$this->drupalGet('node/' . $node->id());
$this->assertText('Access denied', 'Access is denied for the content admin.');
// Modify the field default as the content admin.
$edit = [];
$default = 'Sometimes words have two meanings';
$edit["default_value_input[{$this->fieldName}][0][value]"] = $default;
$this->drupalPostForm(
"admin/structure/types/manage/page/fields/node.page.{$this->fieldName}",
$edit,
t('Save settings')
);
// Log in as the administrator.
$this->drupalLogin($this->adminUser);
// Confirm that the existing node still has the correct field value.
$this->drupalGet('node/' . $node->id());
$this->assertText($value, 'The original field value is visible to an administrator.');
// Confirm that the new default value appears when creating a new node.
$this->drupalGet('node/add/page');
$this->assertRaw($default, 'The updated default value is displayed when creating a new node.');
}
}
@@ -0,0 +1,138 @@
<?php
namespace Drupal\Tests\node\Functional;
/**
* Tests the node access grants cache context service.
*
* @group node
* @group Cache
*/
class NodeAccessGrantsCacheContextTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node_access_test'];
/**
* User with permission to view content.
*/
protected $accessUser;
/**
* User without permission to view content.
*/
protected $noAccessUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
node_access_rebuild();
// Create some content.
$this->drupalCreateNode();
$this->drupalCreateNode();
$this->drupalCreateNode();
$this->drupalCreateNode();
// Create user with simple node access permission. The 'node test view'
// permission is implemented and granted by the node_access_test module.
$this->accessUser = $this->drupalCreateUser(['access content overview', 'access content', 'node test view']);
$this->noAccessUser = $this->drupalCreateUser(['access content overview', 'access content']);
$this->noAccessUser2 = $this->drupalCreateUser(['access content overview', 'access content']);
$this->userMapping = [
1 => $this->rootUser,
2 => $this->accessUser,
3 => $this->noAccessUser,
];
}
/**
* Asserts that for each given user, the expected cache context is returned.
*
* @param array $expected
* Expected values, keyed by user ID, expected cache contexts as values.
*/
protected function assertUserCacheContext(array $expected) {
foreach ($expected as $uid => $context) {
if ($uid > 0) {
$this->drupalLogin($this->userMapping[$uid]);
}
$this->pass('Asserting cache context for user ' . $uid . '.');
$this->assertIdentical($context, $this->container->get('cache_context.user.node_grants')->getContext('view'));
}
$this->drupalLogout();
}
/**
* Tests NodeAccessGrantsCacheContext::getContext().
*/
public function testCacheContext() {
$this->assertUserCacheContext([
0 => 'view.all:0;node_access_test_author:0;node_access_all:0',
1 => 'all',
2 => 'view.all:0;node_access_test_author:2;node_access_test:8888,8889',
3 => 'view.all:0;node_access_test_author:3',
]);
// Grant view to all nodes (because nid = 0) for users in the
// 'node_access_all' realm.
$record = [
'nid' => 0,
'gid' => 0,
'realm' => 'node_access_all',
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
];
db_insert('node_access')->fields($record)->execute();
// Put user accessUser (uid 0) in the realm.
\Drupal::state()->set('node_access_test.no_access_uid', 0);
drupal_static_reset('node_access_view_all_nodes');
$this->assertUserCacheContext([
0 => 'view.all',
1 => 'all',
2 => 'view.all:0;node_access_test_author:2;node_access_test:8888,8889',
3 => 'view.all:0;node_access_test_author:3',
]);
// Put user accessUser (uid 2) in the realm.
\Drupal::state()->set('node_access_test.no_access_uid', $this->accessUser->id());
drupal_static_reset('node_access_view_all_nodes');
$this->assertUserCacheContext([
0 => 'view.all:0;node_access_test_author:0',
1 => 'all',
2 => 'view.all',
3 => 'view.all:0;node_access_test_author:3',
]);
// Put user noAccessUser (uid 3) in the realm.
\Drupal::state()->set('node_access_test.no_access_uid', $this->noAccessUser->id());
drupal_static_reset('node_access_view_all_nodes');
$this->assertUserCacheContext([
0 => 'view.all:0;node_access_test_author:0',
1 => 'all',
2 => 'view.all:0;node_access_test_author:2;node_access_test:8888,8889',
3 => 'view.all',
]);
// Uninstall the node_access_test module
$this->container->get('module_installer')->uninstall(['node_access_test']);
drupal_static_reset('node_access_view_all_nodes');
$this->assertUserCacheContext([
0 => 'view.all',
1 => 'all',
2 => 'view.all',
3 => 'view.all',
]);
}
}
@@ -0,0 +1,334 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\NodeType;
use Drupal\user\Entity\User;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests node access functionality with multiple languages and two node access
* modules.
*
* @group node
*/
class NodeAccessLanguageAwareCombinationTest extends NodeTestBase {
/**
* Enable language and two node access modules.
*
* @var array
*/
public static $modules = ['language', 'node_access_test_language', 'node_access_test'];
/**
* A set of nodes to use in testing.
*
* @var \Drupal\node\NodeInterface[]
*/
protected $nodes = [];
/**
* A normal authenticated user.
*
* @var \Drupal\user\UserInterface.
*/
protected $webUser;
/**
* User 1.
*
* @var \Drupal\user\UserInterface.
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
node_access_test_add_field(NodeType::load('page'));
// Create the 'private' field, which allows the node to be marked as private
// (restricted access) in a given translation.
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_private',
'entity_type' => 'node',
'type' => 'boolean',
'cardinality' => 1,
]);
$field_storage->save();
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'page',
'widget' => [
'type' => 'options_buttons',
],
'settings' => [
'on_label' => 'Private',
'off_label' => 'Not private',
],
])->save();
// After enabling a node access module, the access table has to be rebuild.
node_access_rebuild();
// Add Hungarian and Catalan.
ConfigurableLanguage::createFromLangcode('hu')->save();
ConfigurableLanguage::createFromLangcode('ca')->save();
// Create a normal authenticated user.
$this->webUser = $this->drupalCreateUser(['access content']);
// Load the user 1 user for later use as an admin user with permission to
// see everything.
$this->adminUser = User::load(1);
// The node_access_test_language module allows individual translations of a
// node to be marked private (not viewable by normal users), and the
// node_access_test module allows whole nodes to be marked private. (In a
// real-world implementation, hook_node_access_records_alter() might be
// implemented by one or both modules to enforce that private nodes or
// translations are always private, but we want to test the default,
// additive behavior of node access).
// Create six Hungarian nodes with Catalan translations:
// 1. One public with neither language marked as private.
// 2. One private with neither language marked as private.
// 3. One public with only the Hungarian translation private.
// 4. One public with only the Catalan translation private.
// 5. One public with both the Hungarian and Catalan translations private.
// 6. One private with both the Hungarian and Catalan translations private.
$this->nodes['public_both_public'] = $node = $this->drupalCreateNode([
'body' => [[]],
'langcode' => 'hu',
'field_private' => [['value' => 0]],
'private' => FALSE,
]);
$translation = $node->addTranslation('ca');
$translation->title->value = $this->randomString();
$translation->field_private->value = 0;
$node->save();
$this->nodes['private_both_public'] = $node = $this->drupalCreateNode([
'body' => [[]],
'langcode' => 'hu',
'field_private' => [['value' => 0]],
'private' => TRUE,
]);
$translation = $node->addTranslation('ca');
$translation->title->value = $this->randomString();
$translation->field_private->value = 0;
$node->save();
$this->nodes['public_hu_private'] = $node = $this->drupalCreateNode([
'body' => [[]],
'langcode' => 'hu',
'field_private' => [['value' => 1]],
'private' => FALSE,
]);
$translation = $node->addTranslation('ca');
$translation->title->value = $this->randomString();
$translation->field_private->value = 0;
$node->save();
$this->nodes['public_ca_private'] = $node = $this->drupalCreateNode([
'body' => [[]],
'langcode' => 'hu',
'field_private' => [['value' => 0]],
'private' => FALSE,
]);
$translation = $node->addTranslation('ca');
$translation->title->value = $this->randomString();
$translation->field_private->value = 1;
$node->save();
$this->nodes['public_both_private'] = $node = $this->drupalCreateNode([
'body' => [[]],
'langcode' => 'hu',
'field_private' => [['value' => 1]],
'private' => FALSE,
]);
$translation = $node->addTranslation('ca');
$translation->title->value = $this->randomString();
$translation->field_private->value = 1;
$node->save();
$this->nodes['private_both_private'] = $node = $this->drupalCreateNode([
'body' => [[]],
'langcode' => 'hu',
'field_private' => [['value' => 1]],
'private' => TRUE,
]);
$translation = $node->addTranslation('ca');
$translation->title->value = $this->randomString();
$translation->field_private->value = 1;
$node->save();
$this->nodes['public_no_language_private'] = $this->drupalCreateNode([
'field_private' => [['value' => 1]],
'private' => FALSE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
$this->nodes['public_no_language_public'] = $this->drupalCreateNode([
'field_private' => [['value' => 0]],
'private' => FALSE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
$this->nodes['private_no_language_private'] = $this->drupalCreateNode([
'field_private' => [['value' => 1]],
'private' => TRUE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
$this->nodes['private_no_language_public'] = $this->drupalCreateNode([
'field_private' => [['value' => 1]],
'private' => TRUE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
}
/**
* Tests node access and node access queries with multiple node languages.
*/
public function testNodeAccessLanguageAwareCombination() {
$expected_node_access = ['view' => TRUE, 'update' => FALSE, 'delete' => FALSE];
$expected_node_access_no_access = ['view' => FALSE, 'update' => FALSE, 'delete' => FALSE];
// When the node and both translations are public, access should always be
// granted.
$this->assertNodeAccess($expected_node_access, $this->nodes['public_both_public'], $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['public_both_public']->getTranslation('hu'), $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['public_both_public']->getTranslation('ca'), $this->webUser);
// If the node is marked private but both existing translations are not,
// access should still be granted, because the grants are additive.
$this->assertNodeAccess($expected_node_access, $this->nodes['private_both_public'], $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['private_both_public']->getTranslation('hu'), $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['private_both_public']->getTranslation('ca'), $this->webUser);
// If the node is marked private, but a existing translation is public,
// access should only be granted for the public translation. With the
// Hungarian translation marked as private, but the Catalan translation
// public, the access is granted.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private']->getTranslation('hu'), $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['public_hu_private']->getTranslation('ca'), $this->webUser);
// With the Catalan translation marked as private, but the node public,
// access is granted for the existing Hungarian translation, but not for the
// Catalan.
$this->assertNodeAccess($expected_node_access, $this->nodes['public_ca_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['public_ca_private']->getTranslation('hu'), $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_ca_private']->getTranslation('ca'), $this->webUser);
// With both translations marked as private, but the node public, access
// should be denied in all cases.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private']->getTranslation('hu'), $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private']->getTranslation('ca'), $this->webUser);
// If the node and both its existing translations are private, access should
// be denied in all cases.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_private']->getTranslation('hu'), $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_private']->getTranslation('ca'), $this->webUser);
// No access for all languages as the language aware node access module
// denies access.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_no_language_private'], $this->webUser);
// Access only for request with no language defined.
$this->assertNodeAccess($expected_node_access, $this->nodes['public_no_language_public'], $this->webUser);
// No access for all languages as both node access modules deny access.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_no_language_private'], $this->webUser);
// No access for all languages as the non language aware node access module
// denies access.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_no_language_public'], $this->webUser);
// Query the node table with the node access tag in several languages.
// Query with no language specified. The fallback (hu or und) will be used.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->webUser)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Four nodes should be returned with public Hungarian translations or the
// no language public node.
$this->assertEqual(count($nids), 4, 'db_select() returns 4 nodes when no langcode is specified.');
$this->assertTrue(array_key_exists($this->nodes['public_both_public']->id(), $nids), 'Returned node ID is full public node.');
$this->assertTrue(array_key_exists($this->nodes['public_ca_private']->id(), $nids), 'Returned node ID is Hungarian public only node.');
$this->assertTrue(array_key_exists($this->nodes['private_both_public']->id(), $nids), 'Returned node ID is both public non-language-aware private only node.');
$this->assertTrue(array_key_exists($this->nodes['public_no_language_public']->id(), $nids), 'Returned node ID is no language public node.');
// Query with Hungarian (hu) specified.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'hu')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Three nodes should be returned (with public Hungarian translations).
$this->assertEqual(count($nids), 3, 'db_select() returns 3 nodes.');
$this->assertTrue(array_key_exists($this->nodes['public_both_public']->id(), $nids), 'Returned node ID is both public node.');
$this->assertTrue(array_key_exists($this->nodes['public_ca_private']->id(), $nids), 'Returned node ID is Hungarian public only node.');
$this->assertTrue(array_key_exists($this->nodes['private_both_public']->id(), $nids), 'Returned node ID is both public non-language-aware private only node.');
// Query with Catalan (ca) specified.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'ca')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Three nodes should be returned (with public Catalan translations).
$this->assertEqual(count($nids), 3, 'db_select() returns 3 nodes.');
$this->assertTrue(array_key_exists($this->nodes['public_both_public']->id(), $nids), 'Returned node ID is both public node.');
$this->assertTrue(array_key_exists($this->nodes['public_hu_private']->id(), $nids), 'Returned node ID is Catalan public only node.');
$this->assertTrue(array_key_exists($this->nodes['private_both_public']->id(), $nids), 'Returned node ID is both public non-language-aware private only node.');
// Query with German (de) specified.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// There are no nodes with German translations, so no results are returned.
$this->assertTrue(empty($nids), 'db_select() returns an empty result.');
// Query the nodes table as admin user (full access) with the node access
// tag and no specific langcode.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->adminUser)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// All nodes are returned.
$this->assertEqual(count($nids), 10, 'db_select() returns all nodes.');
// Query the nodes table as admin user (full access) with the node access
// tag and langcode de.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->adminUser)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Even though there is no German translation, all nodes are returned
// because node access filtering does not occur when the user is user 1.
$this->assertEqual(count($nids), 10, 'db_select() returns all nodes.');
}
}
@@ -0,0 +1,276 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\user\Entity\User;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests node_access and db_select() with node_access tag functionality with
* multiple languages with node_access_test_language which is language-aware.
*
* @group node
*/
class NodeAccessLanguageAwareTest extends NodeTestBase {
/**
* Enable language and a language-aware node access module.
*
* @var array
*/
public static $modules = ['language', 'node_access_test_language'];
/**
* A set of nodes to use in testing.
*
* @var \Drupal\node\NodeInterface[]
*/
protected $nodes = [];
/**
* A user with permission to bypass access content.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* A normal authenticated user.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
parent::setUp();
// Create the 'private' field, which allows the node to be marked as private
// (restricted access) in a given translation.
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_private',
'entity_type' => 'node',
'type' => 'boolean',
'cardinality' => 1,
]);
$field_storage->save();
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'page',
'widget' => [
'type' => 'options_buttons',
],
'settings' => [
'on_label' => 'Private',
'off_label' => 'Not private',
],
])->save();
// After enabling a node access module, the access table has to be rebuild.
node_access_rebuild();
// Create a normal authenticated user.
$this->webUser = $this->drupalCreateUser(['access content']);
// Load the user 1 user for later use as an admin user with permission to
// see everything.
$this->adminUser = User::load(1);
// Add Hungarian and Catalan.
ConfigurableLanguage::createFromLangcode('hu')->save();
ConfigurableLanguage::createFromLangcode('ca')->save();
// The node_access_test_language module allows individual translations of a
// node to be marked private (not viewable by normal users).
// Create six nodes:
// 1. Four Hungarian nodes with Catalan translations
// - One with neither language marked as private.
// - One with only the Hungarian translation private.
// - One with only the Catalan translation private.
// - One with both the Hungarian and Catalan translations private.
// 2. Two nodes with no language specified.
// - One public.
// - One private.
$this->nodes['both_public'] = $node = $this->drupalCreateNode([
'body' => [[]],
'langcode' => 'hu',
'field_private' => [['value' => 0]],
]);
$translation = $node->addTranslation('ca');
$translation->title->value = $this->randomString();
$translation->field_private->value = 0;
$node->save();
$this->nodes['ca_private'] = $node = $this->drupalCreateNode([
'body' => [[]],
'langcode' => 'hu',
'field_private' => [['value' => 0]],
]);
$translation = $node->addTranslation('ca');
$translation->title->value = $this->randomString();
$translation->field_private->value = 1;
$node->save();
$this->nodes['hu_private'] = $node = $this->drupalCreateNode([
'body' => [[]],
'langcode' => 'hu',
'field_private' => [['value' => 1]],
]);
$translation = $node->addTranslation('ca');
$translation->title->value = $this->randomString();
$translation->field_private->value = 0;
$node->save();
$this->nodes['both_private'] = $node = $this->drupalCreateNode([
'body' => [[]],
'langcode' => 'hu',
'field_private' => [['value' => 1]],
]);
$translation = $node->addTranslation('ca');
$translation->title->value = $this->randomString();
$translation->field_private->value = 1;
$node->save();
$this->nodes['no_language_public'] = $this->drupalCreateNode([
'field_private' => [['value' => 0]],
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
$this->nodes['no_language_private'] = $this->drupalCreateNode([
'field_private' => [['value' => 1]],
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
}
/**
* Tests node access and node access queries with multiple node languages.
*/
public function testNodeAccessLanguageAware() {
// The node_access_test_language module only grants view access.
$expected_node_access = ['view' => TRUE, 'update' => FALSE, 'delete' => FALSE];
$expected_node_access_no_access = ['view' => FALSE, 'update' => FALSE, 'delete' => FALSE];
// When both Hungarian and Catalan are marked as public, access to the
// Hungarian translation should be granted with the default entity object or
// when the Hungarian translation is specified explicitly.
$this->assertNodeAccess($expected_node_access, $this->nodes['both_public'], $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['both_public']->getTranslation('hu'), $this->webUser);
// Access to the Catalan translation should also be granted.
$this->assertNodeAccess($expected_node_access, $this->nodes['both_public']->getTranslation('ca'), $this->webUser);
// When Hungarian is marked as private, access to the Hungarian translation
// should be denied with the default entity object or when the Hungarian
// translation is specified explicitly.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['hu_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['hu_private']->getTranslation('hu'), $this->webUser);
// Access to the Catalan translation should be granted.
$this->assertNodeAccess($expected_node_access, $this->nodes['hu_private']->getTranslation('ca'), $this->webUser);
// When Catalan is marked as private, access to the Hungarian translation
// should be granted with the default entity object or when the Hungarian
// translation is specified explicitly.
$this->assertNodeAccess($expected_node_access, $this->nodes['ca_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access, $this->nodes['ca_private']->getTranslation('hu'), $this->webUser);
// Access to the Catalan translation should be granted.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['ca_private']->getTranslation('ca'), $this->webUser);
// When both translations are marked as private, access should be denied
// regardless of the entity object specified.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private'], $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private']->getTranslation('hu'), $this->webUser);
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private']->getTranslation('ca'), $this->webUser);
// When no language is specified for a private node, access to every node
// translation is denied.
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['no_language_private'], $this->webUser);
// When no language is specified for a public node, access should be
// granted.
$this->assertNodeAccess($expected_node_access, $this->nodes['no_language_public'], $this->webUser);
// Query the node table with the node access tag in several languages.
// Query with no language specified. The fallback (hu) will be used.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->webUser)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Three nodes should be returned:
// - Node with both translations public.
// - Node with only the Catalan translation marked as private.
// - No language node marked as public.
$this->assertEqual(count($nids), 3, 'db_select() returns 3 nodes when no langcode is specified.');
$this->assertTrue(array_key_exists($this->nodes['both_public']->id(), $nids), 'The node with both translations public is returned.');
$this->assertTrue(array_key_exists($this->nodes['ca_private']->id(), $nids), 'The node with only the Catalan translation private is returned.');
$this->assertTrue(array_key_exists($this->nodes['no_language_public']->id(), $nids), 'The node with no language is returned.');
// Query with Hungarian (hu) specified.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'hu')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Two nodes should be returned: the node with both translations public, and
// the node with only the Catalan translation marked as private.
$this->assertEqual(count($nids), 2, 'db_select() returns 2 nodes when the hu langcode is specified.');
$this->assertTrue(array_key_exists($this->nodes['both_public']->id(), $nids), 'The node with both translations public is returned.');
$this->assertTrue(array_key_exists($this->nodes['ca_private']->id(), $nids), 'The node with only the Catalan translation private is returned.');
// Query with Catalan (ca) specified.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'ca')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Two nodes should be returned: the node with both translations public, and
// the node with only the Hungarian translation marked as private.
$this->assertEqual(count($nids), 2, 'db_select() returns 2 nodes when the hu langcode is specified.');
$this->assertTrue(array_key_exists($this->nodes['both_public']->id(), $nids), 'The node with both translations public is returned.');
$this->assertTrue(array_key_exists($this->nodes['hu_private']->id(), $nids), 'The node with only the Hungarian translation private is returned.');
// Query with German (de) specified.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->webUser)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// There are no nodes with German translations, so no results are returned.
$this->assertTrue(empty($nids), 'db_select() returns an empty result when the de langcode is specified.');
// Query the nodes table as admin user (full access) with the node access
// tag and no specific langcode.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->adminUser)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// All nodes are returned.
$this->assertEqual(count($nids), 6, 'db_select() returns all nodes.');
// Query the nodes table as admin user (full access) with the node access
// tag and langcode de.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $this->adminUser)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Even though there is no German translation, all nodes are returned
// because node access filtering does not occur when the user is user 1.
$this->assertEqual(count($nids), 6, 'db_select() returns all nodes.');
}
}
@@ -0,0 +1,255 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\NodeType;
use Drupal\user\Entity\User;
/**
* Tests 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
*/
class NodeAccessLanguageTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['language', 'node_access_test'];
protected function setUp() {
parent::setUp();
node_access_test_add_field(NodeType::load('page'));
// After enabling a node access module, the access table has to be rebuild.
node_access_rebuild();
// Enable the private node feature of the node_access_test module.
\Drupal::state()->set('node_access_test.private', TRUE);
// Add Hungarian, Catalan and Croatian.
ConfigurableLanguage::createFromLangcode('hu')->save();
ConfigurableLanguage::createFromLangcode('ca')->save();
ConfigurableLanguage::createFromLangcode('hr')->save();
}
/**
* Tests node access with multiple node languages and no private nodes.
*/
public function testNodeAccess() {
$web_user = $this->drupalCreateUser(['access content']);
$expected_node_access = ['view' => TRUE, 'update' => FALSE, 'delete' => FALSE];
$expected_node_access_no_access = ['view' => FALSE, 'update' => FALSE, 'delete' => FALSE];
// Creating a public node with langcode Hungarian, will be saved as the
// fallback in node access table.
$node_public_hu = $this->drupalCreateNode(['body' => [[]], 'langcode' => 'hu', 'private' => FALSE]);
$this->assertTrue($node_public_hu->language()->getId() == 'hu', 'Node created as Hungarian.');
// Tests the default access is provided for the public Hungarian node.
$this->assertNodeAccess($expected_node_access, $node_public_hu, $web_user);
// Tests that Hungarian provided specifically results in the same.
$this->assertNodeAccess($expected_node_access, $node_public_hu->getTranslation('hu'), $web_user);
// Creating a public node with no special langcode, like when no language
// module enabled.
$node_public_no_language = $this->drupalCreateNode([
'private' => FALSE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
$this->assertTrue($node_public_no_language->language()->getId() == LanguageInterface::LANGCODE_NOT_SPECIFIED, 'Node created with not specified language.');
// Tests that access is granted if requested with no language.
$this->assertNodeAccess($expected_node_access, $node_public_no_language, $web_user);
// Reset the node access cache and turn on our test node access code.
\Drupal::entityManager()->getAccessControlHandler('node')->resetCache();
\Drupal::state()->set('node_access_test_secret_catalan', 1);
$node_public_ca = $this->drupalCreateNode(['body' => [[]], 'langcode' => 'ca', 'private' => FALSE]);
$this->assertTrue($node_public_ca->language()->getId() == 'ca', 'Node created as Catalan.');
// Tests that access is granted if requested with no language.
$this->assertNodeAccess($expected_node_access, $node_public_no_language, $web_user);
$this->assertNodeAccess($expected_node_access_no_access, $node_public_ca, $web_user);
// Tests that Hungarian node is still accessible.
$this->assertNodeAccess($expected_node_access, $node_public_hu, $web_user);
$this->assertNodeAccess($expected_node_access, $node_public_hu->getTranslation('hu'), $web_user);
// Tests that Catalan is still not accessible.
$this->assertNodeAccess($expected_node_access_no_access, $node_public_ca->getTranslation('ca'), $web_user);
// Make Catalan accessible.
\Drupal::state()->set('node_access_test_secret_catalan', 0);
// Tests that Catalan is accessible on a node with a Catalan version as the
// static cache has not been reset.
$this->assertNodeAccess($expected_node_access_no_access, $node_public_ca, $web_user);
$this->assertNodeAccess($expected_node_access_no_access, $node_public_ca->getTranslation('ca'), $web_user);
\Drupal::entityManager()->getAccessControlHandler('node')->resetCache();
// Tests that access is granted if requested with no language.
$this->assertNodeAccess($expected_node_access, $node_public_no_language, $web_user);
$this->assertNodeAccess($expected_node_access, $node_public_ca, $web_user);
// Tests that Hungarian node is still accessible.
$this->assertNodeAccess($expected_node_access, $node_public_hu, $web_user);
$this->assertNodeAccess($expected_node_access, $node_public_hu->getTranslation('hu'), $web_user);
// Tests that Catalan is accessible on a node with a Catalan version.
$this->assertNodeAccess($expected_node_access, $node_public_ca->getTranslation('ca'), $web_user);
}
/**
* Tests node access with multiple node languages and private nodes.
*/
public function testNodeAccessPrivate() {
$web_user = $this->drupalCreateUser(['access content']);
$expected_node_access = ['view' => TRUE, 'update' => FALSE, 'delete' => FALSE];
$expected_node_access_no_access = ['view' => FALSE, 'update' => FALSE, 'delete' => FALSE];
// Creating a private node with langcode Hungarian, will be saved as the
// fallback in node access table.
$node_private_hu = $this->drupalCreateNode(['body' => [[]], 'langcode' => 'hu', 'private' => TRUE]);
$this->assertTrue($node_private_hu->language()->getId() == 'hu', 'Node created as Hungarian.');
// Tests the default access is not provided for the private Hungarian node.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_hu, $web_user);
// Tests that Hungarian provided specifically results in the same.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_hu->getTranslation('hu'), $web_user);
// Creating a private node with no special langcode, like when no language
// module enabled.
$node_private_no_language = $this->drupalCreateNode([
'private' => TRUE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
$this->assertTrue($node_private_no_language->language()->getId() == LanguageInterface::LANGCODE_NOT_SPECIFIED, 'Node created with not specified language.');
// Tests that access is not granted if requested with no language.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user);
// Reset the node access cache and turn on our test node access code.
\Drupal::entityManager()->getAccessControlHandler('node')->resetCache();
\Drupal::state()->set('node_access_test_secret_catalan', 1);
// Tests that access is not granted if requested with no language.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user);
// Creating a private node with langcode Catalan to test that the
// node_access_test_secret_catalan flag works.
$private_ca_user = $this->drupalCreateUser(['access content', 'node test view']);
$node_private_ca = $this->drupalCreateNode(['body' => [[]], 'langcode' => 'ca', 'private' => TRUE]);
$this->assertTrue($node_private_ca->language()->getId() == 'ca', 'Node created as Catalan.');
// Tests that Catalan is still not accessible to either user.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_ca, $web_user);
$this->assertNodeAccess($expected_node_access_no_access, $node_private_ca->getTranslation('ca'), $web_user);
$this->assertNodeAccess($expected_node_access_no_access, $node_private_ca, $private_ca_user);
$this->assertNodeAccess($expected_node_access_no_access, $node_private_ca->getTranslation('ca'), $private_ca_user);
\Drupal::entityManager()->getAccessControlHandler('node')->resetCache();
\Drupal::state()->set('node_access_test_secret_catalan', 0);
// Tests that Catalan is still not accessible for a user with no access to
// private nodes.
$this->assertNodeAccess($expected_node_access_no_access, $node_private_ca, $web_user);
$this->assertNodeAccess($expected_node_access_no_access, $node_private_ca->getTranslation('ca'), $web_user);
// Tests that Catalan is accessible by a user with the permission to see
// private nodes.
$this->assertNodeAccess($expected_node_access, $node_private_ca, $private_ca_user);
$this->assertNodeAccess($expected_node_access, $node_private_ca->getTranslation('ca'), $private_ca_user);
}
/**
* Tests db_select() with a 'node_access' tag and langcode metadata.
*/
public function testNodeAccessQueryTag() {
// Create a normal authenticated user.
$web_user = $this->drupalCreateUser(['access content']);
// Load the user 1 user for later use as an admin user with permission to
// see everything.
$admin_user = User::load(1);
// Creating a private node with langcode Hungarian, will be saved as
// the fallback in node access table.
$node_private = $this->drupalCreateNode(['body' => [[]], 'langcode' => 'hu', 'private' => TRUE]);
$this->assertTrue($node_private->language()->getId() == 'hu', '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(['body' => [[]], 'langcode' => 'hu', 'private' => FALSE]);
$this->assertTrue($node_public->language()->getId() == 'hu', 'Node created as Hungarian.');
// Creating a public node with no special langcode, like when no language
// module enabled.
$node_no_language = $this->drupalCreateNode([
'private' => FALSE,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
$this->assertTrue($node_no_language->language()->getId() == LanguageInterface::LANGCODE_NOT_SPECIFIED, 'Node created with not specified language.');
// Query the nodes table as the web user with the node access tag and no
// specific langcode.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $web_user)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// The public node and no language node should be returned. Because no
// langcode is given it will use the fallback node.
$this->assertEqual(count($nids), 2, 'db_select() returns 2 node');
$this->assertTrue(array_key_exists($node_public->id(), $nids), 'Returned node ID is public node.');
$this->assertTrue(array_key_exists($node_no_language->id(), $nids), 'Returned node ID is no language node.');
// Query the nodes table as the web user with the node access tag and
// langcode de.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $web_user)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// Because no nodes are created in German, no nodes are returned.
$this->assertTrue(empty($nids), 'db_select() returns an empty result.');
// Query the nodes table as admin user (full access) with the node access
// tag and no specific langcode.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $admin_user)
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// All nodes are returned.
$this->assertEqual(count($nids), 3, 'db_select() returns all three nodes.');
// Query the nodes table as admin user (full access) with the node access
// tag and langcode de.
$select = db_select('node', 'n')
->fields('n', ['nid'])
->addMetaData('account', $admin_user)
->addMetaData('langcode', 'de')
->addTag('node_access');
$nids = $select->execute()->fetchAllAssoc('nid');
// All nodes are returned because node access tag is not invoked when the
// user is user 1.
$this->assertEqual(count($nids), 3, 'db_select() returns all three nodes.');
}
}
@@ -0,0 +1,72 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\user\RoleInterface;
/**
* Tests the interaction of the node access system with menu links.
*
* @group node
*/
class NodeAccessMenuLinkTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['menu_ui', 'block'];
/**
* A user with permission to manage menu links and create nodes.
*
* @var \Drupal\user\UserInterface
*/
protected $contentAdminUser;
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('system_menu_block:main');
$this->contentAdminUser = $this->drupalCreateUser([
'access content',
'administer content types',
'administer menu'
]);
$this->config('user.role.' . RoleInterface::ANONYMOUS_ID)->set('permissions', [])->save();
}
/**
* SA-CORE-2015-003: Tests menu links to nodes when node access is restricted.
*/
public function testNodeAccessMenuLink() {
$menu_link_title = $this->randomString();
$this->drupalLogin($this->contentAdminUser);
$edit = [
'title[0][value]' => $this->randomString(),
'body[0][value]' => $this->randomString(),
'menu[enabled]' => 1,
'menu[title]' => $menu_link_title,
];
$this->drupalPostForm('node/add/page', $edit, t('Save'));
$this->assertLink($menu_link_title);
// Ensure anonymous users without "access content" permission do not see
// this menu link.
$this->drupalLogout();
$this->drupalGet('');
$this->assertNoLink($menu_link_title);
// Ensure anonymous users with "access content" permission see this menu
// link.
$this->config('user.role.' . RoleInterface::ANONYMOUS_ID)->set('permissions', ['access content'])->save();
$this->drupalGet('');
$this->assertLink($menu_link_title);
}
}
@@ -0,0 +1,100 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\comment\CommentInterface;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\comment\Entity\Comment;
use Drupal\Tests\BrowserTestBase;
/**
* Tests access controlled node views have the right amount of comment pages.
*
* @group node
*/
class NodeAccessPagerTest extends BrowserTestBase {
use CommentTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node_access_test', 'comment', 'forum'];
protected function setUp() {
parent::setUp();
node_access_rebuild();
$this->drupalCreateContentType(['type' => 'page', 'name' => t('Basic page')]);
$this->addDefaultCommentField('node', 'page');
$this->webUser = $this->drupalCreateUser(['access content', 'access comments', 'node test view']);
}
/**
* Tests the comment pager for nodes with multiple grants per realm.
*/
public function testCommentPager() {
// Create a node.
$node = $this->drupalCreateNode();
// Create 60 comments.
for ($i = 0; $i < 60; $i++) {
$comment = Comment::create([
'entity_id' => $node->id(),
'entity_type' => 'node',
'field_name' => 'comment',
'subject' => $this->randomMachineName(),
'comment_body' => [
['value' => $this->randomMachineName()],
],
'status' => CommentInterface::PUBLISHED,
]);
$comment->save();
}
$this->drupalLogin($this->webUser);
// View the node page. With the default 50 comments per page there should
// be two pages (0, 1) but no third (2) page.
$this->drupalGet('node/' . $node->id());
$this->assertText($node->label());
$this->assertText(t('Comments'));
$this->assertRaw('page=1');
$this->assertNoRaw('page=2');
}
/**
* Tests the forum node pager for nodes with multiple grants per realm.
*/
public function testForumPager() {
// Look up the forums vocabulary ID.
$vid = $this->config('forum.settings')->get('vocabulary');
$this->assertTrue($vid, 'Forum navigation vocabulary ID is set.');
// Look up the general discussion term.
$tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vid, 0, 1);
$tid = reset($tree)->tid;
$this->assertTrue($tid, 'General discussion term is found in the forum vocabulary.');
// Create 30 nodes.
for ($i = 0; $i < 30; $i++) {
$this->drupalCreateNode([
'nid' => NULL,
'type' => 'forum',
'taxonomy_forums' => [
['target_id' => $tid],
],
]);
}
// View the general discussion forum page. With the default 25 nodes per
// page there should be two pages for 30 nodes, no more.
$this->drupalLogin($this->webUser);
$this->drupalGet('forum/' . $tid);
$this->assertRaw('page=1');
$this->assertNoRaw('page=2');
}
}
@@ -0,0 +1,118 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\node\Entity\NodeType;
/**
* Ensures that node access rebuild functions work correctly even
* when other modules implements hook_node_grants().
*
* @group node
*/
class NodeAccessRebuildNodeGrantsTest extends NodeTestBase {
/**
* A user to create nodes that only it has access to.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* A user to test the rebuild nodes feature which can't access the nodes.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(['administer site configuration', 'access administration pages', 'access site reports']);
$this->drupalLogin($this->adminUser);
$this->webUser = $this->drupalCreateUser();
}
/**
* Tests rebuilding the node access permissions table with content.
*/
public function testNodeAccessRebuildNodeGrants() {
\Drupal::service('module_installer')->install(['node_access_test']);
\Drupal::state()->set('node_access_test.private', TRUE);
node_access_test_add_field(NodeType::load('page'));
$this->resetAll();
// Create 30 nodes so that _node_access_rebuild_batch_operation() has to run
// more than once.
for ($i = 0; $i < 30; $i++) {
$nodes[] = $this->drupalCreateNode([
'uid' => $this->webUser->id(),
'private' => [['value' => 1]]
]);
}
/** @var \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage */
$grant_storage = \Drupal::service('node.grant_storage');
// Default realm access and node records are present.
foreach ($nodes as $node) {
$this->assertTrue($node->private->value);
$this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the node author.');
$this->assertTrue($grant_storage->access($node, 'view', $this->adminUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the admin user.');
}
$this->assertEqual(1, \Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is an all realm access record');
$this->assertTrue(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions need to be rebuilt');
// Rebuild permissions.
$this->drupalGet('admin/reports/status');
$this->clickLink(t('Rebuild permissions'));
$this->drupalPostForm(NULL, [], t('Rebuild permissions'));
$this->assertText(t('The content access permissions have been rebuilt.'));
// Test if the rebuild by user that cannot bypass node access and does not
// have access to the nodes has been successful.
$this->assertFalse($this->adminUser->hasPermission('bypass node access'));
$this->assertNull(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions have been rebuilt');
foreach ($nodes as $node) {
$this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.');
$this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.');
}
$this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record');
// Test an anonymous node access rebuild from code.
$this->drupalLogout();
node_access_rebuild();
foreach ($nodes as $node) {
$this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.');
$this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.');
}
$this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record');
}
/**
* Tests rebuilding the node access permissions table with no content.
*/
public function testNodeAccessRebuildNoAccessModules() {
// Default realm access is present.
$this->assertEqual(1, \Drupal::service('node.grant_storage')->count(), 'There is an all realm access record');
// No need to rebuild permissions.
$this->assertFalse(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions need to be rebuilt');
// Rebuild permissions.
$this->drupalGet('admin/reports/status');
$this->clickLink(t('Rebuild permissions'));
$this->drupalPostForm(NULL, [], t('Rebuild permissions'));
$this->assertText(t('Content permissions have been rebuilt.'));
$this->assertNull(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions have been rebuilt');
// Default realm access is still present.
$this->assertEqual(1, \Drupal::service('node.grant_storage')->count(), 'There is an all realm access record');
}
}
@@ -0,0 +1,84 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\node\Entity\Node;
/**
* Tests hook_node_access_records when acquiring grants.
*
* @group node
*/
class NodeAccessRecordsTest extends NodeTestBase {
/**
* Enable a module that implements node access API hooks and alter hook.
*
* @var array
*/
public static $modules = ['node_test'];
/**
* Creates a node and tests the creation of node access rules.
*/
public function testNodeAccessRecords() {
// Create an article node.
$node1 = $this->drupalCreateNode(['type' => 'article']);
$this->assertTrue(Node::load($node1->id()), 'Article node created.');
// Check to see if grants added by node_test_node_access_records made it in.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node1->id()])->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_article_realm', 'Grant with article_realm acquired for node without alteration.');
$this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
// Create an unpromoted "Basic page" node.
$node2 = $this->drupalCreateNode(['type' => 'page', 'promote' => 0]);
$this->assertTrue(Node::load($node2->id()), 'Unpromoted basic page node created.');
// Check to see if grants added by node_test_node_access_records made it in.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node2->id()])->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_page_realm', 'Grant with page_realm acquired for node without alteration.');
$this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
// Create an unpromoted, unpublished "Basic page" node.
$node3 = $this->drupalCreateNode(['type' => 'page', 'promote' => 0, 'status' => 0]);
$this->assertTrue(Node::load($node3->id()), 'Unpromoted, unpublished basic page node created.');
// Check to see if grants added by node_test_node_access_records made it in.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node3->id()])->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_page_realm', 'Grant with page_realm acquired for node without alteration.');
$this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
// Create a promoted "Basic page" node.
$node4 = $this->drupalCreateNode(['type' => 'page', 'promote' => 1]);
$this->assertTrue(Node::load($node4->id()), 'Promoted basic page node created.');
// Check to see if grant added by node_test_node_access_records was altered
// by node_test_node_access_records_alter.
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node4->id()])->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->realm, 'test_alter_realm', 'Altered grant with alter_realm acquired for node.');
$this->assertEqual($records[0]->gid, 2, 'Altered grant with gid = 2 acquired for node.');
// Check to see if we can alter grants with hook_node_grants_alter().
$operations = ['view', 'update', 'delete'];
// Create a user that is allowed to access content.
$web_user = $this->drupalCreateUser(['access content']);
foreach ($operations as $op) {
$grants = node_test_node_grants($web_user, $op);
$altered_grants = $grants;
\Drupal::moduleHandler()->alter('node_grants', $altered_grants, $web_user, $op);
$this->assertNotEqual($grants, $altered_grants, format_string('Altered the %op grant for a user.', ['%op' => $op]));
}
// Check that core does not grant access to an unpublished node when an
// empty $grants array is returned.
$node6 = $this->drupalCreateNode(['status' => 0, 'disable_node_access' => TRUE]);
$records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node6->id()])->fetchAll();
$this->assertEqual(count($records), 0, 'Returned no records for unpublished node.');
}
}
@@ -0,0 +1,84 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Component\Utility\Crypt;
use Drupal\Tests\BrowserTestBase;
use Drupal\system\Entity\Action;
/**
* Tests configuration of actions provided by the Node module.
*
* @group node
*/
class NodeActionsConfigurationTest extends BrowserTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['action', 'node'];
/**
* Tests configuration of the node_assign_owner_action action.
*/
public function testAssignOwnerNodeActionConfiguration() {
// Create a user with permission to view the actions administration pages.
$user = $this->drupalCreateUser(['administer actions']);
$this->drupalLogin($user);
// Make a POST request to admin/config/system/actions.
$edit = [];
$edit['action'] = Crypt::hashBase64('node_assign_owner_action');
$this->drupalPostForm('admin/config/system/actions', $edit, t('Create'));
$this->assertResponse(200);
// Make a POST request to the individual action configuration page.
$edit = [];
$action_label = $this->randomMachineName();
$edit['label'] = $action_label;
$edit['id'] = strtolower($action_label);
$edit['owner_uid'] = $user->id();
$this->drupalPostForm('admin/config/system/actions/add/' . Crypt::hashBase64('node_assign_owner_action'), $edit, t('Save'));
$this->assertResponse(200);
// Make sure that the new action was saved properly.
$this->assertText(t('The action has been successfully saved.'), 'The node_assign_owner_action action has been successfully saved.');
$this->assertText($action_label, 'The label of the node_assign_owner_action action appears on the actions administration page after saving.');
// Make another POST request to the action edit page.
$this->clickLink(t('Configure'));
preg_match('|admin/config/system/actions/configure/(.+)|', $this->getUrl(), $matches);
$aid = $matches[1];
$edit = [];
$new_action_label = $this->randomMachineName();
$edit['label'] = $new_action_label;
$edit['owner_uid'] = $user->id();
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertResponse(200);
// Make sure that the action updated properly.
$this->assertText(t('The action has been successfully saved.'), 'The node_assign_owner_action action has been successfully updated.');
$this->assertNoText($action_label, 'The old label for the node_assign_owner_action action does not appear on the actions administration page after updating.');
$this->assertText($new_action_label, 'The new label for the node_assign_owner_action action appears on the actions administration page after updating.');
// Make sure that deletions work properly.
$this->drupalGet('admin/config/system/actions');
$this->clickLink(t('Delete'));
$this->assertResponse(200);
$edit = [];
$this->drupalPostForm("admin/config/system/actions/configure/$aid/delete", $edit, t('Delete'));
$this->assertResponse(200);
// Make sure that the action was actually deleted.
$this->assertRaw(t('The action %action has been deleted.', ['%action' => $new_action_label]), 'The delete confirmation message appears after deleting the node_assign_owner_action action.');
$this->drupalGet('admin/config/system/actions');
$this->assertResponse(200);
$this->assertNoText($new_action_label, 'The label for the node_assign_owner_action action does not appear on the actions administration page after deleting.');
$action = Action::load($aid);
$this->assertFalse($action, 'The node_assign_owner_action action is not available after being deleted.');
}
}
@@ -0,0 +1,204 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\user\RoleInterface;
/**
* Tests node administration page functionality.
*
* @group node
*/
class NodeAdminTest extends NodeTestBase {
/**
* A user with permission to bypass access content.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* A user with the 'access content overview' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser1;
/**
* A normal user with permission to view own unpublished content.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser2;
/**
* A normal user with permission to bypass node access content.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser3;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['views'];
protected function setUp() {
parent::setUp();
// Remove the "view own unpublished content" permission which is set
// by default for authenticated users so we can test this permission
// correctly.
user_role_revoke_permissions(RoleInterface::AUTHENTICATED_ID, ['view own unpublished content']);
$this->adminUser = $this->drupalCreateUser(['access administration pages', 'access content overview', 'administer nodes', 'bypass node access']);
$this->baseUser1 = $this->drupalCreateUser(['access content overview']);
$this->baseUser2 = $this->drupalCreateUser(['access content overview', 'view own unpublished content']);
$this->baseUser3 = $this->drupalCreateUser(['access content overview', 'bypass node access']);
}
/**
* Tests that the table sorting works on the content admin pages.
*/
public function testContentAdminSort() {
$this->drupalLogin($this->adminUser);
$changed = REQUEST_TIME;
foreach (['dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB'] as $prefix) {
$changed += 1000;
$node = $this->drupalCreateNode(['title' => $prefix . $this->randomMachineName(6)]);
db_update('node_field_data')
->fields(['changed' => $changed])
->condition('nid', $node->id())
->execute();
}
// Test that the default sort by node.changed DESC actually fires properly.
$nodes_query = db_select('node_field_data', 'n')
->fields('n', ['title'])
->orderBy('changed', 'DESC')
->execute()
->fetchCol();
$this->drupalGet('admin/content');
foreach ($nodes_query as $delta => $string) {
$elements = $this->xpath('//table[contains(@class, :class)]/tbody/tr[' . ($delta + 1) . ']/td[2]/a[normalize-space(text())=:label]', [':class' => 'views-table', ':label' => $string]);
$this->assertTrue(!empty($elements), 'The node was found in the correct order.');
}
// Compare the rendered HTML node list to a query for the nodes ordered by
// title to account for possible database-dependent sort order.
$nodes_query = db_select('node_field_data', 'n')
->fields('n', ['title'])
->orderBy('title')
->execute()
->fetchCol();
$this->drupalGet('admin/content', ['query' => ['sort' => 'asc', 'order' => 'title']]);
foreach ($nodes_query as $delta => $string) {
$elements = $this->xpath('//table[contains(@class, :class)]/tbody/tr[' . ($delta + 1) . ']/td[2]/a[normalize-space(text())=:label]', [':class' => 'views-table', ':label' => $string]);
$this->assertTrue(!empty($elements), 'The node was found in the correct order.');
}
}
/**
* Tests content overview with different user permissions.
*
* Taxonomy filters are tested separately.
*
* @see TaxonomyNodeFilterTestCase
*/
public function testContentAdminPages() {
$this->drupalLogin($this->adminUser);
// Use an explicit changed time to ensure the expected order in the content
// admin listing. We want these to appear in the table in the same order as
// they appear in the following code, and the 'content' View has a table
// style configuration with a default sort on the 'changed' field DESC.
$time = time();
$nodes['published_page'] = $this->drupalCreateNode(['type' => 'page', 'changed' => $time--]);
$nodes['published_article'] = $this->drupalCreateNode(['type' => 'article', 'changed' => $time--]);
$nodes['unpublished_page_1'] = $this->drupalCreateNode(['type' => 'page', 'changed' => $time--, 'uid' => $this->baseUser1->id(), 'status' => 0]);
$nodes['unpublished_page_2'] = $this->drupalCreateNode(['type' => 'page', 'changed' => $time, 'uid' => $this->baseUser2->id(), 'status' => 0]);
// Verify view, edit, and delete links for any content.
$this->drupalGet('admin/content');
$this->assertResponse(200);
$node_type_labels = $this->xpath('//td[contains(@class, "views-field-type")]');
$delta = 0;
foreach ($nodes as $node) {
$this->assertLinkByHref('node/' . $node->id());
$this->assertLinkByHref('node/' . $node->id() . '/edit');
$this->assertLinkByHref('node/' . $node->id() . '/delete');
// Verify that we can see the content type label.
$this->assertEqual(trim($node_type_labels[$delta]->getText()), $node->type->entity->label());
$delta++;
}
// Verify filtering by publishing status.
$this->drupalGet('admin/content', ['query' => ['status' => TRUE]]);
$this->assertLinkByHref('node/' . $nodes['published_page']->id() . '/edit');
$this->assertLinkByHref('node/' . $nodes['published_article']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id() . '/edit');
// Verify filtering by status and content type.
$this->drupalGet('admin/content', ['query' => ['status' => TRUE, 'type' => 'page']]);
$this->assertLinkByHref('node/' . $nodes['published_page']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['published_article']->id() . '/edit');
// Verify no operation links are displayed for regular users.
$this->drupalLogout();
$this->drupalLogin($this->baseUser1);
$this->drupalGet('admin/content');
$this->assertResponse(200);
$this->assertLinkByHref('node/' . $nodes['published_page']->id());
$this->assertLinkByHref('node/' . $nodes['published_article']->id());
$this->assertNoLinkByHref('node/' . $nodes['published_page']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['published_page']->id() . '/delete');
$this->assertNoLinkByHref('node/' . $nodes['published_article']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['published_article']->id() . '/delete');
// Verify no unpublished content is displayed without permission.
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id());
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id() . '/delete');
// Verify no tableselect.
$this->assertNoFieldByName('nodes[' . $nodes['published_page']->id() . ']', '', 'No tableselect found.');
// Verify unpublished content is displayed with permission.
$this->drupalLogout();
$this->drupalLogin($this->baseUser2);
$this->drupalGet('admin/content');
$this->assertResponse(200);
$this->assertLinkByHref('node/' . $nodes['unpublished_page_2']->id());
// Verify no operation links are displayed.
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->id() . '/delete');
// Verify user cannot see unpublished content of other users.
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id());
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id() . '/edit');
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->id() . '/delete');
// Verify no tableselect.
$this->assertNoFieldByName('nodes[' . $nodes['unpublished_page_2']->id() . ']', '', 'No tableselect found.');
// Verify node access can be bypassed.
$this->drupalLogout();
$this->drupalLogin($this->baseUser3);
$this->drupalGet('admin/content');
$this->assertResponse(200);
foreach ($nodes as $node) {
$this->assertLinkByHref('node/' . $node->id());
$this->assertLinkByHref('node/' . $node->id() . '/edit');
$this->assertLinkByHref('node/' . $node->id() . '/delete');
}
}
}
@@ -0,0 +1,160 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\block\Entity\Block;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\user\RoleInterface;
/**
* Tests node block functionality.
*
* @group node
*/
class NodeBlockFunctionalTest extends NodeTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* An administrative user for testing.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* An unprivileged user for testing.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['block', 'views'];
protected function setUp() {
parent::setUp();
// Create users and test node.
$this->adminUser = $this->drupalCreateUser(['administer content types', 'administer nodes', 'administer blocks', 'access content overview']);
$this->webUser = $this->drupalCreateUser(['access content', 'create article content']);
}
/**
* Tests the recent comments block.
*/
public function testRecentNodeBlock() {
$this->drupalLogin($this->adminUser);
// Disallow anonymous users to view content.
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, [
'access content' => FALSE,
]);
// Enable the recent content block with two items.
$block = $this->drupalPlaceBlock('views_block:content_recent-block_1', ['id' => 'test_block', 'items_per_page' => 2]);
// Test that block is not visible without nodes.
$this->drupalGet('');
$this->assertText(t('No content available.'), 'Block with "No content available." found.');
// Add some test nodes.
$default_settings = ['uid' => $this->webUser->id(), 'type' => 'article'];
$node1 = $this->drupalCreateNode($default_settings);
$node2 = $this->drupalCreateNode($default_settings);
$node3 = $this->drupalCreateNode($default_settings);
// Change the changed time for node so that we can test ordering.
db_update('node_field_data')
->fields([
'changed' => $node1->getChangedTime() + 100,
])
->condition('nid', $node2->id())
->execute();
db_update('node_field_data')
->fields([
'changed' => $node1->getChangedTime() + 200,
])
->condition('nid', $node3->id())
->execute();
// Test that a user without the 'access content' permission cannot
// see the block.
$this->drupalLogout();
$this->drupalGet('');
$this->assertNoText($block->label(), 'Block was not found.');
// Test that only the 2 latest nodes are shown.
$this->drupalLogin($this->webUser);
$this->assertNoText($node1->label(), 'Node not found in block.');
$this->assertText($node2->label(), 'Node found in block.');
$this->assertText($node3->label(), 'Node found in block.');
// Check to make sure nodes are in the right order.
$this->assertTrue($this->xpath('//div[@id="block-test-block"]//div[@class="item-list"]/ul/li[1]/div/span/a[text() = "' . $node3->label() . '"]'), 'Nodes were ordered correctly in block.');
$this->drupalLogout();
$this->drupalLogin($this->adminUser);
// Set the number of recent nodes to show to 10.
$block->getPlugin()->setConfigurationValue('items_per_page', 10);
$block->save();
// Post an additional node.
$node4 = $this->drupalCreateNode($default_settings);
// Test that all four nodes are shown.
$this->drupalGet('');
$this->assertText($node1->label(), 'Node found in block.');
$this->assertText($node2->label(), 'Node found in block.');
$this->assertText($node3->label(), 'Node found in block.');
$this->assertText($node4->label(), 'Node found in block.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user']);
// Enable the "Powered by Drupal" block only on article nodes.
$edit = [
'id' => strtolower($this->randomMachineName()),
'region' => 'sidebar_first',
'visibility[node_type][bundles][article]' => 'article',
];
$theme = \Drupal::service('theme_handler')->getDefault();
$this->drupalPostForm("admin/structure/block/add/system_powered_by_block/$theme", $edit, t('Save block'));
$block = Block::load($edit['id']);
$visibility = $block->getVisibility();
$this->assertTrue(isset($visibility['node_type']['bundles']['article']), 'Visibility settings were saved to configuration');
// Create a page node.
$node5 = $this->drupalCreateNode(['uid' => $this->adminUser->id(), 'type' => 'page']);
$this->drupalLogout();
$this->drupalLogin($this->webUser);
// Verify visibility rules.
$this->drupalGet('');
$label = $block->label();
$this->assertNoText($label, 'Block was not displayed on the front page.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user', 'route']);
$this->drupalGet('node/add/article');
$this->assertText($label, 'Block was displayed on the node/add/article page.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'session', 'theme', 'url.path', 'url.query_args', 'user', 'route']);
$this->drupalGet('node/' . $node1->id());
$this->assertText($label, 'Block was displayed on the node/N when node is of type article.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user', 'route', 'timezone']);
$this->drupalGet('node/' . $node5->id());
$this->assertNoText($label, 'Block was not displayed on nodes of type page.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user', 'route', 'timezone']);
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/structure/block');
$this->assertText($label, 'Block was displayed on the admin/structure/block page.');
$this->assertLinkByHref($block->url());
}
}
@@ -0,0 +1,74 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Core\Entity\EntityInterface;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
/**
* Tests the Node entity's cache tags.
*
* @group node
*/
class NodeCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node'];
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" node type.
NodeType::create([
'name' => 'Camelids',
'type' => 'camelids',
])->save();
// Create a "Llama" node.
$node = Node::create(['type' => 'camelids']);
$node->setTitle('Llama')
->setPublished(TRUE)
->save();
return $node;
}
/**
* {@inheritdoc}
*/
protected function getDefaultCacheContexts() {
$defaults = parent::getDefaultCacheContexts();
// @see \Drupal\node\Controller\NodeViewController::view()
$defaults[] = 'user.roles:anonymous';
return $defaults;
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
return ['timezone'];
}
/**
* {@inheritdoc}
*
* Each node must have an author.
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $node) {
return ['user:' . $node->getOwnerId(), 'user_view'];
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheContextsForEntityListing() {
return ['user.node_grants:view'];
}
}
@@ -0,0 +1,234 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Core\Database\Database;
use Drupal\Core\Language\LanguageInterface;
use Drupal\node\Entity\Node;
/**
* Create a node and test saving it.
*
* @group node
*/
class NodeCreationTest extends NodeTestBase {
/**
* Modules to enable.
*
* Enable dummy module that implements hook_ENTITY_TYPE_insert() for
* exceptions (function node_test_exception_node_insert() ).
*
* @var array
*/
public static $modules = ['node_test_exception', 'dblog', 'test_page_test'];
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(['create page content', 'edit own page content']);
$this->drupalLogin($web_user);
}
/**
* Creates a "Basic page" node and verifies its consistency in the database.
*/
public function testNodeCreation() {
$node_type_storage = \Drupal::entityManager()->getStorage('node_type');
// Test /node/add page with only one content type.
$node_type_storage->load('article')->delete();
$this->drupalGet('node/add');
$this->assertResponse(200);
$this->assertUrl('node/add/page');
// Create a node.
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm('node/add/page', $edit, t('Save'));
// Check that the Basic page has been created.
$this->assertText(t('@post @title has been created.', ['@post' => 'Basic page', '@title' => $edit['title[0][value]']]), 'Basic page created.');
// Verify that the creation message contains a link to a node.
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', [':href' => 'node/']);
$this->assert(isset($view_link), 'The message area contains a link to a node');
// Check that the node exists in the database.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertTrue($node, 'Node found in database.');
// Verify that pages do not show submitted information by default.
$this->drupalGet('node/' . $node->id());
$this->assertNoText($node->getOwner()->getUsername());
$this->assertNoText(format_date($node->getCreatedTime()));
// Change the node type setting to show submitted by information.
/** @var \Drupal\node\NodeTypeInterface $node_type */
$node_type = $node_type_storage->load('page');
$node_type->setDisplaySubmitted(TRUE);
$node_type->save();
$this->drupalGet('node/' . $node->id());
$this->assertText($node->getOwner()->getUsername());
$this->assertText(format_date($node->getCreatedTime()));
// Check if the node revision checkbox is not rendered on node creation form.
$admin_user = $this->drupalCreateUser(['administer nodes', 'create page content']);
$this->drupalLogin($admin_user);
$this->drupalGet('node/add/page');
$this->assertNoFieldById('edit-revision', NULL, 'The revision checkbox is not present.');
}
/**
* Verifies that a transaction rolls back the failed creation.
*/
public function testFailedPageCreation() {
// Create a node.
$edit = [
'uid' => $this->loggedInUser->id(),
'name' => $this->loggedInUser->name,
'type' => 'page',
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'title' => 'testing_transaction_exception',
];
try {
// An exception is generated by node_test_exception_node_insert() if the
// title is 'testing_transaction_exception'.
Node::create($edit)->save();
$this->fail(t('Expected exception has not been thrown.'));
}
catch (\Exception $e) {
$this->pass(t('Expected exception has been thrown.'));
}
if (Database::getConnection()->supportsTransactions()) {
// Check that the node does not exist in the database.
$node = $this->drupalGetNodeByTitle($edit['title']);
$this->assertFalse($node, 'Transactions supported, and node not found in database.');
}
else {
// Check that the node exists in the database.
$node = $this->drupalGetNodeByTitle($edit['title']);
$this->assertTrue($node, 'Transactions not supported, and node found in database.');
// Check that the failed rollback was logged.
$records = static::getWatchdogIdsForFailedExplicitRollback();
$this->assertTrue(count($records) > 0, 'Transactions not supported, and rollback error logged to watchdog.');
}
// Check that the rollback error was logged.
$records = static::getWatchdogIdsForTestExceptionRollback();
$this->assertTrue(count($records) > 0, 'Rollback explanatory error logged to watchdog.');
}
/**
* Creates an unpublished node and confirms correct redirect behavior.
*/
public function testUnpublishedNodeCreation() {
// Set the front page to the test page.
$this->config('system.site')->set('page.front', '/test-page')->save();
// Set "Basic page" content type to be unpublished by default.
$fields = \Drupal::entityManager()->getFieldDefinitions('node', 'page');
$fields['status']->getConfig('page')
->setDefaultValue(FALSE)
->save();
// Create a node.
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm('node/add/page', $edit, t('Save'));
// Check that the user was redirected to the home page.
$this->assertUrl('');
$this->assertText(t('Test page text'));
// Confirm that the node was created.
$this->assertText(t('@post @title has been created.', ['@post' => 'Basic page', '@title' => $edit['title[0][value]']]));
// Verify that the creation message contains a link to a node.
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', [':href' => 'node/']);
$this->assert(isset($view_link), 'The message area contains a link to a node');
}
/**
* Tests the author autocompletion textfield.
*/
public function testAuthorAutocomplete() {
$admin_user = $this->drupalCreateUser(['administer nodes', 'create page content']);
$this->drupalLogin($admin_user);
$this->drupalGet('node/add/page');
$result = $this->xpath('//input[@id="edit-uid-0-value" and contains(@data-autocomplete-path, "user/autocomplete")]');
$this->assertEqual(count($result), 0, 'No autocompletion without access user profiles.');
$admin_user = $this->drupalCreateUser(['administer nodes', 'create page content', 'access user profiles']);
$this->drupalLogin($admin_user);
$this->drupalGet('node/add/page');
$result = $this->xpath('//input[@id="edit-uid-0-target-id" and contains(@data-autocomplete-path, "/entity_reference_autocomplete/user/default")]');
$this->assertEqual(count($result), 1, 'Ensure that the user does have access to the autocompletion');
}
/**
* Check node/add when no node types exist.
*/
public function testNodeAddWithoutContentTypes() {
$this->drupalGet('node/add');
$this->assertResponse(200);
$this->assertNoLinkByHref('/admin/structure/types/add');
// Test /node/add page without content types.
foreach (\Drupal::entityManager()->getStorage('node_type')->loadMultiple() as $entity ) {
$entity->delete();
}
$this->drupalGet('node/add');
$this->assertResponse(403);
$admin_content_types = $this->drupalCreateUser(['administer content types']);
$this->drupalLogin($admin_content_types);
$this->drupalGet('node/add');
$this->assertLinkByHref('/admin/structure/types/add');
}
/**
* Gets the watchdog IDs of the records with the rollback exception message.
*
* @return int[]
* Array containing the IDs of the log records with the rollback exception
* message.
*/
protected static function getWatchdogIdsForTestExceptionRollback() {
// PostgreSQL doesn't support bytea LIKE queries, so we need to unserialize
// first to check for the rollback exception message.
$matches = [];
$query = db_query("SELECT wid, variables FROM {watchdog}");
foreach ($query as $row) {
$variables = (array) unserialize($row->variables);
if (isset($variables['@message']) && $variables['@message'] === 'Test exception for rollback.') {
$matches[] = $row->wid;
}
}
return $matches;
}
/**
* Gets the log records with the explicit rollback failed exception message.
*
* @return \Drupal\Core\Database\StatementInterface
* A prepared statement object (already executed), which contains the log
* records with the explicit rollback failed exception message.
*/
protected static function getWatchdogIdsForFailedExplicitRollback() {
return db_query("SELECT wid FROM {watchdog} WHERE message LIKE 'Explicit rollback failed%'")->fetchAll();
}
}
@@ -0,0 +1,237 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\node\NodeInterface;
use Drupal\user\Entity\User;
/**
* Create a node and test node edit functionality.
*
* @group node
*/
class NodeEditFormTest extends NodeTestBase {
/**
* A normal logged in user.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* A user with permission to bypass content access checks.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* The node storage.
*
* @var \Drupal\node\NodeStorageInterface
*/
protected $nodeStorage;
/**
* Modules to enable.
*
* @var string[]
*/
public static $modules = ['block', 'node', 'datetime'];
protected function setUp() {
parent::setUp();
$this->webUser = $this->drupalCreateUser(['edit own page content', 'create page content']);
$this->adminUser = $this->drupalCreateUser(['bypass node access', 'administer nodes']);
$this->drupalPlaceBlock('local_tasks_block');
$this->nodeStorage = $this->container->get('entity.manager')->getStorage('node');
}
/**
* Checks node edit functionality.
*/
public function testNodeEdit() {
$this->drupalLogin($this->webUser);
$title_key = 'title[0][value]';
$body_key = 'body[0][value]';
// Create node to edit.
$edit = [];
$edit[$title_key] = $this->randomMachineName(8);
$edit[$body_key] = $this->randomMachineName(16);
$this->drupalPostForm('node/add/page', $edit, t('Save'));
// Check that the node exists in the database.
$node = $this->drupalGetNodeByTitle($edit[$title_key]);
$this->assertTrue($node, 'Node found in database.');
// Check that "edit" link points to correct page.
$this->clickLink(t('Edit'));
$this->assertUrl($node->url('edit-form', ['absolute' => TRUE]));
// Check that the title and body fields are displayed with the correct values.
// @todo Ideally assertLink would support HTML, but it doesn't.
$this->assertRaw('Edit<span class="visually-hidden">(active tab)</span>', 'Edit tab found and marked active.');
$this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
$this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
// Edit the content of the node.
$edit = [];
$edit[$title_key] = $this->randomMachineName(8);
$edit[$body_key] = $this->randomMachineName(16);
// Stay on the current page, without reloading.
$this->drupalPostForm(NULL, $edit, t('Save'));
// Check that the title and body fields are displayed with the updated values.
$this->assertText($edit[$title_key], 'Title displayed.');
$this->assertText($edit[$body_key], 'Body displayed.');
// Log in as a second administrator user.
$second_web_user = $this->drupalCreateUser(['administer nodes', 'edit any page content']);
$this->drupalLogin($second_web_user);
// Edit the same node, creating a new revision.
$this->drupalGet("node/" . $node->id() . "/edit");
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit[$body_key] = $this->randomMachineName(16);
$edit['revision'] = TRUE;
$this->drupalPostForm(NULL, $edit, t('Save and keep published'));
// Ensure that the node revision has been created.
$revised_node = $this->drupalGetNodeByTitle($edit['title[0][value]'], TRUE);
$this->assertNotIdentical($node->getRevisionId(), $revised_node->getRevisionId(), 'A new revision has been created.');
// Ensure that the node author is preserved when it was not changed in the
// edit form.
$this->assertIdentical($node->getOwnerId(), $revised_node->getOwnerId(), 'The node author has been preserved.');
// Ensure that the revision authors are different since the revisions were
// made by different users.
$first_node_version = node_revision_load($node->getRevisionId());
$second_node_version = node_revision_load($revised_node->getRevisionId());
$this->assertNotIdentical($first_node_version->getRevisionUser()->id(), $second_node_version->getRevisionUser()->id(), 'Each revision has a distinct user.');
// Check if the node revision checkbox is rendered on node edit form.
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertFieldById('edit-revision', NULL, 'The revision field is present.');
// Check that details form element opens when there are errors on child
// elements.
$this->drupalGet('node/' . $node->id() . '/edit');
$edit = [];
// This invalid date will trigger an error.
$edit['created[0][value][date]'] = $this->randomMachineName(8);
// Get the current amount of open details elements.
$open_details_elements = count($this->cssSelect('details[open="open"]'));
$this->drupalPostForm(NULL, $edit, t('Save and keep published'));
// The node author details must be open.
$this->assertRaw('<details class="node-form-author js-form-wrapper form-wrapper" data-drupal-selector="edit-author" id="edit-author" open="open">');
// Only one extra details element should now be open.
$open_details_elements++;
$this->assertEqual(count($this->cssSelect('details[open="open"]')), $open_details_elements, 'Exactly one extra open &lt;details&gt; element found.');
}
/**
* Tests changing a node's "authored by" field.
*/
public function testNodeEditAuthoredBy() {
$this->drupalLogin($this->adminUser);
// Create node to edit.
$body_key = 'body[0][value]';
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit[$body_key] = $this->randomMachineName(16);
$this->drupalPostForm('node/add/page', $edit, t('Save and publish'));
// Check that the node was authored by the currently logged in user.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertIdentical($node->getOwnerId(), $this->adminUser->id(), 'Node authored by admin user.');
$this->checkVariousAuthoredByValues($node, 'uid[0][target_id]');
// Check that normal users cannot change the authored by information.
$this->drupalLogin($this->webUser);
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertNoFieldByName('uid[0][target_id]');
// Now test with the Autcomplete (Tags) field widget.
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
$form_display = \Drupal::entityManager()->getStorage('entity_form_display')->load('node.page.default');
$widget = $form_display->getComponent('uid');
$widget['type'] = 'entity_reference_autocomplete_tags';
$widget['settings'] = [
'match_operator' => 'CONTAINS',
'size' => 60,
'placeholder' => '',
];
$form_display->setComponent('uid', $widget);
$form_display->save();
$this->drupalLogin($this->adminUser);
// Save the node without making any changes.
$this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save and keep published'));
$this->nodeStorage->resetCache([$node->id()]);
$node = $this->nodeStorage->load($node->id());
$this->assertIdentical($this->webUser->id(), $node->getOwner()->id());
$this->checkVariousAuthoredByValues($node, 'uid[target_id]');
// Hide the 'authored by' field from the form.
$form_display->removeComponent('uid')->save();
// Check that saving the node without making any changes keeps the proper
// author ID.
$this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save and keep published'));
$this->nodeStorage->resetCache([$node->id()]);
$node = $this->nodeStorage->load($node->id());
$this->assertIdentical($this->webUser->id(), $node->getOwner()->id());
}
/**
* Checks that the "authored by" works correctly with various values.
*
* @param \Drupal\node\NodeInterface $node
* A node object.
* @param string $form_element_name
* The name of the form element to populate.
*/
protected function checkVariousAuthoredByValues(NodeInterface $node, $form_element_name) {
// Try to change the 'authored by' field to an invalid user name.
$edit = [
$form_element_name => 'invalid-name',
];
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
$this->assertRaw(t('There are no entities matching "%name".', ['%name' => 'invalid-name']));
// Change the authored by field to an empty string, which should assign
// authorship to the anonymous user (uid 0).
$edit[$form_element_name] = '';
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
$this->nodeStorage->resetCache([$node->id()]);
$node = $this->nodeStorage->load($node->id());
$uid = $node->getOwnerId();
// Most SQL database drivers stringify fetches but entities are not
// necessarily stored in a SQL database. At the same time, NULL/FALSE/""
// won't do.
$this->assertTrue($uid === 0 || $uid === '0', 'Node authored by anonymous user.');
// Go back to the edit form and check that the correct value is displayed
// in the author widget.
$this->drupalGet('node/' . $node->id() . '/edit');
$anonymous_user = User::getAnonymousUser();
$expected = $anonymous_user->label() . ' (' . $anonymous_user->id() . ')';
$this->assertFieldByName($form_element_name, $expected, 'Authored by field displays the correct value for the anonymous user.');
// Change the authored by field to another user's name (that is not
// logged in).
$edit[$form_element_name] = $this->webUser->getUsername();
$this->drupalPostForm(NULL, $edit, t('Save and keep published'));
$this->nodeStorage->resetCache([$node->id()]);
$node = $this->nodeStorage->load($node->id());
$this->assertIdentical($node->getOwnerId(), $this->webUser->id(), 'Node authored by normal user.');
}
}
@@ -0,0 +1,53 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Core\Cache\Cache;
use Drupal\Tests\EntityViewTrait;
/**
* Tests changing view modes for nodes.
*
* @group node
*/
class NodeEntityViewModeAlterTest extends NodeTestBase {
use EntityViewTrait;
/**
* Enable dummy module that implements hook_ENTITY_TYPE_view() for nodes.
*/
public static $modules = ['node_test'];
/**
* Create a "Basic page" node and verify its consistency in the database.
*/
public function testNodeViewModeChange() {
$web_user = $this->drupalCreateUser(['create page content', 'edit own page content']);
$this->drupalLogin($web_user);
// Create a node.
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = t('Data that should appear only in the body for the node.');
$edit['body[0][summary]'] = t('Extra data that should appear only in the teaser for the node.');
$this->drupalPostForm('node/add/page', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
// Set the flag to alter the view mode and view the node.
\Drupal::state()->set('node_test_change_view_mode', 'teaser');
Cache::invalidateTags(['rendered']);
$this->drupalGet('node/' . $node->id());
// Check that teaser mode is viewed.
$this->assertText('Extra data that should appear only in the teaser for the node.', 'Teaser text present');
// Make sure body text is not present.
$this->assertNoText('Data that should appear only in the body for the node.', 'Body text not present');
// Test that the correct build mode has been set.
$build = $this->buildEntityView($node);
$this->assertEqual($build['#view_mode'], 'teaser', 'The view mode has correctly been set to teaser.');
}
}
@@ -0,0 +1,129 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\BrowserTestBase;
/**
* Tests multilingual support for fields.
*
* @group node
*/
class NodeFieldMultilingualTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'language'];
protected function setUp() {
parent::setUp();
// Create Basic page node type.
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
// Setup users.
$admin_user = $this->drupalCreateUser(['administer languages', 'administer content types', 'access administration pages', 'create page content', 'edit own page content']);
$this->drupalLogin($admin_user);
// Add a new language.
ConfigurableLanguage::createFromLangcode('it')->save();
// Enable URL language detection and selection.
$edit = ['language_interface[enabled][language-url]' => '1'];
$this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
// Set "Basic page" content type to use multilingual support.
$edit = [
'language_configuration[language_alterable]' => TRUE,
];
$this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
$this->assertRaw(t('The content type %type has been updated.', ['%type' => 'Basic page']), 'Basic page content type has been updated.');
// Make node body translatable.
$field_storage = FieldStorageConfig::loadByName('node', 'body');
$field_storage->setTranslatable(TRUE);
$field_storage->save();
}
/**
* Tests whether field languages are correctly set through the node form.
*/
public function testMultilingualNodeForm() {
// Create "Basic page" content.
$langcode = language_get_default_langcode('node', 'page');
$title_key = 'title[0][value]';
$title_value = $this->randomMachineName(8);
$body_key = 'body[0][value]';
$body_value = $this->randomMachineName(16);
// Create node to edit.
$edit = [];
$edit[$title_key] = $title_value;
$edit[$body_key] = $body_value;
$this->drupalPostForm('node/add/page', $edit, t('Save'));
// Check that the node exists in the database.
$node = $this->drupalGetNodeByTitle($edit[$title_key]);
$this->assertTrue($node, 'Node found in database.');
$this->assertTrue($node->language()->getId() == $langcode && $node->body->value == $body_value, 'Field language correctly set.');
// Change node language.
$langcode = 'it';
$this->drupalGet("node/{$node->id()}/edit");
$edit = [
$title_key => $this->randomMachineName(8),
'langcode[0][value]' => $langcode,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit[$title_key], TRUE);
$this->assertTrue($node, 'Node found in database.');
$this->assertTrue($node->language()->getId() == $langcode && $node->body->value == $body_value, 'Field language correctly changed.');
// Enable content language URL detection.
$this->container->get('language_negotiator')->saveConfiguration(LanguageInterface::TYPE_CONTENT, [LanguageNegotiationUrl::METHOD_ID => 0]);
// Test multilingual field language fallback logic.
$this->drupalGet("it/node/{$node->id()}");
$this->assertRaw($body_value, 'Body correctly displayed using Italian as requested language');
$this->drupalGet("node/{$node->id()}");
$this->assertRaw($body_value, 'Body correctly displayed using English as requested language');
}
/**
* Tests multilingual field display settings.
*/
public function testMultilingualDisplaySettings() {
// Create "Basic page" content.
$title_key = 'title[0][value]';
$title_value = $this->randomMachineName(8);
$body_key = 'body[0][value]';
$body_value = $this->randomMachineName(16);
// Create node to edit.
$edit = [];
$edit[$title_key] = $title_value;
$edit[$body_key] = $body_value;
$this->drupalPostForm('node/add/page', $edit, t('Save'));
// Check that the node exists in the database.
$node = $this->drupalGetNodeByTitle($edit[$title_key]);
$this->assertTrue($node, 'Node found in database.');
// Check if node body is showed.
$this->drupalGet('node/' . $node->id());
$body = $this->xpath('//article[contains(concat(" ", normalize-space(@class), " "), :node-class)]//div[contains(concat(" ", normalize-space(@class), " "), :content-class)]/descendant::p', [
':node-class' => ' node ',
':content-class' => 'node__content',
]);
$this->assertEqual($body[0]->getText(), $node->body->value, 'Node body found.');
}
}
@@ -0,0 +1,135 @@
<?php
namespace Drupal\Tests\node\Functional;
/**
* Tests all the different buttons on the node form.
*
* @group node
*/
class NodeFormButtonsTest extends NodeTestBase {
use AssertButtonsTrait;
/**
* A normal logged in user.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* A user with permission to bypass access content.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
// Create a user that has no access to change the state of the node.
$this->webUser = $this->drupalCreateUser(['create article content', 'edit own article content']);
// Create a user that has access to change the state of the node.
$this->adminUser = $this->drupalCreateUser(['administer nodes', 'bypass node access']);
}
/**
* Tests that the right buttons are displayed for saving nodes.
*/
public function testNodeFormButtons() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
// Log in as administrative user.
$this->drupalLogin($this->adminUser);
// Verify the buttons on a node add form.
$this->drupalGet('node/add/article');
$this->assertButtons([t('Save and publish'), t('Save as unpublished')]);
// Save the node and assert it's published after clicking
// 'Save and publish'.
$edit = ['title[0][value]' => $this->randomString()];
$this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
// Get the node.
$node_1 = $node_storage->load(1);
$this->assertTrue($node_1->isPublished(), 'Node is published');
// Verify the buttons on a node edit form.
$this->drupalGet('node/' . $node_1->id() . '/edit');
$this->assertButtons([t('Save and keep published'), t('Save and unpublish')]);
// Save the node and verify it's still published after clicking
// 'Save and keep published'.
$this->drupalPostForm(NULL, $edit, t('Save and keep published'));
$node_storage->resetCache([1]);
$node_1 = $node_storage->load(1);
$this->assertTrue($node_1->isPublished(), 'Node is published');
// Save the node and verify it's unpublished after clicking
// 'Save and unpublish'.
$this->drupalPostForm('node/' . $node_1->id() . '/edit', $edit, t('Save and unpublish'));
$node_storage->resetCache([1]);
$node_1 = $node_storage->load(1);
$this->assertFalse($node_1->isPublished(), 'Node is unpublished');
// Verify the buttons on an unpublished node edit screen.
$this->drupalGet('node/' . $node_1->id() . '/edit');
$this->assertButtons([t('Save and keep unpublished'), t('Save and publish')]);
// Create a node as a normal user.
$this->drupalLogout();
$this->drupalLogin($this->webUser);
// Verify the buttons for a normal user.
$this->drupalGet('node/add/article');
$this->assertButtons([t('Save')], FALSE);
// Create the node.
$edit = ['title[0][value]' => $this->randomString()];
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$node_2 = $node_storage->load(2);
$this->assertTrue($node_2->isPublished(), 'Node is published');
// Log in as an administrator and unpublish the node that just
// was created by the normal user.
$this->drupalLogout();
$this->drupalLogin($this->adminUser);
$this->drupalPostForm('node/' . $node_2->id() . '/edit', [], t('Save and unpublish'));
$node_storage->resetCache([2]);
$node_2 = $node_storage->load(2);
$this->assertFalse($node_2->isPublished(), 'Node is unpublished');
// Log in again as the normal user, save the node and verify
// it's still unpublished.
$this->drupalLogout();
$this->drupalLogin($this->webUser);
$this->drupalPostForm('node/' . $node_2->id() . '/edit', [], t('Save'));
$node_storage->resetCache([2]);
$node_2 = $node_storage->load(2);
$this->assertFalse($node_2->isPublished(), 'Node is still unpublished');
$this->drupalLogout();
// Set article content type default to unpublished. This will change the
// the initial order of buttons and/or status of the node when creating
// a node.
$fields = \Drupal::entityManager()->getFieldDefinitions('node', 'article');
$fields['status']->getConfig('article')
->setDefaultValue(FALSE)
->save();
// Verify the buttons on a node add form for an administrator.
$this->drupalLogin($this->adminUser);
$this->drupalGet('node/add/article');
$this->assertButtons([t('Save as unpublished'), t('Save and publish')]);
// Verify the node is unpublished by default for a normal user.
$this->drupalLogout();
$this->drupalLogin($this->webUser);
$edit = ['title[0][value]' => $this->randomString()];
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$node_3 = $node_storage->load(3);
$this->assertFalse($node_3->isPublished(), 'Node is unpublished');
}
}
@@ -0,0 +1,75 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests updating the changed time after API and FORM entity save.
*
* @group node
*/
class NodeFormSaveChangedTimeTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'node',
];
/**
* An user with permissions to create and edit articles.
*
* @var \Drupal\user\UserInterface
*/
protected $authorUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a node type.
$this->drupalCreateContentType([
'type' => 'article',
'name' => 'Article',
]);
$this->authorUser = $this->drupalCreateUser(['access content', 'create article content', 'edit any article content'], 'author');
$this->drupalLogin($this->authorUser);
// Create one node of the above node type .
$this->drupalCreateNode([
'type' => 'article',
]);
}
/**
* Test the changed time after API and FORM save without changes.
*/
public function testChangedTimeAfterSaveWithoutChanges() {
$storage = $this->container->get('entity_type.manager')->getStorage('node');
$storage->resetCache([1]);
$node = $storage->load(1);
$changed_timestamp = $node->getChangedTime();
$node->save();
$storage->resetCache([1]);
$node = $storage->load(1);
$this->assertEqual($changed_timestamp, $node->getChangedTime(), "The entity's changed time wasn't updated after API save without changes.");
// Ensure different save timestamps.
sleep(1);
// Save the node on the regular node edit form.
$this->drupalPostForm('node/1/edit', [], t('Save'));
$storage->resetCache([1]);
$node = $storage->load(1);
$this->assertNotEqual($changed_timestamp, $node->getChangedTime(), "The entity's changed time was updated after form save without changes.");
}
}
@@ -0,0 +1,77 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests help functionality for nodes.
*
* @group node
*/
class NodeHelpTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array.
*/
public static $modules = ['block', 'node', 'help'];
/**
* The name of the test node type to create.
*
* @var string
*/
protected $testType;
/**
* The test 'node help' text to be checked.
*
* @var string
*/
protected $testText;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create user.
$admin_user = $this->drupalCreateUser([
'administer content types',
'administer nodes',
'bypass node access',
]);
$this->drupalLogin($admin_user);
$this->drupalPlaceBlock('help_block');
$this->testType = 'type';
$this->testText = t('Help text to find on node forms.');
// Create content type.
$this->drupalCreateContentType([
'type' => $this->testType,
'help' => $this->testText,
]);
}
/**
* Verifies that help text appears on node add/edit forms.
*/
public function testNodeShowHelpText() {
// Check the node add form.
$this->drupalGet('node/add/' . $this->testType);
$this->assertResponse(200);
$this->assertText($this->testText);
// Create node and check the node edit form.
$node = $this->drupalCreateNode(['type' => $this->testType]);
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertResponse(200);
$this->assertText($this->testText);
}
}
@@ -0,0 +1,45 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\node\NodeInterface;
/**
* Tests the output of node links (read more, add new comment, etc).
*
* @group node
*/
class NodeLinksTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['views'];
/**
* Tests that the links can be hidden in the view display settings.
*/
public function testHideLinks() {
$node = $this->drupalCreateNode([
'type' => 'article',
'promote' => NodeInterface::PROMOTED,
]);
// Links are displayed by default.
$this->drupalGet('node');
$this->assertText($node->getTitle());
$this->assertLink('Read more');
// Hide links.
entity_get_display('node', 'article', 'teaser')
->removeComponent('links')
->save();
$this->drupalGet('node');
$this->assertText($node->getTitle());
$this->assertNoLink('Read more');
}
}
@@ -0,0 +1,62 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\node\Entity\Node;
/**
* Tests the loading of multiple nodes.
*
* @group node
*/
class NodeLoadMultipleTest extends NodeTestBase {
/**
* Enable Views to test the frontpage against Node::loadMultiple() results.
*
* @var array
*/
public static $modules = ['views'];
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(['create article content', 'create page content']);
$this->drupalLogin($web_user);
}
/**
* Creates four nodes and ensures that they are loaded correctly.
*/
public function testNodeMultipleLoad() {
$node1 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]);
$node2 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]);
$node3 = $this->drupalCreateNode(['type' => 'article', 'promote' => 0]);
$node4 = $this->drupalCreateNode(['type' => 'page', 'promote' => 0]);
// Confirm that promoted nodes appear in the default node listing.
$this->drupalGet('node');
$this->assertText($node1->label(), 'Node title appears on the default listing.');
$this->assertText($node2->label(), 'Node title appears on the default listing.');
$this->assertNoText($node3->label(), 'Node title does not appear in the default listing.');
$this->assertNoText($node4->label(), 'Node title does not appear in the default listing.');
// Load nodes with only a condition. Nodes 3 and 4 will be loaded.
$nodes = $this->container->get('entity_type.manager')->getStorage('node')
->loadByProperties(['promote' => 0]);
$this->assertEqual($node3->label(), $nodes[$node3->id()]->label(), 'Node was loaded.');
$this->assertEqual($node4->label(), $nodes[$node4->id()]->label(), 'Node was loaded.');
$count = count($nodes);
$this->assertTrue($count == 2, format_string('@count nodes loaded.', ['@count' => $count]));
// Load nodes by nid. Nodes 1, 2 and 4 will be loaded.
$nodes = Node::loadMultiple([1, 2, 4]);
$count = count($nodes);
$this->assertTrue(count($nodes) == 3, format_string('@count nodes loaded', ['@count' => $count]));
$this->assertTrue(isset($nodes[$node1->id()]), 'Node is correctly keyed in the array');
$this->assertTrue(isset($nodes[$node2->id()]), 'Node is correctly keyed in the array');
$this->assertTrue(isset($nodes[$node4->id()]), 'Node is correctly keyed in the array');
foreach ($nodes as $node) {
$this->assertTrue(is_object($node), 'Node is an object');
}
}
}
@@ -0,0 +1,58 @@
<?php
namespace Drupal\Tests\node\Functional;
/**
* Tests that the post information (submitted by Username on date) text displays
* appropriately.
*
* @group node
*/
class NodePostSettingsTest extends NodeTestBase {
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(['create page content', 'administer content types', 'access user profiles']);
$this->drupalLogin($web_user);
}
/**
* Confirms "Basic page" content type and post information is on a new node.
*/
public function testPagePostInfo() {
// Set "Basic page" content type to display post information.
$edit = [];
$edit['display_submitted'] = TRUE;
$this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
// Create a node.
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm('node/add/page', $edit, t('Save'));
// Check that the post information is displayed.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$elements = $this->xpath('//div[contains(@class, :class)]', [':class' => 'node__submitted']);
$this->assertEqual(count($elements), 1, 'Post information is displayed.');
$node->delete();
// Set "Basic page" content type to display post information.
$edit = [];
$edit['display_submitted'] = FALSE;
$this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
// Create a node.
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->drupalPostForm('node/add/page', $edit, t('Save'));
// Check that the post information is displayed.
$elements = $this->xpath('//div[contains(@class, :class)]', [':class' => 'node__submitted']);
$this->assertEqual(count($elements), 0, 'Post information is not displayed.');
}
}
@@ -0,0 +1,197 @@
<?php
namespace Drupal\Tests\node\Functional;
/**
* Tests that node access queries are properly altered by the node module.
*
* @group node
*/
class NodeQueryAlterTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node_access_test'];
/**
* User with permission to view content.
*/
protected $accessUser;
/**
* User without permission to view content.
*/
protected $noAccessUser;
protected function setUp() {
parent::setUp();
node_access_rebuild();
// Create some content.
$this->drupalCreateNode();
$this->drupalCreateNode();
$this->drupalCreateNode();
$this->drupalCreateNode();
// Create user with simple node access permission. The 'node test view'
// permission is implemented and granted by the node_access_test module.
$this->accessUser = $this->drupalCreateUser(['access content overview', 'access content', 'node test view']);
$this->noAccessUser = $this->drupalCreateUser(['access content overview', 'access content']);
$this->noAccessUser2 = $this->drupalCreateUser(['access content overview', 'access content']);
}
/**
* Tests 'node_access' query alter, for user with access.
*
* Verifies that a non-standard table alias can be used, and that a user with
* node access can view the nodes.
*/
public function testNodeQueryAlterLowLevelWithAccess() {
// User with access should be able to view 4 nodes.
try {
$query = db_select('node', 'mytab')
->fields('mytab');
$query->addTag('node_access');
$query->addMetaData('op', 'view');
$query->addMetaData('account', $this->accessUser);
$result = $query->execute()->fetchAll();
$this->assertEqual(count($result), 4, 'User with access can see correct nodes');
}
catch (\Exception $e) {
$this->fail(t('Altered query is malformed'));
}
}
/**
* Tests 'node_access' query alter with revision-enabled nodes.
*/
public function testNodeQueryAlterWithRevisions() {
// Execute a query that only deals with the 'node_revision' table.
try {
$query = \Drupal::entityTypeManager()->getStorage('node')->getQuery();
$result = $query
->allRevisions()
->execute();
$this->assertEqual(count($result), 4, 'User with access can see correct nodes');
}
catch (\Exception $e) {
$this->fail('Altered query is malformed');
}
}
/**
* Tests 'node_access' query alter, for user without access.
*
* Verifies that a non-standard table alias can be used, and that a user
* without node access cannot view the nodes.
*/
public function testNodeQueryAlterLowLevelNoAccess() {
// User without access should be able to view 0 nodes.
try {
$query = db_select('node', 'mytab')
->fields('mytab');
$query->addTag('node_access');
$query->addMetaData('op', 'view');
$query->addMetaData('account', $this->noAccessUser);
$result = $query->execute()->fetchAll();
$this->assertEqual(count($result), 0, 'User with no access cannot see nodes');
}
catch (\Exception $e) {
$this->fail(t('Altered query is malformed'));
}
}
/**
* Tests 'node_access' query alter, for edit access.
*
* Verifies that a non-standard table alias can be used, and that a user with
* view-only node access cannot edit the nodes.
*/
public function testNodeQueryAlterLowLevelEditAccess() {
// User with view-only access should not be able to edit nodes.
try {
$query = db_select('node', 'mytab')
->fields('mytab');
$query->addTag('node_access');
$query->addMetaData('op', 'update');
$query->addMetaData('account', $this->accessUser);
$result = $query->execute()->fetchAll();
$this->assertEqual(count($result), 0, 'User with view-only access cannot edit nodes');
}
catch (\Exception $e) {
$this->fail($e->getMessage());
$this->fail((string) $query);
$this->fail(t('Altered query is malformed'));
}
}
/**
* Tests 'node_access' query alter override.
*
* Verifies that node_access_view_all_nodes() is called from
* node_query_node_access_alter(). We do this by checking that a user who
* normally would not have view privileges is able to view the nodes when we
* add a record to {node_access} paired with a corresponding privilege in
* hook_node_grants().
*/
public function testNodeQueryAlterOverride() {
$record = [
'nid' => 0,
'gid' => 0,
'realm' => 'node_access_all',
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
];
db_insert('node_access')->fields($record)->execute();
// Test that the noAccessUser still doesn't have the 'view'
// privilege after adding the node_access record.
drupal_static_reset('node_access_view_all_nodes');
try {
$query = db_select('node', 'mytab')
->fields('mytab');
$query->addTag('node_access');
$query->addMetaData('op', 'view');
$query->addMetaData('account', $this->noAccessUser);
$result = $query->execute()->fetchAll();
$this->assertEqual(count($result), 0, 'User view privileges are not overridden');
}
catch (\Exception $e) {
$this->fail(t('Altered query is malformed'));
}
// Have node_test_node_grants return a node_access_all privilege,
// to grant the noAccessUser 'view' access. To verify that
// node_access_view_all_nodes is properly checking the specified
// $account instead of the current user, we will log in as
// noAccessUser2.
$this->drupalLogin($this->noAccessUser2);
\Drupal::state()->set('node_access_test.no_access_uid', $this->noAccessUser->id());
drupal_static_reset('node_access_view_all_nodes');
try {
$query = db_select('node', 'mytab')
->fields('mytab');
$query->addTag('node_access');
$query->addMetaData('op', 'view');
$query->addMetaData('account', $this->noAccessUser);
$result = $query->execute()->fetchAll();
$this->assertEqual(count($result), 4, 'User view privileges are overridden');
}
catch (\Exception $e) {
$this->fail(t('Altered query is malformed'));
}
\Drupal::state()->delete('node_access_test.no_access_uid');
}
}
@@ -0,0 +1,108 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\filter\Entity\FilterFormat;
/**
* Ensures that data added to nodes by other modules appears in RSS feeds.
*
* Create a node, enable the node_test module to ensure that extra data is
* added to the node's renderable array, then verify that the data appears on
* the site-wide RSS feed at rss.xml.
*
* @group node
*/
class NodeRSSContentTest extends NodeTestBase {
/**
* Enable a module that implements hook_node_view().
*
* @var array
*/
public static $modules = ['node_test', 'views'];
protected function setUp() {
parent::setUp();
// Use bypass node access permission here, because the test class uses
// hook_grants_alter() to deny access to everyone on node_access
// queries.
$user = $this->drupalCreateUser(['bypass node access', 'access content', 'create article content']);
$this->drupalLogin($user);
}
/**
* Ensures that a new node includes the custom data when added to an RSS feed.
*/
public function testNodeRSSContent() {
// Create a node.
$node = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]);
$this->drupalGet('rss.xml');
// Check that content added in 'rss' view mode appear in RSS feed.
$rss_only_content = t('Extra data that should appear only in the RSS feed for node @nid.', ['@nid' => $node->id()]);
$this->assertText($rss_only_content, 'Node content designated for RSS appear in RSS feed.');
// Check that content added in view modes other than 'rss' doesn't
// appear in RSS feed.
$non_rss_content = t('Extra data that should appear everywhere except the RSS feed for node @nid.', ['@nid' => $node->id()]);
$this->assertNoText($non_rss_content, 'Node content not designed for RSS does not appear in RSS feed.');
// Check that extra RSS elements and namespaces are added to RSS feed.
$test_element = '<testElement>' . t('Value of testElement RSS element for node @nid.', ['@nid' => $node->id()]) . '</testElement>';
$test_ns = 'xmlns:drupaltest="http://example.com/test-namespace"';
$this->assertRaw($test_element, 'Extra RSS elements appear in RSS feed.');
$this->assertRaw($test_ns, 'Extra namespaces appear in RSS feed.');
// Check that content added in 'rss' view mode doesn't appear when
// viewing node.
$this->drupalGet('node/' . $node->id());
$this->assertNoText($rss_only_content, 'Node content designed for RSS does not appear when viewing node.');
}
/**
* Tests relative, root-relative, protocol-relative and absolute URLs.
*/
public function testUrlHandling() {
// Only the plain_text text format is available by default, which escapes
// all HTML.
FilterFormat::create([
'format' => 'full_html',
'name' => 'Full HTML',
'filters' => [],
])->save();
$defaults = [
'type' => 'article',
'promote' => 1,
];
$this->drupalCreateNode($defaults + [
'body' => [
'value' => '<p><a href="' . file_url_transform_relative(file_create_url('public://root-relative')) . '">Root-relative URL</a></p>',
'format' => 'full_html',
],
]);
$protocol_relative_url = substr(file_create_url('public://protocol-relative'), strlen(\Drupal::request()->getScheme() . ':'));
$this->drupalCreateNode($defaults + [
'body' => [
'value' => '<p><a href="' . $protocol_relative_url . '">Protocol-relative URL</a></p>',
'format' => 'full_html',
],
]);
$absolute_url = file_create_url('public://absolute');
$this->drupalCreateNode($defaults + [
'body' => [
'value' => '<p><a href="' . $absolute_url . '">Absolute URL</a></p>',
'format' => 'full_html',
],
]);
$this->drupalGet('rss.xml');
$this->assertRaw(file_create_url('public://root-relative'), 'Root-relative URL is transformed to absolute.');
$this->assertRaw($protocol_relative_url, 'Protocol-relative URL is left untouched.');
$this->assertRaw($absolute_url, 'Absolute URL is left untouched.');
}
}
@@ -0,0 +1,173 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Tests\Traits\Core\GeneratePermutationsTrait;
/**
* Tests user permissions for node revisions.
*
* @group node
*/
class NodeRevisionPermissionsTest extends NodeTestBase {
use GeneratePermutationsTrait;
/**
* The node revisions.
*
* @var array
*/
protected $nodeRevisions = [];
/**
* The accounts.
*
* @var array
*/
protected $accounts = [];
// Map revision permission names to node revision access ops.
protected $map = [
'view' => 'view all revisions',
'update' => 'revert all revisions',
'delete' => 'delete all revisions',
];
// Map revision permission names to node type revision access ops.
protected $typeMap = [
'view' => 'view page revisions',
'update' => 'revert page revisions',
'delete' => 'delete page revisions',
];
protected function setUp() {
parent::setUp();
$types = ['page', 'article'];
foreach ($types as $type) {
// Create a node with several revisions.
$nodes[$type] = $this->drupalCreateNode(['type' => $type]);
$this->nodeRevisions[$type][] = $nodes[$type];
for ($i = 0; $i < 3; $i++) {
// Create a revision for the same nid and settings with a random log.
$revision = clone $nodes[$type];
$revision->setNewRevision();
$revision->revision_log = $this->randomMachineName(32);
$revision->save();
$this->nodeRevisions[$type][] = $revision;
}
}
}
/**
* Tests general revision access permissions.
*/
public function testNodeRevisionAccessAnyType() {
// Create three users, one with each revision permission.
foreach ($this->map as $op => $permission) {
// Create the user.
$account = $this->drupalCreateUser(
[
'access content',
'edit any page content',
'delete any page content',
$permission,
]
);
$account->op = $op;
$this->accounts[] = $account;
}
// Create an admin account (returns TRUE for all revision permissions).
$admin_account = $this->drupalCreateUser(['access content', 'administer nodes']);
$admin_account->is_admin = TRUE;
$this->accounts['admin'] = $admin_account;
$accounts['admin'] = $admin_account;
// Create a normal account (returns FALSE for all revision permissions).
$normal_account = $this->drupalCreateUser();
$normal_account->op = FALSE;
$this->accounts[] = $normal_account;
$accounts[] = $normal_account;
$revision = $this->nodeRevisions['page'][1];
$parameters = [
'op' => array_keys($this->map),
'account' => $this->accounts,
];
$permutations = $this->generatePermutations($parameters);
$node_revision_access = \Drupal::service('access_check.node.revision');
foreach ($permutations as $case) {
// Skip this test if there are no revisions for the node.
if (!($revision->isDefaultRevision() && (db_query('SELECT COUNT(vid) FROM {node_field_revision} WHERE nid = :nid', [':nid' => $revision->id()])->fetchField() == 1 || $case['op'] == 'update' || $case['op'] == 'delete'))) {
if (!empty($case['account']->is_admin) || $case['account']->hasPermission($this->map[$case['op']])) {
$this->assertTrue($node_revision_access->checkAccess($revision, $case['account'], $case['op']), "{$this->map[$case['op']]} granted.");
}
else {
$this->assertFalse($node_revision_access->checkAccess($revision, $case['account'], $case['op']), "{$this->map[$case['op']]} not granted.");
}
}
}
// Test that access is FALSE for a node administrator with an invalid $node
// or $op parameters.
$admin_account = $accounts['admin'];
$this->assertFalse($node_revision_access->checkAccess($revision, $admin_account, 'invalid-op'), 'NodeRevisionAccessCheck() returns FALSE with an invalid op.');
}
/**
* Tests revision access permissions for a specific content type.
*/
public function testNodeRevisionAccessPerType() {
// Create three users, one with each revision permission.
foreach ($this->typeMap as $op => $permission) {
// Create the user.
$account = $this->drupalCreateUser(
[
'access content',
'edit any page content',
'delete any page content',
$permission,
]
);
$account->op = $op;
$accounts[] = $account;
}
$parameters = [
'op' => array_keys($this->typeMap),
'account' => $accounts,
];
// Test that the accounts have access to the corresponding page revision
// permissions.
$revision = $this->nodeRevisions['page'][1];
$permutations = $this->generatePermutations($parameters);
$node_revision_access = \Drupal::service('access_check.node.revision');
foreach ($permutations as $case) {
// Skip this test if there are no revisions for the node.
if (!($revision->isDefaultRevision() && (db_query('SELECT COUNT(vid) FROM {node_field_revision} WHERE nid = :nid', [':nid' => $revision->id()])->fetchField() == 1 || $case['op'] == 'update' || $case['op'] == 'delete'))) {
if (!empty($case['account']->is_admin) || $case['account']->hasPermission($this->typeMap[$case['op']], $case['account'])) {
$this->assertTrue($node_revision_access->checkAccess($revision, $case['account'], $case['op']), "{$this->typeMap[$case['op']]} granted.");
}
else {
$this->assertFalse($node_revision_access->checkAccess($revision, $case['account'], $case['op']), "{$this->typeMap[$case['op']]} not granted.");
}
}
}
// Test that the accounts have no access to the article revisions.
$revision = $this->nodeRevisions['article'][1];
foreach ($permutations as $case) {
$this->assertFalse($node_revision_access->checkAccess($revision, $case['account'], $case['op']), "{$this->typeMap[$case['op']]} did not grant revision permission for articles.");
}
}
}
@@ -0,0 +1,206 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\node\NodeInterface;
/**
* Create a node with revisions and test viewing, saving, reverting, and
* deleting revisions for user with access to all.
*
* @group node
*/
class NodeRevisionsAllTest extends NodeTestBase {
/**
* A list of nodes created to be used as starting point of different tests.
*
* @var Drupal\node\NodeInterface[]
*/
protected $nodes;
/**
* Revision logs of nodes created by the setup method.
*
* @var string[]
*/
protected $revisionLogs;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create and log in user.
$web_user = $this->drupalCreateUser(
[
'view page revisions',
'revert page revisions',
'delete page revisions',
'edit any page content',
'delete any page content'
]
);
$this->drupalLogin($web_user);
// Create an initial node.
$node = $this->drupalCreateNode();
$settings = get_object_vars($node);
$settings['revision'] = 1;
$nodes = [];
$logs = [];
// Get the original node.
$nodes[] = clone $node;
// Create three revisions.
$revision_count = 3;
for ($i = 0; $i < $revision_count; $i++) {
$logs[] = $node->revision_log = $this->randomMachineName(32);
$node = $this->createNodeRevision($node);
$nodes[] = clone $node;
}
$this->nodes = $nodes;
$this->revisionLogs = $logs;
}
/**
* Creates a new revision for a given node.
*
* @param \Drupal\node\NodeInterface $node
* A node object.
*
* @return \Drupal\node\NodeInterface
* A node object with up to date revision information.
*/
protected function createNodeRevision(NodeInterface $node) {
// Create revision with a random title and body and update variables.
$node->title = $this->randomMachineName();
$node->body = [
'value' => $this->randomMachineName(32),
'format' => filter_default_format(),
];
$node->setNewRevision();
$node->save();
return $node;
}
/**
* Checks node revision operations.
*/
public function testRevisions() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$nodes = $this->nodes;
$logs = $this->revisionLogs;
// Get last node for simple checks.
$node = $nodes[3];
// Create and log in user.
$content_admin = $this->drupalCreateUser(
[
'view all revisions',
'revert all revisions',
'delete all revisions',
'edit any page content',
'delete any page content'
]
);
$this->drupalLogin($content_admin);
// Confirm the correct revision text appears on "view revisions" page.
$this->drupalGet("node/" . $node->id() . "/revisions/" . $node->getRevisionId() . "/view");
$this->assertText($node->body->value, 'Correct text displays for version.');
// Confirm the correct revision log message appears on the "revisions
// overview" page.
$this->drupalGet("node/" . $node->id() . "/revisions");
foreach ($logs as $revision_log) {
$this->assertText($revision_log, 'Revision log message found.');
}
// Confirm that this is the current revision.
$this->assertTrue($node->isDefaultRevision(), 'Third node revision is the current one.');
// Confirm that revisions revert properly.
$this->drupalPostForm("node/" . $node->id() . "/revisions/" . $nodes[1]->getRevisionId() . "/revert", [], t('Revert'));
$this->assertRaw(t('@type %title has been reverted to the revision from %revision-date.',
[
'@type' => 'Basic page',
'%title' => $nodes[1]->getTitle(),
'%revision-date' => format_date($nodes[1]->getRevisionCreationTime())
]),
'Revision reverted.');
$node_storage->resetCache([$node->id()]);
$reverted_node = $node_storage->load($node->id());
$this->assertTrue(($nodes[1]->body->value == $reverted_node->body->value), 'Node reverted correctly.');
// Confirm that this is not the current version.
$node = node_revision_load($node->getRevisionId());
$this->assertFalse($node->isDefaultRevision(), 'Third node revision is not the current one.');
// Confirm that the node can still be updated.
$this->drupalPostForm("node/" . $reverted_node->id() . "/edit", ['body[0][value]' => 'We are Drupal.'], t('Save'));
$this->assertText(t('Basic page @title has been updated.', ['@title' => $reverted_node->getTitle()]), 'Node was successfully saved after reverting a revision.');
$this->assertText('We are Drupal.', 'Node was correctly updated after reverting a revision.');
// Confirm revisions delete properly.
$this->drupalPostForm("node/" . $node->id() . "/revisions/" . $nodes[1]->getRevisionId() . "/delete", [], t('Delete'));
$this->assertRaw(t('Revision from %revision-date of @type %title has been deleted.',
[
'%revision-date' => format_date($nodes[1]->getRevisionCreationTime()),
'@type' => 'Basic page',
'%title' => $nodes[1]->getTitle(),
]),
'Revision deleted.');
$this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid and vid = :vid',
[':nid' => $node->id(), ':vid' => $nodes[1]->getRevisionId()])->fetchField() == 0,
'Revision not found.');
// Set the revision timestamp to an older date to make sure that the
// confirmation message correctly displays the stored revision date.
$old_revision_date = REQUEST_TIME - 86400;
db_update('node_revision')
->condition('vid', $nodes[2]->getRevisionId())
->fields([
'revision_timestamp' => $old_revision_date,
])
->execute();
$this->drupalPostForm("node/" . $node->id() . "/revisions/" . $nodes[2]->getRevisionId() . "/revert", [], t('Revert'));
$this->assertRaw(t('@type %title has been reverted to the revision from %revision-date.', [
'@type' => 'Basic page',
'%title' => $nodes[2]->getTitle(),
'%revision-date' => format_date($old_revision_date),
]));
// Create 50 more revisions in order to trigger paging on the revisions
// overview screen.
$node = $nodes[0];
for ($i = 0; $i < 50; $i++) {
$logs[] = $node->revision_log = $this->randomMachineName(32);
$node = $this->createNodeRevision($node);
$nodes[] = clone $node;
}
$this->drupalGet('node/' . $node->id() . '/revisions');
// Check that the pager exists.
$this->assertRaw('page=1');
// Check that the last revision is displayed on the first page.
$this->assertText(end($logs));
// Go to the second page and check that one of the initial three revisions
// is displayed.
$this->clickLink(t('Page 2'));
$this->assertText($logs[2]);
}
}
@@ -0,0 +1,87 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\node\Entity\NodeType;
/**
* Tests the revision tab display.
*
* This test is similar to NodeRevisionsUITest except that it uses a user with
* the bypass node access permission to make sure that the revision access
* check adds correct cacheability metadata.
*
* @group node
*/
class NodeRevisionsUiBypassAccessTest extends NodeTestBase {
/**
* User with bypass node access permission.
*
* @var \Drupal\user\Entity\User
*/
protected $editor;
/**
* {@inheritdoc}
*/
public static $modules = ['block'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a user.
$this->editor = $this->drupalCreateUser([
'administer nodes',
'edit any page content',
'view page revisions',
'bypass node access',
'access user profiles',
]);
}
/**
* Checks that the Revision tab is displayed correctly.
*/
public function testDisplayRevisionTab() {
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalLogin($this->editor);
$node_storage = $this->container->get('entity.manager')->getStorage('node');
// Set page revision setting 'create new revision'. This will mean new
// revisions are created by default when the node is edited.
$type = NodeType::load('page');
$type->setNewRevision(TRUE);
$type->save();
// Create the node.
$node = $this->drupalCreateNode();
// Verify the checkbox is checked on the node edit form.
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertFieldChecked('edit-revision', "'Create new revision' checkbox is checked");
// Uncheck the create new revision checkbox and save the node.
$edit = ['revision' => FALSE];
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save and keep published');
$this->assertUrl($node->toUrl());
$this->assertNoLink(t('Revisions'));
// Verify the checkbox is checked on the node edit form.
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertFieldChecked('edit-revision', "'Create new revision' checkbox is checked");
// Submit the form without changing the checkbox.
$edit = [];
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save and keep published');
$this->assertUrl($node->toUrl());
$this->assertLink(t('Revisions'));
}
}
@@ -0,0 +1,176 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* Tests the UI for controlling node revision behavior.
*
* @group node
*/
class NodeRevisionsUiTest extends NodeTestBase {
/**
* @var \Drupal\user\Entity\User
*/
protected $editor;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create users.
$this->editor = $this->drupalCreateUser([
'administer nodes',
'edit any page content',
'view page revisions',
'access user profiles',
]);
}
/**
* Checks that unchecking 'Create new revision' works when editing a node.
*/
public function testNodeFormSaveWithoutRevision() {
$this->drupalLogin($this->editor);
$node_storage = $this->container->get('entity.manager')->getStorage('node');
// Set page revision setting 'create new revision'. This will mean new
// revisions are created by default when the node is edited.
$type = NodeType::load('page');
$type->setNewRevision(TRUE);
$type->save();
// Create the node.
$node = $this->drupalCreateNode();
// Verify the checkbox is checked on the node edit form.
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertFieldChecked('edit-revision', "'Create new revision' checkbox is checked");
// Uncheck the create new revision checkbox and save the node.
$edit = ['revision' => FALSE];
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
// Load the node again and check the revision is the same as before.
$node_storage->resetCache([$node->id()]);
$node_revision = $node_storage->load($node->id(), TRUE);
$this->assertEqual($node_revision->getRevisionId(), $node->getRevisionId(), "After an existing node is saved with 'Create new revision' unchecked, a new revision is not created.");
// Verify the checkbox is checked on the node edit form.
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertFieldChecked('edit-revision', "'Create new revision' checkbox is checked");
// Submit the form without changing the checkbox.
$edit = [];
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
// Load the node again and check the revision is different from before.
$node_storage->resetCache([$node->id()]);
$node_revision = $node_storage->load($node->id());
$this->assertNotEqual($node_revision->getRevisionId(), $node->getRevisionId(), "After an existing node is saved with 'Create new revision' checked, a new revision is created.");
}
/**
* Checks HTML double escaping of revision logs.
*/
public function testNodeRevisionDoubleEscapeFix() {
$this->drupalLogin($this->editor);
$nodes = [];
// Create the node.
$node = $this->drupalCreateNode();
$username = [
'#theme' => 'username',
'#account' => $this->editor,
];
$editor = \Drupal::service('renderer')->renderPlain($username);
// Get original node.
$nodes[] = clone $node;
// Create revision with a random title and body and update variables.
$node->title = $this->randomMachineName();
$node->body = [
'value' => $this->randomMachineName(32),
'format' => filter_default_format(),
];
$node->setNewRevision();
$revision_log = 'Revision <em>message</em> with markup.';
$node->revision_log->value = $revision_log;
$node->save();
// Make sure we get revision information.
$node = Node::load($node->id());
$nodes[] = clone $node;
$this->drupalGet('node/' . $node->id() . '/revisions');
// Assert the old revision message.
$date = format_date($nodes[0]->revision_timestamp->value, 'short');
$url = new Url('entity.node.revision', ['node' => $nodes[0]->id(), 'node_revision' => $nodes[0]->getRevisionId()]);
$this->assertRaw(\Drupal::l($date, $url) . ' by ' . $editor);
// Assert the current revision message.
$date = format_date($nodes[1]->revision_timestamp->value, 'short');
$this->assertRaw($nodes[1]->link($date) . ' by ' . $editor . '<p class="revision-log">' . $revision_log . '</p>');
}
/**
* Checks the Revisions tab.
*/
public function testNodeRevisionsTabWithDefaultRevision() {
$this->drupalLogin($this->editor);
// Create the node.
$node = $this->drupalCreateNode();
$storage = \Drupal::entityTypeManager()->getStorage($node->getEntityTypeId());
// Create a new revision based on the default revision.
// Revision 2.
$node = $storage->load($node->id());
$node->setNewRevision(TRUE);
$node->save();
// Revision 3.
$node = $storage->load($node->id());
$node->setNewRevision(TRUE);
$node->save();
// Revision 4.
// Trigger translation changes in order to show the revision.
$node = $storage->load($node->id());
$node->setTitle($this->randomString());
$node->isDefaultRevision(FALSE);
$node->setNewRevision(TRUE);
$node->save();
// Revision 5.
$node = $storage->load($node->id());
$node->isDefaultRevision(FALSE);
$node->setNewRevision(TRUE);
$node->save();
$node_id = $node->id();
$this->drupalGet('node/' . $node_id . '/revisions');
// Verify that the default revision can be an older revision than the latest
// one.
// Assert that the revisions with translations changes are shown: 1 and 4.
$this->assertLinkByHref('/node/' . $node_id . '/revisions/1/revert');
$this->assertLinkByHref('/node/' . $node_id . '/revisions/4/revert');
// Assert that the revisions without translations changes are filtered out:
// 2, 3 and 5.
$this->assertNoLinkByHref('/node/' . $node_id . '/revisions/2/revert');
$this->assertNoLinkByHref('/node/' . $node_id . '/revisions/3/revert');
$this->assertNoLinkByHref('/node/' . $node_id . '/revisions/5/revert');
}
}
@@ -0,0 +1,182 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\node\Entity\Node;
/**
* Tests $node->save() for saving content.
*
* @group node
*/
class NodeSaveTest extends NodeTestBase {
/**
* A normal logged in user.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node_test'];
protected function setUp() {
parent::setUp();
// Create a user that is allowed to post; we'll use this to test the submission.
$web_user = $this->drupalCreateUser(['create article content']);
$this->drupalLogin($web_user);
$this->webUser = $web_user;
}
/**
* Checks whether custom node IDs are saved properly during an import operation.
*
* Workflow:
* - first create a piece of content
* - save the content
* - check if node exists
*/
public function testImport() {
// Node ID must be a number that is not in the database.
$nids = \Drupal::entityManager()->getStorage('node')->getQuery()
->sort('nid', 'DESC')
->range(0, 1)
->execute();
$max_nid = reset($nids);
$test_nid = $max_nid + mt_rand(1000, 1000000);
$title = $this->randomMachineName(8);
$node = [
'title' => $title,
'body' => [['value' => $this->randomMachineName(32)]],
'uid' => $this->webUser->id(),
'type' => 'article',
'nid' => $test_nid,
];
/** @var \Drupal\node\NodeInterface $node */
$node = Node::create($node);
$node->enforceIsNew();
$this->assertEqual($node->getOwnerId(), $this->webUser->id());
$node->save();
// Test the import.
$node_by_nid = Node::load($test_nid);
$this->assertTrue($node_by_nid, 'Node load by node ID.');
$node_by_title = $this->drupalGetNodeByTitle($title);
$this->assertTrue($node_by_title, 'Node load by node title.');
}
/**
* Verifies accuracy of the "created" and "changed" timestamp functionality.
*/
public function testTimestamps() {
// Use the default timestamps.
$edit = [
'uid' => $this->webUser->id(),
'type' => 'article',
'title' => $this->randomMachineName(8),
];
Node::create($edit)->save();
$node = $this->drupalGetNodeByTitle($edit['title']);
$this->assertEqual($node->getCreatedTime(), REQUEST_TIME, 'Creating a node sets default "created" timestamp.');
$this->assertEqual($node->getChangedTime(), REQUEST_TIME, 'Creating a node sets default "changed" timestamp.');
// Store the timestamps.
$created = $node->getCreatedTime();
$node->save();
$node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
$this->assertEqual($node->getCreatedTime(), $created, 'Updating a node preserves "created" timestamp.');
// Programmatically set the timestamps using hook_ENTITY_TYPE_presave().
$node->title = 'testing_node_presave';
$node->save();
$node = $this->drupalGetNodeByTitle('testing_node_presave', TRUE);
$this->assertEqual($node->getCreatedTime(), 280299600, 'Saving a node uses "created" timestamp set in presave hook.');
$this->assertEqual($node->getChangedTime(), 979534800, 'Saving a node uses "changed" timestamp set in presave hook.');
// Programmatically set the timestamps on the node.
$edit = [
'uid' => $this->webUser->id(),
'type' => 'article',
'title' => $this->randomMachineName(8),
'created' => 280299600, // Sun, 19 Nov 1978 05:00:00 GMT
'changed' => 979534800, // Drupal 1.0 release.
];
Node::create($edit)->save();
$node = $this->drupalGetNodeByTitle($edit['title']);
$this->assertEqual($node->getCreatedTime(), 280299600, 'Creating a node programmatically uses programmatically set "created" timestamp.');
$this->assertEqual($node->getChangedTime(), 979534800, 'Creating a node programmatically uses programmatically set "changed" timestamp.');
// Update the timestamps.
$node->setCreatedTime(979534800);
$node->changed = 280299600;
$node->save();
$node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
$this->assertEqual($node->getCreatedTime(), 979534800, 'Updating a node uses user-set "created" timestamp.');
// Allowing setting changed timestamps is required, see
// Drupal\content_translation\ContentTranslationMetadataWrapper::setChangedTime($timestamp)
// for example.
$this->assertEqual($node->getChangedTime(), 280299600, 'Updating a node uses user-set "changed" timestamp.');
}
/**
* Tests node presave and static node load cache.
*
* This test determines changes in hook_ENTITY_TYPE_presave() and verifies
* that the static node load cache is cleared upon save.
*/
public function testDeterminingChanges() {
// Initial creation.
$node = Node::create([
'uid' => $this->webUser->id(),
'type' => 'article',
'title' => 'test_changes',
]);
$node->save();
// Update the node without applying changes.
$node->save();
$this->assertEqual($node->label(), 'test_changes', 'No changes have been determined.');
// Apply changes.
$node->title = 'updated';
$node->save();
// The hook implementations node_test_node_presave() and
// node_test_node_update() determine changes and change the title.
$this->assertEqual($node->label(), 'updated_presave_update', 'Changes have been determined.');
// Test the static node load cache to be cleared.
$node = Node::load($node->id());
$this->assertEqual($node->label(), 'updated_presave', 'Static cache has been cleared.');
}
/**
* Tests saving a node on node insert.
*
* This test ensures that a node has been fully saved when
* hook_ENTITY_TYPE_insert() is invoked, so that the node can be saved again
* in a hook implementation without errors.
*
* @see node_test_node_insert()
*/
public function testNodeSaveOnInsert() {
// node_test_node_insert() triggers a save on insert if the title equals
// 'new'.
$node = $this->drupalCreateNode(['title' => 'new']);
$this->assertEqual($node->getTitle(), 'Node ' . $node->id(), 'Node saved on node insert.');
}
}
@@ -0,0 +1,37 @@
<?php
namespace Drupal\Tests\node\Functional;
/**
* Tests if the syndicate block is available.
*
* @group node
*/
class NodeSyndicateBlockTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['block'];
protected function setUp() {
parent::setUp();
// Create a user and log in.
$admin_user = $this->drupalCreateUser(['administer blocks']);
$this->drupalLogin($admin_user);
}
/**
* Tests that the "Syndicate" block is shown when enabled.
*/
public function testSyndicateBlock() {
// Place the "Syndicate" block and confirm that it is rendered.
$this->drupalPlaceBlock('node_syndicate_block', ['id' => 'test_syndicate_block']);
$this->drupalGet('');
$this->assertFieldByXPath('//div[@id="block-test-syndicate-block"]/*', NULL, 'Syndicate block found.');
}
}
@@ -0,0 +1,38 @@
<?php
namespace Drupal\Tests\node\Functional;
/**
* Tests node template suggestions.
*
* @group node
*/
class NodeTemplateSuggestionsTest extends NodeTestBase {
/**
* Tests if template_preprocess_node() generates the correct suggestions.
*/
public function testNodeThemeHookSuggestions() {
// Create node to be rendered.
$node = $this->drupalCreateNode();
$view_mode = 'full';
// Simulate theming of the node.
$build = \Drupal::entityManager()->getViewBuilder('node')->view($node, $view_mode);
$variables['elements'] = $build;
$suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_node', [$variables]);
$this->assertEqual($suggestions, ['node__full', 'node__page', 'node__page__full', 'node__' . $node->id(), 'node__' . $node->id() . '__full'], 'Found expected node suggestions.');
// Change the view mode.
$view_mode = 'node.my_custom_view_mode';
$build = \Drupal::entityManager()->getViewBuilder('node')->view($node, $view_mode);
$variables['elements'] = $build;
$suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_node', [$variables]);
$this->assertEqual($suggestions, ['node__node_my_custom_view_mode', 'node__page', 'node__page__node_my_custom_view_mode', 'node__' . $node->id(), 'node__' . $node->id() . '__node_my_custom_view_mode'], 'Found expected node suggestions.');
}
}
@@ -0,0 +1,110 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
use Drupal\Tests\BrowserTestBase;
/**
* Sets up page and article content types.
*/
abstract class NodeTestBase extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'datetime'];
/**
* The node access control handler.
*
* @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface
*/
protected $accessHandler;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create Basic page and Article node types.
if ($this->profile != 'standard') {
$this->drupalCreateContentType([
'type' => 'page',
'name' => 'Basic page',
'display_submitted' => FALSE,
]);
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
}
$this->accessHandler = \Drupal::entityManager()->getAccessControlHandler('node');
}
/**
* 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\NodeInterface $node
* The node object to check.
* @param \Drupal\Core\Session\AccountInterface $account
* The user account for which to check access.
*/
public function assertNodeAccess(array $ops, NodeInterface $node, AccountInterface $account) {
foreach ($ops as $op => $result) {
$this->assertEqual($result, $this->accessHandler->access($node, $op, $account), $this->nodeAccessAssertMessage($op, $result, $node->language()->getId()));
}
}
/**
* Asserts that node create access correctly grants or denies access.
*
* @param string $bundle
* The node bundle to check access to.
* @param bool $result
* Whether access should be granted or not.
* @param \Drupal\Core\Session\AccountInterface $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.
*/
public function assertNodeCreateAccess($bundle, $result, AccountInterface $account, $langcode = NULL) {
$this->assertEqual($result, $this->accessHandler->createAccess($bundle, $account, [
'langcode' => $langcode,
]), $this->nodeAccessAssertMessage('create', $result, $langcode));
}
/**
* Constructs an assert message to display which node access was tested.
*
* @param string $operation
* The operation to check access for.
* @param bool $result
* Whether access should be granted or not.
* @param string|null $langcode
* (optional) The language code indicating which translation of the node
* to check. If NULL, the untranslated (fallback) access is checked.
*
* @return string
* An assert message string which contains information in plain English
* about the node access permission test that was performed.
*/
public function nodeAccessAssertMessage($operation, $result, $langcode = NULL) {
return format_string(
'Node access returns @result with operation %op, language code %langcode.',
[
'@result' => $result ? 'true' : 'false',
'%op' => $operation,
'%langcode' => !empty($langcode) ? $langcode : 'empty'
]
);
}
}
@@ -0,0 +1,103 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Component\Utility\Html;
/**
* Tests node title.
*
* @group node
*/
class NodeTitleTest extends NodeTestBase {
use CommentTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['comment', 'views', 'block'];
/**
* A user with permission to bypass access content.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
$this->adminUser = $this->drupalCreateUser(['administer nodes', 'create article content', 'create page content', 'post comments']);
$this->drupalLogin($this->adminUser);
$this->addDefaultCommentField('node', 'page');
}
/**
* Creates one node and tests if the node title has the correct value.
*/
public function testNodeTitle() {
// Create "Basic page" content with title.
// Add the node to the frontpage so we can test if teaser links are
// clickable.
$settings = [
'title' => $this->randomMachineName(8),
'promote' => 1,
];
$node = $this->drupalCreateNode($settings);
// Test <title> tag.
$this->drupalGet('node/' . $node->id());
$xpath = '//title';
$this->assertEqual($this->xpath($xpath)[0]->getText(), $node->label() . ' | Drupal', 'Page title is equal to node title.', 'Node');
// Test breadcrumb in comment preview.
$this->drupalGet('comment/reply/node/' . $node->id() . '/comment');
$xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a';
$this->assertEqual($this->xpath($xpath)[0]->getText(), $node->label(), 'Node breadcrumb is equal to node title.', 'Node');
// Test node title in comment preview.
$this->assertEqual($this->xpath('//article[contains(concat(" ", normalize-space(@class), " "), :node-class)]/h2/a/span', [':node-class' => ' node--type-' . $node->bundle() . ' '])[0]->getText(), $node->label(), 'Node preview title is equal to node title.', 'Node');
// Test node title is clickable on teaser list (/node).
$this->drupalGet('node');
$this->clickLink($node->label());
// Test edge case where node title is set to 0.
$settings = [
'title' => 0,
];
$node = $this->drupalCreateNode($settings);
// Test that 0 appears as <title>.
$this->drupalGet('node/' . $node->id());
$this->assertTitle(0 . ' | Drupal', 'Page title is equal to 0.', 'Node');
// Test that 0 appears in the template <h1>.
$xpath = '//h1';
$this->assertEqual(current($this->xpath($xpath)), 0, 'Node title is displayed as 0.', 'Node');
// Test edge case where node title contains special characters.
$edge_case_title = 'article\'s "title".';
$settings = [
'title' => $edge_case_title,
];
$node = $this->drupalCreateNode($settings);
// Test that the title appears as <title>. The title will be escaped on the
// the page.
$edge_case_title_escaped = Html::escape($edge_case_title);
$this->drupalGet('node/' . $node->id());
$this->assertRaw('<title>' . $edge_case_title_escaped . ' | Drupal</title>', 'Page title is equal to article\'s "title".', 'Node');
// Test that the title appears as <title> when reloading the node page.
$this->drupalGet('node/' . $node->id());
$this->assertRaw('<title>' . $edge_case_title_escaped . ' | Drupal</title>', 'Page title is equal to article\'s "title".', 'Node');
}
}
@@ -0,0 +1,43 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Component\Utility\Html;
/**
* Create a node with dangerous tags in its title and test that they are
* escaped.
*
* @group node
*/
class NodeTitleXSSTest extends NodeTestBase {
/**
* Tests XSS functionality with a node entity.
*/
public function testNodeTitleXSS() {
// Prepare a user to do the stuff.
$web_user = $this->drupalCreateUser(['create page content', 'edit any page content']);
$this->drupalLogin($web_user);
$xss = '<script>alert("xss")</script>';
$title = $xss . $this->randomMachineName();
$edit = [];
$edit['title[0][value]'] = $title;
$this->drupalPostForm('node/add/page', $edit, t('Preview'));
$this->assertNoRaw($xss, 'Harmful tags are escaped when previewing a node.');
$settings = ['title' => $title];
$node = $this->drupalCreateNode($settings);
$this->drupalGet('node/' . $node->id());
// Titles should be escaped.
$this->assertRaw('<title>' . Html::escape($title) . ' | Drupal</title>', 'Title is displayed when viewing a node.');
$this->assertNoRaw($xss, 'Harmful tags are escaped when viewing a node.');
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertNoRaw($xss, 'Harmful tags are escaped when editing a node.');
}
}
@@ -0,0 +1,512 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the Node Translation UI.
*
* @group node
*/
class NodeTranslationUITest extends ContentTranslationUITestBase {
/**
* {inheritdoc}
*/
protected $defaultCacheContexts = [
'languages:language_interface',
'session',
'theme',
'route',
'timezone',
'url.path.parent',
'url.query_args:_wrapper_format',
'user'
];
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['block', 'language', 'content_translation', 'node', 'datetime', 'field_ui', 'help'];
/**
* The profile to install as a basis for testing.
*
* @var string
*/
protected $profile = 'standard';
protected function setUp() {
$this->entityTypeId = 'node';
$this->bundle = 'article';
parent::setUp();
// Ensure the help message is shown even with prefixed paths.
$this->drupalPlaceBlock('help_block', ['region' => 'content']);
// Display the language selector.
$this->drupalLogin($this->administrator);
$edit = ['language_configuration[language_alterable]' => TRUE];
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
$this->drupalLogin($this->translator);
}
/**
* Tests the basic translation UI.
*/
public function testTranslationUI() {
parent::testTranslationUI();
$this->doUninstallTest();
}
/**
* Tests changing the published status on a node without fields.
*/
public function testPublishedStatusNoFields() {
// Test changing the published status of an article without fields.
$this->drupalLogin($this->administrator);
// Delete all fields.
$this->drupalGet('admin/structure/types/manage/article/fields');
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.' . $this->fieldName . '/delete', [], t('Delete'));
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_tags/delete', [], t('Delete'));
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_image/delete', [], t('Delete'));
// Add a node.
$default_langcode = $this->langcodes[0];
$values[$default_langcode] = ['title' => [['value' => $this->randomMachineName()]]];
$this->entityId = $this->createEntity($values[$default_langcode], $default_langcode);
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
// Add a content translation.
$langcode = 'fr';
$language = ConfigurableLanguage::load($langcode);
$values[$langcode] = ['title' => [['value' => $this->randomMachineName()]]];
$entity_type_id = $entity->getEntityTypeId();
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
$entity->getEntityTypeId() => $entity->id(),
'source' => $default_langcode,
'target' => $langcode
], ['language' => $language]);
$this->drupalPostForm($add_url, $this->getEditValues($values, $langcode), t('Save and unpublish (this translation)'));
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
$translation = $entity->getTranslation($langcode);
// Make sure we unpublished the node correctly.
$this->assertFalse($this->manager->getTranslationMetadata($translation)->isPublished(), 'The translation has been correctly unpublished.');
}
/**
* {@inheritdoc}
*/
protected function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), ['administer nodes', "edit any $this->bundle content"]);
}
/**
* {@inheritdoc}
*/
protected function getEditorPermissions() {
return ['administer nodes', 'create article content'];
}
/**
* {@inheritdoc}
*/
protected function getAdministratorPermissions() {
return array_merge(parent::getAdministratorPermissions(), ['access administration pages', 'administer content types', 'administer node fields', 'access content overview', 'bypass node access', 'administer languages', 'administer themes', 'view the administration theme']);
}
/**
* {@inheritdoc}
*/
protected function getNewEntityValues($langcode) {
return ['title' => [['value' => $this->randomMachineName()]]] + parent::getNewEntityValues($langcode);
}
/**
* {@inheritdoc}
*/
protected function getFormSubmitAction(EntityInterface $entity, $langcode) {
if ($entity->getTranslation($langcode)->isPublished()) {
return t('Save and keep published') . $this->getFormSubmitSuffix($entity, $langcode);
}
else {
return t('Save and keep unpublished') . $this->getFormSubmitSuffix($entity, $langcode);
}
}
/**
* {@inheritdoc}
*/
protected function doTestPublishedStatus() {
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
$actions = [
t('Save and keep published'),
t('Save and unpublish'),
];
foreach ($actions as $index => $action) {
// (Un)publish the node translations and check that the translation
// statuses are (un)published accordingly.
foreach ($this->langcodes as $langcode) {
$options = ['language' => $languages[$langcode]];
$url = $entity->urlInfo('edit-form', $options);
$this->drupalPostForm($url, [], $action . $this->getFormSubmitSuffix($entity, $langcode), $options);
}
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
foreach ($this->langcodes as $langcode) {
// The node is created as unpublished thus we switch to the published
// status first.
$status = !$index;
$translation = $entity->getTranslation($langcode);
$this->assertEqual($status, $this->manager->getTranslationMetadata($translation)->isPublished(), 'The translation has been correctly unpublished.');
}
}
}
/**
* {@inheritdoc}
*/
protected function doTestAuthoringInfo() {
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
$values = [];
// Post different base field information for each translation.
foreach ($this->langcodes as $langcode) {
$user = $this->drupalCreateUser();
$values[$langcode] = [
'uid' => $user->id(),
'created' => REQUEST_TIME - mt_rand(0, 1000),
'sticky' => (bool) mt_rand(0, 1),
'promote' => (bool) mt_rand(0, 1),
];
$edit = [
'uid[0][target_id]' => $user->getUsername(),
'created[0][value][date]' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d'),
'created[0][value][time]' => format_date($values[$langcode]['created'], 'custom', 'H:i:s'),
'sticky[value]' => $values[$langcode]['sticky'],
'promote[value]' => $values[$langcode]['promote'],
];
$options = ['language' => $languages[$langcode]];
$url = $entity->urlInfo('edit-form', $options);
$this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode), $options);
}
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
foreach ($this->langcodes as $langcode) {
$translation = $entity->getTranslation($langcode);
$metadata = $this->manager->getTranslationMetadata($translation);
$this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
$this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
$this->assertEqual($translation->isSticky(), $values[$langcode]['sticky'], 'Sticky of Translation correctly stored.');
$this->assertEqual($translation->isPromoted(), $values[$langcode]['promote'], 'Promoted of Translation correctly stored.');
}
}
/**
* Tests that translation page inherits admin status of edit page.
*/
public function testTranslationLinkTheme() {
$this->drupalLogin($this->administrator);
$article = $this->drupalCreateNode(['type' => 'article', 'langcode' => $this->langcodes[0]]);
// Set up Seven as the admin theme and use it for node editing.
$this->container->get('theme_handler')->install(['seven']);
$edit = [];
$edit['admin_theme'] = 'seven';
$edit['use_admin_theme'] = TRUE;
$this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
$this->drupalGet('node/' . $article->id() . '/translations');
$this->assertRaw('core/themes/seven/css/base/elements.css', 'Translation uses admin theme if edit is admin.');
// Turn off admin theme for editing, assert inheritance to translations.
$edit['use_admin_theme'] = FALSE;
$this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
$this->drupalGet('node/' . $article->id() . '/translations');
$this->assertNoRaw('core/themes/seven/css/base/elements.css', 'Translation uses frontend theme if edit is frontend.');
// Assert presence of translation page itself (vs. DisabledBundle below).
$this->assertResponse(200);
}
/**
* Tests that no metadata is stored for a disabled bundle.
*/
public function testDisabledBundle() {
// Create a bundle that does not have translation enabled.
$disabledBundle = $this->randomMachineName();
$this->drupalCreateContentType(['type' => $disabledBundle, 'name' => $disabledBundle]);
// Create a node for each bundle.
$node = $this->drupalCreateNode([
'type' => $this->bundle,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
// Make sure that nothing was inserted into the {content_translation} table.
$rows = db_query('SELECT nid, count(nid) AS count FROM {node_field_data} WHERE type <> :type GROUP BY nid HAVING count(nid) >= 2', [':type' => $this->bundle])->fetchAll();
$this->assertEqual(0, count($rows));
// Ensure the translation tab is not accessible.
$this->drupalGet('node/' . $node->id() . '/translations');
$this->assertResponse(403);
}
/**
* Tests that translations are rendered properly.
*/
public function testTranslationRendering() {
$default_langcode = $this->langcodes[0];
$values[$default_langcode] = $this->getNewEntityValues($default_langcode);
$this->entityId = $this->createEntity($values[$default_langcode], $default_langcode);
$node = \Drupal::entityManager()->getStorage($this->entityTypeId)->load($this->entityId);
$node->setPromoted(TRUE);
// Create translations.
foreach (array_diff($this->langcodes, [$default_langcode]) as $langcode) {
$values[$langcode] = $this->getNewEntityValues($langcode);
$translation = $node->addTranslation($langcode, $values[$langcode]);
// Publish and promote the translation to frontpage.
$translation->setPromoted(TRUE);
$translation->setPublished(TRUE);
}
$node->save();
// Test that the frontpage view displays the correct translations.
\Drupal::service('module_installer')->install(['views'], TRUE);
$this->rebuildContainer();
$this->doTestTranslations('node', $values);
// Enable the translation language renderer.
$view = \Drupal::entityManager()->getStorage('view')->load('frontpage');
$display = &$view->getDisplay('default');
$display['display_options']['rendering_language'] = '***LANGUAGE_entity_translation***';
$view->save();
// Need to check from the beginning, including the base_path, in the url
// since the pattern for the default language might be a substring of
// the strings for other languages.
$base_path = base_path();
// Check the frontpage for 'Read more' links to each translation.
// See also assertTaxonomyPage() in NodeAccessBaseTableTest.
$node_href = 'node/' . $node->id();
foreach ($this->langcodes as $langcode) {
$this->drupalGet('node', ['language' => \Drupal::languageManager()->getLanguage($langcode)]);
$num_match_found = 0;
if ($langcode == 'en') {
// Site default language does not have langcode prefix in the URL.
$expected_href = $base_path . $node_href;
}
else {
$expected_href = $base_path . $langcode . '/' . $node_href;
}
$pattern = '|^' . $expected_href . '$|';
foreach ($this->xpath("//a[text()='Read more']") as $link) {
if (preg_match($pattern, $link->getAttribute('href'), $matches) == TRUE) {
$num_match_found++;
}
}
$this->assertTrue($num_match_found == 1, 'There is 1 Read more link, ' . $expected_href . ', for the ' . $langcode . ' translation of a node on the frontpage. (Found ' . $num_match_found . '.)');
}
// Check the frontpage for 'Add new comment' links that include the
// language.
$comment_form_href = 'node/' . $node->id() . '#comment-form';
foreach ($this->langcodes as $langcode) {
$this->drupalGet('node', ['language' => \Drupal::languageManager()->getLanguage($langcode)]);
$num_match_found = 0;
if ($langcode == 'en') {
// Site default language does not have langcode prefix in the URL.
$expected_href = $base_path . $comment_form_href;
}
else {
$expected_href = $base_path . $langcode . '/' . $comment_form_href;
}
$pattern = '|^' . $expected_href . '$|';
foreach ($this->xpath("//a[text()='Add new comment']") as $link) {
if (preg_match($pattern, $link->getAttribute('href'), $matches) == TRUE) {
$num_match_found++;
}
}
$this->assertTrue($num_match_found == 1, 'There is 1 Add new comment link, ' . $expected_href . ', for the ' . $langcode . ' translation of a node on the frontpage. (Found ' . $num_match_found . '.)');
}
// Test that the node page displays the correct translations.
$this->doTestTranslations('node/' . $node->id(), $values);
// Test that the node page has the correct alternate hreflang links.
$this->doTestAlternateHreflangLinks($node->urlInfo());
}
/**
* Tests that the given path displays the correct translation values.
*
* @param string $path
* The path to be tested.
* @param array $values
* The translation values to be found.
*/
protected function doTestTranslations($path, array $values) {
$languages = $this->container->get('language_manager')->getLanguages();
foreach ($this->langcodes as $langcode) {
$this->drupalGet($path, ['language' => $languages[$langcode]]);
$this->assertText($values[$langcode]['title'][0]['value'], format_string('The %langcode node translation is correctly displayed.', ['%langcode' => $langcode]));
}
}
/**
* Tests that the given path provides the correct alternate hreflang links.
*
* @param \Drupal\Core\Url $url
* The path to be tested.
*/
protected function doTestAlternateHreflangLinks(Url $url) {
$languages = $this->container->get('language_manager')->getLanguages();
$url->setAbsolute();
$urls = [];
foreach ($this->langcodes as $langcode) {
$language_url = clone $url;
$urls[$langcode] = $language_url->setOption('language', $languages[$langcode]);
}
foreach ($this->langcodes as $langcode) {
$this->drupalGet($urls[$langcode]);
foreach ($urls as $alternate_langcode => $language_url) {
// Retrieve desired link elements from the HTML head.
$links = $this->xpath('head/link[@rel = "alternate" and @href = :href and @hreflang = :hreflang]',
[':href' => $language_url->toString(), ':hreflang' => $alternate_langcode]);
$this->assert(isset($links[0]), format_string('The %langcode node translation has the correct alternate hreflang link for %alternate_langcode: %link.', ['%langcode' => $langcode, '%alternate_langcode' => $alternate_langcode, '%link' => $url->toString()]));
}
}
}
/**
* {@inheritdoc}
*/
protected function getFormSubmitSuffix(EntityInterface $entity, $langcode) {
if (!$entity->isNew() && $entity->isTranslatable()) {
$translations = $entity->getTranslationLanguages();
if ((count($translations) > 1 || !isset($translations[$langcode])) && ($field = $entity->getFieldDefinition('status'))) {
return ' ' . ($field->isTranslatable() ? t('(this translation)') : t('(all translations)'));
}
}
return '';
}
/**
* Tests uninstalling content_translation.
*/
protected function doUninstallTest() {
// Delete all the nodes so there is no data.
$nodes = Node::loadMultiple();
foreach ($nodes as $node) {
$node->delete();
}
$language_count = count(\Drupal::configFactory()->listAll('language.content_settings.'));
\Drupal::service('module_installer')->uninstall(['content_translation']);
$this->rebuildContainer();
$this->assertEqual($language_count, count(\Drupal::configFactory()->listAll('language.content_settings.')), 'Languages have been fixed rather than deleted during content_translation uninstall.');
}
/**
* {@inheritdoc}
*/
protected function doTestTranslationEdit() {
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
$type_name = node_get_type_label($entity);
foreach ($this->langcodes as $langcode) {
// We only want to test the title for non-english translations.
if ($langcode != 'en') {
$options = ['language' => $languages[$langcode]];
$url = $entity->urlInfo('edit-form', $options);
$this->drupalGet($url);
$title = t('<em>Edit @type</em> @title [%language translation]', [
'@type' => $type_name,
'@title' => $entity->getTranslation($langcode)->label(),
'%language' => $languages[$langcode]->getName(),
]);
$this->assertRaw($title);
}
}
}
/**
* Tests that revision translations are rendered properly.
*/
public function testRevisionTranslationRendering() {
$storage = \Drupal::entityTypeManager()->getStorage('node');
// Create a node.
$nid = $this->createEntity(['title' => 'First rev en title'], 'en');
$node = $storage->load($nid);
$original_revision_id = $node->getRevisionId();
// Add a French translation.
$translation = $node->addTranslation('fr');
$translation->title = 'First rev fr title';
$translation->setNewRevision(FALSE);
$translation->save();
// Create a new revision.
$node->title = 'Second rev en title';
$node->setNewRevision(TRUE);
$node->save();
// Get an English view of this revision.
$original_revision = $storage->loadRevision($original_revision_id);
$original_revision_url = $original_revision->toUrl('revision')->toString();
// Should be different from regular node URL.
$this->assertNotIdentical($original_revision_url, $original_revision->toUrl()->toString());
$this->drupalGet($original_revision_url);
$this->assertResponse(200);
// Contents should be in English, of correct revision.
$this->assertText('First rev en title');
$this->assertNoText('First rev fr title');
// Get a French view.
$url_fr = $original_revision->getTranslation('fr')->toUrl('revision')->toString();
// Should have different URL from English.
$this->assertNotIdentical($url_fr, $original_revision->toUrl()->toString());
$this->assertNotIdentical($url_fr, $original_revision_url);
$this->drupalGet($url_fr);
$this->assertResponse(200);
// Contents should be in French, of correct revision.
$this->assertText('First rev fr title');
$this->assertNoText('First rev en title');
}
}
@@ -0,0 +1,126 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Core\Language\LanguageInterface;
/**
* Tests node type initial language settings.
*
* @group node
*/
class NodeTypeInitialLanguageTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['language', 'field_ui'];
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(['bypass node access', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'administer languages']);
$this->drupalLogin($web_user);
}
/**
* Tests the node type initial language defaults, and modifies them.
*
* The default initial language must be the site's default, and the language
* locked option must be on.
*/
public function testNodeTypeInitialLanguageDefaults() {
$this->drupalGet('admin/structure/types/manage/article');
$this->assertOptionSelected('edit-language-configuration-langcode', LanguageInterface::LANGCODE_SITE_DEFAULT, 'The default initial language is the site default.');
$this->assertNoFieldChecked('edit-language-configuration-language-alterable', 'Language selector is hidden by default.');
// Tests if the language field cannot be rearranged on the manage fields tab.
$this->drupalGet('admin/structure/types/manage/article/fields');
$language_field = $this->xpath('//*[@id="field-overview"]/*[@id="language"]');
$this->assert(empty($language_field), 'Language field is not visible on manage fields tab.');
$this->drupalGet('node/add/article');
$this->assertNoField('langcode', 'Language is not selectable on node add/edit page by default.');
// Adds a new language and set it as default.
$edit = [
'predefined_langcode' => 'hu',
];
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
$edit = [
'site_default_language' => 'hu',
];
$this->drupalPostForm('admin/config/regional/language', $edit, t('Save configuration'));
// Tests the initial language after changing the site default language.
// First unhide the language selector.
$edit = [
'language_configuration[language_alterable]' => TRUE,
];
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
$this->drupalGet('node/add/article');
$this->assertField('langcode[0][value]', 'Language is selectable on node add/edit page when language not hidden.');
$this->assertOptionSelected('edit-langcode-0-value', 'hu', 'The initial language is the site default on the node add page after the site default language is changed.');
// Tests if the language field can be rearranged on the manage form display
// tab.
$this->drupalGet('admin/structure/types/manage/article/form-display');
$language_field = $this->xpath('//*[@id="langcode"]');
$this->assert(!empty($language_field), 'Language field is visible on manage form display tab.');
// Tests if the language field can be rearranged on the manage display tab.
$this->drupalGet('admin/structure/types/manage/article/display');
$language_display = $this->xpath('//*[@id="langcode"]');
$this->assert(!empty($language_display), 'Language field is visible on manage display tab.');
// Tests if the language field is hidden by default.
$this->assertOptionSelected('edit-fields-langcode-region', 'hidden', 'Language is hidden by default on manage display tab.');
// Changes the initial language settings.
$edit = [
'language_configuration[langcode]' => 'en',
];
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
$this->drupalGet('node/add/article');
$this->assertOptionSelected('edit-langcode-0-value', 'en', 'The initial language is the defined language.');
}
/**
* Tests language field visibility features.
*/
public function testLanguageFieldVisibility() {
// Creates a node to test Language field visibility feature.
$edit = [
'title[0][value]' => $this->randomMachineName(8),
'body[0][value]' => $this->randomMachineName(16),
];
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertTrue($node, 'Node found in database.');
// Loads node page and check if Language field is hidden by default.
$this->drupalGet('node/' . $node->id());
$language_field = $this->xpath('//div[@id=:id]/div', [
':id' => 'field-language-display',
]);
$this->assertTrue(empty($language_field), 'Language field value is not shown by default on node page.');
// Configures Language field formatter and check if it is saved.
$edit = [
'fields[langcode][type]' => 'language',
'fields[langcode][region]' => 'content',
];
$this->drupalPostForm('admin/structure/types/manage/article/display', $edit, t('Save'));
$this->drupalGet('admin/structure/types/manage/article/display');
$this->assertOptionSelected('edit-fields-langcode-type', 'language', 'Language field has been set to visible.');
// Loads node page and check if Language field is shown.
$this->drupalGet('node/' . $node->id());
$language_field = $this->xpath('//div[@id=:id]/div', [
':id' => 'field-language-display',
]);
$this->assertFalse(empty($language_field), 'Language field value is shown on node page.');
}
}
@@ -0,0 +1,176 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Component\Utility\Unicode;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
/**
* Ensures that node types translation work correctly.
*
* Note that the child site is installed in French; therefore, when making
* assertions on translated text it is important to provide a langcode. This
* ensures the asserts pass regardless of the Drupal version.
*
* @group node
*/
class NodeTypeTranslationTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'block',
'config_translation',
'field_ui',
'node',
];
/**
* The default language code to use in this test.
*
* @var array
*/
protected $defaultLangcode = 'fr';
/**
* Languages to enable.
*
* @var array
*/
protected $additionalLangcodes = ['es'];
/**
* Administrator user for tests.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
$admin_permissions = [
'administer content types',
'administer node fields',
'administer languages',
'administer site configuration',
'administer themes',
'translate configuration',
];
// Create and log in user.
$this->adminUser = $this->drupalCreateUser($admin_permissions);
// Add languages.
foreach ($this->additionalLangcodes as $langcode) {
ConfigurableLanguage::createFromLangcode($langcode)->save();
}
}
/**
* {@inheritdoc}
*
* Install Drupal in a language other than English for this test. This is not
* needed to test the node type translation itself but acts as a regression
* test.
*
* @see https://www.drupal.org/node/2584603
*/
protected function installParameters() {
$parameters = parent::installParameters();
$parameters['parameters']['langcode'] = $this->defaultLangcode;
return $parameters;
}
/**
* Tests the node type translation.
*/
public function testNodeTypeTranslation() {
$type = Unicode::strtolower($this->randomMachineName(16));
$name = $this->randomString();
$this->drupalLogin($this->adminUser);
$this->drupalCreateContentType(['type' => $type, 'name' => $name]);
// Translate the node type name.
$langcode = $this->additionalLangcodes[0];
$translated_name = $langcode . '-' . $name;
$edit = [
"translation[config_names][node.type.$type][name]" => $translated_name,
];
// Edit the title label to avoid having an exception when we save the translation.
$this->drupalPostForm("admin/structure/types/manage/$type/translate/$langcode/add", $edit, t('Save translation'));
// Check the name is translated without admin theme for editing.
$this->drupalPostForm('admin/appearance', ['use_admin_theme' => '0'], t('Save configuration'));
$this->drupalGet("$langcode/node/add/$type");
// This is a Spanish page, so ensure the text asserted is translated in
// Spanish and not French by adding the langcode option.
$this->assertRaw(t('Create @name', ['@name' => $translated_name], ['langcode' => $langcode]));
// Check the name is translated with admin theme for editing.
$this->drupalPostForm('admin/appearance', ['use_admin_theme' => '1'], t('Save configuration'));
$this->drupalGet("$langcode/node/add/$type");
// This is a Spanish page, so ensure the text asserted is translated in
// Spanish and not French by adding the langcode option.
$this->assertRaw(t('Create @name', ['@name' => $translated_name], ['langcode' => $langcode]));
}
/**
* Tests the node type title label translation.
*/
public function testNodeTypeTitleLabelTranslation() {
$type = Unicode::strtolower($this->randomMachineName(16));
$name = $this->randomString();
$this->drupalLogin($this->adminUser);
$this->drupalCreateContentType(['type' => $type, 'name' => $name]);
$langcode = $this->additionalLangcodes[0];
// Edit the title label for it to be displayed on the translation form.
$this->drupalPostForm("admin/structure/types/manage/$type", ['title_label' => 'Edited title'], t('Save content type'));
// Assert that the title label is displayed on the translation form with the right value.
$this->drupalGet("admin/structure/types/manage/$type/translate/$langcode/add");
$this->assertText('Edited title');
// Translate the title label.
$this->drupalPostForm(NULL, ["translation[config_names][core.base_field_override.node.$type.title][label]" => 'Translated title'], t('Save translation'));
// Assert that the right title label is displayed on the node add form. The
// translations are created in this test; therefore, the assertions do not
// use t(). If t() were used then the correct langcodes would need to be
// provided.
$this->drupalGet("node/add/$type");
$this->assertText('Edited title');
$this->drupalGet("$langcode/node/add/$type");
$this->assertText('Translated title');
// Add an e-mail field.
$this->drupalPostForm("admin/structure/types/manage/$type/fields/add-field", ['new_storage_type' => 'email', 'label' => 'Email', 'field_name' => 'email'], 'Save and continue');
$this->drupalPostForm(NULL, [], 'Save field settings');
$this->drupalPostForm(NULL, [], 'Save settings');
$type = Unicode::strtolower($this->randomMachineName(16));
$name = $this->randomString();
$this->drupalCreateContentType(['type' => $type, 'name' => $name]);
// Set tabs.
$this->drupalPlaceBlock('local_tasks_block', ['primary' => TRUE]);
// Change default language.
$this->drupalPostForm('admin/config/regional/language', ['site_default_language' => 'es'], 'Save configuration');
// Try re-using the email field.
$this->drupalGet("es/admin/structure/types/manage/$type/fields/add-field");
$this->drupalPostForm(NULL, ['existing_storage_name' => 'field_email', 'existing_storage_label' => 'Email'], 'Save and continue');
$this->assertResponse(200);
$this->drupalGet("es/admin/structure/types/manage/$type/fields/node.$type.field_email/translate");
$this->assertResponse(200);
$this->assertText("The configuration objects have different language codes so they cannot be translated");
}
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the node language extra field display.
*
* @group node
*/
class NodeViewLanguageTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'datetime', 'language'];
/**
* Tests the language extra field display.
*/
public function testViewLanguage() {
// Add Spanish language.
ConfigurableLanguage::createFromLangcode('es')->save();
// Set language field visible.
entity_get_display('node', 'page', 'full')
->setComponent('langcode')
->save();
// Create a node in Spanish.
$node = $this->drupalCreateNode(['langcode' => 'es']);
$this->drupalGet($node->urlInfo());
$this->assertText('Spanish', 'The language field is displayed properly.');
}
}
@@ -0,0 +1,98 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\Component\Utility\Html;
/**
* Tests the node/{node} page.
*
* @group node
* @see \Drupal\node\Controller\NodeController
*/
class NodeViewTest extends NodeTestBase {
/**
* Tests the html head links.
*/
public function testHtmlHeadLinks() {
$node = $this->drupalCreateNode();
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]->getAttribute('href'), $node->url());
// Link relations are checked for access for anonymous users.
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertFalse($result, 'Version history not present for anonymous users without access.');
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertFalse($result, 'Edit form not present for anonymous users without access.');
$this->drupalLogin($this->createUser(['access content']));
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]->getAttribute('href'), $node->url());
// Link relations are present regardless of access for authenticated users.
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertEqual($result[0]->getAttribute('href'), $node->url('version-history'));
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertEqual($result[0]->getAttribute('href'), $node->url('edit-form'));
// Give anonymous users access to edit the node. Do this through the UI to
// ensure caches are handled properly.
$this->drupalLogin($this->rootUser);
$edit = [
'anonymous[edit own ' . $node->bundle() . ' content]' => TRUE
];
$this->drupalPostForm('admin/people/permissions', $edit, 'Save permissions');
$this->drupalLogout();
// Anonymous user's should now see the edit-form link but not the
// version-history link.
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]->getAttribute('href'), $node->url());
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertFalse($result, 'Version history not present for anonymous users without access.');
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertEqual($result[0]->getAttribute('href'), $node->url('edit-form'));
}
/**
* Tests the Link header.
*/
public function testLinkHeader() {
$node = $this->drupalCreateNode();
$expected = [
'<' . Html::escape($node->url('canonical')) . '>; rel="canonical"',
'<' . Html::escape($node->url('canonical'), ['alias' => TRUE]) . '>; rel="shortlink"',
'<' . Html::escape($node->url('revision')) . '>; rel="revision"',
];
$this->drupalGet($node->urlInfo());
$links = $this->drupalGetHeaders()['Link'];
$this->assertEqual($links, $expected);
}
/**
* Tests that we store and retrieve multi-byte UTF-8 characters correctly.
*/
public function testMultiByteUtf8() {
$title = '🐝';
$this->assertTrue(mb_strlen($title, 'utf-8') < strlen($title), 'Title has multi-byte characters.');
$node = $this->drupalCreateNode(['title' => $title]);
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//span[contains(@class, "field--name-title")]');
$this->assertEqual($result[0]->getText(), $title, 'The passed title was returned.');
}
}
@@ -0,0 +1,42 @@
<?php
namespace Drupal\Tests\node\Functional;
use Drupal\node\Entity\Node;
/**
* Create a node and test edit permissions.
*
* @group node
*/
class PageViewTest extends NodeTestBase {
/**
* Tests an anonymous and unpermissioned user attempting to edit the node.
*/
public function testPageView() {
// Create a node to view.
$node = $this->drupalCreateNode();
$this->assertTrue(Node::load($node->id()), 'Node created.');
// Try to edit with anonymous user.
$this->drupalGet("node/" . $node->id() . "/edit");
$this->assertResponse(403);
// Create a user without permission to edit node.
$web_user = $this->drupalCreateUser(['access content']);
$this->drupalLogin($web_user);
// Attempt to access edit page.
$this->drupalGet("node/" . $node->id() . "/edit");
$this->assertResponse(403);
// Create user with permission to edit node.
$web_user = $this->drupalCreateUser(['bypass node access']);
$this->drupalLogin($web_user);
// Attempt to access edit page.
$this->drupalGet("node/" . $node->id() . "/edit");
$this->assertResponse(200);
}
}
@@ -0,0 +1,177 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* Tests if entity access is respected on a node bulk operations form.
*
* @group node
* @see \Drupal\node\Plugin\views\field\BulkForm
* @see \Drupal\node\Tests\NodeTestBase
* @see \Drupal\node\Tests\NodeAccessBaseTableTest
* @see \Drupal\node\Tests\Views\BulkFormTest
*/
class BulkFormAccessTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node_test_views', 'node_access_test'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_node_bulk_form'];
/**
* The node access control handler.
*
* @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface
*/
protected $accessHandler;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
// Create Article node type.
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
$this->accessHandler = \Drupal::entityManager()->getAccessControlHandler('node');
node_access_test_add_field(NodeType::load('article'));
// After enabling a node access module, the access table has to be rebuild.
node_access_rebuild();
// Enable the private node feature of the node_access_test module.
\Drupal::state()->set('node_access_test.private', TRUE);
}
/**
* Tests if nodes that may not be edited, can not be edited in bulk.
*/
public function testNodeEditAccess() {
// Create an account who will be the author of a private node.
$author = $this->drupalCreateUser();
// Create a private node (author may view, edit and delete, others may not).
$node = $this->drupalCreateNode([
'type' => 'article',
'private' => [[
'value' => TRUE,
]],
'uid' => $author->id(),
]);
// Create an account that may view the private node, but not edit it.
$account = $this->drupalCreateUser(['node test view']);
$this->drupalLogin($account);
// Ensure the node is published.
$this->assertTrue($node->isPublished(), 'Node is initially published.');
// Ensure that the node can not be edited.
$this->assertEqual(FALSE, $this->accessHandler->access($node, 'update', $account), 'The node may not be edited.');
// Test editing the node using the bulk form.
$edit = [
'node_bulk_form[0]' => TRUE,
'action' => 'node_unpublish_action',
];
$this->drupalPostForm('test-node-bulk-form', $edit, t('Apply to selected items'));
$this->assertRaw(SafeMarkup::format('No access to execute %action on the @entity_type_label %entity_label.', [
'%action' => 'Unpublish content',
'@entity_type_label' => 'Content',
'%entity_label' => $node->label(),
]));
// Re-load the node and check the status.
$node = Node::load($node->id());
$this->assertTrue($node->isPublished(), 'The node is still published.');
// Create an account that may view the private node, but can update the
// status.
$account = $this->drupalCreateUser(['administer nodes', 'node test view']);
$this->drupalLogin($account);
// Ensure the node is published.
$this->assertTrue($node->isPublished(), 'Node is initially published.');
// Ensure that the private node can not be edited.
$this->assertEqual(FALSE, $node->access('update', $account), 'The node may not be edited.');
$this->assertEqual(TRUE, $node->status->access('edit', $account), 'The node status can be edited.');
// Test editing the node using the bulk form.
$edit = [
'node_bulk_form[0]' => TRUE,
'action' => 'node_unpublish_action',
];
$this->drupalPostForm('test-node-bulk-form', $edit, t('Apply to selected items'));
// Test that the action message isn't shown.
$this->assertNoRaw(SafeMarkup::format('%action was applied to 1 item.', [
'%action' => 'Unpublish content',
]));
// Re-load the node and check the status.
$node = Node::load($node->id());
$this->assertTrue($node->isPublished(), 'The node is still published.');
}
/**
* Tests if nodes that may not be deleted, can not be deleted in bulk.
*/
public function testNodeDeleteAccess() {
// Create an account who will be the author of a private node.
$author = $this->drupalCreateUser();
// Create a private node (author may view, edit and delete, others may not).
$private_node = $this->drupalCreateNode([
'type' => 'article',
'private' => [[
'value' => TRUE,
]],
'uid' => $author->id(),
]);
// Create an account that may view the private node, but not delete it.
$account = $this->drupalCreateUser(['access content', 'administer nodes', 'delete own article content', 'node test view']);
// Create a node that may be deleted too, to ensure the delete confirmation
// page is shown later. In node_access_test.module, nodes may only be
// deleted by the author.
$own_node = $this->drupalCreateNode([
'type' => 'article',
'private' => [[
'value' => TRUE,
]],
'uid' => $account->id(),
]);
$this->drupalLogin($account);
// Ensure that the private node can not be deleted.
$this->assertEqual(FALSE, $this->accessHandler->access($private_node, 'delete', $account), 'The private node may not be deleted.');
// Ensure that the public node may be deleted.
$this->assertEqual(TRUE, $this->accessHandler->access($own_node, 'delete', $account), 'The own node may be deleted.');
// Try to delete the node using the bulk form.
$edit = [
'node_bulk_form[0]' => TRUE,
'node_bulk_form[1]' => TRUE,
'action' => 'node_delete_action',
];
$this->drupalPostForm('test-node-bulk-form', $edit, t('Apply to selected items'));
$this->drupalPostForm(NULL, [], t('Delete'));
// Ensure the private node still exists.
$private_node = Node::load($private_node->id());
$this->assertNotNull($private_node, 'The private node has not been deleted.');
// Ensure the own node is deleted.
$own_node = Node::load($own_node->id());
$this->assertNull($own_node, 'The own node is deleted.');
}
}
@@ -0,0 +1,275 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\views\Views;
/**
* Tests a node bulk form.
*
* @group node
* @see \Drupal\node\Plugin\views\field\BulkForm
*/
class BulkFormTest extends NodeTestBase {
/**
* Modules to be enabled.
*
* @var array
*/
public static $modules = ['node_test_views', 'language'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_node_bulk_form'];
/**
* The test nodes.
*
* @var \Drupal\node\NodeInterface[]
*/
protected $nodes;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
ConfigurableLanguage::createFromLangcode('en-gb')->save();
ConfigurableLanguage::createFromLangcode('it')->save();
// Create some test nodes.
$this->nodes = [];
$langcodes = ['en', 'en-gb', 'it'];
for ($i = 1; $i <= 5; $i++) {
$langcode = $langcodes[($i - 1) % 3];
$values = [
'title' => $this->randomMachineName() . ' [' . $i . ':' . $langcode . ']',
'langcode' => $langcode,
'promote' => FALSE,
];
$node = $this->drupalCreateNode($values);
$this->pass(SafeMarkup::format('Node %title created with language %langcode.', ['%title' => $node->label(), '%langcode' => $node->language()->getId()]));
$this->nodes[] = $node;
}
// Create translations for all languages for some nodes.
for ($i = 0; $i < 2; $i++) {
$node = $this->nodes[$i];
foreach ($langcodes as $langcode) {
if (!$node->hasTranslation($langcode)) {
$title = $this->randomMachineName() . ' [' . $node->id() . ':' . $langcode . ']';
$translation = $node->addTranslation($langcode, ['title' => $title, 'promote' => FALSE]);
$this->pass(SafeMarkup::format('Translation %title created with language %langcode.', ['%title' => $translation->label(), '%langcode' => $translation->language()->getId()]));
}
}
$node->save();
}
// Create a node with only one translation.
$node = $this->nodes[2];
$langcode = 'en';
$title = $this->randomMachineName() . ' [' . $node->id() . ':' . $langcode . ']';
$translation = $node->addTranslation($langcode, ['title' => $title]);
$this->pass(SafeMarkup::format('Translation %title created with language %langcode.', ['%title' => $translation->label(), '%langcode' => $translation->language()->getId()]));
$node->save();
// Check that all created translations are selected by the test view.
$view = Views::getView('test_node_bulk_form');
$view->execute();
$this->assertEqual(count($view->result), 10, 'All created translations are selected.');
// Check the operations are accessible to the logged in user.
$this->drupalLogin($this->drupalCreateUser(['administer nodes', 'access content overview', 'bypass node access']));
$this->drupalGet('test-node-bulk-form');
$elements = $this->xpath('//select[@id="edit-action"]//option');
$this->assertIdentical(count($elements), 8, 'All node operations are found.');
}
/**
* Tests the node bulk form.
*/
public function testBulkForm() {
// Unpublish a node using the bulk form.
$node = reset($this->nodes);
$this->assertTrue($node->isPublished(), 'Node is initially published');
$this->assertTrue($node->getTranslation('en-gb')->isPublished(), 'Node translation is published');
$this->assertTrue($node->getTranslation('it')->isPublished(), 'Node translation is published');
$edit = [
'node_bulk_form[0]' => TRUE,
'action' => 'node_unpublish_action',
];
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
$node = $this->loadNode($node->id());
$this->assertFalse($node->isPublished(), 'Node has been unpublished');
$this->assertTrue($node->getTranslation('en-gb')->isPublished(), 'Node translation has not been unpublished');
$this->assertTrue($node->getTranslation('it')->isPublished(), 'Node translation has not been unpublished');
// Publish action.
$edit = [
'node_bulk_form[0]' => TRUE,
'action' => 'node_publish_action',
];
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
$node = $this->loadNode($node->id());
$this->assertTrue($node->isPublished(), 'Node has been published again');
// Make sticky action.
$this->assertFalse($node->isSticky(), 'Node is not sticky');
$this->assertFalse($node->getTranslation('en-gb')->isSticky(), 'Node translation is not sticky');
$this->assertFalse($node->getTranslation('it')->isSticky(), 'Node translation is not sticky');
$edit = [
'node_bulk_form[0]' => TRUE,
'action' => 'node_make_sticky_action',
];
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
$node = $this->loadNode($node->id());
$this->assertTrue($node->isSticky(), 'Node has been made sticky');
$this->assertFalse($node->getTranslation('en-gb')->isSticky(), 'Node translation has not been made sticky');
$this->assertFalse($node->getTranslation('it')->isSticky(), 'Node translation has not been made sticky');
// Make unsticky action.
$edit = [
'node_bulk_form[0]' => TRUE,
'action' => 'node_make_unsticky_action',
];
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
$node = $this->loadNode($node->id());
$this->assertFalse($node->isSticky(), 'Node is not sticky anymore');
// Promote to front page.
$this->assertFalse($node->isPromoted(), 'Node is not promoted to the front page');
$this->assertFalse($node->getTranslation('en-gb')->isPromoted(), 'Node translation is not promoted to the front page');
$this->assertFalse($node->getTranslation('it')->isPromoted(), 'Node translation is not promoted to the front page');
$edit = [
'node_bulk_form[0]' => TRUE,
'action' => 'node_promote_action',
];
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
$node = $this->loadNode($node->id());
$this->assertTrue($node->isPromoted(), 'Node has been promoted to the front page');
$this->assertFalse($node->getTranslation('en-gb')->isPromoted(), 'Node translation has not been promoted to the front page');
$this->assertFalse($node->getTranslation('it')->isPromoted(), 'Node translation has not been promoted to the front page');
// Demote from front page.
$edit = [
'node_bulk_form[0]' => TRUE,
'action' => 'node_unpromote_action',
];
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
$node = $this->loadNode($node->id());
$this->assertFalse($node->isPromoted(), 'Node has been demoted');
// Select a bunch of translated and untranslated nodes and check that
// operations are always applied to individual translations.
$edit = [
// Original and all translations.
'node_bulk_form[0]' => TRUE, // Node 1, English, original.
'node_bulk_form[1]' => TRUE, // Node 1, British English.
'node_bulk_form[2]' => TRUE, // Node 1, Italian.
// Original and only one translation.
'node_bulk_form[3]' => TRUE, // Node 2, English.
'node_bulk_form[4]' => TRUE, // Node 2, British English, original.
'node_bulk_form[5]' => FALSE, // Node 2, Italian.
// Only a single translation.
'node_bulk_form[6]' => TRUE, // Node 3, English.
'node_bulk_form[7]' => FALSE, // Node 3, Italian, original.
// Only a single untranslated node.
'node_bulk_form[8]' => TRUE, // Node 4, English, untranslated.
'node_bulk_form[9]' => FALSE, // Node 5, British English, untranslated.
'action' => 'node_unpublish_action',
];
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
$node = $this->loadNode(1);
$this->assertFalse($node->getTranslation('en')->isPublished(), '1: English translation has been unpublished');
$this->assertFalse($node->getTranslation('en-gb')->isPublished(), '1: British English translation has been unpublished');
$this->assertFalse($node->getTranslation('it')->isPublished(), '1: Italian translation has been unpublished');
$node = $this->loadNode(2);
$this->assertFalse($node->getTranslation('en')->isPublished(), '2: English translation has been unpublished');
$this->assertFalse($node->getTranslation('en-gb')->isPublished(), '2: British English translation has been unpublished');
$this->assertTrue($node->getTranslation('it')->isPublished(), '2: Italian translation has not been unpublished');
$node = $this->loadNode(3);
$this->assertFalse($node->getTranslation('en')->isPublished(), '3: English translation has been unpublished');
$this->assertTrue($node->getTranslation('it')->isPublished(), '3: Italian translation has not been unpublished');
$node = $this->loadNode(4);
$this->assertFalse($node->isPublished(), '4: Node has been unpublished');
$node = $this->loadNode(5);
$this->assertTrue($node->isPublished(), '5: Node has not been unpublished');
}
/**
* Test multiple deletion.
*/
public function testBulkDeletion() {
// Select a bunch of translated and untranslated nodes and check that
// nodes and individual translations are properly deleted.
$edit = [
// Original and all translations.
'node_bulk_form[0]' => TRUE, // Node 1, English, original.
'node_bulk_form[1]' => TRUE, // Node 1, British English.
'node_bulk_form[2]' => TRUE, // Node 1, Italian.
// Original and only one translation.
'node_bulk_form[3]' => TRUE, // Node 2, English.
'node_bulk_form[4]' => TRUE, // Node 2, British English, original.
'node_bulk_form[5]' => FALSE, // Node 2, Italian.
// Only a single translation.
'node_bulk_form[6]' => TRUE, // Node 3, English.
'node_bulk_form[7]' => FALSE, // Node 3, Italian, original.
// Only a single untranslated node.
'node_bulk_form[8]' => TRUE, // Node 4, English, untranslated.
'node_bulk_form[9]' => FALSE, // Node 5, British English, untranslated.
'action' => 'node_delete_action',
];
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
$label = $this->loadNode(1)->label();
$this->assertText("$label (Original translation) - The following content translations will be deleted:");
$label = $this->loadNode(2)->label();
$this->assertText("$label (Original translation) - The following content translations will be deleted:");
$label = $this->loadNode(3)->getTranslation('en')->label();
$this->assertText($label);
$this->assertNoText("$label (Original translation) - The following content translations will be deleted:");
$label = $this->loadNode(4)->label();
$this->assertText($label);
$this->assertNoText("$label (Original translation) - The following content translations will be deleted:");
$this->drupalPostForm(NULL, [], t('Delete'));
$node = $this->loadNode(1);
$this->assertNull($node, '1: Node has been deleted');
$node = $this->loadNode(2);
$this->assertNull($node, '2: Node has been deleted');
$node = $this->loadNode(3);
$result = count($node->getTranslationLanguages()) && $node->language()->getId() == 'it';
$this->assertTrue($result, '3: English translation has been deleted');
$node = $this->loadNode(4);
$this->assertNull($node, '4: Node has been deleted');
$node = $this->loadNode(5);
$this->assertTrue($node, '5: Node has not been deleted');
$this->assertText('Deleted 8 posts.');
}
/**
* Load the specified node from the storage.
*
* @param int $id
* The node identifier.
*
* @return \Drupal\node\NodeInterface
* The loaded node.
*/
protected function loadNode($id) {
/** @var \Drupal\node\NodeStorage $storage */
$storage = $this->container->get('entity.manager')->getStorage('node');
$storage->resetCache([$id]);
return $storage->load($id);
}
}
@@ -0,0 +1,114 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\node\Entity\NodeType;
/**
* Tests the node_access filter handler.
*
* @group node
* @see \Drupal\node\Plugin\views\filter\Access
*/
class FilterNodeAccessTest extends NodeTestBase {
/**
* An array of users.
*
* @var \Drupal\user\Entity\User[]
*/
protected $users;
/**
* {@inheritdoc}
*/
public static $modules = ['node_access_test'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_filter_node_access'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
node_access_test_add_field(NodeType::load('article'));
node_access_rebuild();
\Drupal::state()->set('node_access_test.private', TRUE);
$num_simple_users = 2;
$this->users = [];
for ($i = 0; $i < $num_simple_users; $i++) {
$this->users[$i] = $this->drupalCreateUser(['access content', 'create article content']);
}
foreach ($this->users as $web_user) {
$this->drupalLogin($web_user);
foreach ([0 => 'Public', 1 => 'Private'] as $is_private => $type) {
$settings = [
'body' => [[
'value' => $type . ' node',
'format' => filter_default_format(),
]],
'title' => t('@private_public Article created by @user', ['@private_public' => $type, '@user' => $web_user->getUsername()]),
'type' => 'article',
'uid' => $web_user->id(),
'private' => (bool) $is_private,
];
$node = $this->drupalCreateNode($settings);
$this->assertEqual($is_private, (int) $node->private->value, 'The private status of the node was properly set in the node_access_test table.');
}
}
}
/**
* Tests the node access filter.
*/
public function testFilterNodeAccess() {
$this->drupalLogin($this->users[0]);
$this->drupalGet('test_filter_node_access');
// Test that the private node of the current user is shown.
$this->assertText('Private Article created by ' . $this->users[0]->getUsername());
// Test that the private node of the other use isn't shown.
$this->assertNoText('Private Article created by ' . $this->users[1]->getUsername());
// Test that both public nodes are shown.
$this->assertText('Public Article created by ' . $this->users[0]->getUsername());
$this->assertText('Public Article created by ' . $this->users[1]->getUsername());
// Switch users and test the other private node is shown.
$this->drupalLogin($this->users[1]);
$this->drupalGet('test_filter_node_access');
// Test that the private node of the current user is shown.
$this->assertText('Private Article created by ' . $this->users[1]->getUsername());
// Test that the private node of the other use isn't shown.
$this->assertNoText('Private Article created by ' . $this->users[0]->getUsername());
// Test that a user with administer nodes permission can't see all nodes.
$administer_nodes_user = $this->drupalCreateUser(['access content', 'administer nodes']);
$this->drupalLogin($administer_nodes_user);
$this->drupalGet('test_filter_node_access');
$this->assertNoText('Private Article created by ' . $this->users[0]->getUsername());
$this->assertNoText('Private Article created by ' . $this->users[1]->getUsername());
$this->assertText('Public Article created by ' . $this->users[0]->getUsername());
$this->assertText('Public Article created by ' . $this->users[1]->getUsername());
// Test that a user with bypass node access can see all nodes.
$bypass_access_user = $this->drupalCreateUser(['access content', 'bypass node access']);
$this->drupalLogin($bypass_access_user);
$this->drupalGet('test_filter_node_access');
$this->assertText('Private Article created by ' . $this->users[0]->getUsername());
$this->assertText('Private Article created by ' . $this->users[1]->getUsername());
$this->assertText('Public Article created by ' . $this->users[0]->getUsername());
$this->assertText('Public Article created by ' . $this->users[1]->getUsername());
}
}
@@ -0,0 +1,54 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\views\Views;
/**
* Tests the node_uid_revision handler.
*
* @group node
*/
class FilterUidRevisionTest extends NodeTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_filter_node_uid_revision'];
/**
* Tests the node_uid_revision filter.
*/
public function testFilter() {
$author = $this->drupalCreateUser();
$no_author = $this->drupalCreateUser();
$expected_result = [];
// Create one node, with the author as the node author.
$node = $this->drupalCreateNode(['uid' => $author->id()]);
$expected_result[] = ['nid' => $node->id()];
// Create one node of which an additional revision author will be the
// author.
$node = $this->drupalCreateNode(['revision_uid' => $no_author->id()]);
$expected_result[] = ['nid' => $node->id()];
$revision = clone $node;
// Force to add a new revision.
$revision->set('vid', NULL);
$revision->set('revision_uid', $author->id());
$revision->save();
// Create one node on which the author has neither authorship of revisions
// or the main node.
$this->drupalCreateNode(['uid' => $no_author->id()]);
$view = Views::getView('test_filter_node_uid_revision');
$view->initHandlers();
$view->filter['uid_revision']->value = [$author->id()];
$this->executeView($view);
$this->assertIdenticalResultset($view, $expected_result, ['nid' => 'nid'], 'Make sure that the view only returns nodes which match either the node or the revision author.');
}
}
@@ -0,0 +1,374 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\Tests\views\Functional\ViewTestBase;
use Drupal\views\Tests\AssertViewsCacheTagsTrait;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
/**
* Tests the default frontpage provided by views.
*
* @group node
*/
class FrontPageTest extends ViewTestBase {
use AssertViewsCacheTagsTrait;
/**
* {@inheritdoc}
*/
protected $dumpHeaders = TRUE;
/**
* The entity storage for nodes.
*
* @var \Drupal\node\NodeStorage
*/
protected $nodeStorage;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'contextual'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->nodeStorage = $this->container->get('entity.manager')
->getStorage('node');
}
/**
* Tests the frontpage.
*/
public function testFrontPage() {
$site_name = $this->randomMachineName();
$this->config('system.site')
->set('name', $site_name)
->save();
$view = Views::getView('frontpage');
// Tests \Drupal\node\Plugin\views\row\RssPluginBase::calculateDependencies().
$expected = [
'config' => [
'core.entity_view_mode.node.rss',
'core.entity_view_mode.node.teaser',
],
'module' => [
'node',
'user',
],
];
$this->assertIdentical($expected, $view->getDependencies());
$view->setDisplay('page_1');
$this->executeView($view);
$view->preview();
$this->assertEqual($view->getTitle(), format_string('Welcome to @site_name', ['@site_name' => $site_name]), 'The welcome title is used for the empty view.');
$view->destroy();
// Create some nodes on the frontpage view. Add more than 10 nodes in order
// to enable paging.
$expected = [];
for ($i = 0; $i < 20; $i++) {
$values = [];
$values['type'] = 'article';
$values['title'] = $this->randomMachineName();
$values['promote'] = TRUE;
$values['status'] = TRUE;
// Test descending sort order.
$values['created'] = REQUEST_TIME - $i;
// Test the sticky order.
if ($i == 5) {
$values['sticky'] = TRUE;
$node = $this->nodeStorage->create($values);
$node->save();
// Put the sticky on at the front.
array_unshift($expected, ['nid' => $node->id()]);
}
else {
$values['sticky'] = FALSE;
$node = $this->nodeStorage->create($values);
$node->save();
array_push($expected, ['nid' => $node->id()]);
}
}
// Create some nodes which aren't on the frontpage, either because they
// aren't promoted or because they aren't published.
$not_expected_nids = [];
$values = [];
$values['type'] = 'article';
$values['title'] = $this->randomMachineName();
$values['status'] = TRUE;
$values['promote'] = FALSE;
$node = $this->nodeStorage->create($values);
$node->save();
$not_expected_nids[] = $node->id();
$values['promote'] = TRUE;
$values['status'] = FALSE;
$values['title'] = $this->randomMachineName();
$node = $this->nodeStorage->create($values);
$node->save();
$not_expected_nids[] = $node->id();
$values['promote'] = TRUE;
$values['sticky'] = TRUE;
$values['status'] = FALSE;
$values['title'] = $this->randomMachineName();
$node = $this->nodeStorage->create($values);
$node->save();
$not_expected_nids[] = $node->id();
$column_map = ['nid' => 'nid'];
$view->setDisplay('page_1');
$this->executeView($view);
$this->assertIdenticalResultset($view, array_slice($expected, 0, 10), $column_map, 'Ensure that the right nodes are displayed on the frontpage.');
$this->assertNotInResultSet($view, $not_expected_nids, 'Ensure no unexpected node is in the result.');
$view->destroy();
$view->setDisplay('page_1');
$view->setCurrentPage(1);
$this->executeView($view);
$this->assertIdenticalResultset($view, array_slice($expected, 10, 10), $column_map, 'Ensure that the right nodes are displayed on second page of the frontpage.');
$this->assertNotInResultSet($view, $not_expected_nids, 'Ensure no unexpected node is in the result.');
$view->destroy();
}
/**
* Verifies that an amount of nids aren't in the result.
*
* @param \Drupal\views\ViewExecutable $view
* An executed View.
* @param array $not_expected_nids
* An array of nids which should not be part of the resultset.
* @param string $message
* (optional) A custom message to display with the assertion.
*/
protected function assertNotInResultSet(ViewExecutable $view, array $not_expected_nids, $message = '') {
$found_nids = array_filter($view->result, function ($row) use ($not_expected_nids) {
return in_array($row->nid, $not_expected_nids);
});
$this->assertFalse($found_nids, $message);
}
/**
* Tests the frontpage when logged in as admin.
*/
public function testAdminFrontPage() {
// When a user with sufficient permissions is logged in, views_ui adds
// contextual links to the homepage view. This verifies there are no errors.
\Drupal::service('module_installer')->install(['views_ui']);
// Log in root user with sufficient permissions.
$this->drupalLogin($this->rootUser);
// Test frontpage view.
$this->drupalGet('node');
$this->assertResponse(200);
// Check that the frontpage view was rendered.
$this->assertPattern('/class=".+view-frontpage/', 'Frontpage view was rendered');
}
/**
* Tests the cache tags when using the "none" cache plugin.
*/
public function testCacheTagsWithCachePluginNone() {
$this->enablePageCaching();
$this->doTestFrontPageViewCacheTags(FALSE);
}
/**
* Tests the cache tags when using the "tag" cache plugin.
*/
public function testCacheTagsWithCachePluginTag() {
$this->enablePageCaching();
$view = Views::getView('frontpage');
$view->setDisplay('page_1');
$view->display_handler->overrideOption('cache', [
'type' => 'tag',
]);
$view->save();
$this->doTestFrontPageViewCacheTags(TRUE);
}
/**
* Tests the cache tags when using the "time" cache plugin.
*/
public function testCacheTagsWithCachePluginTime() {
$this->enablePageCaching();
$view = Views::getView('frontpage');
$view->setDisplay('page_1');
$view->display_handler->overrideOption('cache', [
'type' => 'time',
'options' => [
'results_lifespan' => 3600,
'output_lifespan' => 3600,
],
]);
$view->save();
$this->doTestFrontPageViewCacheTags(TRUE);
}
/**
* Tests the cache tags on the front page.
*
* @param bool $do_assert_views_caches
* Whether to check Views' result & output caches.
*/
protected function doTestFrontPageViewCacheTags($do_assert_views_caches) {
$view = Views::getView('frontpage');
$view->setDisplay('page_1');
$cache_contexts = [
// Cache contexts associated with the view.
'user.node_grants:view',
'languages:' . LanguageInterface::TYPE_INTERFACE,
// Cache contexts associated with the route's access checking.
'user.permissions',
// Default cache contexts of the renderer.
'theme',
'url.query_args',
// Attached feed.
'url.site',
];
$cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts)->getCacheTags();
// Test before there are any nodes.
$empty_node_listing_cache_tags = [
'config:views.view.frontpage',
'node_list',
];
$render_cache_tags = Cache::mergeTags($empty_node_listing_cache_tags, $cache_context_tags);
$render_cache_tags = Cache::mergeTags($render_cache_tags, ['config:system.site']);
$this->assertViewsCacheTags(
$view,
$empty_node_listing_cache_tags,
$do_assert_views_caches,
$render_cache_tags
);
$expected_tags = Cache::mergeTags($empty_node_listing_cache_tags, $cache_context_tags);
$expected_tags = Cache::mergeTags($expected_tags, ['http_response', 'rendered', 'config:user.role.anonymous', 'config:system.site']);
$this->assertPageCacheContextsAndTags(
Url::fromRoute('view.frontpage.page_1'),
$cache_contexts,
$expected_tags
);
// Create some nodes on the frontpage view. Add more than 10 nodes in order
// to enable paging.
$this->drupalCreateContentType(['type' => 'article']);
for ($i = 0; $i < 15; $i++) {
$node = Node::create([
'body' => [
[
'value' => $this->randomMachineName(32),
'format' => filter_default_format(),
]
],
'type' => 'article',
'created' => $i,
'title' => $this->randomMachineName(8),
'nid' => $i + 1,
]);
$node->enforceIsNew(TRUE);
$node->save();
}
$cache_contexts = Cache::mergeContexts($cache_contexts, [
'timezone',
]);
$this->pass('First page');
// First page.
$first_page_result_cache_tags = [
'config:views.view.frontpage',
'node_list',
'node:6',
'node:7',
'node:8',
'node:9',
'node:10',
'node:11',
'node:12',
'node:13',
'node:14',
'node:15',
];
$cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts)->getCacheTags();
$first_page_output_cache_tags = Cache::mergeTags($first_page_result_cache_tags, $cache_context_tags);
$first_page_output_cache_tags = Cache::mergeTags($first_page_output_cache_tags, [
'config:filter.format.plain_text',
'node_view',
'user_view',
'user:0',
]
);
$view->setDisplay('page_1');
$view->setCurrentPage(0);
$this->assertViewsCacheTags(
$view,
$first_page_result_cache_tags,
$do_assert_views_caches,
$first_page_output_cache_tags
);
$this->assertPageCacheContextsAndTags(
Url::fromRoute('view.frontpage.page_1'),
$cache_contexts,
Cache::mergeTags($first_page_output_cache_tags, ['http_response', 'rendered', 'config:user.role.anonymous'])
);
// Second page.
$this->pass('Second page');
$this->assertPageCacheContextsAndTags(Url::fromRoute('view.frontpage.page_1', [], ['query' => ['page' => 1]]), $cache_contexts, [
// The cache tags for the listed nodes.
'node:1',
'node:2',
'node:3',
'node:4',
'node:5',
// The rest.
'config:filter.format.plain_text',
'config:views.view.frontpage',
'node_list',
'node_view',
'user_view',
'user:0',
'http_response',
'rendered',
// FinishResponseSubscriber adds this cache tag to responses that have the
// 'user.permissions' cache context for anonymous users.
'config:user.role.anonymous',
]);
// Let's update a node title on the first page and ensure that the page
// cache entry invalidates.
$node = Node::load(10);
$title = $node->getTitle() . 'a';
$node->setTitle($title);
$node->save();
$this->drupalGet(Url::fromRoute('view.frontpage.page_1'));
$this->assertText($title);
}
}
@@ -0,0 +1,112 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests node field filters with translations.
*
* @group node
*/
class NodeFieldFilterTest extends NodeTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['language'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_field_filters'];
/**
* List of node titles by language.
*
* @var array
*/
public $nodeTitles = [];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
// Create Page content type.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
}
// Add two new languages.
ConfigurableLanguage::createFromLangcode('fr')->save();
ConfigurableLanguage::createFromLangcode('es')->save();
// Set up node titles.
$this->nodeTitles = [
'en' => 'Food in Paris',
'es' => 'Comida en Paris',
'fr' => 'Nouriture en Paris',
];
// Create node with translations.
$node = $this->drupalCreateNode(['title' => $this->nodeTitles['en'], 'langcode' => 'en', 'type' => 'page', 'body' => [['value' => $this->nodeTitles['en']]]]);
foreach (['es', 'fr'] as $langcode) {
$translation = $node->addTranslation($langcode, ['title' => $this->nodeTitles[$langcode]]);
$translation->body->value = $this->nodeTitles[$langcode];
}
$node->save();
}
/**
* Tests body and title filters.
*/
public function testFilters() {
// Test the title filter page, which filters for title contains 'Comida'.
// Should show just the Spanish translation, once.
$this->assertPageCounts('test-title-filter', ['es' => 1, 'fr' => 0, 'en' => 0], 'Comida title filter');
// Test the body filter page, which filters for body contains 'Comida'.
// Should show just the Spanish translation, once.
$this->assertPageCounts('test-body-filter', ['es' => 1, 'fr' => 0, 'en' => 0], 'Comida body filter');
// Test the title Paris filter page, which filters for title contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-title-paris', ['es' => 1, 'fr' => 1, 'en' => 1], 'Paris title filter');
// Test the body Paris filter page, which filters for body contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-body-paris', ['es' => 1, 'fr' => 1, 'en' => 1], 'Paris body filter');
}
/**
* Asserts that the given node translation counts are correct.
*
* @param string $path
* Path of the page to test.
* @param array $counts
* Array whose keys are languages, and values are the number of times
* that translation should be shown on the given page.
* @param string $message
* Message suffix to display.
*/
protected function assertPageCounts($path, $counts, $message) {
// Disable read more links.
entity_get_display('node', 'page', 'teaser')->removeComponent('links')->save();
// Get the text of the page.
$this->drupalGet($path);
$text = $this->getTextContent();
// Check the counts. Note that the title and body are both shown on the
// page, and they are the same. So the title/body string should appear on
// the page twice as many times as the input count.
foreach ($counts as $langcode => $count) {
$this->assertEqual(substr_count($text, $this->nodeTitles[$langcode]), 2 * $count, 'Translation ' . $langcode . ' has count ' . $count . ' with ' . $message);
}
}
}
@@ -0,0 +1,63 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* Tests replacement of Views tokens supplied by the Node module.
*
* @group node
* @see \Drupal\node\Tests\NodeTokenReplaceTest
*/
class NodeFieldTokensTest extends NodeTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_node_tokens'];
/**
* Tests token replacement for Views tokens supplied by the Node module.
*/
public function testViewsTokenReplacement() {
// Create the Article content type with a standard body field.
/* @var $node_type \Drupal\node\NodeTypeInterface */
$node_type = NodeType::create(['type' => 'article', 'name' => 'Article']);
$node_type->save();
node_add_body_field($node_type);
// Create a user and a node.
$account = $this->createUser();
$body = $this->randomMachineName(32);
$summary = $this->randomMachineName(16);
/** @var $node \Drupal\node\NodeInterface */
$node = Node::create([
'type' => 'article',
'tnid' => 0,
'uid' => $account->id(),
'title' => 'Testing Views tokens',
'body' => [['value' => $body, 'summary' => $summary, 'format' => 'plain_text']],
]);
$node->save();
$this->drupalGet('test_node_tokens');
// Body: {{ body }}<br />
$this->assertRaw("Body: <p>$body</p>");
// Raw value: {{ body__value }}<br />
$this->assertRaw("Raw value: $body");
// Raw summary: {{ body__summary }}<br />
$this->assertRaw("Raw summary: $summary");
// Raw format: {{ body__format }}<br />
$this->assertRaw("Raw format: plain_text");
}
}
@@ -0,0 +1,67 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
/**
* Tests the node integration into views.
*
* @group node
*/
class NodeIntegrationTest extends NodeTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_node_view'];
/**
* Tests basic node view with a node type argument.
*/
public function testNodeViewTypeArgument() {
// Create two content types with three nodes each.
$types = [];
$all_nids = [];
for ($i = 0; $i < 2; $i++) {
$type = $this->drupalCreateContentType(['name' => '<em>' . $this->randomMachineName() . '</em>']);
$types[] = $type;
for ($j = 0; $j < 5; $j++) {
// Ensure the right order of the nodes.
$node = $this->drupalCreateNode(['type' => $type->id(), 'created' => REQUEST_TIME - ($i * 5 + $j)]);
$nodes[$type->id()][$node->id()] = $node;
$all_nids[] = $node->id();
}
}
$this->drupalGet('test-node-view');
$this->assertResponse(404);
$this->drupalGet('test-node-view/all');
$this->assertResponse(200);
$this->assertNids($all_nids);
foreach ($types as $type) {
$this->drupalGet("test-node-view/{$type->id()}");
$this->assertEscaped($type->label());
$this->assertNids(array_keys($nodes[$type->id()]));
}
}
/**
* Ensures that a list of nodes appear on the page.
*
* @param array $expected_nids
* An array of node IDs.
*/
protected function assertNids(array $expected_nids = []) {
$result = $this->xpath('//span[@class="field-content"]');
$nids = [];
foreach ($result as $element) {
$nids[] = (int) $element->getText();
}
$this->assertEqual($nids, $expected_nids);
}
}
@@ -0,0 +1,295 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\views\Plugin\views\PluginBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Views;
/**
* Tests node language fields, filters, and sorting.
*
* @group node
*/
class NodeLanguageTest extends NodeTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['language', 'node_test_views'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_language'];
/**
* List of node titles by language.
*
* @var array
*/
public $nodeTitles = [];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp(FALSE);
// Create Page content type.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
ViewTestData::createTestViews(get_class($this), ['node_test_views']);
}
// Add two new languages.
ConfigurableLanguage::createFromLangcode('fr')->save();
ConfigurableLanguage::createFromLangcode('es')->save();
// Make the body field translatable. The title is already translatable by
// definition.
$field_storage = FieldStorageConfig::loadByName('node', 'body');
$field_storage->setTranslatable(TRUE);
$field_storage->save();
// Set up node titles. They should not include the words "French",
// "English", or "Spanish", as there is a language field in the view
// that prints out those words.
$this->nodeTitles = [
LanguageInterface::LANGCODE_NOT_SPECIFIED => [
'First node und',
],
'es' => [
'Primero nodo es',
'Segundo nodo es',
'Tercera nodo es',
],
'en' => [
'First node en',
'Second node en',
],
'fr' => [
'Premier nœud fr',
]
];
// Create nodes with translations.
foreach ($this->nodeTitles['es'] as $index => $title) {
$node = $this->drupalCreateNode(['title' => $title, 'langcode' => 'es', 'type' => 'page', 'promote' => 1]);
foreach (['en', 'fr'] as $langcode) {
if (isset($this->nodeTitles[$langcode][$index])) {
$translation = $node->addTranslation($langcode, ['title' => $this->nodeTitles[$langcode][$index]]);
$translation->body->value = $this->randomMachineName(32);
}
}
$node->save();
}
// Create non-translatable nodes.
foreach ($this->nodeTitles[LanguageInterface::LANGCODE_NOT_SPECIFIED] as $index => $title) {
$node = $this->drupalCreateNode(['title' => $title, 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, 'type' => 'page', 'promote' => 1]);
$node->body->value = $this->randomMachineName(32);
$node->save();
}
$this->container->get('router.builder')->rebuild();
$user = $this->drupalCreateUser(['access content overview', 'access content']);
$this->drupalLogin($user);
}
/**
* Tests translation language filter, field, and sort.
*/
public function testLanguages() {
// Test the page with no arguments. It is filtered to Spanish and French.
// The page shows node titles and languages.
$this->drupalGet('test-language');
$message = 'French/Spanish page';
// Test that the correct nodes are shown.
foreach ($this->nodeTitles as $langcode => $list) {
foreach ($list as $title) {
if ($langcode == 'en') {
$this->assertNoText($title, $title . ' does not appear on ' . $message);
}
else {
$this->assertText($title, $title . ' does appear on ' . $message);
}
}
}
// Test that the language field value is shown.
$this->assertNoText('English', 'English language is not shown on ' . $message);
$this->assertText('French', 'French language is shown on ' . $message);
$this->assertText('Spanish', 'Spanish language is shown on ' . $message);
// Test page sorting, which is by language code, ascending. So the
// Spanish nodes should appear before the French nodes.
$page = $this->getTextContent();
$pos_es_max = 0;
$pos_fr_min = 10000;
foreach ($this->nodeTitles['es'] as $title) {
$pos_es_max = max($pos_es_max, strpos($page, $title));
}
foreach ($this->nodeTitles['fr'] as $title) {
$pos_fr_min = min($pos_fr_min, strpos($page, $title));
}
$this->assertTrue($pos_es_max < $pos_fr_min, 'Spanish translations appear before French on ' . $message);
// Test the argument -- filter to just Spanish.
$this->drupalGet('test-language/es');
// This time, test just the language field.
$message = 'Spanish argument page';
$this->assertNoText('English', 'English language is not shown on ' . $message);
$this->assertNoText('French', 'French language is not shown on ' . $message);
$this->assertText('Spanish', 'Spanish language is shown on ' . $message);
// Test the front page view filter. Only node titles in the current language
// should be displayed on the front page by default.
foreach ($this->nodeTitles as $langcode => $titles) {
// The frontpage view does not display content without a language.
if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
continue;
}
$this->drupalGet(($langcode == 'en' ? '' : "$langcode/") . 'node');
foreach ($titles as $title) {
$this->assertText($title);
}
foreach ($this->nodeTitles as $control_langcode => $control_titles) {
if ($langcode != $control_langcode) {
foreach ($control_titles as $title) {
$this->assertNoText($title);
}
}
}
}
// Test the node admin view filter. By default all translations should show.
$this->drupalGet('admin/content');
foreach ($this->nodeTitles as $titles) {
foreach ($titles as $title) {
$this->assertText($title);
}
}
// When filtered, only the specific languages should show.
foreach ($this->nodeTitles as $langcode => $titles) {
$this->drupalGet('admin/content', ['query' => ['langcode' => $langcode]]);
foreach ($titles as $title) {
$this->assertText($title);
}
foreach ($this->nodeTitles as $control_langcode => $control_titles) {
if ($langcode != $control_langcode) {
foreach ($control_titles as $title) {
$this->assertNoText($title);
}
}
}
}
// Override the config for the front page view, so that the language
// filter is set to the site default language instead. This should just
// show the English nodes, no matter what the content language is.
$config = $this->config('views.view.frontpage');
$config->set('display.default.display_options.filters.langcode.value', [PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT => PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT]);
$config->save();
foreach ($this->nodeTitles as $langcode => $titles) {
if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
continue;
}
$this->drupalGet(($langcode == 'en' ? '' : "$langcode/") . 'node');
foreach ($this->nodeTitles as $control_langcode => $control_titles) {
foreach ($control_titles as $title) {
if ($control_langcode == 'en') {
$this->assertText($title, 'English title is shown when filtering is site default');
}
else {
$this->assertNoText($title, 'Non-English title is not shown when filtering is site default');
}
}
}
}
// Override the config so that the language filter is set to the UI
// language, and make that have a fixed value of 'es'.
//
// IMPORTANT: Make sure this part of the test is last -- it is changing
// language configuration!
$config->set('display.default.display_options.filters.langcode.value', ['***LANGUAGE_language_interface***' => '***LANGUAGE_language_interface***']);
$config->save();
$language_config = $this->config('language.types');
$language_config->set('negotiation.language_interface.enabled', ['language-selected' => 1]);
$language_config->save();
$language_config = $this->config('language.negotiation');
$language_config->set('selected_langcode', 'es');
$language_config->save();
// With a fixed language selected, there is no language-based URL.
$this->drupalGet('node');
foreach ($this->nodeTitles as $control_langcode => $control_titles) {
foreach ($control_titles as $title) {
if ($control_langcode == 'es') {
$this->assertText($title, 'Spanish title is shown when filtering is fixed UI language');
}
else {
$this->assertNoText($title, 'Non-Spanish title is not shown when filtering is fixed UI language');
}
}
}
}
/**
* Tests native name display in language field.
*/
public function testNativeLanguageField() {
$this->assertLanguageNames();
// Modify test view to display native language names and set translations.
$config = $this->config('views.view.test_language');
$config->set('display.default.display_options.fields.langcode.settings.native_language', TRUE);
$config->save();
\Drupal::languageManager()->getLanguageConfigOverride('fr', 'language.entity.fr')->set('label', 'Français')->save();
\Drupal::languageManager()->getLanguageConfigOverride('es', 'language.entity.es')->set('label', 'Español')->save();
$this->assertLanguageNames(TRUE);
// Modify test view to use the views built-in language field and test that.
\Drupal::state()->set('node_test_views.use_basic_handler', TRUE);
Views::viewsData()->clear();
$config = $this->config('views.view.test_language');
$config->set('display.default.display_options.fields.langcode.native_language', FALSE);
$config->clear('display.default.display_options.fields.langcode.settings');
$config->clear('display.default.display_options.fields.langcode.type');
$config->set('display.default.display_options.fields.langcode.plugin_id', 'language');
$config->save();
$this->assertLanguageNames();
$config->set('display.default.display_options.fields.langcode.native_language', TRUE)->save();
$this->assertLanguageNames(TRUE);
}
/**
* Asserts the presence of language names in their English or native forms.
*
* @param bool $native
* (optional) Whether to assert the language name in its native form.
*/
protected function assertLanguageNames($native = FALSE) {
$this->drupalGet('test-language');
if ($native) {
$this->assertText('Français', 'French language shown in native form.');
$this->assertText('Español', 'Spanish language shown in native form.');
$this->assertNoText('French', 'French language not shown in English.');
$this->assertNoText('Spanish', 'Spanish language not shown in English.');
}
else {
$this->assertNoText('Français', 'French language not shown in native form.');
$this->assertNoText('Español', 'Spanish language not shown in native form.');
$this->assertText('French', 'French language shown in English.');
$this->assertText('Spanish', 'Spanish language shown in English.');
}
}
}
@@ -0,0 +1,61 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\Tests\views\Functional\Wizard\WizardTestBase;
use Drupal\views\Views;
/**
* Tests the wizard with node_revision as base table.
*
* @group node
* @see \Drupal\node\Plugin\views\wizard\NodeRevision
*/
class NodeRevisionWizardTest extends WizardTestBase {
/**
* Tests creating a node revision view.
*/
public function testViewAdd() {
$this->drupalCreateContentType(['type' => 'article']);
// Create two nodes with two revision.
$node_storage = \Drupal::entityManager()->getStorage('node');
/** @var \Drupal\node\NodeInterface $node */
$node = $node_storage->create(['title' => $this->randomString(), 'type' => 'article', 'created' => REQUEST_TIME + 40]);
$node->save();
$node = $node->createDuplicate();
$node->setNewRevision();
$node->created->value = REQUEST_TIME + 20;
$node->save();
$node = $node_storage->create(['title' => $this->randomString(), 'type' => 'article', 'created' => REQUEST_TIME + 30]);
$node->save();
$node = $node->createDuplicate();
$node->setNewRevision();
$node->created->value = REQUEST_TIME + 10;
$node->save();
$view = [];
$view['label'] = $this->randomMachineName(16);
$view['id'] = strtolower($this->randomMachineName(16));
$view['description'] = $this->randomMachineName(16);
$view['page[create]'] = FALSE;
$view['show[wizard_key]'] = 'node_revision';
$this->drupalPostForm('admin/structure/views/add', $view, t('Save and edit'));
$view_storage_controller = \Drupal::entityManager()->getStorage('view');
/** @var \Drupal\views\Entity\View $view */
$view = $view_storage_controller->load($view['id']);
$this->assertEqual($view->get('base_table'), 'node_field_revision');
$executable = Views::executableFactory()->get($view);
$this->executeView($executable);
$this->assertIdenticalResultset($executable, [['vid' => 1], ['vid' => 3], ['vid' => 2], ['vid' => 4]],
['vid' => 'vid']);
}
}
@@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\Tests\views\Functional\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* Base class for all node Views tests.
*/
abstract class NodeTestBase extends ViewTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node_test_views'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
if ($import_test_views) {
ViewTestData::createTestViews(get_class($this), ['node_test_views']);
}
}
}
@@ -0,0 +1,80 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\views\Views;
/**
* Tests the node row plugin.
*
* @group node
*/
class PathPluginTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_node_path_plugin'];
/**
* Contains all nodes used by this test.
*
* @var Node[]
*/
protected $nodes;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->drupalCreateContentType(['type' => 'article']);
// Create two nodes.
for ($i = 0; $i < 2; $i++) {
$this->nodes[] = $this->drupalCreateNode(
[
'type' => 'article',
'body' => [
[
'value' => $this->randomMachineName(42),
'format' => filter_default_format(),
'summary' => $this->randomMachineName(),
],
],
]
);
}
}
/**
* Tests the node path plugin.
*/
public function testPathPlugin() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$view = Views::getView('test_node_path_plugin');
$view->initDisplay();
$view->setDisplay('page_1');
$view->initStyle();
$view->rowPlugin->options['view_mode'] = 'full';
// Test with view_mode full.
$output = $view->preview();
$output = $renderer->renderRoot($output);
foreach ($this->nodes as $node) {
$this->assertTrue(strpos($output, 'This is <strong>not escaped</strong> and this is ' . $node->link('the link')) !== FALSE, 'Make sure path field rewriting is not escaped.');
}
}
}
@@ -0,0 +1,89 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
/**
* Tests the different revision link handlers.
*
* @group node
*
* @see \Drupal\node\Plugin\views\field\RevisionLink
* @see \Drupal\node\Plugin\views\field\RevisionLinkDelete
* @see \Drupal\node\Plugin\views\field\RevisionLinkRevert
*/
class RevisionLinkTest extends NodeTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_node_revision_links'];
/**
* Tests revision links.
*/
public function testRevisionLinks() {
// Create one user which can view/revert and delete and one which can only
// do one of them.
$this->drupalCreateContentType(['name' => 'page', 'type' => 'page']);
$account = $this->drupalCreateUser(['revert all revisions', 'view all revisions', 'delete all revisions', 'edit any page content', 'delete any page content']);
$this->drupalLogin($account);
// Create two nodes, one without an additional revision and one with a
// revision.
$nodes = [
$this->drupalCreateNode(),
$this->drupalCreateNode(),
];
$first_revision = $nodes[1]->getRevisionId();
// Create revision of the node.
$nodes[1]->setNewRevision();
$nodes[1]->save();
$second_revision = $nodes[1]->getRevisionId();
$this->drupalGet('test-node-revision-links');
$this->assertResponse(200, 'Test view can be accessed in the path expected');
// The first node revision should link to the node directly as you get an
// access denied if you link to the revision.
$url = $nodes[0]->urlInfo()->toString();
$this->assertLinkByHref($url);
$this->assertNoLinkByHref($url . '/revisions/' . $nodes[0]->getRevisionId() . '/view');
$this->assertNoLinkByHref($url . '/revisions/' . $nodes[0]->getRevisionId() . '/delete');
$this->assertNoLinkByHref($url . '/revisions/' . $nodes[0]->getRevisionId() . '/revert');
// For the second node the current revision got set to the last revision, so
// the first one should also link to the node page itself.
$url = $nodes[1]->urlInfo()->toString();
$this->assertLinkByHref($url);
$this->assertLinkByHref($url . '/revisions/' . $first_revision . '/view');
$this->assertLinkByHref($url . '/revisions/' . $first_revision . '/delete');
$this->assertLinkByHref($url . '/revisions/' . $first_revision . '/revert');
$this->assertNoLinkByHref($url . '/revisions/' . $second_revision . '/view');
$this->assertNoLinkByHref($url . '/revisions/' . $second_revision . '/delete');
$this->assertNoLinkByHref($url . '/revisions/' . $second_revision . '/revert');
$accounts = [
'view' => $this->drupalCreateUser(['view all revisions']),
'revert' => $this->drupalCreateUser(['revert all revisions', 'edit any page content']),
'delete' => $this->drupalCreateUser(['delete all revisions', 'delete any page content']),
];
$url = $nodes[1]->urlInfo()->toString();
// Render the view with users which can only delete/revert revisions.
foreach ($accounts as $allowed_operation => $account) {
$this->drupalLogin($account);
$this->drupalGet('test-node-revision-links');
// Check expected links.
foreach (['revert', 'delete'] as $operation) {
if ($operation == $allowed_operation) {
$this->assertLinkByHref($url . '/revisions/' . $first_revision . '/' . $operation);
}
else {
$this->assertNoLinkByHref($url . '/revisions/' . $first_revision . '/' . $operation);
}
}
}
}
}
@@ -0,0 +1,91 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\views\Views;
/**
* Tests the node row plugin.
*
* @group node
* @see \Drupal\node\Plugin\views\row\NodeRow
*/
class RowPluginTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_node_row_plugin'];
/**
* Contains all nodes used by this test.
*
* @var array
*/
protected $nodes;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->drupalCreateContentType(['type' => 'article']);
// Create two nodes.
for ($i = 0; $i < 2; $i++) {
$this->nodes[] = $this->drupalCreateNode(
[
'type' => 'article',
'body' => [
[
'value' => $this->randomMachineName(42),
'format' => filter_default_format(),
'summary' => $this->randomMachineName(),
],
],
]
);
}
}
/**
* Tests the node row plugin.
*/
public function testRowPlugin() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$view = Views::getView('test_node_row_plugin');
$view->initDisplay();
$view->setDisplay('page_1');
$view->initStyle();
$view->rowPlugin->options['view_mode'] = 'full';
// Test with view_mode full.
$output = $view->preview();
$output = $renderer->renderRoot($output);
foreach ($this->nodes as $node) {
$this->assertFalse(strpos($output, $node->body->summary) !== FALSE, 'Make sure the teaser appears in the output of the view.');
$this->assertTrue(strpos($output, $node->body->value) !== FALSE, 'Make sure the full text appears in the output of the view.');
}
// Test with teasers.
$view->rowPlugin->options['view_mode'] = 'teaser';
$output = $view->preview();
$output = $renderer->renderRoot($output);
foreach ($this->nodes as $node) {
$this->assertTrue(strpos($output, $node->body->summary) !== FALSE, 'Make sure the teaser appears in the output of the view.');
$this->assertFalse(strpos($output, $node->body->value) !== FALSE, 'Make sure the full text does not appears in the output of the view if teaser is set as viewmode.');
}
}
}
@@ -0,0 +1,76 @@
<?php
namespace Drupal\Tests\node\Functional\Views;
use Drupal\node\NodeInterface;
/**
* Tests the node.status_extra field handler.
*
* @group node
* @see \Drupal\node\Plugin\views\filter\Status
*/
class StatusExtraTest extends NodeTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_status_extra'];
/**
* Tests the status extra filter.
*/
public function testStatusExtra() {
$node_author = $this->drupalCreateUser(['view own unpublished content']);
$node_author_not_unpublished = $this->drupalCreateUser();
$normal_user = $this->drupalCreateUser();
$admin_user = $this->drupalCreateUser(['bypass node access']);
// Create one published and one unpublished node by the admin.
$node_published = $this->drupalCreateNode(['uid' => $admin_user->id()]);
$node_unpublished = $this->drupalCreateNode(['uid' => $admin_user->id(), 'status' => NodeInterface::NOT_PUBLISHED]);
// Create one unpublished node by a certain author user.
$node_unpublished2 = $this->drupalCreateNode(['uid' => $node_author->id(), 'status' => NodeInterface::NOT_PUBLISHED]);
// Create one unpublished node by a user who does not have the `view own
// unpublished content` permission.
$node_unpublished3 = $this->drupalCreateNode(['uid' => $node_author_not_unpublished->id(), 'status' => NodeInterface::NOT_PUBLISHED]);
// The administrator should simply see all nodes.
$this->drupalLogin($admin_user);
$this->drupalGet('test_status_extra');
$this->assertText($node_published->label());
$this->assertText($node_unpublished->label());
$this->assertText($node_unpublished2->label());
$this->assertText($node_unpublished3->label());
// The node author should see the published node and his own node.
$this->drupalLogin($node_author);
$this->drupalGet('test_status_extra');
$this->assertText($node_published->label());
$this->assertNoText($node_unpublished->label());
$this->assertText($node_unpublished2->label());
$this->assertNoText($node_unpublished3->label());
// The normal user should just see the published node.
$this->drupalLogin($normal_user);
$this->drupalGet('test_status_extra');
$this->assertText($node_published->label());
$this->assertNoText($node_unpublished->label());
$this->assertNoText($node_unpublished2->label());
$this->assertNoText($node_unpublished3->label());
// The author without the permission to see his own unpublished node should
// just see the published node.
$this->drupalLogin($node_author_not_unpublished);
$this->drupalGet('test_status_extra');
$this->assertText($node_published->label());
$this->assertNoText($node_unpublished->label());
$this->assertNoText($node_unpublished2->label());
$this->assertNoText($node_unpublished3->label());
}
}
@@ -26,7 +26,7 @@ class NodeImportChangeTest extends KernelTestBase {
parent::setUp();
// Set default storage backend.
$this->installConfig(array('field', 'node_test_config'));
$this->installConfig(['field', 'node_test_config']);
}
/**
@@ -18,7 +18,7 @@ class NodeImportCreateTest extends KernelTestBase {
*
* @var array
*/
public static $modules = array('node', 'field', 'text', 'system', 'user');
public static $modules = ['node', 'field', 'text', 'system', 'user'];
/**
* Set the default field storage backend for fields created during tests.
@@ -28,7 +28,7 @@ class NodeImportCreateTest extends KernelTestBase {
$this->installEntitySchema('user');
// Set default storage backend.
$this->installConfig(array('field'));
$this->installConfig(['field']);
}
/**
@@ -42,7 +42,7 @@ class NodeImportCreateTest extends KernelTestBase {
// Enable node_test_config module and check that the content type
// shipped in the module's default config is created.
$this->container->get('module_installer')->install(array('node_test_config'));
$this->container->get('module_installer')->install(['node_test_config']);
$node_type = NodeType::load($node_type_id);
$this->assertTrue($node_type, 'The default content type was created.');
}
@@ -13,6 +13,11 @@ use Drupal\node\Entity\Node;
*/
class MigrateNodeBundleSettingsTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['menu_ui'];
/**
* {@inheritdoc}
*/
@@ -2,7 +2,7 @@
namespace Drupal\Tests\node\Kernel\Migrate\d6;
use Drupal\config\Tests\SchemaCheckTestTrait;
use Drupal\Tests\SchemaCheckTestTrait;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
@@ -10,7 +10,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
*/
class MigrateNodeSettingPromoteTest extends MigrateDrupal6TestBase {
public static $modules = ['node', 'text'];
public static $modules = ['node', 'text', 'menu_ui'];
/**
* {@inheritdoc}
@@ -0,0 +1,32 @@
<?php
namespace Drupal\Tests\node\Kernel\Migrate\d6;
use Drupal\Core\Field\Entity\BaseFieldOverride;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* @group migrate_drupal_6
*/
class MigrateNodeSettingStatusTest extends MigrateDrupal6TestBase {
public static $modules = ['node', 'text', 'menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(['node']);
$this->executeMigration('d6_node_type');
$this->executeMigration('d6_node_setting_status');
}
/**
* Tests migration of the publishing status checkbox's settings.
*/
public function testMigration() {
$this->assertIdentical('Publishing status', BaseFieldOverride::load('node.article.status')->label());
}
}
@@ -10,7 +10,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
*/
class MigrateNodeSettingStickyTest extends MigrateDrupal6TestBase {
public static $modules = ['node', 'text'];
public static $modules = ['node', 'text', 'menu_ui'];
/**
* {@inheritdoc}
@@ -19,7 +19,7 @@ class MigrateNodeTest extends MigrateNodeTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['language', 'content_translation'];
public static $modules = ['language', 'content_translation', 'menu_ui'];
/**
* {@inheritdoc}
@@ -28,7 +28,12 @@ class MigrateNodeTest extends MigrateNodeTestBase {
parent::setUp();
$this->setUpMigratedFiles();
$this->installSchema('file', ['file_usage']);
$this->executeMigrations(['language', 'd6_node', 'd6_node_translation']);
$this->executeMigrations([
'language',
'd6_language_content_settings',
'd6_node',
'd6_node_translation',
]);
}
/**
@@ -51,7 +56,9 @@ class MigrateNodeTest extends MigrateNodeTestBase {
/** @var \Drupal\node\NodeInterface $node_revision */
$node_revision = \Drupal::entityManager()->getStorage('node')->loadRevision(1);
$this->assertIdentical('Test title', $node_revision->getTitle());
$this->assertIdentical('1', $node_revision->getRevisionAuthor()->id(), 'Node revision has the correct user');
$this->assertIdentical('1', $node_revision->getRevisionUser()->id(), 'Node revision has the correct user');
$this->assertSame('1', $node_revision->id(), 'Node 1 loaded.');
$this->assertSame('1', $node_revision->getRevisionId(), 'Node 1 revision 1 loaded.');
// This is empty on the first revision.
$this->assertIdentical(NULL, $node_revision->revision_log->value);
$this->assertIdentical('This is a shared text field', $node->field_test->value);
@@ -65,6 +72,11 @@ class MigrateNodeTest extends MigrateNodeTestBase {
$this->assertIdentical('1', $node->field_test_identical2->value, 'Integer value is correct');
$this->assertIdentical('This is a field with exclude unset.', $node->field_test_exclude_unset->value, 'Field with exclude unset is correct.');
// Test that date fields are migrated.
$this->assertSame('2013-01-02T04:05:00', $node->field_test_date->value, 'Date field is correct');
$this->assertSame('1391357160', $node->field_test_datestamp->value, 'Datestamp field is correct');
$this->assertSame('2015-03-04T06:07:00', $node->field_test_datetime->value, 'Datetime field is correct');
// Test that link fields are migrated.
$this->assertIdentical('https://www.drupal.org/project/drupal', $node->field_test_link->uri);
$this->assertIdentical('Drupal project page', $node->field_test_link->title);
@@ -72,7 +84,20 @@ class MigrateNodeTest extends MigrateNodeTestBase {
// Test the file field meta.
$this->assertIdentical('desc', $node->field_test_filefield->description);
$this->assertIdentical('5', $node->field_test_filefield->target_id);
$this->assertIdentical('4', $node->field_test_filefield->target_id);
// Test that an email field is migrated.
$this->assertSame('PrincessRuwenne@example.com', $node->field_test_email->value);
// Test that node reference field values were migrated.
$node = Node::load(18);
$this->assertCount(2, $node->field_company);
$this->assertSame('Klingon Empire', $node->field_company[0]->entity->label());
$this->assertSame('Romulan Empire', $node->field_company[1]->entity->label());
// Test that user reference field values were migrated.
$this->assertCount(1, $node->field_commander);
$this->assertSame('joe.roe', $node->field_commander[0]->entity->getUsername());
$node = Node::load(2);
$this->assertIdentical('Test title rev 3', $node->getTitle());
@@ -84,6 +109,20 @@ class MigrateNodeTest extends MigrateNodeTestBase {
$this->assertIdentical('Drupal Groups', $node->field_test_link->title);
$this->assertIdentical([], $node->field_test_link->options['attributes']);
$node = Node::load(3);
// Test multivalue field.
$value_1 = $node->field_multivalue->value;
$value_2 = $node->field_multivalue[1]->value;
// SQLite does not support scales for float data types so we need to convert
// the value manually.
if ($this->container->get('database')->driver() == 'sqlite') {
$value_1 = sprintf('%01.2f', $value_1);
$value_2 = sprintf('%01.2f', $value_2);
}
$this->assertSame('33.00', $value_1);
$this->assertSame('44.00', $value_2);
// Test that a link field with an internal link is migrated.
$node = Node::load(9);
$this->assertSame('internal:/node/10', $node->field_test_link->uri);
@@ -96,6 +135,14 @@ class MigrateNodeTest extends MigrateNodeTestBase {
$this->assertIdentical('The Real McCoy', $node->title->value);
$this->assertTrue($node->hasTranslation('fr'), "Node 10 has french translation");
// Test that content_translation_source is set.
$manager = $this->container->get('content_translation.manager');
$this->assertIdentical('en', $manager->getTranslationMetadata($node->getTranslation('fr'))->getSource());
// Test that content_translation_source for a source other than English.
$node = Node::load(12);
$this->assertIdentical('zu', $manager->getTranslationMetadata($node->getTranslation('en'))->getSource());
// Node 11 is a translation of node 10, and should not be imported separately.
$this->assertNull(Node::load(11), "Node 11 doesn't exist in D8, it was a translation");
@@ -143,11 +190,11 @@ class MigrateNodeTest extends MigrateNodeTestBase {
$this->assertIdentical('full_html', $node->body->format);
// Now insert a row indicating a failure and set to update later.
$title = $this->rerunMigration(array(
$title = $this->rerunMigration([
'sourceid1' => 2,
'destid1' => NULL,
'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
));
]);
$node = Node::load(2);
$this->assertIdentical($title, $node->getTitle());
}
@@ -165,10 +212,10 @@ class MigrateNodeTest extends MigrateNodeTestBase {
$title = $this->randomString();
$source_connection = Database::getConnection('default', 'migrate');
$source_connection->update('node_revisions')
->fields(array(
->fields([
'title' => $title,
'format' => 2,
))
])
->condition('vid', 3)
->execute();
$migration = $this->getMigration('d6_node:story');
@@ -13,6 +13,11 @@ use Drupal\node\Entity\NodeType;
*/
class MigrateNodeTypeTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['menu_ui'];
/**
* {@inheritdoc}
*/
@@ -33,12 +38,18 @@ class MigrateNodeTypeTest extends MigrateDrupal6TestBase {
$this->assertIdentical(TRUE, $node_type_page->displaySubmitted());
$this->assertIdentical(FALSE, $node_type_page->isNewRevision());
$this->assertIdentical(DRUPAL_OPTIONAL, $node_type_page->getPreviewMode());
$this->assertIdentical($id_map->lookupDestinationID(array('test_page')), array('test_page'));
$this->assertIdentical($id_map->lookupDestinationID(['test_page']), ['test_page']);
// Test we have a body field.
$field = FieldConfig::loadByName('node', 'test_page', 'body');
$this->assertIdentical('This is the body field label', $field->getLabel(), 'Body field was found.');
// Test default menus.
$expected_available_menus = ['navigation'];
$this->assertSame($expected_available_menus, $node_type_page->getThirdPartySetting('menu_ui', 'available_menus'));
$expected_parent = 'navigation:';
$this->assertSame($expected_parent, $node_type_page->getThirdPartySetting('menu_ui', 'parent'));
// Test the test_story content type.
$node_type_story = NodeType::load('test_story');
$this->assertIdentical('test_story', $node_type_story->id(), 'Node type test_story loaded');
@@ -46,12 +57,18 @@ class MigrateNodeTypeTest extends MigrateDrupal6TestBase {
$this->assertIdentical(TRUE, $node_type_story->displaySubmitted());
$this->assertIdentical(FALSE, $node_type_story->isNewRevision());
$this->assertIdentical(DRUPAL_OPTIONAL, $node_type_story->getPreviewMode());
$this->assertIdentical($id_map->lookupDestinationID(array('test_story')), array('test_story'));
$this->assertIdentical($id_map->lookupDestinationID(['test_story']), ['test_story']);
// Test we don't have a body field.
$field = FieldConfig::loadByName('node', 'test_story', 'body');
$this->assertIdentical(NULL, $field, 'No body field found');
// Test default menus.
$expected_available_menus = ['navigation'];
$this->assertSame($expected_available_menus, $node_type_story->getThirdPartySetting('menu_ui', 'available_menus'));
$expected_parent = 'navigation:';
$this->assertSame($expected_parent, $node_type_story->getThirdPartySetting('menu_ui', 'parent'));
// Test the test_event content type.
$node_type_event = NodeType::load('test_event');
$this->assertIdentical('test_event', $node_type_event->id(), 'Node type test_event loaded');
@@ -59,11 +76,16 @@ class MigrateNodeTypeTest extends MigrateDrupal6TestBase {
$this->assertIdentical(TRUE, $node_type_event->displaySubmitted());
$this->assertIdentical(TRUE, $node_type_event->isNewRevision());
$this->assertIdentical(DRUPAL_OPTIONAL, $node_type_event->getPreviewMode());
$this->assertIdentical($id_map->lookupDestinationID(array('test_event')), array('test_event'));
$this->assertIdentical($id_map->lookupDestinationID(['test_event']), ['test_event']);
// Test we have a body field.
$field = FieldConfig::loadByName('node', 'test_event', 'body');
$this->assertIdentical('Body', $field->getLabel(), 'Body field was found.');
$expected_available_menus = ['navigation'];
$this->assertSame($expected_available_menus, $node_type_event->getThirdPartySetting('menu_ui', 'available_menus'));
$expected_parent = 'navigation:';
$this->assertSame($expected_parent, $node_type_event->getThirdPartySetting('menu_ui', 'parent'));
}
}
@@ -29,7 +29,7 @@ class MigrateViewModesTest extends MigrateDrupal6TestBase {
$this->assertIdentical(FALSE, is_null($view_mode), 'Preview view mode loaded.');
$this->assertIdentical('Preview', $view_mode->label(), 'View mode has correct label.');
// Test the ID map.
$this->assertIdentical(array('node', 'preview'), $this->getMigration('d6_view_modes')->getIdMap()->lookupDestinationID(array(1)));
$this->assertIdentical(['node', 'preview'], $this->getMigration('d6_view_modes')->getIdMap()->lookupDestinationID([1]));
}
}
@@ -0,0 +1,51 @@
<?php
namespace Drupal\Tests\node\Kernel\Migrate\d7;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Test D7NodeDeriver.
*
* @group migrate_drupal_7
*/
class MigrateNodeDeriverTest extends MigrateDrupal7TestBase {
/**
* The migration plugin manager.
*
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
*/
protected $pluginManager;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->pluginManager = $this->container->get('plugin.manager.migration');
}
/**
* Test node translation migrations with translation disabled.
*/
public function testNoTranslations() {
// Without content_translation, there should be no translation migrations.
$migrations = $this->pluginManager->createInstances('d7_node_translation');
$this->assertSame([], $migrations,
"No node translation migrations without content_translation");
}
/**
* Test node translation migrations with translation enabled.
*/
public function testTranslations() {
// With content_translation, there should be translation migrations for
// each content type.
$this->enableModules(['language', 'content_translation', 'node', 'filter']);
$migrations = $this->pluginManager->createInstances('d7_node_translation');
$this->assertArrayHasKey('d7_node_translation:article', $migrations,
"Node translation migrations exist after content_translation installed");
}
}
@@ -2,7 +2,7 @@
namespace Drupal\Tests\node\Kernel\Migrate\d7;
use Drupal\config\Tests\SchemaCheckTestTrait;
use Drupal\Tests\SchemaCheckTestTrait;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
@@ -13,17 +13,23 @@ use Drupal\node\NodeInterface;
*/
class MigrateNodeTest extends MigrateDrupal7TestBase {
public static $modules = array(
/**
* {@inheritdoc}
*/
public static $modules = [
'content_translation',
'comment',
'datetime',
'filter',
'image',
'language',
'link',
'menu_ui',
'node',
'taxonomy',
'telephone',
'text',
);
];
/**
* {@inheritdoc}
@@ -40,15 +46,17 @@ class MigrateNodeTest extends MigrateDrupal7TestBase {
$this->installSchema('system', ['sequences']);
$this->executeMigrations([
'language',
'd7_user_role',
'd7_user',
'd7_node_type',
'd7_language_content_settings',
'd7_comment_type',
'd7_taxonomy_vocabulary',
'd7_field',
'd7_field_instance',
'd7_node:test_content_type',
'd7_node:article',
'd7_node',
'd7_node_translation',
]);
}
@@ -111,7 +119,7 @@ class MigrateNodeTest extends MigrateDrupal7TestBase {
$revision = \Drupal::entityManager()->getStorage('node')->loadRevision($id);
$this->assertTrue($revision instanceof NodeInterface);
$this->assertIdentical($title, $revision->getTitle());
$this->assertIdentical($uid, $revision->getRevisionAuthor()->id());
$this->assertIdentical($uid, $revision->getRevisionUser()->id());
$this->assertIdentical($log, $revision->revision_log->value);
$this->assertIdentical($timestamp, $revision->getRevisionCreationTime());
}
@@ -142,11 +150,36 @@ class MigrateNodeTest extends MigrateDrupal7TestBase {
$this->assertIdentical('93', $node->field_images->height);
$this->assertIdentical('http://google.com', $node->field_link->uri);
$this->assertIdentical('Click Here', $node->field_link->title);
// Test that an email field is migrated.
$this->assertSame('default@example.com', $node->field_email->value);
$this->assertSame('another@example.com', $node->field_email[1]->value);
$node = Node::load(2);
$this->assertSame('en', $node->langcode->value);
$this->assertIdentical("...is that it's the absolute best show ever. Trust me, I would know.", $node->body->value);
$this->assertSame('The thing about Deep Space 9', $node->label());
$this->assertIdentical('internal:/', $node->field_link->uri);
$this->assertIdentical('Home', $node->field_link->title);
$this->assertTrue($node->hasTranslation('is'), "Node 2 has an Icelandic translation");
$translation = $node->getTranslation('is');
$this->assertSame('is', $translation->langcode->value);
$this->assertSame("is - ...is that it's the absolute best show ever. Trust me, I would know.", $translation->body->value);
$this->assertSame('is - The thing about Deep Space 9', $translation->label());
$this->assertSame('internal:/', $translation->field_link->uri);
$this->assertSame('Home', $translation->field_link->title);
// Test that content_translation_source is set.
$manager = $this->container->get('content_translation.manager');
$this->assertSame('en', $manager->getTranslationMetadata($node->getTranslation('is'))->getSource());
// Node 3 is a translation of node 2, and should not be imported separately.
$this->assertNull(Node::load(3), "Node 3 doesn't exist in D8, it was a translation");
// Test that content_translation_source for a source other than English.
$node = Node::load(4);
$this->assertSame('is', $manager->getTranslationMetadata($node->getTranslation('en'))->getSource());
}
}
@@ -12,7 +12,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
*/
class MigrateNodeTitleLabelTest extends MigrateDrupal7TestBase {
public static $modules = ['node', 'text'];
public static $modules = ['node', 'text', 'menu_ui'];
/**
* {@inheritdoc}
@@ -20,14 +20,14 @@ class MigrateNodeTypeTest extends MigrateDrupal7TestBase {
*
* @var array
*/
public static $modules = array('node', 'text', 'filter');
public static $modules = ['node', 'text', 'filter', 'menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(array('node'));
$this->installConfig(['node']);
$this->executeMigration('d7_node_type');
}
@@ -45,12 +45,13 @@ class MigrateNodeTypeTest extends MigrateDrupal7TestBase {
* @param string $help
* The expected help text.
*/
protected function assertEntity($id, $label, $description, $help, $display_submitted, $new_revision, $body_label = NULL) {
protected function assertEntity($id, $label, $description, $help, $display_submitted, $new_revision, $expected_available_menus, $expected_parent, $body_label = NULL) {
/** @var \Drupal\node\NodeTypeInterface $entity */
$entity = NodeType::load($id);
$this->assertTrue($entity instanceof NodeTypeInterface);
$this->assertIdentical($label, $entity->label());
$this->assertIdentical($description, $entity->getDescription());
$this->assertIdentical($help, $entity->getHelp());
$this->assertIdentical($display_submitted, $entity->displaySubmitted(), 'Submission info is displayed');
@@ -62,20 +63,32 @@ class MigrateNodeTypeTest extends MigrateDrupal7TestBase {
$this->assertTrue($body instanceof FieldConfigInterface);
$this->assertIdentical($body_label, $body->label());
}
$this->assertSame($expected_available_menus, $entity->getThirdPartySetting('menu_ui', 'available_menus'));
$this->assertSame($expected_parent, $entity->getThirdPartySetting('menu_ui', 'parent'));
}
/**
* Tests Drupal 7 node type to Drupal 8 migration.
*/
public function testNodeType() {
$this->assertEntity('article', 'Article', 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.', 'Help text for articles', TRUE, FALSE, "Body");
$this->assertEntity('blog', 'Blog entry', 'Use for multi-user blogs. Every user gets a personal blog.', 'Blog away, good sir!', TRUE, FALSE, 'Body');
$expected_available_menus = ['main-menu'];
$expected_parent = 'main-menu:0:';
$this->assertEntity('article', 'Article', 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.', 'Help text for articles', TRUE, FALSE, $expected_available_menus, $expected_parent, "Body");
$this->assertEntity('blog', 'Blog entry', 'Use for multi-user blogs. Every user gets a personal blog.', 'Blog away, good sir!', TRUE, FALSE, $expected_available_menus, $expected_parent, 'Body');
// book's display_submitted flag is not set, so it will default to TRUE.
$this->assertEntity('book', 'Book page', '<em>Books</em> have a built-in hierarchical navigation. Use for handbooks or tutorials.', '', TRUE, TRUE, "Body");
$this->assertEntity('forum', 'Forum topic', 'A <em>forum topic</em> starts a new discussion thread within a forum.', 'No name-calling, no flame wars. Be nice.', TRUE, FALSE, 'Body');
$this->assertEntity('page', 'Basic page', "Use <em>basic pages</em> for your static content, such as an 'About us' page.", 'Help text for basic pages', FALSE, FALSE, "Body");
$this->assertEntity('book', 'Book page', '<em>Books</em> have a built-in hierarchical navigation. Use for handbooks or tutorials.', '', TRUE, TRUE, $expected_available_menus, $expected_parent, "Body");
$this->assertEntity('forum', 'Forum topic', 'A <em>forum topic</em> starts a new discussion thread within a forum.', 'No name-calling, no flame wars. Be nice.', TRUE, FALSE, $expected_available_menus, $expected_parent, 'Body');
$this->assertEntity('page', 'Basic page', "Use <em>basic pages</em> for your static content, such as an 'About us' page.", 'Help text for basic pages', FALSE, FALSE, $expected_available_menus, $expected_parent, "Body");
// This node type does not carry a body field.
$this->assertEntity('test_content_type', 'Test content type', 'This is the description of the test content type.', 'Help text for test content type', FALSE, TRUE);
$expected_available_menus = [
'main-menu',
'management',
'navigation',
'user-menu',
];
$this->assertEntity('test_content_type', 'Test content type', 'This is the description of the test content type.', 'Help text for test content type', FALSE, TRUE, $expected_available_menus, $expected_parent);
}
}
@@ -0,0 +1,262 @@
<?php
namespace Drupal\Tests\node\Kernel;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Session\AccountInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\NodeInterface;
use Drupal\simpletest\ContentTypeCreationTrait;
use Drupal\simpletest\NodeCreationTrait;
use Drupal\simpletest\UserCreationTrait;
use Drupal\user\RoleInterface;
/**
* Tests basic node_access functionality.
*
* @group node
*/
class NodeAccessTest extends KernelTestBase {
use NodeCreationTrait {
getNodeByTitle as drupalGetNodeByTitle;
createNode as drupalCreateNode;
}
use UserCreationTrait {
createUser as drupalCreateUser;
createRole as drupalCreateRole;
createAdminRole as drupalCreateAdminRole;
}
use ContentTypeCreationTrait {
createContentType as drupalCreateContentType;
}
/**
* {@inheritdoc}
*/
public static $modules = [
'node',
'datetime',
'user',
'system',
'filter',
'field',
'text',
];
/**
* Access handler.
*
* @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface
*/
protected $accessHandler;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'sequences');
$this->installSchema('node', 'node_access');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installConfig('filter');
$this->installConfig('node');
$this->accessHandler = $this->container->get('entity_type.manager')
->getAccessControlHandler('node');
// Clear permissions for authenticated users.
$this->config('user.role.' . RoleInterface::AUTHENTICATED_ID)
->set('permissions', [])
->save();
// Create user 1 who has special permissions.
$this->drupalCreateUser();
// Create a node type.
$this->drupalCreateContentType([
'type' => 'page',
'name' => 'Basic page',
'display_submitted' => FALSE,
]);
}
/**
* Runs basic tests for node_access function.
*/
public function testNodeAccess() {
// Ensures user without 'access content' permission can do nothing.
$web_user1 = $this->drupalCreateUser([
'create page content',
'edit any page content',
'delete any page content',
]);
$node1 = $this->drupalCreateNode(['type' => 'page']);
$this->assertNodeCreateAccess($node1->bundle(), FALSE, $web_user1);
$this->assertNodeAccess([
'view' => FALSE,
'update' => FALSE,
'delete' => FALSE,
], $node1, $web_user1);
// Ensures user with 'bypass node access' permission can do everything.
$web_user2 = $this->drupalCreateUser(['bypass node access']);
$node2 = $this->drupalCreateNode(['type' => 'page']);
$this->assertNodeCreateAccess($node2->bundle(), TRUE, $web_user2);
$this->assertNodeAccess([
'view' => TRUE,
'update' => TRUE,
'delete' => TRUE,
], $node2, $web_user2);
// User cannot 'view own unpublished content'.
$web_user3 = $this->drupalCreateUser(['access content']);
$node3 = $this->drupalCreateNode([
'status' => 0,
'uid' => $web_user3->id(),
]);
$this->assertNodeAccess(['view' => FALSE], $node3, $web_user3);
// User cannot create content without permission.
$this->assertNodeCreateAccess($node3->bundle(), FALSE, $web_user3);
// User can 'view own unpublished content', but another user cannot.
$web_user4 = $this->drupalCreateUser([
'access content',
'view own unpublished content',
]);
$web_user5 = $this->drupalCreateUser([
'access content',
'view own unpublished content',
]);
$node4 = $this->drupalCreateNode([
'status' => 0,
'uid' => $web_user4->id(),
]);
$this->assertNodeAccess([
'view' => TRUE,
'update' => FALSE,
], $node4, $web_user4);
$this->assertNodeAccess(['view' => FALSE], $node4, $web_user5);
// Tests the default access provided for a published node.
$node5 = $this->drupalCreateNode();
$this->assertNodeAccess([
'view' => TRUE,
'update' => FALSE,
'delete' => FALSE,
], $node5, $web_user3);
// Tests the "edit any BUNDLE" and "delete any BUNDLE" permissions.
$web_user6 = $this->drupalCreateUser([
'access content',
'edit any page content',
'delete any page content',
]);
$node6 = $this->drupalCreateNode(['type' => 'page']);
$this->assertNodeAccess([
'view' => TRUE,
'update' => TRUE,
'delete' => TRUE,
], $node6, $web_user6);
// Tests the "edit own BUNDLE" and "delete own BUNDLE" permission.
$web_user7 = $this->drupalCreateUser([
'access content',
'edit own page content',
'delete own page content',
]);
// User should not be able to edit or delete nodes they do not own.
$this->assertNodeAccess([
'view' => TRUE,
'update' => FALSE,
'delete' => FALSE,
], $node6, $web_user7);
// User should be able to edit or delete nodes they own.
$node7 = $this->drupalCreateNode([
'type' => 'page',
'uid' => $web_user7->id(),
]);
$this->assertNodeAccess([
'view' => TRUE,
'update' => TRUE,
'delete' => TRUE,
], $node7, $web_user7);
}
/**
* Test operations not supported by node grants.
*/
public function testUnsupportedOperation() {
$this->enableModules(['node_access_test_empty']);
$web_user = $this->drupalCreateUser(['access content']);
$node = $this->drupalCreateNode();
$this->assertNodeAccess(['random_operation' => FALSE], $node, $web_user);
}
/**
* 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\NodeInterface $node
* The node object to check.
* @param \Drupal\Core\Session\AccountInterface $account
* The user account for which to check access.
*/
public function assertNodeAccess(array $ops, NodeInterface $node, AccountInterface $account) {
foreach ($ops as $op => $result) {
$this->assertEquals($result, $this->accessHandler->access($node, $op, $account), $this->nodeAccessAssertMessage($op, $result, $node->language()
->getId()));
}
}
/**
* Asserts that node create access correctly grants or denies access.
*
* @param string $bundle
* The node bundle to check access to.
* @param bool $result
* Whether access should be granted or not.
* @param \Drupal\Core\Session\AccountInterface $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.
*/
public function assertNodeCreateAccess($bundle, $result, AccountInterface $account, $langcode = NULL) {
$this->assertEquals($result, $this->accessHandler->createAccess($bundle, $account, [
'langcode' => $langcode,
]), $this->nodeAccessAssertMessage('create', $result, $langcode));
}
/**
* Constructs an assert message to display which node access was tested.
*
* @param string $operation
* The operation to check access for.
* @param bool $result
* Whether access should be granted or not.
* @param string|null $langcode
* (optional) The language code indicating which translation of the node
* to check. If NULL, the untranslated (fallback) access is checked.
*
* @return string
* An assert message string which contains information in plain English
* about the node access permission test that was performed.
*/
public function nodeAccessAssertMessage($operation, $result, $langcode = NULL) {
return new FormattableMarkup(
'Node access returns @result with operation %op, language code %langcode.',
[
'@result' => $result ? 'true' : 'false',
'%op' => $operation,
'%langcode' => !empty($langcode) ? $langcode : 'empty',
]
);
}
}
@@ -28,7 +28,7 @@ class NodeBodyFieldStorageTest extends KernelTestBase {
$this->installSchema('user', 'users_data');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installConfig(array('field', 'node'));
$this->installConfig(['field', 'node']);
}
/**
@@ -46,7 +46,7 @@ class NodeBodyFieldStorageTest extends KernelTestBase {
$field->delete();
$field_storage = FieldStorageConfig::loadByName('node', 'body');
$this->assertTrue(count($field_storage->getBundles()) == 0, 'Node body field storage exists after deleting the only instance of a field.');
\Drupal::service('module_installer')->uninstall(array('node'));
\Drupal::service('module_installer')->uninstall(['node']);
$field_storage = FieldStorageConfig::loadByName('node', 'body');
$this->assertFalse($field_storage, 'Node body field storage does not exist after uninstalling the Node module.');
}

Some files were not shown because too many files have changed in this diff Show More