From d9b11354bcc6ad033cf4dda92c145eadfb251d66 Mon Sep 17 00:00:00 2001
From: Richard Porter <34412-rbp@users.noreply.drupalcode.org>
Date: Tue, 21 Feb 2023 08:54:27 +0100
Subject: [PATCH] Issue #3098733 by richardbporter, firass.ziedan,
 dasginganinja, gngn, Oscaner, drupal-son, marcoscano, John Cook: Fatal error
 after saving the page not have a reuse entity

---
 .../EntityUsage/Track/CkeditorImage.php       |  7 +++
 src/Plugin/EntityUsage/Track/EntityEmbed.php  |  6 ++
 src/Plugin/EntityUsage/Track/LinkIt.php       |  7 +++
 src/Plugin/EntityUsage/Track/MediaEmbed.php   |  4 ++
 .../EmbeddedContentTest.php                   | 63 +++++++++++++++++++
 5 files changed, 87 insertions(+)

diff --git a/src/Plugin/EntityUsage/Track/CkeditorImage.php b/src/Plugin/EntityUsage/Track/CkeditorImage.php
index e75a5b4..0c47397 100644
--- a/src/Plugin/EntityUsage/Track/CkeditorImage.php
+++ b/src/Plugin/EntityUsage/Track/CkeditorImage.php
@@ -24,6 +24,13 @@ class CkeditorImage extends TextFieldEmbedBase {
     $xpath = new \DOMXPath($dom);
     $entities = [];
     foreach ($xpath->query('//img[@data-entity-type and @data-entity-uuid]') as $node) {
+      // Skip elements with empty data-entity-uuid/type attributes.
+      if (empty($node->getAttribute('data-entity-uuid'))
+        || empty($node->getAttribute('data-entity-type'))
+      ) {
+        continue;
+      }
+
       // Note that this does not cover 100% of the situations. In the (unlikely
       // but possible) use case where the user embeds the same entity twice in
       // the same field, we are just recording 1 usage for this target entity,
diff --git a/src/Plugin/EntityUsage/Track/EntityEmbed.php b/src/Plugin/EntityUsage/Track/EntityEmbed.php
index b721f21..a3f0bc5 100644
--- a/src/Plugin/EntityUsage/Track/EntityEmbed.php
+++ b/src/Plugin/EntityUsage/Track/EntityEmbed.php
@@ -24,6 +24,12 @@ class EntityEmbed extends TextFieldEmbedBase {
     $xpath = new \DOMXPath($dom);
     $entities = [];
     foreach ($xpath->query('//drupal-entity[@data-entity-type and @data-entity-uuid]') as $node) {
+      // Skip elements with empty data-entity-uuid/type attributes.
+      if (empty($node->getAttribute('data-entity-uuid'))
+        || empty($node->getAttribute('data-entity-type'))
+      ) {
+        continue;
+      }
       // Note that this does not cover 100% of the situations. In the (unlikely
       // but possible) use case where the user embeds the same entity twice in
       // the same field, we are just recording 1 usage for this target entity,
diff --git a/src/Plugin/EntityUsage/Track/LinkIt.php b/src/Plugin/EntityUsage/Track/LinkIt.php
index 6bff6e3..49b32fc 100644
--- a/src/Plugin/EntityUsage/Track/LinkIt.php
+++ b/src/Plugin/EntityUsage/Track/LinkIt.php
@@ -24,6 +24,13 @@ class LinkIt extends TextFieldEmbedBase {
     $xpath = new \DOMXPath($dom);
     $entities = [];
     foreach ($xpath->query('//a[@data-entity-type and @data-entity-uuid]') as $node) {
+      // Skip elements with empty data-entity-uuid/type attributes.
+      if (empty($node->getAttribute('data-entity-uuid'))
+        || empty($node->getAttribute('data-entity-type'))
+      ) {
+        continue;
+      }
+
       // Note that this does not cover 100% of the situations. In the (unlikely
       // but possible) use case where the user embeds the same entity twice in
       // the same field, we are just recording 1 usage for this target entity,
diff --git a/src/Plugin/EntityUsage/Track/MediaEmbed.php b/src/Plugin/EntityUsage/Track/MediaEmbed.php
index 22540bf..d04bd67 100644
--- a/src/Plugin/EntityUsage/Track/MediaEmbed.php
+++ b/src/Plugin/EntityUsage/Track/MediaEmbed.php
@@ -24,6 +24,10 @@ class MediaEmbed extends TextFieldEmbedBase {
     $xpath = new \DOMXPath($dom);
     $entities = [];
     foreach ($xpath->query('//drupal-media[@data-entity-type="media" and @data-entity-uuid]') as $node) {
+      // Skip elements with empty data-entity-uuid attributes.
+      if (empty($node->getAttribute('data-entity-uuid'))) {
+        continue;
+      }
       // Note that this does not cover 100% of the situations. In the (unlikely
       // but possible) use case where the user embeds the same entity twice in
       // the same field, we are just recording 1 usage for this target entity,
diff --git a/tests/src/FunctionalJavascript/EmbeddedContentTest.php b/tests/src/FunctionalJavascript/EmbeddedContentTest.php
index f6992e9..50f84f1 100644
--- a/tests/src/FunctionalJavascript/EmbeddedContentTest.php
+++ b/tests/src/FunctionalJavascript/EmbeddedContentTest.php
@@ -112,6 +112,27 @@ class EmbeddedContentTest extends EntityUsageJavascriptTestBase {
     $this->assertEquals($expected, $usage);
   }
 
+  /**
+   * Tests the Entity Embed plugin parsing does not error with malformed HTML.
+   */
+  public function testEntityEmbedWithMalformedHtml() {
+    $embedded_text = '<drupal-entity data-embed-button="node" data-entity-embed-display="entity_reference:entity_reference_label" data-entity-embed-display-settings="{&quot;link&quot;:1}" data-entity-type="" data-entity-uuid=""></drupal-entity>';
+
+    $node = Node::create([
+      'type' => 'eu_test_ct',
+      'title' => 'This is a node with malformed EntityEmbed HTML',
+      'field_eu_test_rich_text' => [
+        'value' => $embedded_text,
+        'format' => 'eu_test_text_format',
+      ],
+    ]);
+
+    $node->save();
+
+    $this->drupalGet('/node/' . $node->id());
+    $this->assertSession()->pageTextContains('This is a node with malformed EntityEmbed HTML');
+  }
+
   /**
    * Tests the LinkIt parsing.
    */
@@ -214,6 +235,27 @@ class EmbeddedContentTest extends EntityUsageJavascriptTestBase {
     $this->assertEquals([], $usage);
   }
 
+  /**
+   * Tests the LinkIt plugin parsing does not error with malformed HTML.
+   */
+  public function testLinkItdWithMalformedHtml() {
+    $embedded_text = '<p>foo <a data-entity-substitution="canonical" data-entity-type="" data-entity-uuid="">linked text</a> bar</p>';
+
+    $node = Node::create([
+      'type' => 'eu_test_ct',
+      'title' => 'This is a node with malformed LinkIt HTML',
+      'field_eu_test_rich_text' => [
+        'value' => $embedded_text,
+        'format' => 'eu_test_text_format',
+      ],
+    ]);
+
+    $node->save();
+
+    $this->drupalGet('/node/' . $node->id());
+    $this->assertSession()->pageTextContains('This is a node with malformed LinkIt HTML');
+  }
+
   /**
    * Tests the HtmlLink parsing.
    */
@@ -505,4 +547,25 @@ class EmbeddedContentTest extends EntityUsageJavascriptTestBase {
     $this->assertEquals($expected, $usage);
   }
 
+  /**
+   * Tests the MediaEmbed plugin parsing does not error with malformed HTML.
+   */
+  public function testMediaEmbeddWithMalformedHtml() {
+    $embedded_text = '<drupal-media data-entity-type="media" data-entity-uuid=""></drupal-media>';
+
+    $node = Node::create([
+      'type' => 'eu_test_ct',
+      'title' => 'This is a node with malformed MediaEmbed HTML',
+      'field_eu_test_rich_text' => [
+        'value' => $embedded_text,
+        'format' => 'eu_test_text_format',
+      ],
+    ]);
+
+    $node->save();
+
+    $this->drupalGet('/node/' . $node->id());
+    $this->assertSession()->pageTextContains('This is a node with malformed MediaEmbed HTML');
+  }
+
 }
-- 
GitLab