From 9beb71e61014eb31fef2ab0312572ff1ea1b3a82 Mon Sep 17 00:00:00 2001 From: catch Date: Thu, 27 Aug 2020 10:25:44 +0100 Subject: [PATCH] Issue #2614720 by quietone, roderik, ofry, Dylan Donkersgoed, Deepak Goyal, samiullah, jonathanshaw, benjifisher, larowlan, Rewted, Ruedische, KlemenDEV, ao2, abramm, vadim.hirbu, HiMyNameIsSeb, dionsj, mgp_novicell, scalas89, rollins, dmytro-aragorn, hchonov, mayurjadhav, paranojik, nplowman: Fatal errors while loading/building orphaned comments --- core/modules/comment/comment.module | 3 +- .../comment/src/CommentLazyBuilders.php | 6 +- .../comment/src/CommentViewBuilder.php | 16 ++- core/modules/comment/src/Entity/Comment.php | 3 +- .../tests/src/Kernel/CommentOrphanTest.php | 134 ++++++++++++++++++ 5 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 core/modules/comment/tests/src/Kernel/CommentOrphanTest.php diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index db68f43cd82..4d5677ecce9 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -607,9 +607,8 @@ function template_preprocess_comment(&$variables) { $variables['submitted'] = t('Submitted by @username on @datetime', ['@username' => $variables['author'], '@datetime' => $variables['created']]); - if ($comment->hasParentComment()) { + if ($comment_parent = $comment->getParentComment()) { // Fetch and store the parent comment information for use in templates. - $comment_parent = $comment->getParentComment(); $account_parent = $comment_parent->getOwner(); $variables['parent_comment'] = $comment_parent; $username = [ diff --git a/core/modules/comment/src/CommentLazyBuilders.php b/core/modules/comment/src/CommentLazyBuilders.php index 90c2c696fc8..90b2811eb48 100644 --- a/core/modules/comment/src/CommentLazyBuilders.php +++ b/core/modules/comment/src/CommentLazyBuilders.php @@ -137,9 +137,9 @@ class CommentLazyBuilders implements TrustedCallbackInterface { if (!$is_in_preview) { /** @var \Drupal\comment\CommentInterface $entity */ $entity = $this->entityTypeManager->getStorage('comment')->load($comment_entity_id); - $commented_entity = $entity->getCommentedEntity(); - - $links['comment'] = $this->buildLinks($entity, $commented_entity); + if ($commented_entity = $entity->getCommentedEntity()) { + $links['comment'] = $this->buildLinks($entity, $commented_entity); + } // Allow other modules to alter the comment links. $hook_context = [ diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php index b3fd17e59c3..e2da95b850a 100644 --- a/core/modules/comment/src/CommentViewBuilder.php +++ b/core/modules/comment/src/CommentViewBuilder.php @@ -80,9 +80,11 @@ class CommentViewBuilder extends EntityViewBuilder { /** @var \Drupal\comment\CommentInterface $entity */ // Store a threading field setting to use later in self::buildComponents(). - $build['#comment_threaded'] = $entity->getCommentedEntity() - ->getFieldDefinition($entity->getFieldName()) - ->getSetting('default_mode') === CommentManagerInterface::COMMENT_MODE_THREADED; + $commented_entity = $entity->getCommentedEntity(); + $build['#comment_threaded'] = + is_null($commented_entity) + || $commented_entity->getFieldDefinition($entity->getFieldName()) + ->getSetting('default_mode') === CommentManagerInterface::COMMENT_MODE_THREADED; // If threading is enabled, don't render cache individual comments, but do // keep the cacheability metadata, so it can bubble up. if ($build['#comment_threaded']) { @@ -140,10 +142,12 @@ class CommentViewBuilder extends EntityViewBuilder { // Commented entities already loaded after self::getBuildDefaults(). $commented_entity = $entity->getCommentedEntity(); + // Set defaults if the commented_entity does not exist. + $bundle = $commented_entity ? $commented_entity->bundle() : ''; + $is_node = $commented_entity ? $commented_entity->getEntityTypeId() === 'node' : NULL; $build[$id]['#entity'] = $entity; - $build[$id]['#theme'] = 'comment__' . $entity->getFieldName() . '__' . $commented_entity->bundle(); - + $build[$id]['#theme'] = 'comment__' . $entity->getFieldName() . '__' . $bundle; $display = $displays[$entity->bundle()]; if ($display->getComponent('links')) { $build[$id]['links'] = [ @@ -164,7 +168,7 @@ class CommentViewBuilder extends EntityViewBuilder { $build[$id]['#attached'] = []; } $build[$id]['#attached']['library'][] = 'comment/drupal.comment-by-viewer'; - if ($attach_history && $commented_entity->getEntityTypeId() === 'node') { + if ($attach_history && $is_node) { $build[$id]['#attached']['library'][] = 'comment/drupal.comment-new-indicator'; // Embed the metadata for the comment "new" indicators on this node. diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php index 624ca268559..f9afe73124d 100644 --- a/core/modules/comment/src/Entity/Comment.php +++ b/core/modules/comment/src/Entity/Comment.php @@ -404,7 +404,8 @@ class Comment extends ContentEntityBase implements CommentInterface { * {@inheritdoc} */ public function getAuthorName() { - if ($this->get('uid')->target_id) { + // If their is a valid user id and the user entity exists return the label. + if ($this->get('uid')->target_id && $this->get('uid')->entity) { return $this->get('uid')->entity->label(); } return $this->get('name')->value ?: \Drupal::config('user.settings')->get('anonymous'); diff --git a/core/modules/comment/tests/src/Kernel/CommentOrphanTest.php b/core/modules/comment/tests/src/Kernel/CommentOrphanTest.php new file mode 100644 index 00000000000..2f1ad83f129 --- /dev/null +++ b/core/modules/comment/tests/src/Kernel/CommentOrphanTest.php @@ -0,0 +1,134 @@ +installEntitySchema('date_format'); + $this->installEntitySchema('comment'); + $this->installSchema('comment', ['comment_entity_statistics']); + } + + /** + * Test loading/deleting/rendering orphaned comments. + * + * @dataProvider providerTestOrphan + */ + public function testOrphan($property) { + + DateFormat::create([ + 'id' => 'fallback', + 'label' => 'Fallback', + 'pattern' => 'Y-m-d', + ])->save(); + + $comment_storage = $this->entityTypeManager->getStorage('comment'); + $node_storage = $this->entityTypeManager->getStorage('node'); + + // Create a page node type. + $this->entityTypeManager->getStorage('node_type')->create([ + 'type' => 'page', + 'name' => 'page', + ])->save(); + + $node = $node_storage->create([ + 'type' => 'page', + 'title' => 'test', + ]); + $node->save(); + + // Create comment field. + $this->entityTypeManager->getStorage('field_storage_config')->create([ + 'type' => 'text_long', + 'entity_type' => 'node', + 'field_name' => 'comment', + ])->save(); + + // Add comment field to page content. + $this->entityTypeManager->getStorage('field_config')->create([ + 'field_storage' => FieldStorageConfig::loadByName('node', 'comment'), + 'entity_type' => 'node', + 'bundle' => 'page', + 'label' => 'Comment', + ])->save(); + + // Make two comments + $comment1 = $comment_storage->create([ + 'field_name' => 'comment', + 'comment_body' => 'test', + 'entity_id' => $node->id(), + 'entity_type' => 'node', + 'comment_type' => 'default', + ])->save(); + + $comment_storage->create([ + 'field_name' => 'comment', + 'comment_body' => 'test', + 'entity_id' => $node->id(), + 'entity_type' => 'node', + 'comment_type' => 'default', + 'pid' => $comment1, + ])->save(); + + // Render the comments. + $renderer = \Drupal::service('renderer'); + $comments = $comment_storage->loadMultiple(); + foreach ($comments as $comment) { + $built = $this->buildEntityView($comment, 'full', NULL); + $renderer->renderPlain($built); + } + + // Make comment 2 an orphan by setting the property to an invalid value. + \Drupal::database()->update('comment_field_data') + ->fields([$property => 10]) + ->condition('cid', 2) + ->execute(); + $comment_storage->resetCache(); + $node_storage->resetCache(); + + // Render the comments with an orphan comment. + $comments = $comment_storage->loadMultiple(); + foreach ($comments as $comment) { + $built = $this->buildEntityView($comment, 'full', NULL); + $renderer->renderPlain($built); + } + + $node = $node_storage->load($node->id()); + $built = $this->buildEntityView($node, 'full', NULL); + $renderer->renderPlain($built); + } + + /** + * Provides test data for testOrphan. + */ + public function providerTestOrphan() { + return [ + ['entity_id'], + ['uid'], + ['pid'], + ]; + } + +}