diff --git a/docs/cheat-sheet.md b/docs/cheat-sheet.md index 642260c..fb034db 100644 --- a/docs/cheat-sheet.md +++ b/docs/cheat-sheet.md @@ -118,8 +118,11 @@ Note that drupal_field() does not work for view modes powered by Layout Builder. {# Render image specified by file URI. #} {{ drupal_image('public://ocean.jpg') }} -{# Render image using 'thumbnail' image style and custom attributes. #} -{{ drupal_image('public://ocean.jpg', 'thumbnail', {alt: 'The alternative text'|t, title: 'The title text'|t}) }} +{# Render image specified by file URI with alt text #} +{{ drupal_image('public://ocean.jpg', alt='The alternative text'|t) }} + +{# Render image using 'thumbnail' image style, alt text and custom attributes. #} +{{ drupal_image('public://ocean.jpg', 'thumbnail', {title: 'The title text'|t}, alt='The alternative text'|t) }} {# Render image using 'thumbnail' image style with lazy/eager loading (by attribute). #} {{ drupal_image('public://ocean.jpg', 'thumbnail', {loading: 'lazy'}) }} diff --git a/src/TwigTweakExtension.php b/src/TwigTweakExtension.php index 36e3004..be3ae99 100644 --- a/src/TwigTweakExtension.php +++ b/src/TwigTweakExtension.php @@ -232,7 +232,7 @@ class TwigTweakExtension extends AbstractExtension { /** * Builds an image. */ - public static function drupalImage(string $selector, string $style = NULL, array $attributes = [], bool $responsive = FALSE, bool $check_access = TRUE): array { + public static function drupalImage(string $selector, string $style = NULL, array $attributes = [], bool $responsive = FALSE, bool $check_access = TRUE, string $alt = ''): array { // Determine selector type by its value. if (preg_match('/^\d+$/', $selector)) { @@ -257,7 +257,7 @@ class TwigTweakExtension extends AbstractExtension { ksort($files); $file = reset($files); - return \Drupal::service('twig_tweak.image_view_builder')->build($file, $style, $attributes, $responsive, $check_access); + return \Drupal::service('twig_tweak.image_view_builder')->build($file, $style, $attributes, $responsive, $check_access, $alt); } /** diff --git a/src/View/ImageViewBuilder.php b/src/View/ImageViewBuilder.php index de1d6da..378920c 100644 --- a/src/View/ImageViewBuilder.php +++ b/src/View/ImageViewBuilder.php @@ -24,11 +24,13 @@ class ImageViewBuilder { * (optional) Indicates that the provided image style is responsive. * @param bool $check_access * (optional) Indicates that access check is required. + * @param bool $alt + * (optional) The image "alt" text. * * @return array * A renderable array to represent the image. */ - public function build(FileInterface $file, string $style = NULL, array $attributes = [], bool $responsive = FALSE, bool $check_access = TRUE): array { + public function build(FileInterface $file, string $style = NULL, array $attributes = [], bool $responsive = FALSE, bool $check_access = TRUE, string $alt = ''): array { $access = $check_access ? $file->access('view', NULL, TRUE) : AccessResult::allowed(); @@ -40,14 +42,18 @@ class ImageViewBuilder { if ($responsive) { $build['#type'] = 'responsive_image'; $build['#responsive_image_style_id'] = $style; + // This is currently inconsistent: https://www.drupal.org/project/drupal/issues/3359410 + $build['#attributes']['alt'] = $alt; } else { $build['#theme'] = 'image_style'; $build['#style_name'] = $style; + $build['#alt'] = $alt; } } else { $build['#theme'] = 'image'; + $build['#alt'] = $alt; } } diff --git a/tests/src/Kernel/ImageViewBuilderTest.php b/tests/src/Kernel/ImageViewBuilderTest.php index 53aa42b..a0726db 100644 --- a/tests/src/Kernel/ImageViewBuilderTest.php +++ b/tests/src/Kernel/ImageViewBuilderTest.php @@ -78,6 +78,7 @@ final class ImageViewBuilderTest extends AbstractTestCase { '#uri' => 'public://ocean.jpg', '#attributes' => [], '#theme' => 'image', + '#alt' => '', '#cache' => [ 'contexts' => [ 'user', @@ -93,13 +94,36 @@ final class ImageViewBuilderTest extends AbstractTestCase { self::assertRenderArray($expected_build, $build); self::assertSame('', $this->renderPlain($build)); + // -- Without style, with alt. + $build = $view_builder->build($public_image, NULL, [], FALSE, TRUE, 'The attribute'); + $expected_build = [ + '#uri' => 'public://ocean.jpg', + '#attributes' => [], + '#theme' => 'image', + '#alt' => 'The attribute', + '#cache' => [ + 'contexts' => [ + 'user', + 'user.permissions', + ], + 'tags' => [ + 'file:1', + 'tag_for_public://ocean.jpg', + ], + 'max-age' => 70, + ], + ]; + self::assertRenderArray($expected_build, $build); + self::assertSame('The attribute', $this->renderPlain($build)); + // -- With style. - $build = $view_builder->build($public_image, 'large', ['alt' => 'Ocean']); + $build = $view_builder->build($public_image, 'large', ['title' => 'The title']); $expected_build = [ '#uri' => 'public://ocean.jpg', - '#attributes' => ['alt' => 'Ocean'], + '#attributes' => ['title' => 'The title'], '#theme' => 'image_style', '#style_name' => 'large', + '#alt' => '', '#cache' => [ 'contexts' => [ 'user', @@ -113,13 +137,66 @@ final class ImageViewBuilderTest extends AbstractTestCase { ], ]; self::assertRenderArray($expected_build, $build); - self::assertSame('Ocean', $this->renderPlain($build)); + self::assertSame('', $this->renderPlain($build)); // -- With responsive style. - $build = $view_builder->build($public_image, 'wide', ['alt' => 'Ocean'], TRUE); + $build = $view_builder->build($public_image, 'wide', ['title' => 'The title'], TRUE); $expected_build = [ '#uri' => 'public://ocean.jpg', - '#attributes' => ['alt' => 'Ocean'], + '#attributes' => [ + 'title' => 'The title', + 'alt' => '', + ], + '#type' => 'responsive_image', + '#responsive_image_style_id' => 'wide', + '#cache' => [ + 'contexts' => [ + 'user', + 'user.permissions', + ], + 'tags' => [ + 'file:1', + 'tag_for_public://ocean.jpg', + ], + 'max-age' => 70, + ], + ]; + self::assertRenderArray($expected_build, $build); + self::assertSame('', $this->renderPlain($build)); + + // -- With style and alt + $build = $view_builder->build($public_image, 'large', ['title' => 'The title'], FALSE, TRUE, 'The attribute'); + $expected_build = [ + '#uri' => 'public://ocean.jpg', + '#attributes' => [ + 'title' => 'The title', + ], + '#theme' => 'image_style', + '#style_name' => 'large', + '#alt' => 'The attribute', + '#cache' => [ + 'contexts' => [ + 'user', + 'user.permissions', + ], + 'tags' => [ + 'file:1', + 'tag_for_public://ocean.jpg', + ], + 'max-age' => 70, + ], + ]; + self::assertRenderArray($expected_build, $build); + self::assertSame('The attribute', $this->renderPlain($build)); + + // -- With responsive style and alt. + $build = $view_builder->build($public_image, 'wide', ['title' => 'The title'], TRUE, TRUE, 'The attribute'); + $expected_build = [ + '#uri' => 'public://ocean.jpg', + '#attributes' => [ + 'title' => 'The title', + 'alt' => 'The attribute', + ], '#type' => 'responsive_image', '#responsive_image_style_id' => 'wide', '#cache' => [ @@ -135,7 +212,7 @@ final class ImageViewBuilderTest extends AbstractTestCase { ], ]; self::assertRenderArray($expected_build, $build); - self::assertSame('Ocean', $this->renderPlain($build)); + self::assertSame('The attribute', $this->renderPlain($build)); // -- Private image with access check. $build = $view_builder->build($private_image); @@ -158,6 +235,7 @@ final class ImageViewBuilderTest extends AbstractTestCase { '#uri' => 'private://sea.jpg', '#attributes' => [], '#theme' => 'image', + '#alt' => '', '#cache' => [ 'contexts' => [], 'tags' => ['file:2'],