From e45fc718683125264c472578941286558e83aa5b Mon Sep 17 00:00:00 2001
From: Lee Rowlands <lee.rowlands@previousnext.com.au>
Date: Fri, 31 Mar 2023 12:53:36 +1000
Subject: [PATCH] Issue #2745179 by juancasantito, heddn, dpi, acbramley, Xano,
 dawehner, alexpott, catch: Uncaught exception in link formatter if a link
 field has malformed data

(cherry picked from commit 98f29f420c5e9f0e93bb487420a69c012924791b)
---
 core/modules/link/src/LinkItemInterface.php   |   3 +
 .../Field/FieldFormatter/LinkFormatter.php    |   8 +-
 .../link/tests/src/Unit/LinkFormatterTest.php | 154 ++++++++++++++++++
 3 files changed, 164 insertions(+), 1 deletion(-)
 create mode 100644 core/modules/link/tests/src/Unit/LinkFormatterTest.php

diff --git a/core/modules/link/src/LinkItemInterface.php b/core/modules/link/src/LinkItemInterface.php
index 530617044832..ed726473afb7 100644
--- a/core/modules/link/src/LinkItemInterface.php
+++ b/core/modules/link/src/LinkItemInterface.php
@@ -37,6 +37,9 @@ public function isExternal();
    *
    * @return \Drupal\Core\Url
    *   Returns a Url object.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when there is a problem with field data.
    */
   public function getUrl();
 
diff --git a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
index 75a46fa4aa34..c8709c568999 100644
--- a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
+++ b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
@@ -235,7 +235,13 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    *   A Url object.
    */
   protected function buildUrl(LinkItemInterface $item) {
-    $url = $item->getUrl() ?: Url::fromRoute('<none>');
+    try {
+      $url = $item->getUrl();
+    }
+    catch (\InvalidArgumentException $e) {
+      // @todo Add logging here in https://www.drupal.org/project/drupal/issues/3348020
+      $url = Url::fromRoute('<none>');
+    }
 
     $settings = $this->getSettings();
     $options = $item->options;
diff --git a/core/modules/link/tests/src/Unit/LinkFormatterTest.php b/core/modules/link/tests/src/Unit/LinkFormatterTest.php
new file mode 100644
index 000000000000..3670652847a7
--- /dev/null
+++ b/core/modules/link/tests/src/Unit/LinkFormatterTest.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Drupal\Tests\link\Unit;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemList;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Path\PathValidatorInterface;
+use Drupal\Core\Routing\UrlGenerator;
+use Drupal\Core\Url;
+use Drupal\link\LinkItemInterface;
+use Drupal\link\Plugin\Field\FieldFormatter\LinkFormatter;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the Field Formatter for the link field type.
+ *
+ * @group link
+ */
+class LinkFormatterTest extends UnitTestCase {
+
+  /**
+   * Tests when LinkItem::getUrl with malformed URL renders empty link.
+   *
+   * LinkItem::getUrl will throw \InvalidArgumentException.
+   */
+  public function testFormatterLinkItemUrlMalformed() {
+    $entity = $this->createMock(EntityInterface::class);
+
+    $linkItem = $this->createMock(LinkItemInterface::class);
+    $exception = new \InvalidArgumentException();
+    $linkItem->expects($this->any())
+      ->method('getParent')
+      ->willReturn($entity);
+    $linkItem->expects($this->once())
+      ->method('getUrl')
+      ->willThrowException($exception);
+    $linkItem->expects($this->any())
+      ->method('__get')
+      ->with('options')
+      ->willReturn([]);
+    $fieldDefinition = $this->createMock(FieldDefinitionInterface::class);
+    $fieldList = new FieldItemList($fieldDefinition, '', $linkItem);
+
+    $fieldTypePluginManager = $this->createMock(FieldTypePluginManagerInterface::class);
+    $fieldTypePluginManager->expects($this->once())
+      ->method('createFieldItem')
+      ->will($this->returnValue($linkItem));
+    $urlGenerator = $this->createMock(UrlGenerator::class);
+    $urlGenerator->expects($this->once())
+      ->method('generateFromRoute')
+      ->with('<none>', [], [], FALSE)
+      ->willReturn('http://example.com');
+    $container = new ContainerBuilder();
+    $container->set('plugin.manager.field.field_type', $fieldTypePluginManager);
+    $container->set('url_generator', $urlGenerator);
+    \Drupal::setContainer($container);
+    $fieldList->setValue([$linkItem]);
+
+    $pathValidator = $this->createMock(PathValidatorInterface::class);
+    $linkFormatter = new LinkFormatter('', [], $fieldDefinition, [], '', '', [], $pathValidator);
+    $elements = $linkFormatter->viewElements($fieldList, 'es');
+    $this->assertEquals('link', $elements[0]['#type']);
+  }
+
+  /**
+   * Tests when LinkItem::getUrl throws an unexpected exception.
+   */
+  public function testFormatterLinkItemUrlUnexpectedException() {
+    $exception = new \Exception('Unexpected!!!');
+
+    $linkItem = $this->createMock(LinkItemInterface::class);
+    $entity = $this->createMock(EntityInterface::class);
+    $linkItem->expects($this->any())
+      ->method('getParent')
+      ->willReturn($entity);
+    $linkItem->expects($this->once())
+      ->method('getUrl')
+      ->willThrowException($exception);
+    $linkItem->expects($this->any())
+      ->method('__get')
+      ->with('options')
+      ->willReturn([]);
+    $fieldDefinition = $this->createMock(FieldDefinitionInterface::class);
+    $fieldList = new FieldItemList($fieldDefinition, '', $linkItem);
+
+    $fieldTypePluginManager = $this->createMock(FieldTypePluginManagerInterface::class);
+    $fieldTypePluginManager->expects($this->once())
+      ->method('createFieldItem')
+      ->will($this->returnValue($linkItem));
+    $container = new ContainerBuilder();
+    $container->set('plugin.manager.field.field_type', $fieldTypePluginManager);
+    \Drupal::setContainer($container);
+    $fieldList->setValue([$linkItem]);
+
+    $pathValidator = $this->createMock(PathValidatorInterface::class);
+    $linkFormatter = new LinkFormatter('', [], $fieldDefinition, [], '', '', [], $pathValidator);
+    $this->expectException(\Exception::class);
+    $this->expectExceptionMessage('Unexpected!!!');
+    $linkFormatter->viewElements($fieldList, 'fr');
+  }
+
+  /**
+   * Tests when LinkItem::getUrl returns a functional URL.
+   */
+  public function testFormatterLinkItem() {
+    $expectedUrl = Url::fromUri('route:<front>');
+
+    $linkItem = $this->createMock(LinkItemInterface::class);
+    $entity = $this->createMock(EntityInterface::class);
+    $linkItem->expects($this->any())
+      ->method('getParent')
+      ->willReturn($entity);
+    $linkItem->expects($this->once())
+      ->method('getUrl')
+      ->willReturn($expectedUrl);
+    $linkItem->expects($this->any())
+      ->method('__get')
+      ->with('options')
+      ->willReturn([]);
+    $fieldDefinition = $this->createMock(FieldDefinitionInterface::class);
+    $fieldList = new FieldItemList($fieldDefinition, '', $linkItem);
+
+    $fieldTypePluginManager = $this->createMock(FieldTypePluginManagerInterface::class);
+    $fieldTypePluginManager->expects($this->once())
+      ->method('createFieldItem')
+      ->will($this->returnValue($linkItem));
+    $urlGenerator = $this->createMock(UrlGenerator::class);
+    $urlGenerator->expects($this->once())
+      ->method('generateFromRoute')
+      ->with('<front>', [], [], FALSE)
+      ->willReturn('http://example.com');
+    $container = new ContainerBuilder();
+    $container->set('plugin.manager.field.field_type', $fieldTypePluginManager);
+    $container->set('url_generator', $urlGenerator);
+    \Drupal::setContainer($container);
+    $fieldList->setValue([$linkItem]);
+
+    $pathValidator = $this->createMock(PathValidatorInterface::class);
+    $linkFormatter = new LinkFormatter('', [], $fieldDefinition, [], '', '', [], $pathValidator);
+    $elements = $linkFormatter->viewElements($fieldList, 'zh');
+    $this->assertEquals([
+      [
+        '#type' => 'link',
+        '#title' => 'http://example.com',
+        '#options' => [],
+        '#url' => $expectedUrl,
+      ],
+    ], $elements);
+  }
+
+}
-- 
GitLab