diff options
Diffstat (limited to 'core/modules/node/src')
15 files changed, 137 insertions, 56 deletions
diff --git a/core/modules/node/src/Controller/NodeController.php b/core/modules/node/src/Controller/NodeController.php index e860d0c1d2a..d5a35f64285 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 d5afa396568..295e9ab78ce 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 d5f84e0359b..8a6b4d887c8 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 8e25f2eb066..d2dbf545c3c 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 index aa8b39d5682..84f74aee98c 100644 --- a/core/modules/node/src/Hook/NodeRequirements.php +++ b/core/modules/node/src/Hook/NodeRequirements.php @@ -4,6 +4,7 @@ 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; @@ -144,7 +145,7 @@ class NodeRequirements { 'title' => $this->t('Content status filter'), 'value' => $this->t('Redundant filters detected'), 'description' => $node_status_filter_description, - 'severity' => REQUIREMENT_WARNING, + 'severity' => RequirementSeverity::Warning, ]; } } diff --git a/core/modules/node/src/Hook/NodeThemeHooks.php b/core/modules/node/src/Hook/NodeThemeHooks.php index 7ee443c458f..7ed0ef91f5f 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/NodeAccessControlHandlerInterface.php b/core/modules/node/src/NodeAccessControlHandlerInterface.php index 588391394ee..0d67cfb7bd6 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 eea6cc10012..fbaab21a211 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 cce74476563..d343a2f350b 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 e913f5326f3..5f651830192 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 b10c63527e5..45cfe1eb45c 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 00000000000..5cbf21d56d4 --- /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 982152080a6..9be0cc9d7b6 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 4934a2f2e63..4d579f687ce 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 b7f186fa07d..cf962a2897e 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']); } } |