From f944ef3f47d4e187f080e8d5a553ea2da8ec21b4 Mon Sep 17 00:00:00 2001
From: Lee Rowlands <lee.rowlands@previousnext.com.au>
Date: Thu, 15 Aug 2019 10:32:38 +1000
Subject: [PATCH] Issue #3052931 by gabesullice, Wim Leers, larowlan: Permit
 arrays as target attribute values in Drupal\jsonapi\JsonApiResource\Link

---
 .../jsonapi/src/JsonApiResource/Link.php      | 16 +--
 .../src/Unit/JsonApiResource/LinkTest.php     | 99 +++++++++++++++++++
 2 files changed, 101 insertions(+), 14 deletions(-)
 create mode 100644 core/modules/jsonapi/tests/src/Unit/JsonApiResource/LinkTest.php

diff --git a/core/modules/jsonapi/src/JsonApiResource/Link.php b/core/modules/jsonapi/src/JsonApiResource/Link.php
index 1da2d0afe8a1..5634f983df56 100644
--- a/core/modules/jsonapi/src/JsonApiResource/Link.php
+++ b/core/modules/jsonapi/src/JsonApiResource/Link.php
@@ -76,9 +76,7 @@ public function __construct(CacheableMetadata $cacheability, Url $url, array $li
     assert(/* !empty($link_relation_types) && */Inspector::assertAllStrings($link_relation_types));
     assert(Inspector::assertAllStrings(array_keys($target_attributes)));
     assert(Inspector::assertAll(function ($target_attribute_value) {
-      return is_string($target_attribute_value)
-        || is_array($target_attribute_value)
-        && Inspector::assertAllStrings($target_attribute_value);
+      return is_string($target_attribute_value) || is_array($target_attribute_value);
     }, array_values($target_attributes)));
     $generated_url = $url->setAbsolute()->toString(TRUE);
     $this->href = $generated_url->getGeneratedUrl();
@@ -160,17 +158,7 @@ public static function compare(Link $a, Link $b) {
   public static function merge(Link $a, Link $b) {
     assert(static::compare($a, $b) === 0);
     $merged_rels = array_unique(array_merge($a->getLinkRelationTypes(), $b->getLinkRelationTypes()));
-    $merged_attributes = $a->getTargetAttributes();
-    foreach ($b->getTargetAttributes() as $key => $value) {
-      if (isset($merged_attributes[$key])) {
-        // The attribute values can be either a string or an array of strings.
-        $value = array_unique(array_merge(
-          is_string($merged_attributes[$key]) ? [$merged_attributes[$key]] : $merged_attributes[$key],
-          is_string($value) ? [$value] : $value
-        ));
-      }
-      $merged_attributes[$key] = count($value) === 1 ? reset($value) : $value;
-    }
+    $merged_attributes = array_merge_recursive($a->getTargetAttributes(), $b->getTargetAttributes());
     $merged_cacheability = (new CacheableMetadata())->addCacheableDependency($a)->addCacheableDependency($b);
     return new static($merged_cacheability, $a->getUri(), $merged_rels, $merged_attributes);
   }
diff --git a/core/modules/jsonapi/tests/src/Unit/JsonApiResource/LinkTest.php b/core/modules/jsonapi/tests/src/Unit/JsonApiResource/LinkTest.php
new file mode 100644
index 000000000000..91441a8ea5ec
--- /dev/null
+++ b/core/modules/jsonapi/tests/src/Unit/JsonApiResource/LinkTest.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\Tests\jsonapi\Unit\JsonApiResource;
+
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\GeneratedUrl;
+use Drupal\Core\Url;
+use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
+use Drupal\jsonapi\JsonApiResource\Link;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\jsonapi\JsonApiResource\Link
+ * @group jsonapi
+ *
+ * @internal
+ */
+class LinkTest extends UnitTestCase {
+
+  /**
+   * @covers ::merge
+   * @dataProvider mergeTargetAttributesProvider
+   */
+  public function testMergeTargetAttributes($a, $b, $expected) {
+    $this->assertSame($expected->getTargetAttributes(), Link::merge($a, $b)->getTargetAttributes());
+  }
+
+  /**
+   * Provides test data for link merging.
+   */
+  public function mergeTargetAttributesProvider() {
+    $cases = [
+      'strings' => [
+        ['key' => 'foo'],
+        ['key' => 'bar'],
+        ['key' => ['foo', 'bar']],
+      ],
+      'string and array' => [
+        ['key' => 'foo'],
+        ['key' => ['bar', 'baz']],
+        ['key' => ['foo', 'bar', 'baz']],
+      ],
+      'one-dimensional indexed arrays' => [
+        ['key' => ['foo']],
+        ['key' => ['bar']],
+        ['key' => ['foo', 'bar']],
+      ],
+      'one-dimensional keyed arrays' => [
+        ['key' => ['foo' => 'tball']],
+        ['key' => ['bar' => 'ista']],
+        [
+          'key' => [
+            'foo' => 'tball',
+            'bar' => 'ista',
+          ],
+        ],
+      ],
+      'two-dimensional indexed arrays' => [
+        ['one' => ['two' => ['foo']]],
+        ['one' => ['two' => ['bar']]],
+        ['one' => ['two' => ['foo', 'bar']]],
+      ],
+      'two-dimensional keyed arrays' => [
+        ['one' => ['two' => ['foo' => 'tball']]],
+        ['one' => ['two' => ['bar' => 'ista']]],
+        [
+          'one' => [
+            'two' => [
+              'foo' => 'tball',
+              'bar' => 'ista',
+            ],
+          ],
+        ],
+      ],
+    ];
+    $this->mockUrlAssembler();
+    return array_map(function ($arguments) {
+      return array_map(function ($attributes) {
+        return new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/'), ['item'], $attributes);
+      }, $arguments);
+    }, $cases);
+  }
+
+  /**
+   * Mocks the unrouted URL assembler.
+   */
+  protected function mockUrlAssembler() {
+    $url_assembler = $this->getMockBuilder(UnroutedUrlAssemblerInterface::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+    $url_assembler->method('assemble')->willReturn((new GeneratedUrl())->setGeneratedUrl('https://jsonapi.org/'));
+
+    $container = new ContainerBuilder();
+    $container->set('unrouted_url_assembler', $url_assembler);
+    \Drupal::setContainer($container);
+  }
+
+}
-- 
GitLab