From 9beb71e61014eb31fef2ab0312572ff1ea1b3a82 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
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 db68f43cd827..4d5677ecce92 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 90c2c696fc8f..90b2811eb48f 100644
--- a/core/modules/comment/src/CommentLazyBuilders.php
+++ b/core/modules/comment/src/CommentLazyBuilders.php
@@ -137,9 +137,9 @@ public function renderLinks($comment_entity_id, $view_mode, $langcode, $is_in_pr
     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 b3fd17e59c30..e2da95b850ab 100644
--- a/core/modules/comment/src/CommentViewBuilder.php
+++ b/core/modules/comment/src/CommentViewBuilder.php
@@ -80,9 +80,11 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode) {
 
     /** @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 @@ public function buildComponents(array &$build, array $entities, array $displays,
 
       // 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 @@ public function buildComponents(array &$build, array $entities, array $displays,
         $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 624ca268559e..f9afe73124d3 100644
--- a/core/modules/comment/src/Entity/Comment.php
+++ b/core/modules/comment/src/Entity/Comment.php
@@ -404,7 +404,8 @@ public function setSubject($subject) {
    * {@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 000000000000..2f1ad83f1296
--- /dev/null
+++ b/core/modules/comment/tests/src/Kernel/CommentOrphanTest.php
@@ -0,0 +1,134 @@
+<?php
+
+namespace Drupal\Tests\comment\Kernel;
+
+use Drupal\Core\Datetime\Entity\DateFormat;
+use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
+use Drupal\Tests\EntityViewTrait;
+use Drupal\field\Entity\FieldStorageConfig;
+
+/**
+ * Tests loading and rendering orphan comments.
+ *
+ * @group comment
+ */
+class CommentOrphanTest extends EntityKernelTestBase {
+
+  use EntityViewTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['comment', 'node'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->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'],
+    ];
+  }
+
+}
-- 
GitLab