summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/node/src
diff options
context:
space:
mode:
Diffstat (limited to 'core/modules/node/src')
-rw-r--r--core/modules/node/src/Controller/NodeController.php7
-rw-r--r--core/modules/node/src/Form/NodeForm.php43
-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/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
18 files changed, 303 insertions, 66 deletions
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/Form/NodeForm.php b/core/modules/node/src/Form/NodeForm.php
index d5afa396568c..295e9ab78ce0 100644
--- a/core/modules/node/src/Form/NodeForm.php
+++ b/core/modules/node/src/Form/NodeForm.php
@@ -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/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/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']);