summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/node
diff options
context:
space:
mode:
Diffstat (limited to 'core/modules/node')
-rw-r--r--core/modules/node/config/optional/views.view.content.yml1
-rw-r--r--core/modules/node/config/optional/views.view.glossary.yml1
-rw-r--r--core/modules/node/config/schema/node.schema.yml4
-rw-r--r--core/modules/node/js/node.preview.js7
-rw-r--r--core/modules/node/node.install127
-rw-r--r--core/modules/node/node.module17
-rw-r--r--core/modules/node/node.routing.yml1
-rw-r--r--core/modules/node/node.services.yml20
-rw-r--r--core/modules/node/src/Controller/NodeController.php7
-rw-r--r--core/modules/node/src/Entity/Node.php2
-rw-r--r--core/modules/node/src/Entity/NodeType.php2
-rw-r--r--core/modules/node/src/Form/NodeForm.php (renamed from core/modules/node/src/NodeForm.php)45
-rw-r--r--core/modules/node/src/Form/NodeTypeForm.php (renamed from core/modules/node/src/NodeTypeForm.php)2
-rw-r--r--core/modules/node/src/Hook/NodeHooks.php9
-rw-r--r--core/modules/node/src/Hook/NodeHooks1.php2
-rw-r--r--core/modules/node/src/Hook/NodeRequirements.php155
-rw-r--r--core/modules/node/src/Hook/NodeThemeHooks.php4
-rw-r--r--core/modules/node/src/NodeAccessControlHandler.php9
-rw-r--r--core/modules/node/src/NodeAccessControlHandlerInterface.php2
-rw-r--r--core/modules/node/src/NodeGrantDatabaseStorage.php23
-rw-r--r--core/modules/node/src/NodeGrantDatabaseStorageInterface.php10
-rw-r--r--core/modules/node/src/NodePermissions.php24
-rw-r--r--core/modules/node/src/PageCache/DenyNodePreview.php44
-rw-r--r--core/modules/node/src/Plugin/Block/SyndicateBlock.php6
-rw-r--r--core/modules/node/src/Plugin/views/UidRevisionTrait.php38
-rw-r--r--core/modules/node/src/Plugin/views/argument/UidRevision.php7
-rw-r--r--core/modules/node/src/Plugin/views/filter/Access.php2
-rw-r--r--core/modules/node/src/Plugin/views/filter/UidRevision.php13
-rw-r--r--core/modules/node/src/Plugin/views/wizard/Node.php10
-rw-r--r--core/modules/node/src/Plugin/views/wizard/NodeRevision.php5
-rw-r--r--core/modules/node/tests/modules/node_no_default_author/node_no_default_author.info.yml5
-rw-r--r--core/modules/node/tests/modules/node_no_default_author/src/Hook/NodeNoDefaultAuthorHooks.php31
-rw-r--r--core/modules/node/tests/modules/node_test/src/Hook/NodeTestHooks.php4
-rw-r--r--core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml10
-rw-r--r--core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml2
-rw-r--r--core/modules/node/tests/src/Functional/NodeAccessCacheRedirectWarningTest.php89
-rw-r--r--core/modules/node/tests/src/Functional/NodeCreationTest.php20
-rw-r--r--core/modules/node/tests/src/Functional/NodeEditFormTest.php17
-rw-r--r--core/modules/node/tests/src/Functional/NodeRevisionsAuthorTest.php102
-rw-r--r--core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php16
-rw-r--r--core/modules/node/tests/src/Functional/NodeSyndicateBlockTest.php2
-rw-r--r--core/modules/node/tests/src/Functional/NodeTranslationUITest.php24
-rw-r--r--core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php1
-rw-r--r--core/modules/node/tests/src/Kernel/NodeRequirementsStatusFilterWarningTest.php8
-rw-r--r--core/modules/node/tests/src/Unit/PageCache/DenyNodePreviewTest.php92
45 files changed, 638 insertions, 384 deletions
diff --git a/core/modules/node/config/optional/views.view.content.yml b/core/modules/node/config/optional/views.view.content.yml
index d0bbb09cf5a0..09ba3dcc35ae 100644
--- a/core/modules/node/config/optional/views.view.content.yml
+++ b/core/modules/node/config/optional/views.view.content.yml
@@ -625,6 +625,7 @@ display:
empty_table: true
caption: ''
description: ''
+ class: ''
row:
type: fields
query:
diff --git a/core/modules/node/config/optional/views.view.glossary.yml b/core/modules/node/config/optional/views.view.glossary.yml
index 2698473d5271..868d51fb9e26 100644
--- a/core/modules/node/config/optional/views.view.glossary.yml
+++ b/core/modules/node/config/optional/views.view.glossary.yml
@@ -343,6 +343,7 @@ display:
summary: ''
order: asc
empty_table: false
+ class: ''
row:
type: fields
options:
diff --git a/core/modules/node/config/schema/node.schema.yml b/core/modules/node/config/schema/node.schema.yml
index 08fe92cc401e..8c81f68ae604 100644
--- a/core/modules/node/config/schema/node.schema.yml
+++ b/core/modules/node/config/schema/node.schema.yml
@@ -24,7 +24,7 @@ node.type.*:
label: 'Machine-readable name'
constraints:
# Node type machine names are specifically limited to 32 characters.
- # @see \Drupal\node\NodeTypeForm::form()
+ # @see \Drupal\node\Form\NodeTypeForm::form()
Length:
max: 32
description:
@@ -50,7 +50,7 @@ node.type.*:
constraints:
# These are the values of the DRUPAL_DISABLED, DRUPAL_OPTIONAL, and
# DRUPAL_REQUIRED constants.
- # @see \Drupal\node\NodeTypeForm::form()
+ # @see \Drupal\node\Form\NodeTypeForm::form()
Choice: [0, 1, 2]
display_submitted:
type: boolean
diff --git a/core/modules/node/js/node.preview.js b/core/modules/node/js/node.preview.js
index 50bc58ade774..e23be0b71e22 100644
--- a/core/modules/node/js/node.preview.js
+++ b/core/modules/node/js/node.preview.js
@@ -34,13 +34,13 @@
const $previewDialog = $(
`<div>${Drupal.theme('nodePreviewModal')}</div>`,
).appendTo('body');
- Drupal.dialog($previewDialog, {
+ const confirmationDialog = Drupal.dialog($previewDialog, {
title: Drupal.t('Leave preview?'),
buttons: [
{
text: Drupal.t('Cancel'),
click() {
- $(this).dialog('close');
+ confirmationDialog.close();
},
},
{
@@ -50,7 +50,8 @@
},
},
],
- }).showModal();
+ });
+ confirmationDialog.showModal();
}
}
diff --git a/core/modules/node/node.install b/core/modules/node/node.install
index fb5c14c47675..4e232bdc2de9 100644
--- a/core/modules/node/node.install
+++ b/core/modules/node/node.install
@@ -5,137 +5,10 @@
* Install, update and uninstall functions for the node module.
*/
-use Drupal\Core\Link;
-use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
-use Drupal\Core\Url;
use Drupal\Core\Database\Database;
use Drupal\user\RoleInterface;
/**
- * Implements hook_requirements().
- */
-function node_requirements($phase): array {
- $requirements = [];
- if ($phase === 'runtime') {
- // Only show rebuild button if there are either 0, or 2 or more, rows
- // in the {node_access} table, or if there are modules that
- // implement hook_node_grants().
- $grant_count = \Drupal::entityTypeManager()->getAccessControlHandler('node')->countGrants();
- $has_node_grants_implementations = \Drupal::moduleHandler()->hasImplementations('node_grants');
- if ($grant_count != 1 || $has_node_grants_implementations) {
- $value = \Drupal::translation()->formatPlural($grant_count, 'One permission in use', '@count permissions in use', ['@count' => $grant_count]);
- }
- else {
- $value = t('Disabled');
- }
-
- $requirements['node_access'] = [
- 'title' => t('Node Access Permissions'),
- 'value' => $value,
- 'description' => t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Rebuilding will remove all privileges to content and replace them with permissions based on the current modules and settings. Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed, content will automatically use the new permissions. <a href=":rebuild">Rebuild permissions</a>', [
- ':rebuild' => Url::fromRoute('node.configure_rebuild_confirm')->toString(),
- ]),
- ];
-
- // Report when the "Published status or admin user" has no impact on the
- // result of dependent views due to active node access modules.
- // @see https://www.drupal.org/node/3472976
- if ($has_node_grants_implementations && \Drupal::moduleHandler()->moduleExists('views')) {
- $node_status_filter_problematic_views = [];
- $active_view_ids = \Drupal::entityQuery('view')
- ->condition('status', TRUE)
- ->accessCheck(FALSE)
- ->execute();
-
- $views_storage = \Drupal::entityTypeManager()->getStorage('view');
- foreach ($views_storage->loadMultiple($active_view_ids) as $view) {
- foreach ($view->get('display') as $display_id => $display) {
- if (array_key_exists('filters', $display['display_options'])) {
- foreach ($display['display_options']['filters'] as $filter) {
- if (array_key_exists('plugin_id', $filter) && $filter['plugin_id'] === 'node_status') {
- $node_status_filter_problematic_views[$view->id()][$display_id] = [
- 'view_label' => $view->label(),
- 'display_name' => $display['display_title'] ?? $display_id,
- ];
- break;
- }
- }
- }
- }
- }
-
- if ($node_status_filter_problematic_views !== []) {
- $node_access_implementations = [];
- $module_data = \Drupal::service('extension.list.module')->getAllInstalledInfo();
- foreach (['node_grants', 'node_grants_alter'] as $hook) {
- \Drupal::moduleHandler()->invokeAllWith(
- $hook,
- static function (callable $hook, string $module) use (&$node_access_implementations, $module_data) {
- $node_access_implementations[$module] = $module_data[$module]['name'];
- }
- );
- }
- uasort($node_access_implementations, 'strnatcasecmp');
- $views_ui_enabled = \Drupal::moduleHandler()->moduleExists('views_ui');
- $node_status_filter_problematic_views_list = [];
- foreach ($node_status_filter_problematic_views as $view_id => $displays) {
- foreach ($displays as $display_id => $info) {
- $text = "{$info['view_label']} ({$info['display_name']})";
- if ($views_ui_enabled) {
- $url = Url::fromRoute('entity.view.edit_display_form', [
- 'view' => $view_id,
- 'display_id' => $display_id,
- ]);
- if ($url->access()) {
- $node_status_filter_problematic_views_list[] = Link::fromTextAndUrl($text, $url)->toString();
- }
- else {
- $node_status_filter_problematic_views_list[] = $text;
- }
- }
- else {
- $node_status_filter_problematic_views_list[] = $text;
- }
- }
- }
-
- $node_status_filter_problematic_views_count = count($node_status_filter_problematic_views_list);
- $node_status_filter_description_arguments = [
- '%modules' => implode(', ', $node_access_implementations),
- '%status_filter' => t('Published status or admin user'),
- ];
-
- if ($node_status_filter_problematic_views_count > 1) {
- $node_status_filter_problematic_views_list = [
- '#theme' => 'item_list',
- '#items' => $node_status_filter_problematic_views_list,
- ];
- $node_status_filter_description_arguments['@views'] = \Drupal::service('renderer')->renderInIsolation($node_status_filter_problematic_views_list);
- }
- else {
- $node_status_filter_description_arguments['%view'] = reset($node_status_filter_problematic_views_list);
- }
-
- $node_status_filter_description = new PluralTranslatableMarkup(
- $node_status_filter_problematic_views_count,
- 'The %view view uses the %status_filter filter but it has no effect because the following module(s) control access: %modules. Review and consider removing the filter.',
- 'The following views use the %status_filter filter but it has no effect because the following module(s) control access: %modules. Review and consider removing the filter from these views: @views',
- $node_status_filter_description_arguments,
- );
-
- $requirements['node_status_filter'] = [
- 'title' => t('Content status filter'),
- 'value' => t('Redundant filters detected'),
- 'description' => $node_status_filter_description,
- 'severity' => REQUIREMENT_WARNING,
- ];
- }
- }
- }
- return $requirements;
-}
-
-/**
* Implements hook_schema().
*/
function node_schema(): array {
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 4ee3c48a00b2..d68a04fcbc8a 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -36,8 +36,12 @@ use Drupal\node\NodeTypeInterface;
* @return array|false
* A renderable array containing a list of linked node titles fetched from
* $result, or FALSE if there are no rows in $result.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. There is no replacement.
+ * @see https://www.drupal.org/node/3531959
*/
function node_title_list(StatementInterface $result, $title = NULL) {
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3531959', E_USER_DEPRECATED);
$items = [];
$num_rows = FALSE;
$nids = [];
@@ -121,8 +125,12 @@ function node_get_type_label(NodeInterface $node) {
*
* @return string
* The node type description.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Use $node_type->getDescription() instead.
+ * @see https://www.drupal.org/node/3531945
*/
function node_type_get_description(NodeTypeInterface $node_type) {
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Use $node_type->getDescription() instead. See https://www.drupal.org/node/3531945', E_USER_DEPRECATED);
return $node_type->getDescription();
}
@@ -387,6 +395,11 @@ function node_form_system_themes_admin_form_submit($form, FormStateInterface $fo
}
/**
+ * @addtogroup node_access
+ * @{
+ */
+
+/**
* Fetches an array of permission IDs granted to the given user ID.
*
* The implementation here provides only the universal "all" grant. A node
@@ -658,6 +671,10 @@ function _node_access_rebuild_batch_finished($success, $results, $operations): v
}
/**
+ * @} End of "addtogroup node_access".
+ */
+
+/**
* Marks a node to be re-indexed by the node_search plugin.
*
* @param int $nid
diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml
index 4ceb843af183..aa53d913cf7e 100644
--- a/core/modules/node/node.routing.yml
+++ b/core/modules/node/node.routing.yml
@@ -45,6 +45,7 @@ entity.node.preview:
requirements:
_node_preview_access: '{node_preview}'
options:
+ no_cache: TRUE
parameters:
node_preview:
type: 'node_preview'
diff --git a/core/modules/node/node.services.yml b/core/modules/node/node.services.yml
index e5daad354296..83eb19f7355d 100644
--- a/core/modules/node/node.services.yml
+++ b/core/modules/node/node.services.yml
@@ -1,3 +1,16 @@
+parameters:
+ node.moved_classes:
+ 'Drupal\node\NodeForm':
+ class: 'Drupal\node\Form\NodeForm'
+ deprecation_version: drupal:11.2.0
+ removed_version: drupal:12.0.0
+ change_record: https://www.drupal.org/node/3517871
+ 'Drupal\node\NodeTypeForm':
+ class: 'Drupal\node\Form\NodeTypeForm'
+ deprecation_version: drupal:11.2.0
+ removed_version: drupal:12.0.0
+ change_record: https://www.drupal.org/node/3517871
+
services:
_defaults:
autoconfigure: true
@@ -23,13 +36,6 @@ services:
tags:
- { name: paramconverter }
lazy: true
- node.page_cache_response_policy.deny_node_preview:
- class: Drupal\node\PageCache\DenyNodePreview
- arguments: ['@current_route_match']
- public: false
- tags:
- - { name: page_cache_response_policy }
- - { name: dynamic_page_cache_response_policy }
cache_context.user.node_grants:
class: Drupal\node\Cache\NodeAccessGrantsCacheContext
arguments: ['@current_user']
diff --git a/core/modules/node/src/Controller/NodeController.php b/core/modules/node/src/Controller/NodeController.php
index e860d0c1d2a0..d5a35f64285a 100644
--- a/core/modules/node/src/Controller/NodeController.php
+++ b/core/modules/node/src/Controller/NodeController.php
@@ -3,7 +3,6 @@
namespace Drupal\node\Controller;
use Drupal\Component\Utility\Xss;
-use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
@@ -197,10 +196,12 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
'username' => $this->renderer->renderInIsolation($username),
'message' => ['#markup' => $revision->revision_log->value, '#allowed_tags' => Xss::getHtmlTagList()],
],
+ // @todo Fix this properly in https://www.drupal.org/project/drupal/issues/3227637.
+ '#cache' => [
+ 'max-age' => 0,
+ ],
],
];
- // @todo Simplify once https://www.drupal.org/node/2334319 lands.
- $this->renderer->addCacheableDependency($column['data'], CacheableMetadata::createFromRenderArray($username));
$row[] = $column;
if ($is_current_revision) {
diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php
index f4c519a43c8a..0f61a74f1c01 100644
--- a/core/modules/node/src/Entity/Node.php
+++ b/core/modules/node/src/Entity/Node.php
@@ -11,8 +11,8 @@ use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\node\Form\DeleteMultiple;
use Drupal\node\Form\NodeDeleteForm;
+use Drupal\node\Form\NodeForm;
use Drupal\node\NodeAccessControlHandler;
-use Drupal\node\NodeForm;
use Drupal\node\NodeInterface;
use Drupal\node\NodeListBuilder;
use Drupal\node\NodeStorage;
diff --git a/core/modules/node/src/Entity/NodeType.php b/core/modules/node/src/Entity/NodeType.php
index 35b911d7ffb7..48d295635f3d 100644
--- a/core/modules/node/src/Entity/NodeType.php
+++ b/core/modules/node/src/Entity/NodeType.php
@@ -9,7 +9,7 @@ use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\node\Form\NodeTypeDeleteConfirm;
use Drupal\node\NodeTypeAccessControlHandler;
-use Drupal\node\NodeTypeForm;
+use Drupal\node\Form\NodeTypeForm;
use Drupal\node\NodeTypeInterface;
use Drupal\node\NodeTypeListBuilder;
use Drupal\user\Entity\EntityPermissionsRouteProvider;
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/Form/NodeForm.php
index ab81a4e3f928..295e9ab78ce0 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/Form/NodeForm.php
@@ -1,6 +1,6 @@
<?php
-namespace Drupal\node;
+namespace Drupal\node\Form;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
@@ -10,6 +10,7 @@ use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
+use Drupal\Core\Utility\Error;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -163,7 +164,7 @@ class NodeForm extends ContentEntityForm {
$form['meta']['author'] = [
'#type' => 'item',
'#title' => $this->t('Author'),
- '#markup' => $node->getOwner()->getAccountName(),
+ '#markup' => $node->getOwner()?->getDisplayName(),
'#wrapper_attributes' => ['class' => ['entity-meta__author']],
];
@@ -278,21 +279,22 @@ class NodeForm extends ContentEntityForm {
public function save(array $form, FormStateInterface $form_state) {
$node = $this->entity;
$insert = $node->isNew();
- $node->save();
- $node_link = $node->toLink($this->t('View'))->toString();
- $context = ['@type' => $node->getType(), '%title' => $node->label(), 'link' => $node_link];
- $t_args = ['@type' => node_get_type_label($node), '%title' => $node->toLink()->toString()];
-
- if ($insert) {
- $this->logger('content')->info('@type: added %title.', $context);
- $this->messenger()->addStatus($this->t('@type %title has been created.', $t_args));
- }
- else {
- $this->logger('content')->info('@type: updated %title.', $context);
- $this->messenger()->addStatus($this->t('@type %title has been updated.', $t_args));
- }
- if ($node->id()) {
+ try {
+ $node->save();
+ $node_link = $node->toLink($this->t('View'))->toString();
+ $context = ['@type' => $node->getType(), '%title' => $node->label(), 'link' => $node_link];
+ $t_args = ['@type' => node_get_type_label($node), '%title' => $node->access('view') ? $node->toLink()->toString() : $node->label()];
+
+ if ($insert) {
+ $this->logger('content')->info('@type: added %title.', $context);
+ $this->messenger()->addStatus($this->t('@type %title has been created.', $t_args));
+ }
+ else {
+ $this->logger('content')->info('@type: updated %title.', $context);
+ $this->messenger()->addStatus($this->t('@type %title has been updated.', $t_args));
+ }
+
$form_state->setValue('nid', $node->id());
$form_state->set('nid', $node->id());
if ($node->access('view')) {
@@ -310,10 +312,15 @@ class NodeForm extends ContentEntityForm {
$store = $this->tempStoreFactory->get('node_preview');
$store->delete($node->uuid());
}
- else {
+ catch (\Exception $e) {
// In the unlikely case something went wrong on save, the node will be
- // rebuilt and node form redisplayed the same way as in preview.
- $this->messenger()->addError($this->t('The post could not be saved.'));
+ // rebuilt and node form redisplayed.
+ $this->messenger()->addError($this->t('The content could not be saved. Contact the site administrator if the problem persists.'));
+ // It's likely that this exception is an EntityStorageException in which
+ // case we won't have the actual backtrace available. Attempt to get the
+ // previous exception if available to include the backtrace.
+ $e = $e->getPrevious() ?: $e;
+ \Drupal::logger('node')->error('%type saving node form: @message in %function (line %line of %file) @backtrace_string.', Error::decodeException($e));
$form_state->setRebuild();
}
}
diff --git a/core/modules/node/src/NodeTypeForm.php b/core/modules/node/src/Form/NodeTypeForm.php
index 3328ade970da..93d510d4387e 100644
--- a/core/modules/node/src/NodeTypeForm.php
+++ b/core/modules/node/src/Form/NodeTypeForm.php
@@ -1,6 +1,6 @@
<?php
-namespace Drupal\node;
+namespace Drupal\node\Form;
use Drupal\Core\Entity\BundleEntityFormBase;
use Drupal\Core\Entity\EntityFieldManagerInterface;
diff --git a/core/modules/node/src/Hook/NodeHooks.php b/core/modules/node/src/Hook/NodeHooks.php
index d5f84e0359ba..8a6b4d887c89 100644
--- a/core/modules/node/src/Hook/NodeHooks.php
+++ b/core/modules/node/src/Hook/NodeHooks.php
@@ -66,4 +66,13 @@ class NodeHooks {
}
}
+ /**
+ * Implements hook_block_alter().
+ */
+ #[Hook('block_alter')]
+ public function blockAlter(&$definitions): void {
+ // Hide the deprecated Syndicate block from the UI.
+ $definitions['node_syndicate_block']['_block_ui_hidden'] = TRUE;
+ }
+
}
diff --git a/core/modules/node/src/Hook/NodeHooks1.php b/core/modules/node/src/Hook/NodeHooks1.php
index 8e25f2eb066c..d2dbf545c3ca 100644
--- a/core/modules/node/src/Hook/NodeHooks1.php
+++ b/core/modules/node/src/Hook/NodeHooks1.php
@@ -92,7 +92,7 @@ class NodeHooks1 {
case 'entity.entity_view_display.node.default':
case 'entity.entity_view_display.node.view_mode':
$type = $route_match->getParameter('node_type');
- return '<p>' . $this->t('Content items can be displayed using different view modes: Teaser, Full content, Print, RSS, etc. <em>Teaser</em> is a short format that is typically used in lists of multiple content items. <em>Full content</em> is typically used when the content is displayed on its own page.') . '</p>' . '<p>' . $this->t('Here, you can define which fields are shown and hidden when %type content is displayed in each view mode, and define how the fields are displayed in each view mode.', ['%type' => $type->label()]) . '</p>';
+ return '<p>' . $this->t('Content items can be displayed using different view modes: Teaser, Full content, Print, RSS, etc. <em>Teaser</em> is a short format that is typically used in lists of multiple content items. <em>Full content</em> is typically used when the content is displayed on its own page.') . '</p><p>' . $this->t('Here, you can define which fields are shown and hidden when %type content is displayed in each view mode, and define how the fields are displayed in each view mode.', ['%type' => $type->label()]) . '</p>';
case 'entity.node.version_history':
return '<p>' . $this->t('Revisions allow you to track differences between multiple versions of your content, and revert to older versions.') . '</p>';
diff --git a/core/modules/node/src/Hook/NodeRequirements.php b/core/modules/node/src/Hook/NodeRequirements.php
new file mode 100644
index 000000000000..84f74aee98cf
--- /dev/null
+++ b/core/modules/node/src/Hook/NodeRequirements.php
@@ -0,0 +1,155 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\node\Hook;
+
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Extension\ModuleExtensionList;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Link;
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\Url;
+
+/**
+ * Requirements for the Node module.
+ */
+class NodeRequirements {
+
+ use StringTranslationTrait;
+
+ public function __construct(
+ protected readonly EntityTypeManagerInterface $entityTypeManager,
+ protected readonly ModuleHandlerInterface $moduleHandler,
+ protected readonly TranslationInterface $translation,
+ protected readonly ModuleExtensionList $moduleExtensionList,
+ ) {}
+
+ /**
+ * Implements hook_runtime_requirements().
+ */
+ #[Hook('runtime_requirements')]
+ public function runtime(): array {
+ $requirements = [];
+ // Only show rebuild button if there are either 0, or 2 or more, rows
+ // in the {node_access} table, or if there are modules that
+ // implement hook_node_grants().
+ $grant_count = $this->entityTypeManager->getAccessControlHandler('node')->countGrants();
+ $has_node_grants_implementations = $this->moduleHandler->hasImplementations('node_grants');
+ if ($grant_count != 1 || $has_node_grants_implementations) {
+ $value = $this->translation->formatPlural($grant_count, 'One permission in use', '@count permissions in use', ['@count' => $grant_count]);
+ }
+ else {
+ $value = $this->t('Disabled');
+ }
+
+ $requirements['node_access'] = [
+ 'title' => $this->t('Node Access Permissions'),
+ 'value' => $value,
+ 'description' => $this->t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Rebuilding will remove all privileges to content and replace them with permissions based on the current modules and settings. Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed, content will automatically use the new permissions. <a href=":rebuild">Rebuild permissions</a>', [
+ ':rebuild' => Url::fromRoute('node.configure_rebuild_confirm')->toString(),
+ ]),
+ ];
+
+ // Report when the "Published status or admin user" has no impact on the
+ // result of dependent views due to active node access modules.
+ // @see https://www.drupal.org/node/3472976
+ if ($has_node_grants_implementations && $this->moduleHandler->moduleExists('views')) {
+ $node_status_filter_problematic_views = [];
+ $query = $this->entityTypeManager->getStorage('view')->getQuery();
+ $query->condition('status', TRUE);
+ $query->accessCheck(FALSE);
+ $active_view_ids = $query->execute();
+
+ $views_storage = $this->entityTypeManager->getStorage('view');
+ foreach ($views_storage->loadMultiple($active_view_ids) as $view) {
+ foreach ($view->get('display') as $display_id => $display) {
+ if (array_key_exists('filters', $display['display_options'])) {
+ foreach ($display['display_options']['filters'] as $filter) {
+ if (array_key_exists('plugin_id', $filter) && $filter['plugin_id'] === 'node_status') {
+ $node_status_filter_problematic_views[$view->id()][$display_id] = [
+ 'view_label' => $view->label(),
+ 'display_name' => $display['display_title'] ?? $display_id,
+ ];
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if ($node_status_filter_problematic_views !== []) {
+ $node_access_implementations = [];
+ $module_data = $this->moduleExtensionList->getAllInstalledInfo();
+ foreach (['node_grants', 'node_grants_alter'] as $hook) {
+ $this->moduleHandler->invokeAllWith(
+ $hook,
+ static function (callable $hook, string $module) use (&$node_access_implementations, $module_data) {
+ $node_access_implementations[$module] = $module_data[$module]['name'];
+ }
+ );
+ }
+ uasort($node_access_implementations, 'strnatcasecmp');
+ $views_ui_enabled = $this->moduleHandler->moduleExists('views_ui');
+ $node_status_filter_problematic_views_list = [];
+ foreach ($node_status_filter_problematic_views as $view_id => $displays) {
+ foreach ($displays as $display_id => $info) {
+ $text = "{$info['view_label']} ({$info['display_name']})";
+ if ($views_ui_enabled) {
+ $url = Url::fromRoute('entity.view.edit_display_form', [
+ 'view' => $view_id,
+ 'display_id' => $display_id,
+ ]);
+ if ($url->access()) {
+ $node_status_filter_problematic_views_list[] = Link::fromTextAndUrl($text, $url)->toString();
+ }
+ else {
+ $node_status_filter_problematic_views_list[] = $text;
+ }
+ }
+ else {
+ $node_status_filter_problematic_views_list[] = $text;
+ }
+ }
+ }
+
+ $node_status_filter_problematic_views_count = count($node_status_filter_problematic_views_list);
+ $node_status_filter_description_arguments = [
+ '%modules' => implode(', ', $node_access_implementations),
+ '%status_filter' => $this->t('Published status or admin user'),
+ ];
+
+ if ($node_status_filter_problematic_views_count > 1) {
+ $node_status_filter_problematic_views_list = [
+ '#theme' => 'item_list',
+ '#items' => $node_status_filter_problematic_views_list,
+ ];
+ $node_status_filter_description_arguments['@views'] = \Drupal::service('renderer')->renderInIsolation($node_status_filter_problematic_views_list);
+ }
+ else {
+ $node_status_filter_description_arguments['%view'] = reset($node_status_filter_problematic_views_list);
+ }
+
+ $node_status_filter_description = new PluralTranslatableMarkup(
+ $node_status_filter_problematic_views_count,
+ 'The %view view uses the %status_filter filter but it has no effect because the following module(s) control access: %modules. Review and consider removing the filter.',
+ 'The following views use the %status_filter filter but it has no effect because the following module(s) control access: %modules. Review and consider removing the filter from these views: @views',
+ $node_status_filter_description_arguments,
+ );
+
+ $requirements['node_status_filter'] = [
+ 'title' => $this->t('Content status filter'),
+ 'value' => $this->t('Redundant filters detected'),
+ 'description' => $node_status_filter_description,
+ 'severity' => RequirementSeverity::Warning,
+ ];
+ }
+ }
+ return $requirements;
+ }
+
+}
diff --git a/core/modules/node/src/Hook/NodeThemeHooks.php b/core/modules/node/src/Hook/NodeThemeHooks.php
index 7ee443c458f8..7ed0ef91f5fd 100644
--- a/core/modules/node/src/Hook/NodeThemeHooks.php
+++ b/core/modules/node/src/Hook/NodeThemeHooks.php
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Drupal\node\Hook;
-use Drupal\Core\Hook\Attribute\Preprocess;
+use Drupal\Core\Hook\Attribute\Hook;
/**
* Hook implementations for the node module.
@@ -14,7 +14,7 @@ class NodeThemeHooks {
/**
* Implements hook_preprocess_HOOK() for node field templates.
*/
- #[Preprocess('field__node')]
+ #[Hook('preprocess_field__node')]
public function preprocessFieldNode(&$variables): void {
// Set a variable 'is_inline' in cases where inline markup is required,
// without any block elements such as <div>.
diff --git a/core/modules/node/src/NodeAccessControlHandler.php b/core/modules/node/src/NodeAccessControlHandler.php
index 963ab53ded41..7121f62e2837 100644
--- a/core/modules/node/src/NodeAccessControlHandler.php
+++ b/core/modules/node/src/NodeAccessControlHandler.php
@@ -223,7 +223,16 @@ class NodeAccessControlHandler extends EntityAccessControlHandler implements Nod
return NULL;
}
+ // When access is granted due to the 'view own unpublished content'
+ // permission and for no other reason, node grants are bypassed. However,
+ // to ensure the full set of cacheable metadata is available to variation
+ // cache, additionally add the node_grants cache context so that if the
+ // status or the owner of the node changes, cache redirects will continue to
+ // reflect the latest state without needing to be invalidated.
$cacheability->addCacheContexts(['user']);
+ if ($this->moduleHandler->hasImplementations('node_grants')) {
+ $cacheability->addCacheContexts(['user.node_grants:view']);
+ }
if ($account->id() != $node->getOwnerId()) {
return NULL;
}
diff --git a/core/modules/node/src/NodeAccessControlHandlerInterface.php b/core/modules/node/src/NodeAccessControlHandlerInterface.php
index 588391394eec..0d67cfb7bd69 100644
--- a/core/modules/node/src/NodeAccessControlHandlerInterface.php
+++ b/core/modules/node/src/NodeAccessControlHandlerInterface.php
@@ -30,6 +30,8 @@ interface NodeAccessControlHandlerInterface {
/**
* Creates the default node access grant entry on the grant storage.
+ *
+ * @see \Drupal\node\NodeGrantDatabaseStorageInterface::writeDefault()
*/
public function writeDefaultGrant();
diff --git a/core/modules/node/src/NodeGrantDatabaseStorage.php b/core/modules/node/src/NodeGrantDatabaseStorage.php
index eea6cc100127..fbaab21a2119 100644
--- a/core/modules/node/src/NodeGrantDatabaseStorage.php
+++ b/core/modules/node/src/NodeGrantDatabaseStorage.php
@@ -112,6 +112,13 @@ class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface {
if (count($grants) > 0) {
$query->condition($grants);
}
+ if ($query->execute()->fetchField()) {
+ $access_result = AccessResult::allowed();
+ }
+ else {
+ $access_result = AccessResult::neutral();
+ }
+ $access_result->addCacheContexts(['user.node_grants:' . $operation]);
// Only the 'view' node grant can currently be cached; the others currently
// don't have any cacheability metadata. Hopefully, we can add that in the
@@ -119,20 +126,10 @@ class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface {
// cases. For now, this must remain marked as uncacheable, even when it is
// theoretically cacheable, because we don't have the necessary metadata to
// know it for a fact.
- $set_cacheability = function (AccessResult $access_result) use ($operation) {
- $access_result->addCacheContexts(['user.node_grants:' . $operation]);
- if ($operation !== 'view') {
- $access_result->setCacheMaxAge(0);
- }
- return $access_result;
- };
-
- if ($query->execute()->fetchField()) {
- return $set_cacheability(AccessResult::allowed());
- }
- else {
- return $set_cacheability(AccessResult::neutral());
+ if ($operation !== 'view') {
+ $access_result->setCacheMaxAge(0);
}
+ return $access_result;
}
/**
diff --git a/core/modules/node/src/NodeGrantDatabaseStorageInterface.php b/core/modules/node/src/NodeGrantDatabaseStorageInterface.php
index cce744765630..d343a2f350b4 100644
--- a/core/modules/node/src/NodeGrantDatabaseStorageInterface.php
+++ b/core/modules/node/src/NodeGrantDatabaseStorageInterface.php
@@ -42,8 +42,8 @@ interface NodeGrantDatabaseStorageInterface {
* @param string $base_table
* The base table of the query.
*
- * @return int
- * Status of the access check.
+ * @return void
+ * No return value.
*/
public function alterQuery($query, array $tables, $operation, AccountInterface $account, $base_table);
@@ -83,6 +83,12 @@ interface NodeGrantDatabaseStorageInterface {
/**
* Creates the default node access grant entry.
+ *
+ * The default node access grant is a special grant added to the node_access
+ * table when no modules implement hook_node_grants. It grants view access
+ * to any published node.
+ *
+ * @see self::access()
*/
public function writeDefault();
diff --git a/core/modules/node/src/NodePermissions.php b/core/modules/node/src/NodePermissions.php
index e913f5326f38..5f6518301929 100644
--- a/core/modules/node/src/NodePermissions.php
+++ b/core/modules/node/src/NodePermissions.php
@@ -2,6 +2,9 @@
namespace Drupal\node;
+use Drupal\Core\DependencyInjection\AutowireTrait;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\BundlePermissionHandlerTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\node\Entity\NodeType;
@@ -9,19 +12,34 @@ use Drupal\node\Entity\NodeType;
/**
* Provides dynamic permissions for nodes of different types.
*/
-class NodePermissions {
+class NodePermissions implements ContainerInjectionInterface {
+
+ use AutowireTrait;
use BundlePermissionHandlerTrait;
use StringTranslationTrait;
+ public function __construct(
+ protected ?EntityTypeManagerInterface $entityTypeManager = NULL,
+ ) {
+ if ($entityTypeManager === NULL) {
+ @trigger_error('Calling ' . __METHOD__ . ' without the $entityTypeManager argument is deprecated in drupal:11.2.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/node/3515921', E_USER_DEPRECATED);
+ $this->entityTypeManager = \Drupal::entityTypeManager();
+ }
+ }
+
/**
* Returns an array of node type permissions.
*
* @return array
* The node type permissions.
- * @see \Drupal\user\PermissionHandlerInterface::getPermissions()
+ *
+ * @see \Drupal\user\PermissionHandlerInterface::getPermissions()
*/
public function nodeTypePermissions() {
- return $this->generatePermissions(NodeType::loadMultiple(), [$this, 'buildPermissions']);
+ return $this->generatePermissions(
+ $this->entityTypeManager->getStorage('node_type')->loadMultiple(),
+ [$this, 'buildPermissions']
+ );
}
/**
diff --git a/core/modules/node/src/PageCache/DenyNodePreview.php b/core/modules/node/src/PageCache/DenyNodePreview.php
deleted file mode 100644
index 0325f52b2017..000000000000
--- a/core/modules/node/src/PageCache/DenyNodePreview.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-namespace Drupal\node\PageCache;
-
-use Drupal\Core\PageCache\ResponsePolicyInterface;
-use Drupal\Core\Routing\RouteMatchInterface;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-
-/**
- * Cache policy for node preview page.
- *
- * This policy rule denies caching of responses generated by the
- * entity.node.preview route.
- */
-class DenyNodePreview implements ResponsePolicyInterface {
-
- /**
- * The current route match.
- *
- * @var \Drupal\Core\Routing\RouteMatchInterface
- */
- protected $routeMatch;
-
- /**
- * Constructs a deny node preview page cache policy.
- *
- * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
- * The current route match.
- */
- public function __construct(RouteMatchInterface $route_match) {
- $this->routeMatch = $route_match;
- }
-
- /**
- * {@inheritdoc}
- */
- public function check(Response $response, Request $request) {
- if ($this->routeMatch->getRouteName() === 'entity.node.preview') {
- return static::DENY;
- }
- }
-
-}
diff --git a/core/modules/node/src/Plugin/Block/SyndicateBlock.php b/core/modules/node/src/Plugin/Block/SyndicateBlock.php
index b10c63527e5b..45cfe1eb45c6 100644
--- a/core/modules/node/src/Plugin/Block/SyndicateBlock.php
+++ b/core/modules/node/src/Plugin/Block/SyndicateBlock.php
@@ -14,6 +14,11 @@ use Drupal\Core\Url;
/**
* Provides a 'Syndicate' block that links to the site's RSS feed.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+ * replacement.
+ *
+ * @see https://www.drupal.org/node/3519248
*/
#[Block(
id: "node_syndicate_block",
@@ -43,6 +48,7 @@ class SyndicateBlock extends BlockBase implements ContainerFactoryPluginInterfac
* The config factory.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $configFactory) {
+ @trigger_error('The Syndicate block is deprecated in drupal:11.2.0 and will be removed from drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3519248', E_USER_DEPRECATED);
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->configFactory = $configFactory;
}
diff --git a/core/modules/node/src/Plugin/views/UidRevisionTrait.php b/core/modules/node/src/Plugin/views/UidRevisionTrait.php
new file mode 100644
index 000000000000..5cbf21d56d42
--- /dev/null
+++ b/core/modules/node/src/Plugin/views/UidRevisionTrait.php
@@ -0,0 +1,38 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\node\Plugin\views;
+
+/**
+ * Checks for nodes that a user posted or created a revision on.
+ */
+trait UidRevisionTrait {
+
+ /**
+ * Checks for nodes that a user posted or created a revision on.
+ *
+ * @param array $uids
+ * A list of user ids.
+ * @param int $group
+ * See \Drupal\views\Plugin\views\query\Sql::addWhereExpression() $group.
+ */
+ public function uidRevisionQuery(array $uids, int $group = 0): void {
+ $this->ensureMyTable();
+
+ // As per https://www.php.net/manual/en/pdo.prepare.php "you cannot use a
+ // named parameter marker of the same name more than once in a prepared
+ // statement".
+ $placeholder_1 = $this->placeholder() . '[]';
+ $placeholder_2 = $this->placeholder() . '[]';
+
+ $args = array_values($uids);
+
+ $this->query->addWhereExpression($group, "$this->tableAlias.uid IN ($placeholder_1) OR
+ EXISTS (SELECT 1 FROM {node_revision} nr WHERE nr.revision_uid IN ($placeholder_2) AND nr.nid = $this->tableAlias.nid)", [
+ $placeholder_1 => $args,
+ $placeholder_2 => $args,
+ ]);
+ }
+
+}
diff --git a/core/modules/node/src/Plugin/views/argument/UidRevision.php b/core/modules/node/src/Plugin/views/argument/UidRevision.php
index 982152080a6b..9be0cc9d7b6f 100644
--- a/core/modules/node/src/Plugin/views/argument/UidRevision.php
+++ b/core/modules/node/src/Plugin/views/argument/UidRevision.php
@@ -2,6 +2,7 @@
namespace Drupal\node\Plugin\views\argument;
+use Drupal\node\Plugin\views\UidRevisionTrait;
use Drupal\user\Plugin\views\argument\Uid;
use Drupal\views\Attribute\ViewsArgument;
@@ -15,13 +16,13 @@ use Drupal\views\Attribute\ViewsArgument;
)]
class UidRevision extends Uid {
+ use UidRevisionTrait;
+
/**
* {@inheritdoc}
*/
public function query($group_by = FALSE) {
- $this->ensureMyTable();
- $placeholder = $this->placeholder();
- $this->query->addWhereExpression(0, "$this->tableAlias.uid = $placeholder OR ((SELECT COUNT(DISTINCT vid) FROM {node_revision} nr WHERE nr.revision_uid = $placeholder AND nr.nid = $this->tableAlias.nid) > 0)", [$placeholder => $this->argument]);
+ $this->uidRevisionQuery([$this->argument]);
}
}
diff --git a/core/modules/node/src/Plugin/views/filter/Access.php b/core/modules/node/src/Plugin/views/filter/Access.php
index 4934a2f2e635..4d579f687ce4 100644
--- a/core/modules/node/src/Plugin/views/filter/Access.php
+++ b/core/modules/node/src/Plugin/views/filter/Access.php
@@ -36,7 +36,7 @@ class Access extends FilterPluginBase {
*/
public function query() {
$account = $this->view->getUser();
- if (!$account->hasPermission('bypass node access')) {
+ if (!$account->hasPermission('bypass node access') && $this->moduleHandler->hasImplementations('node_grants')) {
$table = $this->ensureMyTable();
$grants = $this->query->getConnection()->condition('OR');
foreach (node_access_grants('view', $account) as $realm => $gids) {
diff --git a/core/modules/node/src/Plugin/views/filter/UidRevision.php b/core/modules/node/src/Plugin/views/filter/UidRevision.php
index b7f186fa07d1..cf962a2897e0 100644
--- a/core/modules/node/src/Plugin/views/filter/UidRevision.php
+++ b/core/modules/node/src/Plugin/views/filter/UidRevision.php
@@ -2,6 +2,7 @@
namespace Drupal\node\Plugin\views\filter;
+use Drupal\node\Plugin\views\UidRevisionTrait;
use Drupal\user\Plugin\views\filter\Name;
use Drupal\views\Attribute\ViewsFilter;
@@ -13,19 +14,13 @@ use Drupal\views\Attribute\ViewsFilter;
#[ViewsFilter("node_uid_revision")]
class UidRevision extends Name {
+ use UidRevisionTrait;
+
/**
* {@inheritdoc}
*/
public function query($group_by = FALSE) {
- $this->ensureMyTable();
-
- $placeholder = $this->placeholder() . '[]';
-
- $args = array_values($this->value);
-
- $this->query->addWhereExpression($this->options['group'], "$this->tableAlias.uid IN($placeholder) OR
- ((SELECT COUNT(DISTINCT vid) FROM {node_revision} nr WHERE nr.revision_uid IN ($placeholder) AND nr.nid = $this->tableAlias.nid) > 0)", [$placeholder => $args],
- $args);
+ $this->uidRevisionQuery($this->value, $this->options['group']);
}
}
diff --git a/core/modules/node/src/Plugin/views/wizard/Node.php b/core/modules/node/src/Plugin/views/wizard/Node.php
index 84a21c3f7f2e..d66fa956f9aa 100644
--- a/core/modules/node/src/Plugin/views/wizard/Node.php
+++ b/core/modules/node/src/Plugin/views/wizard/Node.php
@@ -89,11 +89,7 @@ class Node extends WizardPluginBase {
}
/**
- * Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::getAvailableSorts().
- *
- * @return array
- * An array whose keys are the available sort options and whose
- * corresponding values are human readable labels.
+ * {@inheritdoc}
*/
public function getAvailableSorts() {
// You can't execute functions in properties, so override the method
@@ -238,9 +234,7 @@ class Node extends WizardPluginBase {
}
/**
- * Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::buildFilters().
- *
- * Add some options for filter by taxonomy terms.
+ * {@inheritdoc}
*/
protected function buildFilters(&$form, FormStateInterface $form_state) {
parent::buildFilters($form, $form_state);
diff --git a/core/modules/node/src/Plugin/views/wizard/NodeRevision.php b/core/modules/node/src/Plugin/views/wizard/NodeRevision.php
index c10389cc2392..96422504cf5e 100644
--- a/core/modules/node/src/Plugin/views/wizard/NodeRevision.php
+++ b/core/modules/node/src/Plugin/views/wizard/NodeRevision.php
@@ -29,11 +29,10 @@ class NodeRevision extends WizardPluginBase {
protected $createdColumn = 'changed';
/**
- * Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::rowStyleOptions().
- *
- * Node revisions do not support full posts or teasers, so remove them.
+ * {@inheritdoc}
*/
protected function rowStyleOptions() {
+ // Node revisions do not support full posts or teasers, so remove them.
$options = parent::rowStyleOptions();
unset($options['teasers']);
unset($options['full_posts']);
diff --git a/core/modules/node/tests/modules/node_no_default_author/node_no_default_author.info.yml b/core/modules/node/tests/modules/node_no_default_author/node_no_default_author.info.yml
new file mode 100644
index 000000000000..c4f56344370f
--- /dev/null
+++ b/core/modules/node/tests/modules/node_no_default_author/node_no_default_author.info.yml
@@ -0,0 +1,5 @@
+name: 'Node no default author'
+type: module
+description: 'Disables the default value callback for the uid field on node.'
+package: Testing
+version: VERSION
diff --git a/core/modules/node/tests/modules/node_no_default_author/src/Hook/NodeNoDefaultAuthorHooks.php b/core/modules/node/tests/modules/node_no_default_author/src/Hook/NodeNoDefaultAuthorHooks.php
new file mode 100644
index 000000000000..700e82236adc
--- /dev/null
+++ b/core/modules/node/tests/modules/node_no_default_author/src/Hook/NodeNoDefaultAuthorHooks.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\node_no_default_author\Hook;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for node_no_default_author.
+ */
+class NodeNoDefaultAuthorHooks {
+
+ /**
+ * Implements hook_entity_base_field_info_alter().
+ */
+ #[Hook('entity_base_field_info_alter')]
+ public function entityBaseFieldInfoAlter(&$fields, EntityTypeInterface $entity_type): void {
+ if ($entity_type->id() === 'node') {
+ $fields['uid']->setDefaultValueCallback(static::class . '::noDefaultAuthor');
+ }
+ }
+
+ /**
+ * An empty callback to set for the default value callback of uid.
+ */
+ public static function noDefaultAuthor(): void {
+ }
+
+}
diff --git a/core/modules/node/tests/modules/node_test/src/Hook/NodeTestHooks.php b/core/modules/node/tests/modules/node_test/src/Hook/NodeTestHooks.php
index 1887bc56ea6c..201e781d1963 100644
--- a/core/modules/node/tests/modules/node_test/src/Hook/NodeTestHooks.php
+++ b/core/modules/node/tests/modules/node_test/src/Hook/NodeTestHooks.php
@@ -32,14 +32,14 @@ class NodeTestHooks {
];
// Add content that should be displayed only in the RSS feed.
$build['extra_feed_content'] = [
- '#markup' => '<p>' . 'Extra data that should appear only in the RSS feed for node ' . $node->id() . '.</p>',
+ '#markup' => '<p>Extra data that should appear only in the RSS feed for node ' . $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'] = [
- '#markup' => '<p>' . 'Extra data that should appear everywhere except the RSS feed for node ' . $node->id() . '.</p>',
+ '#markup' => '<p>Extra data that should appear everywhere except the RSS feed for node ' . $node->id() . '.</p>',
];
}
}
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml
index 766f656fc2bf..65ca46658711 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_access_join.yml
@@ -68,6 +68,16 @@ display:
quantity: 9
style:
type: table
+ options:
+ grouping: { }
+ class: ''
+ row_class: ''
+ default_row_class: true
+ override: true
+ sticky: false
+ caption: ''
+ summary: ''
+ description: ''
row:
type: fields
fields:
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
index d7a3fa080fed..107165a27fb6 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
@@ -19,6 +19,8 @@ display:
display_options:
style:
type: table
+ options:
+ class: ''
row:
type: fields
fields:
diff --git a/core/modules/node/tests/src/Functional/NodeAccessCacheRedirectWarningTest.php b/core/modules/node/tests/src/Functional/NodeAccessCacheRedirectWarningTest.php
new file mode 100644
index 000000000000..0d49a7c416ce
--- /dev/null
+++ b/core/modules/node/tests/src/Functional/NodeAccessCacheRedirectWarningTest.php
@@ -0,0 +1,89 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\node\Functional;
+
+/**
+ * Tests the node access grants cache context service.
+ *
+ * @group node
+ * @group Cache
+ */
+class NodeAccessCacheRedirectWarningTest extends NodeTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $modules = ['block', 'node_access_test_empty'];
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $defaultTheme = 'stark';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void {
+ parent::setUp();
+
+ node_access_rebuild();
+ }
+
+ /**
+ * Ensures that node access checks don't cause cache redirect warnings.
+ *
+ * @covers \Drupal\node\NodeAccessControlHandler
+ */
+ public function testNodeAccessCacheRedirectWarning(): void {
+ $this->drupalPlaceBlock('local_tasks_block');
+
+ // Ensure that both a node_grants implementation exists, and that the
+ // current user has 'view own unpublished nodes' permission. Node's access
+ // control handler bypasses node grants when 'view own published nodes' is
+ // granted and the node is unpublished, which means that the code path is
+ // significantly different when a node is published vs. unpublished, and
+ // that cache contexts vary depend on the state of the node.
+ $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('node_grants'));
+
+ $author = $this->drupalCreateUser([
+ 'create page content',
+ 'edit any page content',
+ 'view own unpublished content',
+ ]);
+ $this->drupalLogin($author);
+
+ $node = $this->drupalCreateNode(['uid' => $author->id(), 'status' => 0]);
+
+ $this->drupalGet($node->toUrl());
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->pageTextContains($node->label());
+
+ $node->setPublished();
+ $node->save();
+
+ $this->drupalGet($node->toUrl());
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->pageTextContains($node->label());
+
+ // When the node has been viewed in both the unpublished and published state
+ // a cache redirect should exist for the local tasks block. Repeating the
+ // process of changing the node status and viewing the node will test that
+ // no stale redirect is found.
+ $node->setUnpublished();
+ $node->save();
+
+ $this->drupalGet($node->toUrl());
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->pageTextContains($node->label());
+
+ $node->setPublished();
+ $node->save();
+
+ $this->drupalGet($node->toUrl());
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->pageTextContains($node->label());
+ }
+
+}
diff --git a/core/modules/node/tests/src/Functional/NodeCreationTest.php b/core/modules/node/tests/src/Functional/NodeCreationTest.php
index f0192966a1b5..7e99e3ba2ec1 100644
--- a/core/modules/node/tests/src/Functional/NodeCreationTest.php
+++ b/core/modules/node/tests/src/Functional/NodeCreationTest.php
@@ -183,8 +183,9 @@ class NodeCreationTest extends NodeTestBase {
// Confirm that the node was created.
$this->assertSession()->pageTextContains('Basic page ' . $edit['title[0][value]'] . ' has been created.');
- // Verify that the creation message contains a link to a node.
- $this->assertSession()->elementExists('xpath', '//div[@data-drupal-messages]//a[contains(@href, "node/")]');
+ // Verify that the creation message doesn't contain a link to a node since
+ // the user cannot view unpublished nodes.
+ $this->assertSession()->elementNotExists('xpath', '//div[@data-drupal-messages]//a[contains(@href, "node/")]');
}
/**
@@ -310,6 +311,21 @@ class NodeCreationTest extends NodeTestBase {
}
/**
+ * Tests exception handling when saving a node through the form.
+ */
+ public function testNodeCreateExceptionHandling(): void {
+ $this->drupalGet('node/add/page');
+
+ $this->submitForm([
+ 'title[0][value]' => 'testing_transaction_exception',
+ 'body[0][value]' => $this->randomMachineName(16),
+ ], 'Save');
+
+ $this->assertSession()->pageTextNotContains('The website encountered an unexpected error.');
+ $this->assertSession()->pageTextContains('The content could not be saved. Contact the site administrator if the problem persists.');
+ }
+
+ /**
* Gets the watchdog IDs of the records with the rollback exception message.
*
* @return int[]
diff --git a/core/modules/node/tests/src/Functional/NodeEditFormTest.php b/core/modules/node/tests/src/Functional/NodeEditFormTest.php
index bc8cfa927e43..f661ae18ebbb 100644
--- a/core/modules/node/tests/src/Functional/NodeEditFormTest.php
+++ b/core/modules/node/tests/src/Functional/NodeEditFormTest.php
@@ -251,10 +251,25 @@ class NodeEditFormTest extends NodeTestBase {
$edit['body[0][value]'] = $this->randomMachineName(16);
$this->submitForm($edit, 'Save');
+ // Enable user_hooks_test to test the users display name is visible on the
+ // edit form.
+ \Drupal::service('module_installer')->install(['user_hooks_test']);
+ \Drupal::keyValue('user_hooks_test')->set('user_format_name_alter', TRUE);
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
- $this->drupalGet("node/" . $node->id() . "/edit");
+ $this->drupalGet($node->toUrl('edit-form'));
$this->assertSession()->pageTextContains('Published');
$this->assertSession()->pageTextContains($this->container->get('date.formatter')->format($node->getChangedTime(), 'short'));
+ $this->assertSession()->responseContains('<em>' . $this->adminUser->id() . '</em>');
+ }
+
+ /**
+ * Tests the node form when the author is NULL.
+ */
+ public function testNodeFormNullAuthor(): void {
+ \Drupal::service('module_installer')->install(['node_no_default_author']);
+ $this->drupalLogin($this->adminUser);
+ $this->drupalGet('node/add/page');
+ $this->assertSession()->statusCodeEquals(200);
}
/**
diff --git a/core/modules/node/tests/src/Functional/NodeRevisionsAuthorTest.php b/core/modules/node/tests/src/Functional/NodeRevisionsAuthorTest.php
new file mode 100644
index 000000000000..5a930df3e2df
--- /dev/null
+++ b/core/modules/node/tests/src/Functional/NodeRevisionsAuthorTest.php
@@ -0,0 +1,102 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\node\Functional;
+
+use Drupal\Core\Url;
+
+/**
+ * Tests reverting node revisions correctly sets authorship information.
+ *
+ * @group node
+ */
+class NodeRevisionsAuthorTest extends NodeTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $defaultTheme = 'stark';
+
+ /**
+ * Tests node authorship is retained after reverting revisions.
+ */
+ public function testNodeRevisionRevertAuthors(): void {
+ // Create and log in user.
+ $initialUser = $this->drupalCreateUser([
+ 'view page revisions',
+ 'revert page revisions',
+ 'edit any page content',
+ ]);
+ $initialRevisionUser = $this->drupalCreateUser();
+ // Third user is an author only and needs no permissions
+ $initialRevisionAuthor = $this->drupalCreateUser();
+
+ // Create initial node (author: $user1).
+ $this->drupalLogin($initialUser);
+ $node = $this->drupalCreateNode();
+ $originalRevisionId = $node->getRevisionId();
+ $originalBody = $node->body->value;
+ $originalTitle = $node->getTitle();
+
+ // Create a revision (as $initialUser) showing $initialRevisionAuthor
+ // as author.
+ $node->setRevisionLogMessage('Changed author');
+ $revisedTitle = $this->randomMachineName();
+ $node->setTitle($revisedTitle);
+ $revisedBody = $this->randomMachineName(32);
+ $node->set('body', [
+ 'value' => $revisedBody,
+ 'format' => filter_default_format(),
+ ]);
+ $node->setOwnerId($initialRevisionAuthor->id());
+ $node->setRevisionUserId($initialRevisionUser->id());
+ $node->setNewRevision();
+ $node->save();
+ $revisedRevisionId = $node->getRevisionId();
+
+ $nodeStorage = \Drupal::entityTypeManager()->getStorage('node');
+
+ self::assertEquals($node->getOwnerId(), $initialRevisionAuthor->id());
+ self::assertEquals($node->getRevisionUserId(), $initialRevisionUser->id());
+
+ // Revert to the original node revision.
+ $this->drupalGet(Url::fromRoute('node.revision_revert_confirm', [
+ 'node' => $node->id(),
+ 'node_revision' => $originalRevisionId,
+ ]));
+ $this->submitForm([], 'Revert');
+ $this->assertSession()->pageTextContains(\sprintf('Basic page %s has been reverted', $originalTitle));
+
+ // With the revert done, reload the node and verify that the authorship
+ // fields have reverted correctly.
+ $nodeStorage->resetCache([$node->id()]);
+ /** @var \Drupal\node\NodeInterface $revertedNode */
+ $revertedNode = $nodeStorage->load($node->id());
+ self::assertEquals($originalBody, $revertedNode->body->value);
+ self::assertEquals($initialUser->id(), $revertedNode->getOwnerId());
+ self::assertEquals($initialUser->id(), $revertedNode->getRevisionUserId());
+
+ // Revert again to the revised version and check that node author and
+ // revision author fields are correct.
+ // Revert to the original node.
+ $this->drupalGet(Url::fromRoute('node.revision_revert_confirm', [
+ 'node' => $revertedNode->id(),
+ 'node_revision' => $revisedRevisionId,
+ ]));
+ $this->submitForm([], 'Revert');
+ $this->assertSession()->pageTextContains(\sprintf('Basic page %s has been reverted', $revisedTitle));
+
+ // With the reversion done, reload the node and verify that the
+ // authorship fields have reverted correctly.
+ $nodeStorage->resetCache([$revertedNode->id()]);
+ /** @var \Drupal\node\NodeInterface $re_reverted_node */
+ $re_reverted_node = $nodeStorage->load($revertedNode->id());
+ self::assertEquals($revisedBody, $re_reverted_node->body->value);
+ self::assertEquals($initialRevisionAuthor->id(), $re_reverted_node->getOwnerId());
+ // The new revision user will be the current logged in user as set in
+ // NodeRevisionRevertForm.
+ self::assertEquals($initialUser->id(), $re_reverted_node->getRevisionUserId());
+ }
+
+}
diff --git a/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php b/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php
index 201d4b6c7d25..88fe3e34e3ed 100644
--- a/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php
+++ b/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php
@@ -215,20 +215,4 @@ class NodeRevisionsUiTest extends NodeTestBase {
$this->assertSession()->elementsCount('xpath', $xpath, 1);
}
- /**
- * Tests the node revisions page is cacheable by dynamic page cache.
- */
- public function testNodeRevisionsCacheability(): void {
- $this->drupalLogin($this->editor);
- $node = $this->drupalCreateNode();
- // Admin paths are always uncacheable by dynamic page cache, swap node
- // to non admin theme to test cacheability.
- $this->config('node.settings')->set('use_admin_theme', FALSE)->save();
- \Drupal::service('router.builder')->rebuild();
- $this->drupalGet($node->toUrl('version-history'));
- $this->assertSession()->responseHeaderEquals('X-Drupal-Dynamic-Cache', 'MISS');
- $this->drupalGet($node->toUrl('version-history'));
- $this->assertSession()->responseHeaderEquals('X-Drupal-Dynamic-Cache', 'HIT');
- }
-
}
diff --git a/core/modules/node/tests/src/Functional/NodeSyndicateBlockTest.php b/core/modules/node/tests/src/Functional/NodeSyndicateBlockTest.php
index c3a3d46b4960..f8d52b06ecb3 100644
--- a/core/modules/node/tests/src/Functional/NodeSyndicateBlockTest.php
+++ b/core/modules/node/tests/src/Functional/NodeSyndicateBlockTest.php
@@ -8,6 +8,7 @@ namespace Drupal\Tests\node\Functional;
* Tests if the syndicate block is available.
*
* @group node
+ * @group legacy
*/
class NodeSyndicateBlockTest extends NodeTestBase {
@@ -40,6 +41,7 @@ class NodeSyndicateBlockTest extends NodeTestBase {
$this->drupalPlaceBlock('node_syndicate_block', ['id' => 'test_syndicate_block', 'label' => 'Subscribe to RSS Feed']);
$this->drupalGet('');
$this->assertSession()->elementExists('xpath', '//div[@id="block-test-syndicate-block"]/*');
+ $this->expectDeprecation('The Syndicate block is deprecated in drupal:11.2.0 and will be removed from drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3519248');
// Verify syndicate block title.
$this->assertSession()->pageTextContains('Subscribe to RSS Feed');
diff --git a/core/modules/node/tests/src/Functional/NodeTranslationUITest.php b/core/modules/node/tests/src/Functional/NodeTranslationUITest.php
index 2bb252f7c6e1..ac1e8664badf 100644
--- a/core/modules/node/tests/src/Functional/NodeTranslationUITest.php
+++ b/core/modules/node/tests/src/Functional/NodeTranslationUITest.php
@@ -242,21 +242,19 @@ class NodeTranslationUITest extends ContentTranslationUITestBase {
// Set up the default admin theme and use it for node editing.
$this->container->get('theme_installer')->install(['claro']);
- $edit = [];
- $edit['admin_theme'] = 'claro';
- $edit['use_admin_theme'] = TRUE;
- $this->drupalGet('admin/appearance');
- $this->submitForm($edit, 'Save configuration');
- $this->drupalGet('node/' . $article->id() . '/translations');
+ $this->config('system.theme')->set('admin', 'claro')->save();
+
// Verify that translation uses the admin theme if edit is admin.
+ $this->drupalGet('node/' . $article->id() . '/translations');
$this->assertSession()->responseContains('core/themes/claro/css/base/elements.css');
// Turn off admin theme for editing, assert inheritance to translations.
- $edit['use_admin_theme'] = FALSE;
- $this->drupalGet('admin/appearance');
- $this->submitForm($edit, 'Save configuration');
- $this->drupalGet('node/' . $article->id() . '/translations');
+ $this->config('node.settings')->set('use_admin_theme', FALSE)->save();
+ // Changing node.settings:use_admin_theme requires a route rebuild.
+ $this->container->get('router.builder')->rebuild();
+
// Verify that translation uses the frontend theme if edit is frontend.
+ $this->drupalGet('node/' . $article->id() . '/translations');
$this->assertSession()->responseNotContains('core/themes/claro/css/base/elements.css');
// Assert presence of translation page itself (vs. DisabledBundle below).
@@ -561,12 +559,10 @@ class NodeTranslationUITest extends ContentTranslationUITestBase {
'translatable' => TRUE,
])->save();
- $this->drupalLogin($this->administrator);
// Make the image field a multi-value field in order to display a
// details form element.
- $edit = ['field_storage[subform][cardinality_number]' => 2];
- $this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_image');
- $this->submitForm($edit, 'Save');
+ $fieldStorage = FieldStorageConfig::loadByName('node', 'field_image');
+ $fieldStorage->setCardinality(2)->save();
// Enable the display of the image field.
EntityFormDisplay::load('node.article.default')
diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php
index cbe9b346623e..ac47588d5ec4 100644
--- a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php
+++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php
@@ -18,6 +18,7 @@ use Drupal\Tests\migrate_drupal\Traits\NodeMigrateTypeTestTrait;
* Test class for a complete node migration for Drupal 7.
*
* @group migrate_drupal_7
+ * @group #slow
*/
class MigrateNodeCompleteTest extends MigrateDrupal7TestBase {
diff --git a/core/modules/node/tests/src/Kernel/NodeRequirementsStatusFilterWarningTest.php b/core/modules/node/tests/src/Kernel/NodeRequirementsStatusFilterWarningTest.php
index 6ddb8c8c987d..b86b69e8ad1e 100644
--- a/core/modules/node/tests/src/Kernel/NodeRequirementsStatusFilterWarningTest.php
+++ b/core/modules/node/tests/src/Kernel/NodeRequirementsStatusFilterWarningTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Drupal\Tests\node\Kernel;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\views\Entity\View;
@@ -77,7 +78,7 @@ class NodeRequirementsStatusFilterWarningTest extends KernelTestBase {
$requirements = $this->getRequirements();
$this->assertArrayHasKey('node_status_filter', $requirements);
- $this->assertEquals(REQUIREMENT_WARNING, $requirements['node_status_filter']['severity']);
+ $this->assertEquals(RequirementSeverity::Warning, $requirements['node_status_filter']['severity']);
}
/**
@@ -102,7 +103,7 @@ class NodeRequirementsStatusFilterWarningTest extends KernelTestBase {
$requirements = $this->getRequirements();
$this->assertArrayHasKey('node_status_filter', $requirements);
- $this->assertEquals(REQUIREMENT_WARNING, $requirements['node_status_filter']['severity']);
+ $this->assertEquals(RequirementSeverity::Warning, $requirements['node_status_filter']['severity']);
}
/**
@@ -197,8 +198,7 @@ class NodeRequirementsStatusFilterWarningTest extends KernelTestBase {
* The requirements raised by the Node module.
*/
private function getRequirements(): array {
- $this->container->get('module_handler')->loadInclude('node', 'install');
- return node_requirements('runtime');
+ return $this->container->get('module_handler')->invoke('node', 'runtime_requirements');
}
/**
diff --git a/core/modules/node/tests/src/Unit/PageCache/DenyNodePreviewTest.php b/core/modules/node/tests/src/Unit/PageCache/DenyNodePreviewTest.php
deleted file mode 100644
index 1baf081ed000..000000000000
--- a/core/modules/node/tests/src/Unit/PageCache/DenyNodePreviewTest.php
+++ /dev/null
@@ -1,92 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Tests\node\Unit\PageCache;
-
-use Drupal\Core\PageCache\ResponsePolicyInterface;
-use Drupal\node\PageCache\DenyNodePreview;
-use Drupal\Tests\UnitTestCase;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-
-/**
- * @coversDefaultClass \Drupal\node\PageCache\DenyNodePreview
- * @group node
- */
-class DenyNodePreviewTest extends UnitTestCase {
-
- /**
- * The response policy under test.
- *
- * @var \Drupal\node\PageCache\DenyNodePreview
- */
- protected $policy;
-
- /**
- * A request object.
- *
- * @var \Symfony\Component\HttpFoundation\Request
- */
- protected $request;
-
- /**
- * A response object.
- *
- * @var \Symfony\Component\HttpFoundation\Response
- */
- protected $response;
-
- /**
- * The current route match.
- *
- * @var \Drupal\Core\Routing\RouteMatch|\PHPUnit\Framework\MockObject\MockObject
- */
- protected $routeMatch;
-
- /**
- * {@inheritdoc}
- */
- protected function setUp(): void {
- parent::setUp();
-
- $this->routeMatch = $this->createMock('Drupal\Core\Routing\RouteMatchInterface');
- $this->policy = new DenyNodePreview($this->routeMatch);
- $this->response = new Response();
- $this->request = new Request();
- }
-
- /**
- * Asserts that caching is denied on the node preview route.
- *
- * @dataProvider providerPrivateImageStyleDownloadPolicy
- * @covers ::check
- */
- public function testPrivateImageStyleDownloadPolicy($expected_result, $route_name): void {
- $this->routeMatch->expects($this->once())
- ->method('getRouteName')
- ->willReturn($route_name);
-
- $actual_result = $this->policy->check($this->response, $this->request);
- $this->assertSame($expected_result, $actual_result);
- }
-
- /**
- * Provides data and expected results for the test method.
- *
- * @return array
- * Data and expected results.
- */
- public static function providerPrivateImageStyleDownloadPolicy() {
- return [
- [ResponsePolicyInterface::DENY, 'entity.node.preview'],
- [NULL, 'some.other.route'],
- [NULL, NULL],
- [NULL, FALSE],
- [NULL, TRUE],
- [NULL, new \stdClass()],
- [NULL, [1, 2, 3]],
- ];
- }
-
-}