[FEATURE] Render thumbnails in file list module deferred 46/57646/19
authorFrank Naegler <frank.naegler@typo3.org>
Fri, 20 Jul 2018 19:03:47 +0000 (21:03 +0200)
committerAndreas Wolf <andreas.wolf@typo3.org>
Fri, 1 Feb 2019 16:11:02 +0000 (17:11 +0100)
This patch adds a new ViewHelper to render thumbnails deferred in
the backend. This increase the performance of the file list.

Resolves: #85607
Related: #85605
Releases: master
Change-Id: Id97876e889605d7d8d3075ee98a4fc34b002f395
Reviewed-on: https://review.typo3.org/57646
Tested-by: TYPO3com <noreply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Andreas Wolf <andreas.wolf@typo3.org>
Tested-by: Andreas Wolf <andreas.wolf@typo3.org>
typo3/sysext/backend/Classes/Controller/File/ThumbnailController.php
typo3/sysext/backend/Classes/Utility/BackendUtility.php
typo3/sysext/backend/Classes/ViewHelpers/ThumbnailViewHelper.php [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-85607-NewThumbnailViewHelperToRenderThumbnailsDeferred.rst [new file with mode: 0644]
typo3/sysext/filelist/Classes/FileList.php
typo3/sysext/filelist/Resources/Private/Templates/FileList/Search.html

index ba7c4fe..27294c0 100644 (file)
@@ -18,6 +18,8 @@ namespace TYPO3\CMS\Backend\Controller\File;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Http\Response;
+use TYPO3\CMS\Core\Imaging\IconRegistry;
+use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
@@ -86,10 +88,13 @@ class ThumbnailController
     protected function generateThumbnail($fileId, array $configuration): ResponseInterface
     {
         $file = ResourceFactory::getInstance()->getFileObject($fileId);
-        if (empty($file) || $file->isMissing()) {
+        if ($file === null || $file->isMissing()) {
             return $this->generateNotFoundResponse();
         }
 
+        $context = $configuration['_context'] ?? ProcessedFile::CONTEXT_IMAGEPREVIEW;
+        unset($configuration['_context']);
+
         $processingConfiguration = $this->defaultConfiguration;
         ArrayUtility::mergeRecursiveWithOverrule(
             $processingConfiguration,
@@ -97,13 +102,27 @@ class ThumbnailController
         );
 
         $processedImage = $file->process(
-            ProcessedFile::CONTEXT_IMAGECROPSCALEMASK,
+            $context,
             $processingConfiguration
         );
-        $filePath = $processedImage->getForLocalProcessing(false);
-        return new Response($filePath, 200, [
-            'Content-Type' => $processedImage->getMimeType()
-        ]);
+        if (strpos($processedImage->getMimeType(), 'image') === 0) {
+            $filePath = $processedImage->getForLocalProcessing(false);
+            return new Response($filePath, 200, [
+                'Content-Type' => $processedImage->getMimeType()
+            ]);
+        }
+
+        $mimeIdentifier = GeneralUtility::trimExplode('/', $file->getMimeType())[0] . '/*';
+        $fileTypeIdentifier = GeneralUtility::makeInstance(IconRegistry::class)
+            ->getIconIdentifierForMimeType($mimeIdentifier);
+        $file = GeneralUtility::getFileAbsFileName('EXT:core/Resources/Public/Icons/T3Icons/mimetypes/' . $fileTypeIdentifier . '.svg');
+        if (file_exists($file)) {
+            return new Response($file, 200, [
+                'Content-Type' => 'image/svg+xml'
+            ]);
+        }
+
+        return $this->generateNotFoundResponse();
     }
 
     /**
index e8096a6..09bfd2c 100644 (file)
@@ -36,6 +36,7 @@ use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Log\LogManager;
+use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
 use TYPO3\CMS\Core\Routing\RouterInterface;
@@ -1137,23 +1138,12 @@ class BackendUtility
                 ) {
                     $cropVariantCollection = CropVariantCollection::create((string)$fileReferenceObject->getProperty('crop'));
                     $cropArea = $cropVariantCollection->getCropArea();
-                    $parameters = json_encode([
-                        'fileId' => $fileObject->getUid(),
-                        'configuration' => [
-                            'width' => $sizeParts[0],
-                            'height' => $sizeParts[1] . 'c',
-                            'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($fileReferenceObject),
-                        ]
+                    $imageUrl = self::getThumbnailUrl($fileObject->getUid(), [
+                        'width' => $sizeParts[0],
+                        'height' => $sizeParts[1] . 'c',
+                        'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($fileReferenceObject),
+                        '_context' => $cropArea->isEmpty() ? ProcessedFile::CONTEXT_IMAGEPREVIEW : ProcessedFile::CONTEXT_IMAGECROPSCALEMASK
                     ]);
-                    $uriParameters = [
-                        'parameters' => $parameters,
-                        'hmac' => GeneralUtility::hmac(
-                            $parameters,
-                            \TYPO3\CMS\Backend\Controller\File\ThumbnailController::class
-                        ),
-                    ];
-                    $imageUrl = (string)GeneralUtility::makeInstance(UriBuilder::class)
-                        ->buildUriFromRoute('thumbnails', $uriParameters);
                     $attributes = [
                         'src' => $imageUrl,
                         'width' => (int)$sizeParts[0],
@@ -1179,6 +1169,29 @@ class BackendUtility
     }
 
     /**
+     * @param int $fileId
+     * @param array $configuration
+     * @return string
+     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
+     */
+    public static function getThumbnailUrl(int $fileId, array $configuration): string
+    {
+        $parameters = json_encode([
+            'fileId' => $fileId,
+            'configuration' => $configuration
+        ]);
+        $uriParameters = [
+            'parameters' => $parameters,
+            'hmac' => GeneralUtility::hmac(
+                $parameters,
+                \TYPO3\CMS\Backend\Controller\File\ThumbnailController::class
+            ),
+        ];
+        return (string)GeneralUtility::makeInstance(UriBuilder::class)
+            ->buildUriFromRoute('thumbnails', $uriParameters);
+    }
+
+    /**
      * Returns title-attribute information for a page-record informing about id, doktype, hidden, starttime, endtime, fe_group etc.
      *
      * @param array $row Input must be a page row ($row) with the proper fields set (be sure - send the full range of fields for the table)
diff --git a/typo3/sysext/backend/Classes/ViewHelpers/ThumbnailViewHelper.php b/typo3/sysext/backend/Classes/ViewHelpers/ThumbnailViewHelper.php
new file mode 100644 (file)
index 0000000..0ac0a38
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+namespace TYPO3\CMS\Backend\ViewHelpers;
+
+/*                                                                        *
+ * This script is part of the TYPO3 project - inspiring people to share!  *
+ *                                                                        *
+ * TYPO3 is free software; you can redistribute it and/or modify it under *
+ * the terms of the GNU General Public License version 2 as published by  *
+ * the Free Software Foundation.                                          *
+ *                                                                        *
+ * This script is distributed in the hope that it will be useful, but     *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN-    *
+ * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General      *
+ * Public License for more details.                                       *
+ *                                                                        */
+
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
+use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
+use TYPO3\CMS\Core\Resource\ProcessedFile;
+use TYPO3\CMS\Fluid\ViewHelpers\ImageViewHelper;
+use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
+
+/**
+ * Class ThumbnailViewHelper
+ */
+class ThumbnailViewHelper extends ImageViewHelper
+{
+
+    /**
+     * Initialize arguments.
+     */
+    public function initializeArguments()
+    {
+        parent::initializeArguments();
+        $this->registerArgument('context', 'string', 'context for image rendering', false, ProcessedFile::CONTEXT_IMAGEPREVIEW);
+    }
+
+    /**
+     * Resizes a given image (if required) and renders the respective img tag
+     *
+     * @see https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/Image/
+     *
+     * @throws Exception
+     * @return string Rendered tag
+     */
+    public function render()
+    {
+        if (($this->arguments['src'] === null && $this->arguments['image'] === null) || ($this->arguments['src'] !== null && $this->arguments['image'] !== null)) {
+            throw new Exception('You must either specify a string src or a File object.', 1533290762);
+        }
+
+        try {
+            $image = $this->imageService->getImage($this->arguments['src'], $this->arguments['image'], $this->arguments['treatIdAsReference']);
+
+            $cropString = $this->arguments['crop'];
+            if ($cropString === null && $image->hasProperty('crop') && $image->getProperty('crop')) {
+                $cropString = $image->getProperty('crop');
+            }
+            $cropVariantCollection = CropVariantCollection::create((string)$cropString);
+            $cropVariant = $this->arguments['cropVariant'] ?: 'default';
+            $cropArea = $cropVariantCollection->getCropArea($cropVariant);
+            $processingInstructions = [];
+            if (!empty($this->arguments['context'])) {
+                $processingInstructions['_context'] = $this->arguments['context'];
+            }
+            if (!$cropArea->isEmpty()) {
+                $processingInstructions['crop'] = $cropArea->makeAbsoluteBasedOnFile($image);
+            }
+            foreach (['width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight'] as $argument) {
+                if (!empty($this->arguments[$argument])) {
+                    $processingInstructions[$argument] = $this->arguments[$argument];
+                }
+            }
+            $imageUri = BackendUtility::getThumbnailUrl($image->getUid(), $processingInstructions);
+
+            if (!$this->tag->hasAttribute('data-focus-area')) {
+                $focusArea = $cropVariantCollection->getFocusArea($cropVariant);
+                if (!$focusArea->isEmpty()) {
+                    $this->tag->addAttribute('data-focus-area', $focusArea->makeAbsoluteBasedOnFile($image));
+                }
+            }
+            $this->tag->addAttribute('src', $imageUri);
+            $this->tag->addAttribute('width', $this->arguments['maxWidth'] ?? $this->arguments['width']);
+
+            $alt = $image->getProperty('alternative');
+            $title = $image->getProperty('title');
+
+            // The alt-attribute is mandatory to have valid html-code, therefore add it even if it is empty
+            if (empty($this->arguments['alt'])) {
+                $this->tag->addAttribute('alt', $alt);
+            }
+            if (empty($this->arguments['title']) && $title) {
+                $this->tag->addAttribute('title', $title);
+            }
+        } catch (ResourceDoesNotExistException $e) {
+            // thrown if file does not exist
+            throw new Exception($e->getMessage(), 1533294109, $e);
+        } catch (\UnexpectedValueException $e) {
+            // thrown if a file has been replaced with a folder
+            throw new Exception($e->getMessage(), 1533294113, $e);
+        } catch (\RuntimeException $e) {
+            // RuntimeException thrown if a file is outside of a storage
+            throw new Exception($e->getMessage(), 1533294116, $e);
+        } catch (\InvalidArgumentException $e) {
+            // thrown if file storage does not exist
+            throw new Exception($e->getMessage(), 1533294120, $e);
+        }
+
+        return $this->tag->render();
+    }
+}
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-85607-NewThumbnailViewHelperToRenderThumbnailsDeferred.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-85607-NewThumbnailViewHelperToRenderThumbnailsDeferred.rst
new file mode 100644 (file)
index 0000000..7efc035
--- /dev/null
@@ -0,0 +1,26 @@
+.. include:: ../../Includes.txt
+
+=======================================================================
+Feature: #85607 - New ThumbnailViewHelper to render thumbnails deferred
+=======================================================================
+
+See :issue:`85607`
+
+Description
+===========
+
+A new ViewHelper for the backend to render thumbnails deferred was introduced.
+
+The :php:`\TYPO3\CMS\Backend\ViewHelpers\ThumbnailViewHelper` extends the :php:`ImageViewHelper` and generate the image tag with the special URI.
+
+.. code-block:: HTML
+
+       <be:thumbnail image="{file.resource}" maxWidth="{thumbnail.width}" maxHeight="{thumbnail.height}" />
+
+
+Impact
+======
+
+Thumbnails do not block the rendering of a page, because the processing runs an extra http process.
+
+.. index:: Backend, Fluid, ext:backend
index 5a8ae00..9ebe847 100644 (file)
@@ -30,7 +30,6 @@ use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\FolderInterface;
 use TYPO3\CMS\Core\Resource\InaccessibleFolder;
-use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\Utility\ListUtility;
 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
@@ -1110,20 +1109,13 @@ class FileList
                                 . '</span>';
                         // Thumbnails?
                         } elseif ($this->thumbs && ($this->isImage($ext) || $this->isMediaFile($ext))) {
-                            $processedFile = $fileObject->process(
-                                ProcessedFile::CONTEXT_IMAGEPREVIEW,
-                                [
-                                    'width' => $this->thumbnailConfiguration->getWidth(),
-                                    'height' => $this->thumbnailConfiguration->getHeight()
-                                ]
-                            );
-                            if ($processedFile) {
-                                $thumbUrl = $processedFile->getPublicUrl(true);
-                                $theData[$field] .= '<br /><img src="' . htmlspecialchars($thumbUrl) . '" ' .
-                                    'width="' . $processedFile->getProperty('width') . '" ' .
-                                    'height="' . $processedFile->getProperty('height') . '" ' .
-                                    'title="' . htmlspecialchars($fileName) . '" alt="" />';
-                            }
+                            $imageUri = BackendUtility::getThumbnailUrl($fileObject->getUid(), [
+                                'width' => $this->thumbnailConfiguration->getWidth(),
+                                'height' => $this->thumbnailConfiguration->getHeight()
+                            ]);
+                            $theData[$field] .= '<br /><img src="' . htmlspecialchars($imageUri) . '" ' .
+                                'width="' . $this->thumbnailConfiguration->getWidth() . '" ' .
+                                'title="' . htmlspecialchars($fileName) . '" alt="" />';
                         }
                         break;
                     default:
index 1aa4b43..35f7793 100644 (file)
@@ -55,7 +55,7 @@
                                                        </f:if>
                                                        <f:if condition="{file.isImage}">
                                                                <br>
-                                                               <f:image image="{file.resource}" maxWidth="{thumbnail.width}" maxHeight="{thumbnail.height}" />
+                                                               <be:thumbnail image="{file.resource}" maxWidth="{thumbnail.width}" maxHeight="{thumbnail.height}" />
                                                        </f:if>
                                                </td>
                                                <td class="col-control nowrap">