From d05ea9f7d8dab8e02140fdb60fa63a0722be8ffd Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Tue, 18 Feb 2020 14:33:15 +0100
Subject: [PATCH] [FEATURE] Define target file extension in Image-related
 ViewHelpers

When rendering custom formats with the <source> tag (as an example),
it currently is not possible to specificy a target file extension (e.g. webp
as alternative).

A new argument "fileExtension" for the <f:image>, <f:media> and
<f:uri.media> is introduced to specify a custom output format for having
variants on image variants.

Resolves: #90416
Releases: master
Change-Id: I5fe2a60b968578e504cda851d2d5dd0f0eefe7e0
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/63289
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
---
 ...ileExtensionInImage-relatedViewHelpers.rst | 43 +++++++++++++++++++
 .../Classes/ViewHelpers/ImageViewHelper.php   |  4 ++
 .../Classes/ViewHelpers/MediaViewHelper.php   |  9 +++-
 .../ViewHelpers/Uri/ImageViewHelper.php       |  4 ++
 .../Unit/ViewHelpers/ImageViewHelperTest.php  | 22 +++++++++-
 5 files changed, 79 insertions(+), 3 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-90416-SpecificTargetFileExtensionInImage-relatedViewHelpers.rst

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-90416-SpecificTargetFileExtensionInImage-relatedViewHelpers.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-90416-SpecificTargetFileExtensionInImage-relatedViewHelpers.rst
new file mode 100644
index 000000000000..3764b3d240c5
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-90416-SpecificTargetFileExtensionInImage-relatedViewHelpers.rst
@@ -0,0 +1,43 @@
+.. include:: ../../Includes.txt
+
+=============================================================================
+Feature: #90416 - Specific target file extension in Image-related ViewHelpers
+=============================================================================
+
+See :issue:`90416`
+
+Description
+===========
+
+TYPO3 Core's shipped Fluid ViewHelpers now allow to optionally
+specify a target file extension via the new attribute `fileExtension`.
+
+This affects the following ViewHelpers:
+- :html:`<f:image>`
+- :html:`<f:media>`
+- :html:`<f:uri.image>`
+
+This is rather important for specific scenarios where a :html:`<picture>` tag with multiple images are requested, allowing
+to e.g. customize rendering for `webp` support, if the servers' ImageMagick version supports `webp` conversion.
+
+In other regard, this might become useful to specify the output
+for preview images of `pdf` files which can be converted via `GhostScript` if installed.
+
+
+Impact
+======
+
+TYPO3 Integrators can now use the additional attribute
+in their custom Fluid Templates for specific use cases.
+
+Example:
+
+.. code-block:: html
+
+   <picture>
+     <source srcset="{f:uri.image(image: fileObject, treatIdAsReference: true, fileExtension: 'webp')}" type="image/webp">
+     <source srcset="{f:uri.image(image: fileObject, treatIdAsReference: true, fileExtension: 'jpg')}" type="image/jpeg">
+     <f:image image="{fileObject}" treatIdAsReference="true" alt="{fileObject.alternative}">
+   <picture>
+
+.. index:: Fluid, ext:fluid
\ No newline at end of file
diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/ImageViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/ImageViewHelper.php
index 6661fac47ccd..c6f8a449d757 100644
--- a/typo3/sysext/fluid/Classes/ViewHelpers/ImageViewHelper.php
+++ b/typo3/sysext/fluid/Classes/ViewHelpers/ImageViewHelper.php
@@ -120,6 +120,7 @@ class ImageViewHelper extends AbstractTagBasedViewHelper
         $this->registerArgument('image', 'object', 'a FAL object');
         $this->registerArgument('crop', 'string|bool', 'overrule cropping of image (setting to FALSE disables the cropping set in FileReference)');
         $this->registerArgument('cropVariant', 'string', 'select a cropping variant, in case multiple croppings have been specified or stored in FileReference', false, 'default');
+        $this->registerArgument('fileExtension', 'string', 'Custom file extension to use');
 
         $this->registerArgument('width', 'string', 'width of the image. This can be a numeric value representing the fixed width of the image in pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See imgResource.width for possible options.');
         $this->registerArgument('height', 'string', 'height of the image. This can be a numeric value representing the fixed height of the image in pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See imgResource.width for possible options.');
@@ -162,6 +163,9 @@ class ImageViewHelper extends AbstractTagBasedViewHelper
                 'maxHeight' => $this->arguments['maxHeight'],
                 'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($image),
             ];
+            if (!empty($this->arguments['fileExtension'] ?? '')) {
+                $processingInstructions['fileExtension'] = $this->arguments['fileExtension'];
+            }
             $processedImage = $this->imageService->applyProcessingInstructions($image, $processingInstructions);
             $imageUri = $this->imageService->getImageUri($processedImage, $this->arguments['absolute']);
 
diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/MediaViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/MediaViewHelper.php
index e826a6dff734..8012c2751bc8 100644
--- a/typo3/sysext/fluid/Classes/ViewHelpers/MediaViewHelper.php
+++ b/typo3/sysext/fluid/Classes/ViewHelpers/MediaViewHelper.php
@@ -85,6 +85,7 @@ class MediaViewHelper extends AbstractTagBasedViewHelper
         $this->registerArgument('width', 'string', 'This can be a numeric value representing the fixed width of in pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See imgResource.width for possible options.');
         $this->registerArgument('height', 'string', 'This can be a numeric value representing the fixed height in pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See imgResource.width for possible options.');
         $this->registerArgument('cropVariant', 'string', 'select a cropping variant, in case multiple croppings have been specified or stored in FileReference', false, 'default');
+        $this->registerArgument('fileExtension', 'string', 'Custom file extension to use for images');
     }
 
     /**
@@ -114,7 +115,7 @@ class MediaViewHelper extends AbstractTagBasedViewHelper
 
         // Fallback to image when no renderer is found
         if ($fileRenderer === null) {
-            return $this->renderImage($file, $width, $height);
+            return $this->renderImage($file, $width, $height, $this->arguments['fileExtension'] ?? null);
         }
         $additionalConfig = array_merge_recursive($this->arguments, $additionalConfig);
         return $fileRenderer->render($file, $width, $height, $additionalConfig);
@@ -126,9 +127,10 @@ class MediaViewHelper extends AbstractTagBasedViewHelper
      * @param FileInterface $image
      * @param string $width
      * @param string $height
+     * @param string|null $fileExtension
      * @return string Rendered img tag
      */
-    protected function renderImage(FileInterface $image, $width, $height)
+    protected function renderImage(FileInterface $image, $width, $height, ?string $fileExtension)
     {
         $cropVariant = $this->arguments['cropVariant'] ?: 'default';
         $cropString = $image instanceof FileReference ? $image->getProperty('crop') : '';
@@ -139,6 +141,9 @@ class MediaViewHelper extends AbstractTagBasedViewHelper
             'height' => $height,
             'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($image),
         ];
+        if (!empty($fileExtension)) {
+            $processingInstructions['fileExtension'] = $fileExtension;
+        }
         $imageService = $this->getImageService();
         $processedImage = $imageService->applyProcessingInstructions($image, $processingInstructions);
         $imageUri = $imageService->getImageUri($processedImage);
diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ImageViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ImageViewHelper.php
index 9300f0b4412b..c3dd6554dcf1 100644
--- a/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ImageViewHelper.php
+++ b/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ImageViewHelper.php
@@ -94,6 +94,7 @@ class ImageViewHelper extends AbstractViewHelper
         $this->registerArgument('image', 'object', 'image');
         $this->registerArgument('crop', 'string|bool', 'overrule cropping of image (setting to FALSE disables the cropping set in FileReference)');
         $this->registerArgument('cropVariant', 'string', 'select a cropping variant, in case multiple croppings have been specified or stored in FileReference', false, 'default');
+        $this->registerArgument('fileExtension', 'string', 'Custom file extension to use');
 
         $this->registerArgument('width', 'string', 'width of the image. This can be a numeric value representing the fixed width of the image in pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See imgResource.width for possible options.');
         $this->registerArgument('height', 'string', 'height of the image. This can be a numeric value representing the fixed height of the image in pixels. But you can also perform simple calculations by adding "m" or "c" to the value. See imgResource.width for possible options.');
@@ -145,6 +146,9 @@ class ImageViewHelper extends AbstractViewHelper
                 'maxHeight' => $arguments['maxHeight'],
                 'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($image),
             ];
+            if (!empty($arguments['fileExtension'])) {
+                $processingInstructions['fileExtension'] = $arguments['fileExtension'];
+            }
 
             $processedImage = $imageService->applyProcessingInstructions($image, $processingInstructions);
             return $imageService->getImageUri($processedImage, $absolute);
diff --git a/typo3/sysext/fluid/Tests/Unit/ViewHelpers/ImageViewHelperTest.php b/typo3/sysext/fluid/Tests/Unit/ViewHelpers/ImageViewHelperTest.php
index 5666fef075ab..adf7ae6b7847 100644
--- a/typo3/sysext/fluid/Tests/Unit/ViewHelpers/ImageViewHelperTest.php
+++ b/typo3/sysext/fluid/Tests/Unit/ViewHelpers/ImageViewHelperTest.php
@@ -116,6 +116,26 @@ class ImageViewHelperTest extends ViewHelperBaseTestcase
                     'title' => 'title'
                 ]
             ],
+            [
+                [
+                    'src' => 'test',
+                    'width' => 100,
+                    'height' => 200,
+                    'minWidth' => 300,
+                    'maxWidth' => 400,
+                    'minHeight' => 500,
+                    'maxHeight' => 600,
+                    'crop' => null,
+                    'fileExtension' => 'jpg'
+                ],
+                [
+                    'src' => 'test.jpg',
+                    'width' => '100',
+                    'height' => '200',
+                    'alt' => 'alternative',
+                    'title' => 'title'
+                ]
+            ],
         ];
     }
 
@@ -157,7 +177,7 @@ class ImageViewHelperTest extends ViewHelperBaseTestcase
         $imageService = $this->createMock(ImageService::class);
         $imageService->expects(self::once())->method('getImage')->willReturn($image);
         $imageService->expects(self::once())->method('applyProcessingInstructions')->with($image, self::anything())->willReturn($processedFile);
-        $imageService->expects(self::once())->method('getImageUri')->with($processedFile)->willReturn('test.png');
+        $imageService->expects(self::once())->method('getImageUri')->with($processedFile)->willReturn($expected['src']);
 
         $this->viewHelper->injectImageService($imageService);
 
-- 
GitLab