From 438daf82b488a75a23847705c76e5a2df8ca8ada Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 21 Jul 2021 13:50:19 +0300 Subject: [PATCH] image refactoring and svg support --- application/Espo/EntryPoints/Avatar.php | 4 +- application/Espo/EntryPoints/Image.php | 63 ++++++++++-------- .../Espo/Resources/metadata/app/image.json | 35 ++++++++++ .../src/views/fields/attachment-multiple.js | 57 ++++++++++------ client/src/views/fields/file.js | 66 ++++++++++++------- 5 files changed, 150 insertions(+), 75 deletions(-) create mode 100644 application/Espo/Resources/metadata/app/image.json diff --git a/application/Espo/EntryPoints/Avatar.php b/application/Espo/EntryPoints/Avatar.php index 9d7fe6fee6..b2196c9ce4 100644 --- a/application/Espo/EntryPoints/Avatar.php +++ b/application/Espo/EntryPoints/Avatar.php @@ -107,13 +107,13 @@ class Avatar extends Image implements Di\MetadataAware $size = 'small'; } - if (empty($this->imageSizes[$size])) { + if (empty($this->getSizes()[$size])) { $this->renderBlank($response); return; } - $width = $this->imageSizes[$size][0]; + $width = $this->getSizes()[$size][0]; $response ->setHeader('Cache-Control', 'max-age=360000, must-revalidate') diff --git a/application/Espo/EntryPoints/Image.php b/application/Espo/EntryPoints/Image.php index 56cf8cd9f7..3b40fa081c 100644 --- a/application/Espo/EntryPoints/Image.php +++ b/application/Espo/EntryPoints/Image.php @@ -43,34 +43,13 @@ use Espo\Core\{ FileStorage\Manager as FileStorageManager, Utils\File\Manager as FileManager, Utils\Config, + Utils\Metadata, }; use Espo\Entities\Attachment; class Image implements EntryPoint { - protected $allowedFileTypes = [ - 'image/jpeg', - 'image/png', - 'image/gif', - 'image/webp', - ]; - - protected $imageSizes = [ - 'xxx-small' => [18, 18], - 'xx-small' => [32, 32], - 'x-small' => [64, 64], - 'small' => [128, 128], - 'medium' => [256, 256], - 'large' => [512, 512], - 'x-large' => [864, 864], - 'xx-large' => [1024, 1024], - ]; - - protected $fixOrientationFileTypeList = [ - 'image/jpeg', - ]; - protected $allowedRelatedTypeList = null; protected $allowedFieldList = null; @@ -85,18 +64,22 @@ class Image implements EntryPoint protected $config; + private $metadata; + public function __construct( FileStorageManager $fileStorageManager, Acl $acl, EntityManager $entityManager, FileManager $fileManager, - Config $config + Config $config, + Metadata $metadata ) { $this->fileStorageManager = $fileStorageManager; $this->acl = $acl; $this->entityManager = $entityManager; $this->fileManager = $fileManager; $this->config = $config; + $this->metadata = $metadata; } public function run(Request $request, Response $response): void @@ -125,8 +108,8 @@ class Image implements EntryPoint $fileType = $attachment->get('type'); - if (!in_array($fileType, $this->allowedFileTypes)) { - throw new Error(); + if (!in_array($fileType, $this->getAllowedFileTypeList())) { + throw new Forbidden("Not allowed file type '{$fileType}'."); } if ($this->allowedRelatedTypeList) { @@ -141,7 +124,9 @@ class Image implements EntryPoint } } - if ($size) { + $toResize = $size && in_array($fileType, $this->getResizableFileTypeList()); + + if ($toResize) { $fileName = $size . '-' . $attachment->get('name'); $contents = $this->getThumbContents($attachment, $size); @@ -173,7 +158,7 @@ class Image implements EntryPoint protected function getThumbContents(Attachment $attachment, string $size): string { - if (!array_key_exists($size, $this->imageSizes)) { + if (!array_key_exists($size, $this->getSizes())) { throw new Error("Bad size."); } @@ -242,7 +227,7 @@ class Image implements EntryPoint list($originalWidth, $originalHeight) = getimagesize($filePath); - list($width, $height) = $this->imageSizes[$size]; + list($width, $height) = $this->getSizes()[$size]; if ($originalWidth <= $width && $originalHeight <= $height) { $targetWidth = $originalWidth; @@ -323,7 +308,7 @@ class Image implements EntryPoint break; } - if (in_array($fileType, $this->fixOrientationFileTypeList)) { + if (in_array($fileType, $this->getFixOrientationFileTypeList())) { $targetImage = $this->fixOrientation($targetImage, $filePath); } @@ -353,4 +338,24 @@ class Image implements EntryPoint return $targetImage; } + + private function getAllowedFileTypeList(): array + { + return $this->metadata->get(['app', 'image', 'allowedFileTypeList']) ?? []; + } + + private function getResizableFileTypeList(): array + { + return $this->metadata->get(['app', 'image', 'resizableFileTypeList']) ?? []; + } + + private function getFixOrientationFileTypeList(): array + { + return $this->metadata->get(['app', 'image', 'fixOrientationFileTypeList']) ?? []; + } + + protected function getSizes(): array + { + return $this->metadata->get(['app', 'image', 'sizes']) ?? []; + } } diff --git a/application/Espo/Resources/metadata/app/image.json b/application/Espo/Resources/metadata/app/image.json new file mode 100644 index 0000000000..92cf59f5e5 --- /dev/null +++ b/application/Espo/Resources/metadata/app/image.json @@ -0,0 +1,35 @@ +{ + "allowedFileTypeList": [ + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + "image/svg+xml" + ], + "resizableFileTypeList": [ + "image/jpeg", + "image/png", + "image/gif", + "image/webp" + ], + "fixOrientationFileTypeList": [ + "image/jpeg" + ], + "previewFileTypeList": [ + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + "image/svg+xml" + ], + "sizes": { + "xxx-small": [18, 18], + "xx-small": [32, 32], + "x-small": [64, 64], + "small": [128, 128], + "medium": [256, 256], + "large": [512, 512], + "x-large": [864, 864], + "xx-large": [1024, 1024] + } +} diff --git a/client/src/views/fields/attachment-multiple.js b/client/src/views/fields/attachment-multiple.js index 011e664835..c7a29da136 100644 --- a/client/src/views/fields/attachment-multiple.js +++ b/client/src/views/fields/attachment-multiple.js @@ -52,13 +52,6 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) { showPreviews: true, - previewTypeList: [ - 'image/jpeg', - 'image/png', - 'image/gif', - 'image/webp', - ], - accept: null, validations: ['ready', 'required'], @@ -175,7 +168,8 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) { this.previewSize = this.options.previewSize || this.params.previewSize || this.previewSize; - var self = this; + this.previewTypeList = this.getMetadata().get(['app', 'image', 'previewFileTypeList']) || []; + this.imageSizes = this.getMetadata().get(['app', 'image', 'sizes']) || {}; this.nameHash = _.clone(this.model.get(this.nameHashName)) || {}; @@ -358,13 +352,21 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) { name = Handlebars.Utils.escapeExpression(name); id = Handlebars.Utils.escapeExpression(id); - var preview = name; - - if (~this.previewTypeList.indexOf(type)) { - preview = ''; + if (!~this.previewTypeList.indexOf(type)) { + return name; } - return preview; + let html = $('') + .attr('src', this.getImageUrl(id, 'small')) + .attr('title', name) + .css({ + maxWidth: (this.imageSizes[this.previewSize] || {})[0], + maxHeight: (this.imageSizes[this.previewSize] || {})[1], + }) + .get(0) + .outerHTML; + + return html; }, addAttachmentBox: function (name, type, id) { @@ -609,22 +611,35 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) { return true; } - return false + return false; }, getDetailPreview: function (name, type, id) { name = Handlebars.Utils.escapeExpression(name); id = Handlebars.Utils.escapeExpression(id); - var preview = name; - - if (this.isTypeIsImage(type)) { - preview = ''; + if (!this.isTypeIsImage(type)) { + return name; } - return preview; + let html = $('') + .attr('data-action', 'showImagePreview') + .attr('data-id', id) + .attr('title', name) + .attr('href', this.getImageUrl(id)) + .append( + $('') + .attr('src', this.getImageUrl(id, this.previewSize)) + .addClass('image-preview') + .css({ + maxWidth: (this.imageSizes[this.previewSize] || {})[0], + maxHeight: (this.imageSizes[this.previewSize] || {})[1], + }) + ) + .get(0) + .outerHTML; + + return html; }, getValueForDisplay: function () { diff --git a/client/src/views/fields/file.js b/client/src/views/fields/file.js index 511c310a61..0818232d0e 100644 --- a/client/src/views/fields/file.js +++ b/client/src/views/fields/file.js @@ -44,13 +44,6 @@ define('views/fields/file', 'views/fields/link', function (Dep) { accept: false, - previewTypeList: [ - 'image/jpeg', - 'image/png', - 'image/gif', - 'image/webp', - ], - defaultType: false, previewSize: 'small', @@ -168,6 +161,9 @@ define('views/fields/file', 'views/fields/link', function (Dep) { this.previewSize = this.options.previewSize || this.params.previewSize || this.previewSize; + this.previewTypeList = this.getMetadata().get(['app', 'image', 'previewFileTypeList']) || []; + this.imageSizes = this.getMetadata().get(['app', 'image', 'sizes']) || {}; + var sourceDefs = this.getMetadata().get(['clientDefs', 'Attachment', 'sourceDefs']) || {}; this.sourceList = Espo.Utils.clone(this.params.sourceList || []); @@ -298,7 +294,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) { return name; } - var previewSize = this.previewSize; + let previewSize = this.previewSize; if (this.isListMode()) { previewSize = 'small'; @@ -310,35 +306,59 @@ define('views/fields/file', 'views/fields/link', function (Dep) { id = Handlebars.Utils.escapeExpression(id); - var src = this.getBasePath() + '?entryPoint=image&size=' + previewSize + '&id=' + id; + let src = this.getBasePath() + '?entryPoint=image&size=' + previewSize + '&id=' + id; - var img = ''; + let $img = $('') + .attr('src', src) + .addClass('image-preview') + .css({ + maxWidth: (this.imageSizes[this.previewSize] || {})[0], + maxHeight: (this.imageSizes[this.previewSize] || {})[1], + }); if (this.mode === 'listLink') { - var link = '#' + this.model.entityType + '/view/' + this.model.id; + let link = '#' + this.model.entityType + '/view/' + this.model.id; - return '' + img + ''; + let html = $('') + .attr('href', link) + .append($img) + .get(0) + .outerHTML; + + return html; } - var preview = '' + - '' + - img + - ''; + let html = $('') + .attr('data-action', 'showImagePreview') + .attr('data-id', id) + .attr('title', name) + .attr('href', this.getImageUrl(id)) + .append($img) + .get(0) + .outerHTML; - return preview; + return html; }, getEditPreview: function (name, type, id) { name = Handlebars.Utils.escapeExpression(name); id = Handlebars.Utils.escapeExpression(id); - var preview = name; - - if (~this.previewTypeList.indexOf(type)) { - preview = ''; + if (!~this.previewTypeList.indexOf(type)) { + return null; } - return preview; + let html = $('') + .attr('src', this.getImageUrl(id, 'small')) + .attr('title', name) + .css({ + maxWidth: (this.imageSizes[this.previewSize] || {})[0], + maxHeight: (this.imageSizes[this.previewSize] || {})[1], + }) + .get(0) + .outerHTML; + + return html; }, getValueForDisplay: function () { @@ -549,7 +569,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) { var $att = $('
') .append(removeLink) - .append( + .append( $('' + preview + '') .addClass('gray-box') );