From f74cbaa9280e96b8b23dcf46e469c05ad61d4952 Mon Sep 17 00:00:00 2001
From: Axel Rutz <axel.rutz@machbarmacher.net>
Date: Wed, 17 Oct 2018 21:30:26 +0200
Subject: [PATCH] Issue #3007424: Multiple usages of FieldPluginBase::getEntity
 do not check for NULL, leading to WSOD

---
 .../comment/src/Plugin/views/field/EntityLink.php     |  3 +++
 .../comment/src/Plugin/views/field/LinkApprove.php    |  6 +++++-
 .../comment/src/Plugin/views/field/LinkReply.php      |  3 +++
 .../contact/src/Plugin/views/field/ContactLink.php    |  9 ++++++++-
 core/modules/media_library/media_library.module       |  5 +++--
 .../src/Plugin/views/field/MediaLibrarySelectForm.php |  5 +++--
 .../node/src/Plugin/views/field/RevisionLink.php      |  5 ++++-
 .../src/Plugin/views/field/RevisionLinkDelete.php     |  3 +++
 .../src/Plugin/views/field/RevisionLinkRevert.php     |  3 +++
 .../user/src/Plugin/views/field/Permissions.php       |  6 +++++-
 .../Entity/Render/EntityTranslationRenderTrait.php    |  4 ++--
 .../modules/views/src/Plugin/views/field/BulkForm.php |  4 ++++
 .../views/src/Plugin/views/field/EntityLink.php       |  6 +++++-
 .../views/src/Plugin/views/field/EntityOperations.php |  3 +++
 .../modules/views/src/Plugin/views/field/LinkBase.php | 11 ++++++++---
 .../views/src/Plugin/views/field/RenderedEntity.php   |  3 +++
 16 files changed, 65 insertions(+), 14 deletions(-)

diff --git a/core/modules/comment/src/Plugin/views/field/EntityLink.php b/core/modules/comment/src/Plugin/views/field/EntityLink.php
index a2c6911942..86e393a0a4 100644
--- a/core/modules/comment/src/Plugin/views/field/EntityLink.php
+++ b/core/modules/comment/src/Plugin/views/field/EntityLink.php
@@ -70,6 +70,9 @@ public function preRender(&$values) {
    */
   public function render(ResultRow $values) {
     $entity = $this->getEntity($values);
+    if (!$entity) {
+      return '';
+    }
 
     // Only render the links, if they are defined.
     return !empty($this->build[$entity->id()]['links']['comment__comment']) ? \Drupal::service('renderer')->render($this->build[$entity->id()]['links']['comment__comment']) : '';
diff --git a/core/modules/comment/src/Plugin/views/field/LinkApprove.php b/core/modules/comment/src/Plugin/views/field/LinkApprove.php
index 683c3cbb4c..86687f52f0 100644
--- a/core/modules/comment/src/Plugin/views/field/LinkApprove.php
+++ b/core/modules/comment/src/Plugin/views/field/LinkApprove.php
@@ -19,7 +19,11 @@ class LinkApprove extends LinkBase {
    * {@inheritdoc}
    */
   protected function getUrlInfo(ResultRow $row) {
-    return Url::fromRoute('comment.approve', ['comment' => $this->getEntity($row)->id()]);
+    $entity = $this->getEntity($row);
+    if (!$entity) {
+      return NULL;
+    }
+    return Url::fromRoute('comment.approve', ['comment' => $entity->id()]);
   }
 
   /**
diff --git a/core/modules/comment/src/Plugin/views/field/LinkReply.php b/core/modules/comment/src/Plugin/views/field/LinkReply.php
index 3ddbd08152..816f341a6c 100644
--- a/core/modules/comment/src/Plugin/views/field/LinkReply.php
+++ b/core/modules/comment/src/Plugin/views/field/LinkReply.php
@@ -21,6 +21,9 @@ class LinkReply extends LinkBase {
   protected function getUrlInfo(ResultRow $row) {
     /** @var \Drupal\comment\CommentInterface $comment */
     $comment = $this->getEntity($row);
+    if (!$comment) {
+      return NULL;
+    }
     return Url::fromRoute('comment.reply', [
       'entity_type' => $comment->getCommentedEntityTypeId(),
       'entity' => $comment->getCommentedEntityId(),
diff --git a/core/modules/contact/src/Plugin/views/field/ContactLink.php b/core/modules/contact/src/Plugin/views/field/ContactLink.php
index a98be137dd..af72aa3313 100644
--- a/core/modules/contact/src/Plugin/views/field/ContactLink.php
+++ b/core/modules/contact/src/Plugin/views/field/ContactLink.php
@@ -30,7 +30,11 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   protected function getUrlInfo(ResultRow $row) {
-    return Url::fromRoute('entity.user.contact_form', ['user' => $this->getEntity($row)->id()]);
+    $entity = $this->getEntity($row);
+    if (!$entity) {
+      return NULL;
+    }
+    return Url::fromRoute('entity.user.contact_form', ['user' => $entity->id()]);
   }
 
   /**
@@ -38,6 +42,9 @@ protected function getUrlInfo(ResultRow $row) {
    */
   protected function renderLink(ResultRow $row) {
     $entity = $this->getEntity($row);
+    if (!$entity) {
+      return '';
+    }
 
     $this->options['alter']['make_link'] = TRUE;
     $this->options['alter']['url'] = $this->getUrlInfo($row);
diff --git a/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module
index 977da24ae5..9efc5fc9a3 100644
--- a/core/modules/media_library/media_library.module
+++ b/core/modules/media_library/media_library.module
@@ -141,9 +141,10 @@ function media_library_form_views_form_media_library_page_alter(array &$form, Fo
     foreach (Element::getVisibleChildren($form['media_bulk_form']) as $key) {
       if (isset($view->result[$key])) {
         $media = $view->field['media_bulk_form']->getEntity($view->result[$key]);
-        $form['media_bulk_form'][$key]['#title'] = t('Select @label', [
+        $title = $media ? t('Select @label', [
           '@label' => $media->label(),
-        ]);
+        ]) : '';
+        $form['media_bulk_form'][$key]['#title'] = $title;
       }
     }
   }
diff --git a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
index 72a4b50bbf..ccc58e7a17 100644
--- a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
+++ b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
@@ -54,14 +54,15 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
     $form[$this->options['id']]['#tree'] = TRUE;
     foreach ($this->view->result as $row_index => $row) {
       $entity = $this->getEntity($row);
-      $form[$this->options['id']][$row_index] = [
+      $checkbox = $entity ? [
         '#type' => 'checkbox',
         '#title' => $this->t('Select @label', [
           '@label' => $entity->label(),
         ]),
         '#title_display' => 'invisible',
         '#return_value' => $entity->id(),
-      ];
+      ] : [];
+      $form[$this->options['id']][$row_index] = $checkbox;
     }
 
     // @todo Remove in https://www.drupal.org/project/drupal/issues/2504115
diff --git a/core/modules/node/src/Plugin/views/field/RevisionLink.php b/core/modules/node/src/Plugin/views/field/RevisionLink.php
index 29d0176634..f3ac5ee869 100644
--- a/core/modules/node/src/Plugin/views/field/RevisionLink.php
+++ b/core/modules/node/src/Plugin/views/field/RevisionLink.php
@@ -21,6 +21,9 @@ class RevisionLink extends LinkBase {
   protected function getUrlInfo(ResultRow $row) {
     /** @var \Drupal\node\NodeInterface $node */
     $node = $this->getEntity($row);
+    if (!$node) {
+      return NULL;
+    }
     // Current revision uses the node view path.
     return !$node->isDefaultRevision() ?
       Url::fromRoute('entity.node.revision', ['node' => $node->id(), 'node_revision' => $node->getRevisionId()]) :
@@ -33,7 +36,7 @@ protected function getUrlInfo(ResultRow $row) {
   protected function renderLink(ResultRow $row) {
     /** @var \Drupal\node\NodeInterface $node */
     $node = $this->getEntity($row);
-    if (!$node->getRevisionid()) {
+    if (!$node || !$node->getRevisionid()) {
       return '';
     }
     $text = parent::renderLink($row);
diff --git a/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php b/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php
index 2e1f683b52..8f92a2ee1b 100644
--- a/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php
+++ b/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php
@@ -20,6 +20,9 @@ class RevisionLinkDelete extends RevisionLink {
   protected function getUrlInfo(ResultRow $row) {
     /** @var \Drupal\node\NodeInterface $node */
     $node = $this->getEntity($row);
+    if (!$node) {
+      return NULL;
+    }
     return Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $node->getRevisionId()]);
   }
 
diff --git a/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php b/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php
index 33b20b8885..7c6c10f8b3 100644
--- a/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php
+++ b/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php
@@ -20,6 +20,9 @@ class RevisionLinkRevert extends RevisionLink {
   protected function getUrlInfo(ResultRow $row) {
     /** @var \Drupal\node\NodeInterface $node */
     $node = $this->getEntity($row);
+    if (!$node) {
+      return NULL;
+    }
     return Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $node->getRevisionId()]);
   }
 
diff --git a/core/modules/user/src/Plugin/views/field/Permissions.php b/core/modules/user/src/Plugin/views/field/Permissions.php
index bab5503ba8..bda3f0578c 100644
--- a/core/modules/user/src/Plugin/views/field/Permissions.php
+++ b/core/modules/user/src/Plugin/views/field/Permissions.php
@@ -81,7 +81,11 @@ public function preRender(&$values) {
 
     $rids = [];
     foreach ($values as $result) {
-      $user_rids = $this->getEntity($result)->getRoles();
+      $user = $this->getEntity($result);
+      if (!$user) {
+        continue;
+      }
+      $user_rids = $user->getRoles();
       $uid = $this->getValue($result);
 
       foreach ($user_rids as $rid) {
diff --git a/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php b/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php
index 8fbc191d14..5f62212953 100644
--- a/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php
+++ b/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php
@@ -58,7 +58,7 @@ protected function getEntityTranslationRenderer() {
   /**
    * Returns the entity translation matching the configured row language.
    *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
+   * @param \Drupal\Core\Entity\EntityInterface|null $entity
    *   The entity object the field value being processed is attached to.
    * @param \Drupal\views\ResultRow $row
    *   The result row the field value being processed belongs to.
@@ -66,7 +66,7 @@ protected function getEntityTranslationRenderer() {
    * @return \Drupal\Core\Entity\FieldableEntityInterface
    *   The entity translation object for the specified row.
    */
-  public function getEntityTranslation(EntityInterface $entity, ResultRow $row) {
+  public function getEntityTranslation(EntityInterface $entity = NULL, ResultRow $row) {
     // We assume the same language should be used for all entity fields
     // belonging to a single row, even if they are attached to different entity
     // types. Below we apply language fallback to ensure a valid value is always
diff --git a/core/modules/views/src/Plugin/views/field/BulkForm.php b/core/modules/views/src/Plugin/views/field/BulkForm.php
index 3ce092b983..8e3825d5b2 100644
--- a/core/modules/views/src/Plugin/views/field/BulkForm.php
+++ b/core/modules/views/src/Plugin/views/field/BulkForm.php
@@ -270,6 +270,10 @@ public function viewsForm(&$form, FormStateInterface $form_state) {
       $form[$this->options['id']]['#tree'] = TRUE;
       foreach ($this->view->result as $row_index => $row) {
         $entity = $this->getEntityTranslation($this->getEntity($row), $row);
+        if (!$entity) {
+          $form[$this->options['id']][$row_index] = [];
+          continue;
+        }
 
         $form[$this->options['id']][$row_index] = [
           '#type' => 'checkbox',
diff --git a/core/modules/views/src/Plugin/views/field/EntityLink.php b/core/modules/views/src/Plugin/views/field/EntityLink.php
index 75b8da3949..3bffc865dc 100644
--- a/core/modules/views/src/Plugin/views/field/EntityLink.php
+++ b/core/modules/views/src/Plugin/views/field/EntityLink.php
@@ -36,7 +36,11 @@ protected function renderLink(ResultRow $row) {
    */
   protected function getUrlInfo(ResultRow $row) {
     $template = $this->getEntityLinkTemplate();
-    return $this->getEntity($row)->toUrl($template)->setAbsolute($this->options['absolute']);
+    $entity = $this->getEntity($row);
+    if (!$entity) {
+      return NULL;
+    }
+    return $entity->toUrl($template)->setAbsolute($this->options['absolute']);
   }
 
   /**
diff --git a/core/modules/views/src/Plugin/views/field/EntityOperations.php b/core/modules/views/src/Plugin/views/field/EntityOperations.php
index d31cd1ea30..d7b79b4bd5 100644
--- a/core/modules/views/src/Plugin/views/field/EntityOperations.php
+++ b/core/modules/views/src/Plugin/views/field/EntityOperations.php
@@ -109,6 +109,9 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    */
   public function render(ResultRow $values) {
     $entity = $this->getEntityTranslation($this->getEntity($values), $values);
+    if (!$entity) {
+      return [];
+    }
     $operations = $this->entityManager->getListBuilder($entity->getEntityTypeId())->getOperations($entity);
     if ($this->options['destination']) {
       foreach ($operations as &$operation) {
diff --git a/core/modules/views/src/Plugin/views/field/LinkBase.php b/core/modules/views/src/Plugin/views/field/LinkBase.php
index 31dca4c7c3..535d9131c6 100644
--- a/core/modules/views/src/Plugin/views/field/LinkBase.php
+++ b/core/modules/views/src/Plugin/views/field/LinkBase.php
@@ -124,7 +124,9 @@ public function query() {
   public function render(ResultRow $row) {
     $access = $this->checkUrlAccess($row);
     $build = ['#markup' => $access->isAllowed() ? $this->renderLink($row) : ''];
-    BubbleableMetadata::createFromObject($access)->applyTo($build);
+    if ($access) {
+      BubbleableMetadata::createFromObject($access)->applyTo($build);
+    }
     return $build;
   }
 
@@ -134,11 +136,14 @@ public function render(ResultRow $row) {
    * @param \Drupal\views\ResultRow $row
    *   A view result row.
    *
-   * @return \Drupal\Core\Access\AccessResultInterface
+   * @return \Drupal\Core\Access\AccessResultInterface|null
    *   The access result.
    */
   protected function checkUrlAccess(ResultRow $row) {
     $url = $this->getUrlInfo($row);
+    if (!$url) {
+      return NULL;
+    }
     return $this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters(), $this->currentUser(), TRUE);
   }
 
@@ -148,7 +153,7 @@ protected function checkUrlAccess(ResultRow $row) {
    * @param \Drupal\views\ResultRow $row
    *   A view result row.
    *
-   * @return \Drupal\Core\Url
+   * @return \Drupal\Core\Url|null
    *   The URI elements of the link.
    */
   abstract protected function getUrlInfo(ResultRow $row);
diff --git a/core/modules/views/src/Plugin/views/field/RenderedEntity.php b/core/modules/views/src/Plugin/views/field/RenderedEntity.php
index 8676386577..217d876683 100644
--- a/core/modules/views/src/Plugin/views/field/RenderedEntity.php
+++ b/core/modules/views/src/Plugin/views/field/RenderedEntity.php
@@ -106,6 +106,9 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    */
   public function render(ResultRow $values) {
     $entity = $this->getEntityTranslation($this->getEntity($values), $values);
+    if (!$entity) {
+      return [];
+    }
     $build = [];
     if (isset($entity)) {
       $access = $entity->access('view', NULL, TRUE);
-- 
2.17.1

