From f56078f02f479ab0fde84e51f7771c58a098163c Mon Sep 17 00:00:00 2001 From: Christian Fasching Date: Thu, 25 Apr 2024 12:44:30 +0200 Subject: [PATCH 01/13] Update README.md (#16985) --- doc/01_Getting_Started/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/01_Getting_Started/README.md b/doc/01_Getting_Started/README.md index b44c3da24d6..234b03be696 100644 --- a/doc/01_Getting_Started/README.md +++ b/doc/01_Getting_Started/README.md @@ -5,6 +5,7 @@ This section provides a quick getting started tutorial for Pimcore and covers th 1. Installation of Pimcore: - via [Docker](../01_Getting_Started/00_Installation/00_Docker_Based_Installation.md) - via [Webserver](../01_Getting_Started/00_Installation/01_Webserver_Installation.md) + - via [PaaS](https://pimcore.com/docs/platform/Paas/) 2. [Advanced Installation Topics](./02_Advanced_Installation_Topics/README.md): - [Symfony Messenger and How to Handle Failed Jobs](./02_Advanced_Installation_Topics/01_Symfony_Messenger.md) 3. [Directory Structure of Pimcore](./03_Directory_Structure.md) From 59f813f858580e8bdffc731a1b4e1e4e02ae706e Mon Sep 17 00:00:00 2001 From: Remo Liebi Date: Fri, 26 Apr 2024 08:05:37 +0200 Subject: [PATCH 02/13] Avoid letting video thumbnail processor fail catastrophically (#16809) * Avoid letting video thumbnail processor fail catastrophically * php stan fix * skip earlier if video was not found --- .../src/Command/ThumbnailsVideoCommand.php | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/bundles/CoreBundle/src/Command/ThumbnailsVideoCommand.php b/bundles/CoreBundle/src/Command/ThumbnailsVideoCommand.php index 6700cdf17fd..708e78d3ec5 100644 --- a/bundles/CoreBundle/src/Command/ThumbnailsVideoCommand.php +++ b/bundles/CoreBundle/src/Command/ThumbnailsVideoCommand.php @@ -141,8 +141,16 @@ protected function waitTillFinished(int $videoId, string|Asset\Video\Thumbnail\C // initial delay $video = Asset\Video::getById($videoId); + + if (!$video instanceof Asset\Video) { + $message = 'video ['.$videoId.'] could not be found. Skipping ...'; + Logger::error($message); + + return; + } + $thumb = $video->getThumbnail($thumbnail); - if ($thumb['status'] != 'finished') { + if ($thumb !== null && $thumb['status'] !== 'finished') { sleep(20); } @@ -150,16 +158,26 @@ protected function waitTillFinished(int $videoId, string|Asset\Video\Thumbnail\C \Pimcore::collectGarbage(); $video = Asset\Video::getById($videoId); + $thumb = $video->getThumbnail($thumbnail); - if ($thumb['status'] == 'finished') { + + if ($thumb === null) { + $message = 'video ['.$videoId.'] with thumbnail ['.(is_string($thumbnail) ? $thumbnail : $thumbnail->getName()).'] is invalid. Skipping ...'; + Logger::error($message); + $this->output->writeln($message); + + break; + } + + if ($thumb['status'] === 'finished') { $finished = true; Logger::debug('video [' . $video->getId() . '] FINISHED'); - } elseif ($thumb['status'] == 'inprogress') { + } elseif ($thumb['status'] === 'inprogress') { Logger::debug('video [' . $video->getId() . '] in progress ...'); sleep(5); } else { // error - Logger::debug('video [' . $video->getId() . "] has status: '" . $thumb['status'] . "' -> skipping"); + Logger::debug('video [' . $video->getId() . "] has status: ['" . $thumb['status'] . "'] -> skipping ..."); break; } From 6b40dbbb3d44b72c1ddd6e88b68000ce82e8c029 Mon Sep 17 00:00:00 2001 From: Jaunet Nathan Date: Fri, 26 Apr 2024 09:07:30 +0200 Subject: [PATCH 03/13] Stop TaskManager on panel destroy (#16988) --- bundles/ApplicationLoggerBundle/public/js/log/admin.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bundles/ApplicationLoggerBundle/public/js/log/admin.js b/bundles/ApplicationLoggerBundle/public/js/log/admin.js index e990d379c5d..11d99af6c63 100644 --- a/bundles/ApplicationLoggerBundle/public/js/log/admin.js +++ b/bundles/ApplicationLoggerBundle/public/js/log/admin.js @@ -394,6 +394,10 @@ pimcore.bundle.applicationlogger.log.admin = Class.create({ this.store.load(); } pimcore.layout.refresh(); + + this.panel.on("destroy", function () { + Ext.TaskManager.stop(this.autoRefreshTask); + }.bind(this)); } return this.panel; }, From 07a760fa84585154f13ec1e644a1a42cd38b6831 Mon Sep 17 00:00:00 2001 From: Sebastian Blank Date: Fri, 26 Apr 2024 14:46:28 +0200 Subject: [PATCH 04/13] Fix: Problem with Notification grid column filter (#16968) --- models/Notification/Listing.php | 2 +- .../Service/NotificationService.php | 33 +++++++-- .../NotificationServiceFilterParser.php | 70 ++++++++++++++----- 3 files changed, 79 insertions(+), 26 deletions(-) diff --git a/models/Notification/Listing.php b/models/Notification/Listing.php index 5f8c0c5b773..2c0fc148a06 100644 --- a/models/Notification/Listing.php +++ b/models/Notification/Listing.php @@ -35,7 +35,7 @@ public function isValidOrderKey(string $key): bool * * @return Model\Notification[] */ - public function getItems(int $offset, int $limit): array + public function getItems(int $offset, ?int $limit): array { $this->setOffset($offset); $this->setLimit($limit); diff --git a/models/Notification/Service/NotificationService.php b/models/Notification/Service/NotificationService.php index b0980909bbc..63893a8c530 100644 --- a/models/Notification/Service/NotificationService.php +++ b/models/Notification/Service/NotificationService.php @@ -166,25 +166,46 @@ public function findAndMarkAsRead(int $id, ?int $recipientId = null): Notificati return $notification; } + /** + * @param array $filter + * @param array{offset?: int|string, limit?: int|string|null} $options + * + * @return array{total: int, data: Notification[]} + */ public function findAll(array $filter = [], array $options = []): array { $listing = new Listing(); - if (!empty($filter)) { + if ($filter) { $conditions = []; + $conditionVariables = []; foreach ($filter as $key => $value) { - $conditions[] = $key . ' = :' . $key; + if (isset($value['condition'])) { + $conditions[] = $value['condition']; + $conditionVariables[] = $value['conditionVariables'] ?? []; + } else { + $conditions[] = $key . ' = :' . $key; + $conditionVariables[] = [$key => $value]; + } } $condition = implode(' AND ', $conditions); - $listing->setCondition($condition, $filter); + $listing->setCondition($condition, array_merge(...$conditionVariables)); } $listing->setOrderKey('creationDate'); $listing->setOrder('DESC'); - $options += ['offset' => 0, 'limit' => 0]; - $offset = (int) $options['offset']; - $limit = (int) $options['limit']; + $offset = $options['offset'] ?? 0; + $limit = $options['limit'] ?? null; + + if (is_string($offset)) { + //TODO: Trigger deprecation + $offset = (int) $offset; + } + if (is_string($limit)) { + //TODO: Trigger deprecation + $limit = (int) $limit; + } $this->beginTransaction(); diff --git a/models/Notification/Service/NotificationServiceFilterParser.php b/models/Notification/Service/NotificationServiceFilterParser.php index 4a43c94c2ef..a4a24548e05 100644 --- a/models/Notification/Service/NotificationServiceFilterParser.php +++ b/models/Notification/Service/NotificationServiceFilterParser.php @@ -17,6 +17,7 @@ namespace Pimcore\Model\Notification\Service; +use Carbon\Carbon; use Symfony\Component\HttpFoundation\Request; /** @@ -48,25 +49,23 @@ class NotificationServiceFilterParser private Request $request; - private array $properties; + private array $properties = [ + 'title' => 'title', + 'timestamp' => 'creationDate', + ]; - /** - * ExtJSFilterParser constructor. - * - */ public function __construct(Request $request) { $this->request = $request; - $this->properties = [ - 'title' => 'title', - 'date' => 'creationDate', - ]; } + /** + * @return array}> + */ public function parse(): array { $result = []; - $filter = $this->request->get(self::KEY_FILTER, '[]'); + $filter = $this->request->request->get(self::KEY_FILTER, '[]'); $items = json_decode($filter, true); foreach ($items as $item) { @@ -74,13 +73,19 @@ public function parse(): array switch ($type) { case self::TYPE_STRING: - [$key, $value] = $this->parseString($item); - $result[$key] = $value; + [$key, $condition, $conditionVariables] = $this->parseString($item); + $result[$key] = [ + 'condition' => $condition, + 'conditionVariables' => $conditionVariables, + ]; break; case self::TYPE_DATE: - [$key, $value] = $this->parseDate($item); - $result[$key] = $value; + [$key, $condition, $conditionVariables] = $this->parseDate($item); + $result[$key] = [ + 'condition' => $condition, + 'conditionVariables' => $conditionVariables, + ]; break; } @@ -90,6 +95,8 @@ public function parse(): array } /** + * @return array{0: string, 1: string, 2: array} + * * @throws \Exception */ private function parseString(array $item): array @@ -100,7 +107,12 @@ private function parseString(array $item): array switch ($item[self::KEY_OPERATOR]) { case self::OPERATOR_LIKE: - $result = ["{$property} LIKE ?", "%{$value}%"]; + $key = $property . '_like'; + $result = [ + $key, + "{$property} LIKE :{$key}", + [$key => "%{$value}%"], + ]; break; } @@ -113,25 +125,45 @@ private function parseString(array $item): array } /** + * @return array{0: string, 1: string, 2: array} + * * @throws \Exception */ private function parseDate(array $item): array { $result = null; $property = $this->getDbProperty($item); - $value = strtotime($item[self::KEY_VALUE]); + $value = new Carbon($item[self::KEY_VALUE]); switch ($item[self::KEY_OPERATOR]) { case self::OPERATOR_EQ: - $result = ["{$property} BETWEEN ? AND ?", [$value, $value + (86400 - 1)]]; + $key = $property . '_eq'; + $result = [ + $key, + "{$property} BETWEEN :{$key}_start AND :{$key}_end", + [ + $key . '_start' => $value->toDateTimeString(), + $key . '_end' => $value->addDay()->subSecond()->toDateTimeString(), + ], + ]; break; case self::OPERATOR_GT: - $result = ["{$property} > ?", [$value]]; + $key = $property . '_gt'; + $result = [ + $key, + "{$property} > :{$key}", + [$key => $value->toDateTimeString()], + ]; break; case self::OPERATOR_LT: - $result = ["{$property} < ?", [$value]]; + $key = $property . '_lt'; + $result = [ + $key, + "{$property} < :{$key}", + [$key => $value->addDay()->subSecond()->toDateTimeString()], + ]; break; } From 254ff7cdb2c7a52579eb252defbc493c4a55cba6 Mon Sep 17 00:00:00 2001 From: robertSt7 <104770750+robertSt7@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:13:54 +0200 Subject: [PATCH 05/13] Fix: set convert_unsafe_embeds in TinyMce (#16996) --- bundles/TinymceBundle/public/js/editor.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bundles/TinymceBundle/public/js/editor.js b/bundles/TinymceBundle/public/js/editor.js index 6f57addbce0..6a6a0f1ae25 100644 --- a/bundles/TinymceBundle/public/js/editor.js +++ b/bundles/TinymceBundle/public/js/editor.js @@ -96,6 +96,7 @@ pimcore.bundle.tinymce.editor = Class.create({ base_url: '/bundles/pimcoretinymce/build/tinymce', suffix: '.min', convert_urls: false, + convert_unsafe_embeds: true, extended_valid_elements: 'a[class|name|href|target|title|pimcore_id|pimcore_type],img[class|style|longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align|pimcore_id|pimcore_type]', init_instance_callback: function (editor) { editor.on('input', function (eChange) { From 56948c9d99a97b86342b0e6b07560d7a83a5f9a4 Mon Sep 17 00:00:00 2001 From: questionmark78 Date: Thu, 2 May 2024 15:07:54 +0200 Subject: [PATCH 06/13] Fix: Empty glossary line crashes rendering (#17001) (#17002) --- bundles/GlossaryBundle/src/Tool/Processor.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bundles/GlossaryBundle/src/Tool/Processor.php b/bundles/GlossaryBundle/src/Tool/Processor.php index 961fe765cb4..3276e273c7c 100644 --- a/bundles/GlossaryBundle/src/Tool/Processor.php +++ b/bundles/GlossaryBundle/src/Tool/Processor.php @@ -192,6 +192,9 @@ private function prepareData(array $data): array // fix htmlentities issues $tmpData = []; foreach ($data as $d) { + if (!($d['text'])) { + continue; + } $text = htmlentities($d['text'], ENT_COMPAT, 'UTF-8'); if ($d['text'] !== $text) { $td = $d; From e1d9916025e4285344ecf8a49ac89a145038ed55 Mon Sep 17 00:00:00 2001 From: Sebastian Blank Date: Fri, 3 May 2024 08:28:49 +0200 Subject: [PATCH 07/13] Fix database error when filtering in localized field in the object search (#16971) * Fix database error when filtering in localized field in the object search * Load localizedfields field definition only once * Add Pimcore\Model\DataObject\ClassDefinition\Data\Localizedfields namespace --- .../src/Controller/SearchController.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bundles/SimpleBackendSearchBundle/src/Controller/SearchController.php b/bundles/SimpleBackendSearchBundle/src/Controller/SearchController.php index 5bd7bb71f6c..7386b9cff51 100644 --- a/bundles/SimpleBackendSearchBundle/src/Controller/SearchController.php +++ b/bundles/SimpleBackendSearchBundle/src/Controller/SearchController.php @@ -30,6 +30,7 @@ use Pimcore\Extension\Bundle\Exception\AdminClassicBundleNotFoundException; use Pimcore\Model\Asset; use Pimcore\Model\DataObject; +use Pimcore\Model\DataObject\ClassDefinition\Data\Localizedfields; use Pimcore\Model\Document; use Pimcore\Model\Element; use Pimcore\Model\Element\AdminStyle; @@ -133,6 +134,7 @@ public function findAction(Request $request, EventDispatcherInterface $eventDisp // filtering for objects if (!empty($allParams['filter']) && !empty($allParams['class'])) { $class = DataObject\ClassDefinition::getByName($allParams['class']); + $localizedFields = $class->getFieldDefinition('localizedfields'); // add Localized Fields filtering $params = $this->decodeJson($allParams['filter']); @@ -141,12 +143,12 @@ public function findAction(Request $request, EventDispatcherInterface $eventDisp foreach ($params as $paramConditionObject) { //this loop divides filter parameters to localized and unlocalized groups - $definitionExists = in_array($paramConditionObject['property'], DataObject\Service::getSystemFields()) - || $class->getFieldDefinition($paramConditionObject['property']); - if ($definitionExists) { //TODO: for sure, we can add additional condition like getLocalizedFieldDefinition()->getFieldDefiniton(... + if (in_array($paramConditionObject['property'], DataObject\Service::getSystemFields())) { $unlocalizedFieldsFilters[] = $paramConditionObject; - } else { + } elseif ($localizedFields instanceof Localizedfields && $localizedFields->getFieldDefinition($paramConditionObject['property'])) { $localizedFieldsFilters[] = $paramConditionObject; + } elseif ($class->getFieldDefinition($paramConditionObject['property'])) { + $unlocalizedFieldsFilters[] = $paramConditionObject; } } From 37c1b55b22de79d452c977d68ecad4d82f6a043f Mon Sep 17 00:00:00 2001 From: JiaJia Ji Date: Fri, 3 May 2024 09:05:17 +0200 Subject: [PATCH 08/13] fix localized values always on user setting instead of grid (#17006) --- models/DataObject/Service.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/models/DataObject/Service.php b/models/DataObject/Service.php index 67c693c25a6..4580e532d85 100644 --- a/models/DataObject/Service.php +++ b/models/DataObject/Service.php @@ -393,7 +393,7 @@ public static function gridObjectData(AbstractObject $object, array $fields = nu if (in_array($key, Concrete::SYSTEM_COLUMN_NAMES)) { $data[$dataKey] = $object->$getter(); } else { - $valueObject = self::getValueForObject($object, $key, $brickType, $brickKey, $def, $context, $brickDescriptor); + $valueObject = self::getValueForObject($object, $key, $brickType, $brickKey, $def, $context, $brickDescriptor, $requestedLanguage); $data['inheritedFields'][$dataKey] = ['inherited' => $valueObject->objectid != $object->getId(), 'objectid' => $valueObject->objectid]; if ($csvMode || method_exists($def, 'getDataForGrid')) { @@ -641,13 +641,13 @@ public static function getFieldForBrickType(ClassDefinition $class, string $bric * * @return \stdClass value and objectid where the value comes from */ - private static function getValueForObject(Concrete $object, string $key, string $brickType = null, string $brickKey = null, ClassDefinition\Data $fieldDefinition = null, array $context = [], array $brickDescriptor = null): \stdClass + private static function getValueForObject(Concrete $object, string $key, string $brickType = null, string $brickKey = null, ClassDefinition\Data $fieldDefinition = null, array $context = [], array $brickDescriptor = null, string $requestedLanguage = null): \stdClass { $getter = 'get' . ucfirst($key); $value = null; try { - $value = $object->$getter(AdminTool::getCurrentUser()?->getLanguage()); + $value = $object->$getter($requestedLanguage ?? AdminTool::getCurrentUser()?->getLanguage()); } catch (\Throwable) { } @@ -687,7 +687,7 @@ private static function getValueForObject(Concrete $object, string $key, string if ($fieldDefinition->isEmpty($value)) { $parent = self::hasInheritableParentObject($object); if (!empty($parent)) { - return self::getValueForObject($parent, $key, $brickType, $brickKey, $fieldDefinition, $context, $brickDescriptor); + return self::getValueForObject($parent, $key, $brickType, $brickKey, $fieldDefinition, $context, $brickDescriptor, $requestedLanguage); } } From 5dc451381218d0b039a3796fa89994fc4ea5c6df Mon Sep 17 00:00:00 2001 From: JiaJia Ji Date: Fri, 3 May 2024 09:29:03 +0200 Subject: [PATCH 09/13] [Bug]: Fix wysiwyg max character, soften the warning and add visual helper (#16975) * improve title and translation fix max character, soften the warning and add visual helper * Update bundles/TinymceBundle/public/js/editor.js Co-authored-by: robertSt7 <104770750+robertSt7@users.noreply.github.com> --------- Co-authored-by: robertSt7 <104770750+robertSt7@users.noreply.github.com> --- bundles/TinymceBundle/public/js/editor.js | 13 +++++++++++-- bundles/TinymceBundle/translations/admin.en.yaml | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/bundles/TinymceBundle/public/js/editor.js b/bundles/TinymceBundle/public/js/editor.js index 6a6a0f1ae25..81eff87ceb7 100644 --- a/bundles/TinymceBundle/public/js/editor.js +++ b/bundles/TinymceBundle/public/js/editor.js @@ -26,6 +26,8 @@ pimcore.bundle.tinymce.editor = Class.create({ if (e.detail.context === 'object') { if (!isNaN(e.detail.config.maxCharacters) && e.detail.config.maxCharacters > 0) { this.maxChars = e.detail.config.maxCharacters; + }else{ + this.maxChars = -1; } } @@ -83,6 +85,8 @@ pimcore.bundle.tinymce.editor = Class.create({ defaultConfig = pimcore[e.detail.context][subSpace].wysiwyg ? pimcore[e.detail.context][subSpace].wysiwyg.defaultEditorConfig : {}; } + const maxChars = this.maxChars; + tinymce.init(Object.assign({ selector: `#${this.textareaId}`, height: 500, @@ -100,9 +104,14 @@ pimcore.bundle.tinymce.editor = Class.create({ extended_valid_elements: 'a[class|name|href|target|title|pimcore_id|pimcore_type],img[class|style|longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align|pimcore_id|pimcore_type]', init_instance_callback: function (editor) { editor.on('input', function (eChange) { + tinymce.activeEditor.getBody().style.border = ''; + tinymce.activeEditor.getElement().setAttribute('title', ''); + const charCount = tinymce.activeEditor.plugins.wordcount.body.getCharacterCount(); - if (this.maxChars !== -1 && charCount > this.maxChars) { - pimcore.helpers.showNotification(t('error'), t('char_count_limit_reached'), 'error'); + + if (maxChars !== -1 && charCount > maxChars) { + tinymce.activeEditor.getBody().style.border = '1px solid red'; + tinymce.activeEditor.getElement().setAttribute('title', t('maximum_length_is') + ' ' + maxChars); } document.dispatchEvent(new CustomEvent(pimcore.events.changeWysiwyg, { detail: { diff --git a/bundles/TinymceBundle/translations/admin.en.yaml b/bundles/TinymceBundle/translations/admin.en.yaml index 43c181d42bf..017bf689b42 100644 --- a/bundles/TinymceBundle/translations/admin.en.yaml +++ b/bundles/TinymceBundle/translations/admin.en.yaml @@ -1,2 +1,2 @@ error: Error -char_count_limit_reached: The character limit has been reached! \ No newline at end of file +maximum_length_is: The maximum length for this field is \ No newline at end of file From a6821a16ea38086bf6012e682e1743488244bd85 Mon Sep 17 00:00:00 2001 From: Matthias Schuhmayer <38959016+mattamon@users.noreply.github.com> Date: Fri, 3 May 2024 13:09:04 +0200 Subject: [PATCH 10/13] Merge pull request from GHSA-277c-5vvj-9pwx * Introduce allowed formats and max scale factor * Add documentation, upgrade notes and use floatNode for max_scale_factor * Update to max_scaling_factor * Cast format to lower * Fix typo --- .../src/DependencyInjection/Configuration.php | 12 ++++++++ .../01_Image_Thumbnails.md | 12 +++++++- .../03_Working_with_Thumbnails/README.md | 27 ++++++++++++++++- .../09_Upgrade_Notes/README.md | 30 +++++++++++++++++++ models/Asset/Document/ImageThumbnail.php | 8 +++++ models/Asset/Image/Thumbnail.php | 12 ++++++++ .../Asset/Thumbnail/ImageThumbnailTrait.php | 25 ++++++++++++++++ models/Asset/Video/ImageThumbnail.php | 8 ++++- .../ThumbnailFormatNotSupportedException.php | 23 ++++++++++++++ .../ThumbnailMaxScalingFactorException.php | 23 ++++++++++++++ 10 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 models/Exception/ThumbnailFormatNotSupportedException.php create mode 100644 models/Exception/ThumbnailMaxScalingFactorException.php diff --git a/bundles/CoreBundle/src/DependencyInjection/Configuration.php b/bundles/CoreBundle/src/DependencyInjection/Configuration.php index 507c5ce3a90..00b96b9344b 100644 --- a/bundles/CoreBundle/src/DependencyInjection/Configuration.php +++ b/bundles/CoreBundle/src/DependencyInjection/Configuration.php @@ -352,6 +352,18 @@ private function addAssetNode(ArrayNodeDefinition $rootNode): void ->arrayNode('assets') ->addDefaultsIfNotSet() ->children() + ->arrayNode('thumbnails') + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('allowed_formats') + ->defaultValue(['avif', 'eps', 'gif', 'jpeg', 'jpg', 'pjpeg', 'png', 'svg', 'tiff', 'webm', 'webp']) + ->scalarPrototype()->end() + ->end() + ->floatNode('max_scaling_factor') + ->defaultValue(5.0) + ->end() + ->end() + ->end() ->arrayNode('frontend_prefixes') ->addDefaultsIfNotSet() ->children() diff --git a/doc/04_Assets/03_Working_with_Thumbnails/01_Image_Thumbnails.md b/doc/04_Assets/03_Working_with_Thumbnails/01_Image_Thumbnails.md index 426b6fef40e..37325a4bcb0 100644 --- a/doc/04_Assets/03_Working_with_Thumbnails/01_Image_Thumbnails.md +++ b/doc/04_Assets/03_Working_with_Thumbnails/01_Image_Thumbnails.md @@ -9,7 +9,8 @@ which are not stored as an asset inside Pimcore. > **IMPORTANT** > Use Imagick PECL extension for best results, GDlib is just a fallback with limited functionality > (only PNG, JPG, GIF) and less quality! -> Using ImageMagick Pimcore supports hundreds of formats including: AI, EPS, TIFF, PNG, JPG, GIF, PSD, ... +> Using ImageMagick Pimcore can support hundreds of formats including: AI, EPS, TIFF, PNG, JPG, GIF, PSD, etc. +> Not all formats are allowed out of the box. To extend the list [see](./README.md#allowed-formats). To use the thumbnailing service of Pimcore, you have to create a transformation pipeline first. To do so, open _Settings_ > _Thumbnails_ and click on _Add Thumbnail_ to create a new configuration. @@ -329,6 +330,15 @@ This is a special functionality to allow embedding high resolution (ppi/dpi) ima The following is only necessary in special use-cases like Web-to-Print, in typical web-based cases, Pimcore automatically adds the `srcset` attribute to `` and `` tags automatically, so no manual work is necessary. +The high resolution scaling factor is limited to `5.0` eg. `@5x`. Float values are supported. +If you need to scale an image more than that, you can use the `max_scaling_factor` option in the configuration. +```yaml + pimcore: + assets: + thumbnails: + max_scaling_factor: 6.0 +``` + ### Use in the Thumbnail Configuration: ![High Resolution](../../img/thumbnail_high_resolution.png) diff --git a/doc/04_Assets/03_Working_with_Thumbnails/README.md b/doc/04_Assets/03_Working_with_Thumbnails/README.md index 02d38f5b2b7..58802ab5093 100644 --- a/doc/04_Assets/03_Working_with_Thumbnails/README.md +++ b/doc/04_Assets/03_Working_with_Thumbnails/README.md @@ -4,7 +4,32 @@ Pimcore provides a sophisticated thumbnail processing engine for calculating thu different output channels Pimcore can calculate and provide optimized images in terms of dimensions, file sizes, formats and much more. -This functionality allows true single source publishing with Pimcore. +This functionality allows true single source publishing with Pimcore. + +### Allowed formats +Pimcore allows the following formats for thumbnails out of the box: +`'avif', 'eps', 'gif', 'jpeg', 'jpg', 'pjpeg', 'png', 'svg', 'tiff', 'webm', 'webp'`. + +If you want to use a different format, you can easily extend the list of supported formats. +Keep in mind that you must copy the whole list of formats and add your desired format to it. +```yaml +pimcore: + assets: + thumbnails: + allowed_formats: + - 'avif' + - 'eps' + - 'gif' + - 'jpeg' + - 'jpg' + - 'pjpeg' + - 'png' + - 'svg' + - 'tiff' + - 'webm' + - 'webp' + - 'pdf' # Add your desired format here +``` ##### Thumbnails are available for following file types: * [Image Thumbnails](./01_Image_Thumbnails.md) diff --git a/doc/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md b/doc/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md index 2e57d5882a5..ec8f12199e7 100644 --- a/doc/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md +++ b/doc/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md @@ -1,5 +1,35 @@ # Upgrade Notes +## Pimcore 11.2.4 +### Assets Thumbnails +- Thumbnail generation for Assets, Documents and Videos now only support the following formats out of the box: `'avif', 'eps', 'gif', 'jpeg', 'jpg', 'pjpeg', 'png', 'svg', 'tiff', 'webm', 'webp'`. +- You can extend this list by adding your formats on the bottom: +```yaml + pimcore: + assets: + thumbnails: + allowed_formats: + - 'avif' + - 'eps' + - 'gif' + - 'jpeg' + - 'jpg' + - 'pjpeg' + - 'png' + - 'svg' + - 'tiff' + - 'webm' + - 'webp' + - 'pdf' # Add your desired format here +``` +- High resolution scaling factor for image thumbnails has now been limited to a maximum of `5.0`. If you need to scale an image more than that, you can use the `max_scaling_factor` option in the configuration. +```yaml + pimcore: + assets: + thumbnails: + max_scaling_factor: 6.0 +``` + ## Pimcore 11.2.0 ### Elements #### [Documents]: diff --git a/models/Asset/Document/ImageThumbnail.php b/models/Asset/Document/ImageThumbnail.php index 5b81dac7f81..d9b4b7f0006 100644 --- a/models/Asset/Document/ImageThumbnail.php +++ b/models/Asset/Document/ImageThumbnail.php @@ -24,6 +24,7 @@ use Pimcore\Model; use Pimcore\Model\Asset\Image; use Pimcore\Model\Exception\NotFoundException; +use Pimcore\Model\Exception\ThumbnailFormatNotSupportedException; use Pimcore\Tool\Storage; use Symfony\Component\EventDispatcher\GenericEvent; use Symfony\Component\Lock\LockFactory; @@ -70,8 +71,15 @@ public function getPath(array $args = []): string return $path; } + /** + * @throws ThumbnailFormatNotSupportedException + */ public function generate(bool $deferredAllowed = true): void { + if (!$this->checkAllowedFormats($this->config->getFormat())) { + throw new ThumbnailFormatNotSupportedException(); + } + $deferred = $deferredAllowed && $this->deferred; $generated = false; diff --git a/models/Asset/Image/Thumbnail.php b/models/Asset/Image/Thumbnail.php index b679ebfda85..d609d39e8c1 100644 --- a/models/Asset/Image/Thumbnail.php +++ b/models/Asset/Image/Thumbnail.php @@ -24,6 +24,8 @@ use Pimcore\Model\Asset\Image\Thumbnail\Config; use Pimcore\Model\Asset\Thumbnail\ImageThumbnailTrait; use Pimcore\Model\Exception\NotFoundException; +use Pimcore\Model\Exception\ThumbnailFormatNotSupportedException; +use Pimcore\Model\Exception\ThumbnailMaxScalingFactorException; use Pimcore\Tool; use Symfony\Component\EventDispatcher\GenericEvent; @@ -103,10 +105,20 @@ protected function useOriginalFile(string $filename): bool } /** + * @throws ThumbnailFormatNotSupportedException + * @throws ThumbnailMaxScalingFactorException * @internal */ public function generate(bool $deferredAllowed = true): void { + if (!$this->checkAllowedFormats($this->config->getFormat(), $this->asset)) { + throw new ThumbnailFormatNotSupportedException(); + } + + if (!$this->checkMaxScalingFactor($this->config->getHighResolution())) { + throw new ThumbnailMaxScalingFactorException(); + } + $deferred = false; $generated = false; diff --git a/models/Asset/Thumbnail/ImageThumbnailTrait.php b/models/Asset/Thumbnail/ImageThumbnailTrait.php index fcdb14cd502..9ed6b9e6bd7 100644 --- a/models/Asset/Thumbnail/ImageThumbnailTrait.php +++ b/models/Asset/Thumbnail/ImageThumbnailTrait.php @@ -16,6 +16,7 @@ namespace Pimcore\Model\Asset\Thumbnail; +use Pimcore\Config as PimcoreConfig; use Pimcore\Helper\TemporaryFileHelperTrait; use Pimcore\Model\Asset; use Pimcore\Model\Asset\Image; @@ -428,4 +429,28 @@ public function getAsFormat(string $format): static return $thumb; } + + private function checkAllowedFormats(string $format, ?Asset $asset = null): bool + { + $format = strtolower($format); + if($asset) { + $original = pathinfo($asset->getRealFullPath(), PATHINFO_EXTENSION); + if ($format === $original || $format === 'source') { + return true; + } + } + + $assetConfig = PimcoreConfig::getSystemConfiguration('assets'); + return in_array( + $format, + $assetConfig['thumbnails']['allowed_formats'], + true + ); + } + + private function checkMaxScalingFactor(float $scalingFactor): bool + { + $assetConfig = PimcoreConfig::getSystemConfiguration('assets'); + return $scalingFactor <= $assetConfig['thumbnails']['max_scaling_factor']; + } } diff --git a/models/Asset/Video/ImageThumbnail.php b/models/Asset/Video/ImageThumbnail.php index 129b97288ad..992aaca917a 100644 --- a/models/Asset/Video/ImageThumbnail.php +++ b/models/Asset/Video/ImageThumbnail.php @@ -16,12 +16,14 @@ namespace Pimcore\Model\Asset\Video; +use Exception; use Pimcore\Event\AssetEvents; use Pimcore\Event\FrontendEvents; use Pimcore\File; use Pimcore\Logger; use Pimcore\Model; use Pimcore\Model\Asset\Image; +use Pimcore\Model\Exception\ThumbnailFormatNotSupportedException; use Pimcore\Tool\Storage; use Symfony\Component\EventDispatcher\GenericEvent; use Symfony\Component\Lock\LockFactory; @@ -75,12 +77,16 @@ public function getPath(array $args = []): string } /** - * @throws \Exception + * @throws Exception * * @internal */ public function generate(bool $deferredAllowed = true): void { + if (!$this->checkAllowedFormats($this->config->getFormat())) { + throw new ThumbnailFormatNotSupportedException(); + } + $deferred = $deferredAllowed && $this->deferred; $generated = false; diff --git a/models/Exception/ThumbnailFormatNotSupportedException.php b/models/Exception/ThumbnailFormatNotSupportedException.php new file mode 100644 index 00000000000..542df8f7389 --- /dev/null +++ b/models/Exception/ThumbnailFormatNotSupportedException.php @@ -0,0 +1,23 @@ + Date: Fri, 3 May 2024 11:10:02 +0000 Subject: [PATCH 11/13] Apply php-cs-fixer changes --- models/Asset/Image/Thumbnail.php | 1 + models/Asset/Thumbnail/ImageThumbnailTrait.php | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/models/Asset/Image/Thumbnail.php b/models/Asset/Image/Thumbnail.php index d609d39e8c1..50610402adb 100644 --- a/models/Asset/Image/Thumbnail.php +++ b/models/Asset/Image/Thumbnail.php @@ -107,6 +107,7 @@ protected function useOriginalFile(string $filename): bool /** * @throws ThumbnailFormatNotSupportedException * @throws ThumbnailMaxScalingFactorException + * * @internal */ public function generate(bool $deferredAllowed = true): void diff --git a/models/Asset/Thumbnail/ImageThumbnailTrait.php b/models/Asset/Thumbnail/ImageThumbnailTrait.php index 9ed6b9e6bd7..faa5d2624b0 100644 --- a/models/Asset/Thumbnail/ImageThumbnailTrait.php +++ b/models/Asset/Thumbnail/ImageThumbnailTrait.php @@ -434,13 +434,14 @@ private function checkAllowedFormats(string $format, ?Asset $asset = null): bool { $format = strtolower($format); if($asset) { - $original = pathinfo($asset->getRealFullPath(), PATHINFO_EXTENSION); + $original = pathinfo($asset->getRealFullPath(), PATHINFO_EXTENSION); if ($format === $original || $format === 'source') { return true; } } $assetConfig = PimcoreConfig::getSystemConfiguration('assets'); + return in_array( $format, $assetConfig['thumbnails']['allowed_formats'], @@ -451,6 +452,7 @@ private function checkAllowedFormats(string $format, ?Asset $asset = null): bool private function checkMaxScalingFactor(float $scalingFactor): bool { $assetConfig = PimcoreConfig::getSystemConfiguration('assets'); + return $scalingFactor <= $assetConfig['thumbnails']['max_scaling_factor']; } } From f26238db9463d37882c5f20925c1df109aed5230 Mon Sep 17 00:00:00 2001 From: mattamon Date: Fri, 3 May 2024 13:19:11 +0200 Subject: [PATCH 12/13] Fix scaling factor can be null --- models/Asset/Thumbnail/ImageThumbnailTrait.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/models/Asset/Thumbnail/ImageThumbnailTrait.php b/models/Asset/Thumbnail/ImageThumbnailTrait.php index faa5d2624b0..8e2a6a28c58 100644 --- a/models/Asset/Thumbnail/ImageThumbnailTrait.php +++ b/models/Asset/Thumbnail/ImageThumbnailTrait.php @@ -449,8 +449,12 @@ private function checkAllowedFormats(string $format, ?Asset $asset = null): bool ); } - private function checkMaxScalingFactor(float $scalingFactor): bool + private function checkMaxScalingFactor(?float $scalingFactor = null): bool { + if ($scalingFactor === null) { + return true; + } + $assetConfig = PimcoreConfig::getSystemConfiguration('assets'); return $scalingFactor <= $assetConfig['thumbnails']['max_scaling_factor']; From 2a436408f00ccb30d1b8e87c0a7e0aac0e523d00 Mon Sep 17 00:00:00 2001 From: mattamon Date: Fri, 3 May 2024 11:37:32 +0000 Subject: [PATCH 13/13] Apply php-cs-fixer changes --- models/Asset/Thumbnail/ImageThumbnailTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/Asset/Thumbnail/ImageThumbnailTrait.php b/models/Asset/Thumbnail/ImageThumbnailTrait.php index 8e2a6a28c58..2adb8941a7d 100644 --- a/models/Asset/Thumbnail/ImageThumbnailTrait.php +++ b/models/Asset/Thumbnail/ImageThumbnailTrait.php @@ -452,7 +452,7 @@ private function checkAllowedFormats(string $format, ?Asset $asset = null): bool private function checkMaxScalingFactor(?float $scalingFactor = null): bool { if ($scalingFactor === null) { - return true; + return true; } $assetConfig = PimcoreConfig::getSystemConfiguration('assets');