diff --git a/src/View/ImageViewBuilder.php b/src/View/ImageViewBuilder.php index de1d6da..72761cf 100644 --- a/src/View/ImageViewBuilder.php +++ b/src/View/ImageViewBuilder.php @@ -4,6 +4,7 @@ namespace Drupal\twig_tweak\View; use Drupal\Core\Access\AccessResult; use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Image\ImageFactory; use Drupal\file\FileInterface; /** @@ -11,6 +12,20 @@ use Drupal\file\FileInterface; */ class ImageViewBuilder { + /** + * The provider image factory. + * + * @var \Drupal\Core\Image\ImageFactory + */ + protected $imageFactory; + + /** + * Constructs an ImageViewBuilder object. + */ + public function __construct(ImageFactory $imageFactory) { + $this->imageFactory = $imageFactory; + } + /** * Builds an image. * @@ -37,6 +52,20 @@ class ImageViewBuilder { $build['#uri'] = $file->getFileUri(); $build['#attributes'] = $attributes; if ($style) { + // If an image style is given, image module needs the original + // image dimensions to calculate image style's + // width and height and set the attributes. + // See https://www.drupal.org/project/twig_tweak/issues/3356042 + $uri = $file->getFileUri(); + $image = $this->imageFactory->get($uri); + if ($image->isValid()) { + $build['#width'] = $image->getWidth(); + $build['#height'] = $image->getHeight(); + } + else { + $build['#width'] = $build['#height'] = NULL; + } + if ($responsive) { $build['#type'] = 'responsive_image'; $build['#responsive_image_style_id'] = $style; diff --git a/tests/src/Kernel/ImageViewBuilderTest.php b/tests/src/Kernel/ImageViewBuilderTest.php index 53aa42b..3dab2ee 100644 --- a/tests/src/Kernel/ImageViewBuilderTest.php +++ b/tests/src/Kernel/ImageViewBuilderTest.php @@ -4,14 +4,14 @@ namespace Drupal\Tests\twig_tweak\Kernel; use Drupal\Core\Cache\Cache; use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\DrupalKernel; +use Drupal\Core\File\FileSystemInterface; use Drupal\file\Entity\File; +use Drupal\file\FileInterface; use Drupal\image\Entity\ImageStyle; use Drupal\responsive_image\Entity\ResponsiveImageStyle; -use Symfony\Component\HttpFoundation\Request; /** - * A test for ImageViewBuilderTest. + * A test class for testing the image view builder. * * @group twig_tweak */ @@ -31,21 +31,110 @@ final class ImageViewBuilderTest extends AbstractTestCase { 'breakpoint', ]; + /** + * The ImageStyle. + * + * @var \Drupal\image\Entity\ImageStyle + */ + protected $imageStyle; + + /** + * The ResponsiveImageStyle. + * + * @var \Drupal\responsive_image\Entity\ResponsiveImageStyle + */ + protected $responsiveImageStyle; + + /** + * The public image uri. + * + * @var string + */ + protected string $publicImageUri; + + /** + * The private image uri. + * + * @var string + */ + protected string $privateImageUri; + + /** + * The public image file. + * + * @var \Drupal\file\FileInterface + */ + protected $publicImage; + + /** + * The private image file. + * + * @var \Drupal\file\FileInterface + */ + protected $privateImage; + /** * {@inheritdoc} */ public function setUp(): void { parent::setUp(); - // Add file_private_path setting. - $request = Request::create('/'); - $site_path = DrupalKernel::findSitePath($request); - $this->setSetting('file_private_path', $site_path . '/private'); - $this->installEntitySchema('file'); $this->installSchema('file', 'file_usage'); - ImageStyle::create(['name' => 'large'])->save(); - ResponsiveImageStyle::create(['id' => 'wide'])->save(); + + $fileSystemService = \Drupal::service('file_system'); + + $fileSystemService->prepareDirectory($this->siteDirectory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); + $privateFilesDirectory = $this->siteDirectory . '/private'; + $fileSystemService->prepareDirectory($privateFilesDirectory, FileSystemInterface::CREATE_DIRECTORY); + $this->setSetting('file_private_path', $privateFilesDirectory); + + $this->imageStyle = ImageStyle::create([ + 'name' => 'small', + 'label' => 'Small', + ]); + $this->imageStyle->save(); + // Add a crop effect: + $this->imageStyle->addImageEffect([ + 'id' => 'image_resize', + 'data' => [ + 'width' => 10, + 'height' => 10, + ], + 'weight' => 0, + ]); + $this->imageStyle->save(); + + $this->responsiveImageStyle = ResponsiveImageStyle::create([ + 'id' => 'wide', + 'label' => 'Wide', + 'breakpoint_group' => 'twig_tweak_image_view_builder', + 'fallback_image_style' => 'small', + ]); + $this->responsiveImageStyle->save(); + + // Create a copy of a test image file in root. + // Original sizes: 40x20px. + $this->publicImageUri = 'public://image-test-do.jpg'; + $fileSystemService->copy('core/tests/fixtures/files/image-test.jpg', $this->publicImageUri, FileSystemInterface::EXISTS_REPLACE); + $this->assertFileExists($this->publicImageUri); + $this->publicImage = File::create([ + 'uri' => $this->publicImageUri, + 'status' => FileInterface::STATUS_PERMANENT, + ]); + $this->publicImage->save(); + + // Create a copy of a test image file in root. + // Original sizes: 40x20px. + $this->privateImageUri = 'private://image-test-do.png'; + $fileSystemService + ->copy('core/tests/fixtures/files/image-test.png', $this->privateImageUri, FileSystemInterface::EXISTS_REPLACE); + $this->assertFileExists($this->privateImageUri); + $this->privateImage = File::create([ + 'uri' => $this->privateImageUri, + 'status' => FileInterface::STATUS_PERMANENT, + ]); + $this->privateImage->save(); } /** @@ -61,21 +150,20 @@ final class ImageViewBuilderTest extends AbstractTestCase { * Test callback. */ public function testImageViewBuilder(): void { - $view_builder = $this->container->get('twig_tweak.image_view_builder'); - /** @var \Drupal\file\FileInterface $public_image */ - $public_image = File::create(['uri' => 'public://ocean.jpg']); - $public_image->save(); - - /** @var \Drupal\file\FileInterface $private_image */ - $private_image = File::create(['uri' => 'private://sea.jpg']); - $private_image->save(); + $uri = $this->publicImage->getFileUri(); + $image = \Drupal::service('image.factory')->get($uri); + $imageOriginalWidth = $image->getWidth(); + $imageOriginalHeight = $image->getHeight(); + self::assertTrue($image->isValid()); + self::assertEquals(40, $imageOriginalWidth); + self::assertEquals(20, $imageOriginalHeight); // -- Without style. - $build = $view_builder->build($public_image); + $build = $view_builder->build($this->publicImage); $expected_build = [ - '#uri' => 'public://ocean.jpg', + '#uri' => $this->publicImageUri, '#attributes' => [], '#theme' => 'image', '#cache' => [ @@ -85,21 +173,23 @@ final class ImageViewBuilderTest extends AbstractTestCase { ], 'tags' => [ 'file:1', - 'tag_for_public://ocean.jpg', + 'tag_for_' . $this->publicImageUri, ], 'max-age' => 70, ], ]; self::assertRenderArray($expected_build, $build); - self::assertSame('', $this->renderPlain($build)); + self::assertSame('', $this->renderPlain($build)); // -- With style. - $build = $view_builder->build($public_image, 'large', ['alt' => 'Ocean']); + $build = $view_builder->build($this->publicImage, 'small', ['alt' => 'Image Test Do']); $expected_build = [ - '#uri' => 'public://ocean.jpg', - '#attributes' => ['alt' => 'Ocean'], + '#uri' => $this->publicImageUri, + '#attributes' => ['alt' => 'Image Test Do'], + '#width' => $imageOriginalWidth, + '#height' => $imageOriginalHeight, '#theme' => 'image_style', - '#style_name' => 'large', + '#style_name' => 'small', '#cache' => [ 'contexts' => [ 'user', @@ -107,19 +197,21 @@ final class ImageViewBuilderTest extends AbstractTestCase { ], 'tags' => [ 'file:1', - 'tag_for_public://ocean.jpg', + 'tag_for_' . $this->publicImageUri, ], 'max-age' => 70, ], ]; self::assertRenderArray($expected_build, $build); - self::assertSame('Ocean', $this->renderPlain($build)); + self::assertSame('Image Test Do', $this->renderPlain($build)); // -- With responsive style. - $build = $view_builder->build($public_image, 'wide', ['alt' => 'Ocean'], TRUE); + $build = $view_builder->build($this->publicImage, 'wide', ['alt' => 'Image Test Do'], TRUE); $expected_build = [ - '#uri' => 'public://ocean.jpg', - '#attributes' => ['alt' => 'Ocean'], + '#uri' => $this->publicImageUri, + '#attributes' => ['alt' => 'Image Test Do'], + '#width' => $imageOriginalWidth, + '#height' => $imageOriginalHeight, '#type' => 'responsive_image', '#responsive_image_style_id' => 'wide', '#cache' => [ @@ -129,22 +221,22 @@ final class ImageViewBuilderTest extends AbstractTestCase { ], 'tags' => [ 'file:1', - 'tag_for_public://ocean.jpg', + 'tag_for_' . $this->publicImageUri, ], 'max-age' => 70, ], ]; self::assertRenderArray($expected_build, $build); - self::assertSame('Ocean', $this->renderPlain($build)); + self::assertSame('Image Test Do', $this->renderPlain($build)); // -- Private image with access check. - $build = $view_builder->build($private_image); + $build = $view_builder->build($this->privateImage); $expected_build = [ '#cache' => [ 'contexts' => ['user'], 'tags' => [ 'file:2', - 'tag_for_private://sea.jpg', + 'tag_for_' . $this->privateImageUri, ], 'max-age' => 70, ], @@ -153,9 +245,9 @@ final class ImageViewBuilderTest extends AbstractTestCase { self::assertSame('', $this->renderPlain($build)); // -- Private image without access check. - $build = $view_builder->build($private_image, NULL, [], FALSE, FALSE); + $build = $view_builder->build($this->privateImage, NULL, [], FALSE, FALSE); $expected_build = [ - '#uri' => 'private://sea.jpg', + '#uri' => $this->privateImageUri, '#attributes' => [], '#theme' => 'image', '#cache' => [ @@ -165,7 +257,7 @@ final class ImageViewBuilderTest extends AbstractTestCase { ], ]; self::assertRenderArray($expected_build, $build); - self::assertSame('', $this->renderPlain($build)); + self::assertSame('', $this->renderPlain($build)); } /** diff --git a/twig_tweak.services.yml b/twig_tweak.services.yml index 6e33f41..b224788 100644 --- a/twig_tweak.services.yml +++ b/twig_tweak.services.yml @@ -31,6 +31,7 @@ services: twig_tweak.image_view_builder: class: Drupal\twig_tweak\View\ImageViewBuilder + arguments: ['@image.factory'] twig_tweak.url_extractor: class: Drupal\twig_tweak\UrlExtractor