diff --git a/README.md b/README.md index b36d008..4f3821d 100644 --- a/README.md +++ b/README.md @@ -303,6 +303,12 @@ For string arguments it works similar to core `file_url()` Twig function. {{ 'public://sea.jpg'|file_url }} ``` +In order to generate absolute URL set "relative" parameter to `false`. +```twig +{{ 'public://sea.jpg'|file_url(relative=false) }} +{{ 'public://sea.jpg'|file_url(false) }} +``` + When field item list passed the URL will be extracted from the first item. In order to get URL of specific item specify its delta explicitly using array notation. @@ -316,6 +322,12 @@ Media fields are fully supported including OEmbed resources. {{ node.field_media|file_url }} ``` +It is also possible to extract file URL directly from an entity. +```twig +{{ image|file_url }} +{{ media|file_url }} +``` + ### PHP PHP filter is disabled by default. You can enable it in settings.php file as follows: diff --git a/src/TwigTweakExtension.php b/src/TwigTweakExtension.php index 19dc858..ca94129 100644 --- a/src/TwigTweakExtension.php +++ b/src/TwigTweakExtension.php @@ -605,18 +605,7 @@ class TwigTweakExtension extends AbstractExtension { * A URL that may be used to access the file. */ public static function fileUrlFilter($input): ?string { - if (is_string($input)) { - return file_url_transform_relative(file_create_url($input)); - } - if ($input instanceof EntityReferenceFieldItemListInterface) { - $referenced_entities = $input->referencedEntities(); - if (isset($referenced_entities[0])) { - return self::getUrlFromEntity($referenced_entities[0]); - } - } - elseif ($input instanceof EntityReferenceItem) { - return self::getUrlFromEntity($input->entity); - } + return \Drupal::service('twig_tweak.url_extractor')->extractUrl($input); } /** @@ -666,29 +655,4 @@ class TwigTweakExtension extends AbstractExtension { } } - /** - * Extracts file URL from content entity. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * Entity object that contains information about the file. - * - * @return string|null - * A URL that may be used to access the file. - */ - private static function getUrlFromEntity(EntityInterface $entity): ?string { - if ($entity instanceof MediaInterface) { - $source = $entity->getSource(); - $value = $source->getSourceFieldValue($entity); - if ($source instanceof OEmbedInterface) { - return $value; - } - elseif ($file = File::load($value)) { - return $file->createFileUrl(); - } - } - elseif ($entity instanceof FileInterface) { - return $entity->createFileUrl(); - } - } - } diff --git a/src/UrlExtractor.php b/src/UrlExtractor.php new file mode 100644 index 0000000..00c2795 --- /dev/null +++ b/src/UrlExtractor.php @@ -0,0 +1,96 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * Extracts file URL from a string or object. + * + * @param string|object $input + * Can be either file URI or an object that contains the URI. + * @param bool $relative + * (optional) Whether the URL should be root-relative, defaults to TRUE. + * + * @return string|null + * A URL that may be used to access the file. + */ + public function extractUrl($input, bool $relative = TRUE): ?string { + if (is_string($input)) { + $url = file_create_url($input); + return $relative ? file_url_transform_relative($url) : $url; + } + elseif ($input instanceof ContentEntityInterface) { + return $this->getUrlFromEntity($input, $relative); + } + elseif ($input instanceof EntityReferenceFieldItemListInterface) { + if ($item = $input->first()) { + return $this->getUrlFromEntity($item->entity, $relative); + } + } + elseif ($input instanceof EntityReferenceItem) { + return $this->getUrlFromEntity($input->entity, $relative); + } + return NULL; + } + + /** + * Extracts file URL from content entity. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * Entity object that contains information about the file. + * @param bool $relative + * (optional) Whether the URL should be root-relative, defaults to TRUE. + * + * @return string|null + * A URL that may be used to access the file. + */ + private function getUrlFromEntity(ContentEntityInterface $entity, bool $relative = TRUE): ?string { + if ($entity instanceof MediaInterface) { + $source = $entity->getSource(); + $value = $source->getSourceFieldValue($entity); + if (!$value) { + return NULL; + } + elseif ($source instanceof OEmbedInterface) { + return $value; + } + else { + $file = $this->entityTypeManager->getStorage('file')->load($value); + if ($file) { + return $file->createFileUrl($relative); + } + } + } + elseif ($entity instanceof FileInterface) { + return $entity->createFileUrl($relative); + } + return NULL; + } + +} diff --git a/tests/src/Kernel/UrlExtractorTest.php b/tests/src/Kernel/UrlExtractorTest.php new file mode 100644 index 0000000..53d51f8 --- /dev/null +++ b/tests/src/Kernel/UrlExtractorTest.php @@ -0,0 +1,168 @@ +installConfig(['node', 'twig_tweak_test']); + $this->installSchema('file', 'file_usage'); + $this->installEntitySchema('file'); + $this->installEntitySchema('media'); + + $test_files = $this->getTestFiles('image'); + // + $image_file = File::create([ + 'uri' => $test_files[0]->uri, + 'uuid' => 'a2cb2b6f-7bf8-4da4-9de5-316e93487518', + 'status' => FILE_STATUS_PERMANENT, + ]); + $image_file->save(); + + $media_file = File::create([ + 'uri' => $test_files[2]->uri, + 'uuid' => '5dd794d0-cb75-4130-9296-838aebc1fe74', + 'status' => FILE_STATUS_PERMANENT, + ]); + $media_file->save(); + + $media = Media::create([ + 'bundle' => 'image', + 'name' => 'Image 1', + 'field_media_image' => ['target_id' => $media_file->id()], + ]); + $media->save(); + + $node_values = [ + 'title' => 'Alpha', + 'type' => 'page', + 'field_image' => [ + 'target_id' => $image_file->id(), + ], + 'field_media' => [ + 'target_id' => $media->id(), + ], + ]; + $this->node = Node::create($node_values); + } + + /** + * Test callback. + */ + public function testUrlExtractor(): void { + + $extractor = $this->container->get('twig_tweak.url_extractor'); + $base_url = file_create_url(''); + + $request = \Drupal::request(); + $absolute_url = "{$request->getScheme()}://{$request->getHost()}/foo/bar.txt"; + $url = $extractor->extractUrl($absolute_url); + self::assertSame('/foo/bar.txt', $url); + + $url = $extractor->extractUrl($absolute_url, FALSE); + self::assertSame($base_url . 'foo/bar.txt', $url); + + $url = $extractor->extractUrl('foo/bar.jpg'); + self::assertSame('/foo/bar.jpg', $url); + + $url = $extractor->extractUrl('foo/bar.jpg', FALSE); + self::assertSame($base_url . 'foo/bar.jpg', $url); + + $url = $extractor->extractUrl(''); + self::assertSame('/', $url); + + $url = $extractor->extractUrl('', FALSE); + self::assertSame($base_url, $url); + + $url = $extractor->extractUrl(NULL); + self::assertNull($url); + + $url = $extractor->extractUrl($this->node); + self::assertNull($url); + + $url = $extractor->extractUrl($this->node->get('title')); + self::assertNull($url); + + $url = $extractor->extractUrl($this->node->get('field_image')[0]); + self::assertStringEndsWith('/files/image-test.png', $url); + self::assertStringNotContainsString($base_url, $url); + + $url = $extractor->extractUrl($this->node->get('field_image')[0], FALSE); + self::assertStringStartsWith($base_url, $url); + self::assertStringEndsWith('/files/image-test.png', $url); + + $url = $extractor->extractUrl($this->node->get('field_image')[1]); + self::assertNull($url); + + $url = $extractor->extractUrl($this->node->get('field_image')); + self::assertStringEndsWith('/files/image-test.png', $url); + + $url = $extractor->extractUrl($this->node->get('field_image')->entity); + self::assertStringEndsWith('/files/image-test.png', $url); + + $this->node->get('field_image')->removeItem(0); + $url = $extractor->extractUrl($this->node->get('field_image')); + self::assertNull($url); + + $url = $extractor->extractUrl($this->node->get('field_media')[0]); + self::assertStringEndsWith('/files/image-test.gif', $url); + + $url = $extractor->extractUrl($this->node->get('field_media')[1]); + self::assertNull($url); + + $url = $extractor->extractUrl($this->node->get('field_media')); + self::assertStringEndsWith('/files/image-test.gif', $url); + + $url = $extractor->extractUrl($this->node->get('field_media')->entity); + self::assertStringEndsWith('/files/image-test.gif', $url); + + $this->node->get('field_media')->removeItem(0); + $url = $extractor->extractUrl($this->node->get('field_media')); + self::assertNull($url); + } + +} diff --git a/twig_tweak.services.yml b/twig_tweak.services.yml index 8ae8efa..582c740 100644 --- a/twig_tweak.services.yml +++ b/twig_tweak.services.yml @@ -31,3 +31,7 @@ services: twig_tweak.image_view_builder: class: Drupal\twig_tweak\View\ImageViewBuilder + + twig_tweak.url_extractor: + class: Drupal\twig_tweak\UrlExtractor + arguments: ['@entity_type.manager']