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('
', $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('
', $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('
', $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('
', $this->renderPlain($build));
+ self::assertSame('
', $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'],