Loading core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php +2 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,7 @@ protected function createEntity() { 'title' => 'Llama Gabilondo', 'description' => 'Llama Gabilondo', 'link' => 'https://nl.wikipedia.org/wiki/Llama', 'resolvable_uri' => 'https://nl.wikipedia.org/wiki/Llama', 'weight' => 0, 'menu_name' => 'main', ]); Loading Loading @@ -117,6 +118,7 @@ protected function getExpectedDocument(): array { 'bundle' => 'menu_link_content', 'link' => [ 'uri' => 'https://nl.wikipedia.org/wiki/Llama', 'resolvable_uri' => 'https://nl.wikipedia.org/wiki/Llama', 'title' => NULL, 'options' => [], ], Loading core/modules/jsonapi/tests/src/Kernel/Normalizer/FieldItemNormalizerTest.php +2 −0 Original line number Diff line number Diff line Loading @@ -84,6 +84,7 @@ public function testNormalizeFieldItem(): void { 'options' => [ 'query' => 'foo=bar', ], 'resolvable_uri' => 'https://www.drupal.org', ], ], 'internal_property_value' => [ Loading Loading @@ -112,6 +113,7 @@ public function testNormalizeFieldItem(): void { 'options' => [ 'query' => 'foo=bar', ], 'resolvable_uri' => 'https://www.drupal.org', ], $result->getNormalization()); // Verify a field with one public property and one internal only returns the Loading core/modules/link/src/Plugin/DataType/LinkResolvableUriComputed.php 0 → 100644 +92 −0 Original line number Diff line number Diff line <?php namespace Drupal\link\Plugin\DataType; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\Attribute\DataType; use Drupal\Core\TypedData\Plugin\DataType\Uri; use Drupal\Core\Url; /** * Defines a data type for a Link Resolvable URI. */ #[DataType( id: 'resolvable_uri', label: new TranslatableMarkup('Link Resolvable URI'), )] class LinkResolvableUriComputed extends Uri implements CacheableDependencyInterface { /** * The generated URL. * * @var \Drupal\Core\GeneratedUrl|null */ protected $processed = NULL; /** * {@inheritdoc} */ public function getValue() { if ($this->processed !== NULL) { return $this->processed->getGeneratedUrl(); } /** @var \Drupal\link\Plugin\Field\FieldType\LinkItem $item */ $item = $this->getParent(); $this->processed = $item->getUrl()->toString(TRUE); return $this->processed->getGeneratedUrl(); } /** * {@inheritdoc} */ public function setValue($value, $notify = TRUE): void { if (!empty($value)) { $parsed = UrlHelper::parse($value); // If the path is not an external URL then add 'internal:' prefix to make // it a valid uri. if (strpos($parsed['path'], ':') === FALSE) { $parsed['path'] = 'internal:' . $parsed['path']; } $url = Url::fromUri($parsed['path'], [ 'query' => $parsed['query'], 'fragment' => $parsed['fragment'], ]); $this->processed = $url->toString(TRUE); } else { $this->processed = NULL; } // Notify the parent of any changes. if ($notify && isset($this->parent)) { $this->parent->onChange($this->name); } } /** * {@inheritdoc} */ public function getCacheTags() { $this->getValue(); return $this->processed->getCacheTags(); } /** * {@inheritdoc} */ public function getCacheContexts() { $this->getValue(); return $this->processed->getCacheContexts(); } /** * {@inheritdoc} */ public function getCacheMaxAge() { $this->getValue(); return $this->processed->getCacheMaxAge(); } } core/modules/link/src/Plugin/Field/FieldType/LinkItem.php +42 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ namespace Drupal\link\Plugin\Field\FieldType; use Drupal\Component\Utility\Random; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Field\Attribute\FieldType; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemBase; Loading Loading @@ -51,6 +52,13 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel $properties['uri'] = DataDefinition::create('uri') ->setLabel(new TranslatableMarkup('URI')); $properties['resolvable_uri'] = DataDefinition::create('resolvable_uri') ->setLabel(new TranslatableMarkup('Resolvable URI')) ->setDescription(new TranslatableMarkup('The processed URL for this link suitable for using in anchor href attributes.')) ->setComputed(TRUE) ->setInternal(FALSE) ->setReadOnly(TRUE); $properties['title'] = DataDefinition::create('string') ->setLabel(new TranslatableMarkup('Link text')); Loading Loading @@ -172,6 +180,34 @@ public function getUrl() { return Url::fromUri($this->uri, (array) $this->options); } /** * {@inheritdoc} */ public function onChange($property_name, $notify = TRUE): void { // Make sure that the link item values can be kept in sync with computed // property url. if ($property_name === 'resolvable_uri') { $property = $this->get('resolvable_uri'); if ($url = $property->getValue()) { $parsed = UrlHelper::parse($url); // If the path is not an external URL then add 'internal:' prefix to // make it a valid uri. if (strpos($parsed['path'], ':') === FALSE) { $parsed['path'] = 'internal:' . $parsed['path']; } $this->writePropertyValue('uri', $parsed['path']); // Only set the options if we have query parameters or a fragment. if (!empty($parsed['query']) || !empty($parsed['fragment'])) { $this->writePropertyValue('options', [ 'query' => $parsed['query'], 'fragment' => $parsed['fragment'], ]); } } } parent::onChange($property_name, $notify); } /** * {@inheritdoc} */ Loading @@ -194,6 +230,12 @@ public function setValue($values, $notify = TRUE) { ]; } parent::setValue($values, $notify); // Support setting the field item with only url property, but make sure // values stay in sync if only url property is passed. // NULL is a valid value, so we use array_key_exists(). if (is_array($values) && array_key_exists('resolvable_uri', $values) && !array_key_exists('uri', $values)) { $this->onChange('resolvable_uri', FALSE); } } } core/modules/link/tests/src/Kernel/LinkItemTest.php +41 −4 Original line number Diff line number Diff line Loading @@ -91,6 +91,7 @@ public function testLinkItem(): void { ], 'external' => TRUE, ], $entity->field_test->first()->getUrl()->getOptions()); $this->assertEquals($url, $entity->field_test->resolvable_uri); $entity->name->value = $this->randomMachineName(); $entity->save(); Loading @@ -99,6 +100,7 @@ public function testLinkItem(): void { $entity = EntityTest::load($id); $this->assertInstanceOf(FieldItemListInterface::class, $entity->field_test); $this->assertInstanceOf(FieldItemInterface::class, $entity->field_test[0]); $this->assertEquals($url, $entity->field_test->resolvable_uri); $this->assertEquals($parsed_url['path'], $entity->field_test->uri); $this->assertEquals($parsed_url['path'], $entity->field_test[0]->uri); $this->assertEquals($title, $entity->field_test->title); Loading @@ -123,24 +125,26 @@ public function testLinkItem(): void { $new_class = $this->randomMachineName(); $entity->field_test->uri = $new_url; $entity->field_test->title = $new_title; $entity->field_test->first()->get('options')->set('query', NULL); $entity->field_test->first()->get('options')->set('query', []); $entity->field_test->first()->get('options')->set('attributes', ['class' => $new_class]); $this->assertEquals($new_url, $entity->field_test->uri); $this->assertEquals($new_title, $entity->field_test->title); $this->assertEquals($new_class, $entity->field_test->options['attributes']['class']); $this->assertNull($entity->field_test->options['query']); $this->assertEmpty($entity->field_test->options['query']); // Read changed entity and assert changed values. $entity->save(); $entity = EntityTest::load($id); $this->assertEquals($new_url, $entity->field_test->uri); $this->assertEquals($entity->field_test->resolvable_uri, $new_url); $this->assertEquals($new_title, $entity->field_test->title); $this->assertEquals($new_class, $entity->field_test->options['attributes']['class']); // Check that if we only set uri the default values for title and options // are also initialized. // Check that if we only set uri the default values for url, title, and // options are also initialized. $entity->field_test = ['uri' => 'internal:/node/add']; $this->assertEquals('internal:/node/add', $entity->field_test->uri); $this->assertEquals($entity->field_test->resolvable_uri, '/node/add'); $this->assertNull($entity->field_test->title); $this->assertSame([], $entity->field_test->options); Loading @@ -151,6 +155,7 @@ public function testLinkItem(): void { 'options' => ['query' => NULL], ]; $this->assertEquals('internal:/node/add', $entity->field_test->uri); $this->assertEquals($entity->field_test->resolvable_uri, '/node/add'); $this->assertNull($entity->field_test->title); $this->assertNull($entity->field_test->options['query']); Loading Loading @@ -178,6 +183,38 @@ public function testLinkItem(): void { $entity->field_test_external->generateSampleItems(); $entity->field_test_internal->generateSampleItems(); $this->entityValidateAndSave($entity); // Test setting up computed property also sets up other values. $entity = EntityTest::create(); $url = 'https://www.drupal.org?test_param=test_value#top'; $parsed_url = UrlHelper::parse($url); $entity->field_test->resolvable_uri = $url; $this->assertEquals($parsed_url['path'], $entity->field_test->uri); $this->assertEquals([ 'query' => $parsed_url['query'], 'fragment' => $parsed_url['fragment'], 'external' => TRUE, ], $entity->field_test->first()->getUrl()->getOptions()); $entity->name->value = $this->randomMachineName(); $entity->save(); // Verify that the field value is changed. $id = $entity->id(); $entity = EntityTest::load($id); $this->assertInstanceOf(FieldItemListInterface::class, $entity->field_test); $this->assertInstanceOf(FieldItemInterface::class, $entity->field_test[0]); $this->assertEquals($url, $entity->field_test->resolvable_uri); $this->assertEquals($entity->field_test->uri, $parsed_url['path']); $this->assertEquals($entity->field_test[0]->uri, $parsed_url['path']); $this->assertEquals($entity->field_test->options['query'], $parsed_url['query']); $this->assertEquals($entity->field_test->options['fragment'], $parsed_url['fragment']); // Check that if we only set url the default values for uri, title, and // options are also initialized. $entity->field_test = ['resolvable_uri' => '/node/add']; $this->assertEquals($entity->field_test->uri, 'internal:/node/add'); $this->assertEquals($entity->field_test->resolvable_uri, '/node/add'); $this->assertNull($entity->field_test->title); } } Loading
core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php +2 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,7 @@ protected function createEntity() { 'title' => 'Llama Gabilondo', 'description' => 'Llama Gabilondo', 'link' => 'https://nl.wikipedia.org/wiki/Llama', 'resolvable_uri' => 'https://nl.wikipedia.org/wiki/Llama', 'weight' => 0, 'menu_name' => 'main', ]); Loading Loading @@ -117,6 +118,7 @@ protected function getExpectedDocument(): array { 'bundle' => 'menu_link_content', 'link' => [ 'uri' => 'https://nl.wikipedia.org/wiki/Llama', 'resolvable_uri' => 'https://nl.wikipedia.org/wiki/Llama', 'title' => NULL, 'options' => [], ], Loading
core/modules/jsonapi/tests/src/Kernel/Normalizer/FieldItemNormalizerTest.php +2 −0 Original line number Diff line number Diff line Loading @@ -84,6 +84,7 @@ public function testNormalizeFieldItem(): void { 'options' => [ 'query' => 'foo=bar', ], 'resolvable_uri' => 'https://www.drupal.org', ], ], 'internal_property_value' => [ Loading Loading @@ -112,6 +113,7 @@ public function testNormalizeFieldItem(): void { 'options' => [ 'query' => 'foo=bar', ], 'resolvable_uri' => 'https://www.drupal.org', ], $result->getNormalization()); // Verify a field with one public property and one internal only returns the Loading
core/modules/link/src/Plugin/DataType/LinkResolvableUriComputed.php 0 → 100644 +92 −0 Original line number Diff line number Diff line <?php namespace Drupal\link\Plugin\DataType; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\Attribute\DataType; use Drupal\Core\TypedData\Plugin\DataType\Uri; use Drupal\Core\Url; /** * Defines a data type for a Link Resolvable URI. */ #[DataType( id: 'resolvable_uri', label: new TranslatableMarkup('Link Resolvable URI'), )] class LinkResolvableUriComputed extends Uri implements CacheableDependencyInterface { /** * The generated URL. * * @var \Drupal\Core\GeneratedUrl|null */ protected $processed = NULL; /** * {@inheritdoc} */ public function getValue() { if ($this->processed !== NULL) { return $this->processed->getGeneratedUrl(); } /** @var \Drupal\link\Plugin\Field\FieldType\LinkItem $item */ $item = $this->getParent(); $this->processed = $item->getUrl()->toString(TRUE); return $this->processed->getGeneratedUrl(); } /** * {@inheritdoc} */ public function setValue($value, $notify = TRUE): void { if (!empty($value)) { $parsed = UrlHelper::parse($value); // If the path is not an external URL then add 'internal:' prefix to make // it a valid uri. if (strpos($parsed['path'], ':') === FALSE) { $parsed['path'] = 'internal:' . $parsed['path']; } $url = Url::fromUri($parsed['path'], [ 'query' => $parsed['query'], 'fragment' => $parsed['fragment'], ]); $this->processed = $url->toString(TRUE); } else { $this->processed = NULL; } // Notify the parent of any changes. if ($notify && isset($this->parent)) { $this->parent->onChange($this->name); } } /** * {@inheritdoc} */ public function getCacheTags() { $this->getValue(); return $this->processed->getCacheTags(); } /** * {@inheritdoc} */ public function getCacheContexts() { $this->getValue(); return $this->processed->getCacheContexts(); } /** * {@inheritdoc} */ public function getCacheMaxAge() { $this->getValue(); return $this->processed->getCacheMaxAge(); } }
core/modules/link/src/Plugin/Field/FieldType/LinkItem.php +42 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ namespace Drupal\link\Plugin\Field\FieldType; use Drupal\Component\Utility\Random; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Field\Attribute\FieldType; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemBase; Loading Loading @@ -51,6 +52,13 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel $properties['uri'] = DataDefinition::create('uri') ->setLabel(new TranslatableMarkup('URI')); $properties['resolvable_uri'] = DataDefinition::create('resolvable_uri') ->setLabel(new TranslatableMarkup('Resolvable URI')) ->setDescription(new TranslatableMarkup('The processed URL for this link suitable for using in anchor href attributes.')) ->setComputed(TRUE) ->setInternal(FALSE) ->setReadOnly(TRUE); $properties['title'] = DataDefinition::create('string') ->setLabel(new TranslatableMarkup('Link text')); Loading Loading @@ -172,6 +180,34 @@ public function getUrl() { return Url::fromUri($this->uri, (array) $this->options); } /** * {@inheritdoc} */ public function onChange($property_name, $notify = TRUE): void { // Make sure that the link item values can be kept in sync with computed // property url. if ($property_name === 'resolvable_uri') { $property = $this->get('resolvable_uri'); if ($url = $property->getValue()) { $parsed = UrlHelper::parse($url); // If the path is not an external URL then add 'internal:' prefix to // make it a valid uri. if (strpos($parsed['path'], ':') === FALSE) { $parsed['path'] = 'internal:' . $parsed['path']; } $this->writePropertyValue('uri', $parsed['path']); // Only set the options if we have query parameters or a fragment. if (!empty($parsed['query']) || !empty($parsed['fragment'])) { $this->writePropertyValue('options', [ 'query' => $parsed['query'], 'fragment' => $parsed['fragment'], ]); } } } parent::onChange($property_name, $notify); } /** * {@inheritdoc} */ Loading @@ -194,6 +230,12 @@ public function setValue($values, $notify = TRUE) { ]; } parent::setValue($values, $notify); // Support setting the field item with only url property, but make sure // values stay in sync if only url property is passed. // NULL is a valid value, so we use array_key_exists(). if (is_array($values) && array_key_exists('resolvable_uri', $values) && !array_key_exists('uri', $values)) { $this->onChange('resolvable_uri', FALSE); } } }
core/modules/link/tests/src/Kernel/LinkItemTest.php +41 −4 Original line number Diff line number Diff line Loading @@ -91,6 +91,7 @@ public function testLinkItem(): void { ], 'external' => TRUE, ], $entity->field_test->first()->getUrl()->getOptions()); $this->assertEquals($url, $entity->field_test->resolvable_uri); $entity->name->value = $this->randomMachineName(); $entity->save(); Loading @@ -99,6 +100,7 @@ public function testLinkItem(): void { $entity = EntityTest::load($id); $this->assertInstanceOf(FieldItemListInterface::class, $entity->field_test); $this->assertInstanceOf(FieldItemInterface::class, $entity->field_test[0]); $this->assertEquals($url, $entity->field_test->resolvable_uri); $this->assertEquals($parsed_url['path'], $entity->field_test->uri); $this->assertEquals($parsed_url['path'], $entity->field_test[0]->uri); $this->assertEquals($title, $entity->field_test->title); Loading @@ -123,24 +125,26 @@ public function testLinkItem(): void { $new_class = $this->randomMachineName(); $entity->field_test->uri = $new_url; $entity->field_test->title = $new_title; $entity->field_test->first()->get('options')->set('query', NULL); $entity->field_test->first()->get('options')->set('query', []); $entity->field_test->first()->get('options')->set('attributes', ['class' => $new_class]); $this->assertEquals($new_url, $entity->field_test->uri); $this->assertEquals($new_title, $entity->field_test->title); $this->assertEquals($new_class, $entity->field_test->options['attributes']['class']); $this->assertNull($entity->field_test->options['query']); $this->assertEmpty($entity->field_test->options['query']); // Read changed entity and assert changed values. $entity->save(); $entity = EntityTest::load($id); $this->assertEquals($new_url, $entity->field_test->uri); $this->assertEquals($entity->field_test->resolvable_uri, $new_url); $this->assertEquals($new_title, $entity->field_test->title); $this->assertEquals($new_class, $entity->field_test->options['attributes']['class']); // Check that if we only set uri the default values for title and options // are also initialized. // Check that if we only set uri the default values for url, title, and // options are also initialized. $entity->field_test = ['uri' => 'internal:/node/add']; $this->assertEquals('internal:/node/add', $entity->field_test->uri); $this->assertEquals($entity->field_test->resolvable_uri, '/node/add'); $this->assertNull($entity->field_test->title); $this->assertSame([], $entity->field_test->options); Loading @@ -151,6 +155,7 @@ public function testLinkItem(): void { 'options' => ['query' => NULL], ]; $this->assertEquals('internal:/node/add', $entity->field_test->uri); $this->assertEquals($entity->field_test->resolvable_uri, '/node/add'); $this->assertNull($entity->field_test->title); $this->assertNull($entity->field_test->options['query']); Loading Loading @@ -178,6 +183,38 @@ public function testLinkItem(): void { $entity->field_test_external->generateSampleItems(); $entity->field_test_internal->generateSampleItems(); $this->entityValidateAndSave($entity); // Test setting up computed property also sets up other values. $entity = EntityTest::create(); $url = 'https://www.drupal.org?test_param=test_value#top'; $parsed_url = UrlHelper::parse($url); $entity->field_test->resolvable_uri = $url; $this->assertEquals($parsed_url['path'], $entity->field_test->uri); $this->assertEquals([ 'query' => $parsed_url['query'], 'fragment' => $parsed_url['fragment'], 'external' => TRUE, ], $entity->field_test->first()->getUrl()->getOptions()); $entity->name->value = $this->randomMachineName(); $entity->save(); // Verify that the field value is changed. $id = $entity->id(); $entity = EntityTest::load($id); $this->assertInstanceOf(FieldItemListInterface::class, $entity->field_test); $this->assertInstanceOf(FieldItemInterface::class, $entity->field_test[0]); $this->assertEquals($url, $entity->field_test->resolvable_uri); $this->assertEquals($entity->field_test->uri, $parsed_url['path']); $this->assertEquals($entity->field_test[0]->uri, $parsed_url['path']); $this->assertEquals($entity->field_test->options['query'], $parsed_url['query']); $this->assertEquals($entity->field_test->options['fragment'], $parsed_url['fragment']); // Check that if we only set url the default values for uri, title, and // options are also initialized. $entity->field_test = ['resolvable_uri' => '/node/add']; $this->assertEquals($entity->field_test->uri, 'internal:/node/add'); $this->assertEquals($entity->field_test->resolvable_uri, '/node/add'); $this->assertNull($entity->field_test->title); } }