diff --git a/app/Fields/File.php b/app/Fields/File.php index a0a36907867a..1d4f662978f8 100644 --- a/app/Fields/File.php +++ b/app/Fields/File.php @@ -166,6 +166,39 @@ public static function loadFromPath(string $path) return $instance; } + /** + * Load file instance from base string. + * + * @param string $contents + * @param array $param + * + * @return \self|null + */ + public static function loadFromBase(string $contents, array $param = []): ?self + { + $result = explode(',', $contents, 2); + $contentType = $isBase64 = false; + if (2 === \count($result)) { + [$metadata, $data] = $result; + foreach (explode(';', $metadata) as $cur) { + if ('base64' === $cur) { + $isBase64 = true; + } elseif ('data:' === substr($cur, 0, 5)) { + $contentType = str_replace('data:', '', $cur); + } + } + } else { + $data = $result[0]; + } + $data = rawurldecode($data); + $rawData = $isBase64 ? base64_decode($data) : $data; + if (\strlen($rawData) < 12) { + Log::error('Incorrect content value: ' . $contents, __CLASS__); + return null; + } + return static::loadFromContent($rawData, false, array_merge($param, ['mimeType' => $contentType])); + } + /** * Load file instance from content. * @@ -881,27 +914,7 @@ public static function getMimeContentType($fileName) */ public static function saveFromString(string $contents, array $param = []) { - $result = explode(',', $contents, 2); - $contentType = $isBase64 = false; - if (2 === \count($result)) { - [$metadata, $data] = $result; - foreach (explode(';', $metadata) as $cur) { - if ('base64' === $cur) { - $isBase64 = true; - } elseif ('data:' === substr($cur, 0, 5)) { - $contentType = str_replace('data:', '', $cur); - } - } - } else { - $data = $result[0]; - } - $data = rawurldecode($data); - $rawData = $isBase64 ? base64_decode($data) : $data; - if (\strlen($rawData) < 12) { - Log::error('Incorrect content value: ' . $contents, __CLASS__); - return false; - } - $fileInstance = static::loadFromContent($rawData, false, array_merge($param, ['mimeType' => $contentType])); + $fileInstance = static::loadFromBase($contents, $param); if ($fileInstance->validateAndSecure()) { return $fileInstance; } @@ -1230,7 +1243,7 @@ public function insertTempFile(array $params): int 'createdtime' => date('Y-m-d H:i:s'), 'fieldname' => null, 'key' => null, - 'crmid' => 0 + 'crmid' => 0, ]; foreach ($data as $key => &$value) { if (isset($params[$key])) { diff --git a/app/Validator.php b/app/Validator.php index f17ef3a0c07d..00bc2791e9bb 100644 --- a/app/Validator.php +++ b/app/Validator.php @@ -483,4 +483,20 @@ public static function path(string $input): bool return !self::dirName($dir); }); } + + /** + * Check base64. + * + * @param string $input + * + * @return bool + */ + public static function base64(string $input): bool + { + if (empty($input)) { + return false; + } + $explode = explode(',', $input); + return 2 === \count($explode) && 1 === preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $explode[1]); + } } diff --git a/config/version.php b/config/version.php index 2c7a68e5ad44..3c9d33945116 100644 --- a/config/version.php +++ b/config/version.php @@ -1,7 +1,7 @@ '6.3.220', + 'appVersion' => '6.3.221', 'patchVersion' => '2022.05.05', 'lib_roundcube' => '0.2.12', ]; diff --git a/modules/Vtiger/actions/Fields.php b/modules/Vtiger/actions/Fields.php index 2d5c58c68822..1d1af8876a9c 100644 --- a/modules/Vtiger/actions/Fields.php +++ b/modules/Vtiger/actions/Fields.php @@ -59,6 +59,7 @@ public function __construct() $this->exposeMethod('validateByMode'); $this->exposeMethod('verifyPhoneNumber'); $this->exposeMethod('changeFavoriteOwner'); + $this->exposeMethod('validateFile'); } /** @@ -68,7 +69,7 @@ public function __construct() * * @throws \App\Exceptions\NoPermitted */ - public function getOwners(App\Request $request) + public function getOwners(App\Request $request): void { if (!App\Config::performance('SEARCH_OWNERS_BY_AJAX')) { throw new \App\Exceptions\NoPermitted('LBL_PERMISSION_DENIED', 406); @@ -121,7 +122,7 @@ public function getOwners(App\Request $request) * * @throws \App\Exceptions\NoPermitted */ - public function getUserRole(App\Request $request) + public function getUserRole(App\Request $request): void { if (!App\Config::performance('SEARCH_ROLES_BY_AJAX')) { throw new \App\Exceptions\NoPermitted('LBL_PERMISSION_DENIED', 406); @@ -148,7 +149,7 @@ public function getUserRole(App\Request $request) * * @throws \App\Exceptions\NoPermitted */ - public function getReference(App\Request $request) + public function getReference(App\Request $request): void { if ($request->has('fieldName')) { $fieldModel = Vtiger_Module_Model::getInstance($request->getModule())->getFieldByName($request->getByType('fieldName', 2)); @@ -195,7 +196,7 @@ public function getReference(App\Request $request) * * @throws \App\Exceptions\NoPermitted */ - public function verifyPhoneNumber(App\Request $request) + public function verifyPhoneNumber(App\Request $request): void { if ('phone' !== $this->fieldModel->getFieldDataType()) { throw new \App\Exceptions\NoPermitted('ERR_NO_PERMISSIONS_TO_FIELD'); @@ -224,7 +225,7 @@ public function verifyPhoneNumber(App\Request $request) * * @param \App\Request $request */ - public function findAddress(App\Request $request) + public function findAddress(App\Request $request): void { $instance = \App\Map\Address::getInstance($request->getByType('type')); $response = new Vtiger_Response(); @@ -243,7 +244,7 @@ public function findAddress(App\Request $request) * @throws \App\Exceptions\NoPermitted * @throws \yii\db\Exception */ - public function changeFavoriteOwner(App\Request $request) + public function changeFavoriteOwner(App\Request $request): void { if (!App\Config::module('Users', 'FAVORITE_OWNERS') || (\App\User::getCurrentUserRealId() !== \App\User::getCurrentUserId())) { throw new \App\Exceptions\NoPermitted('LBL_PERMISSION_DENIED', 406); @@ -265,7 +266,7 @@ public function changeFavoriteOwner(App\Request $request) * * @throws \App\Exceptions\NoPermitted */ - public function validateForField(App\Request $request) + public function validateForField(App\Request $request): void { $fieldModel = Vtiger_Module_Model::getInstance($request->getModule())->getFieldByName($request->getByType('fieldName', 2)); if (!$fieldModel || !$fieldModel->isActiveField() || !$fieldModel->isViewEnabled()) { @@ -288,7 +289,7 @@ public function validateForField(App\Request $request) * * @throws \App\Exceptions\NoPermitted */ - public function validateByMode(App\Request $request) + public function validateByMode(App\Request $request): void { if ($request->isEmpty('purifyMode') || !$request->has('value')) { throw new \App\Exceptions\NoPermitted('ERR_ILLEGAL_VALUE', 406); @@ -299,4 +300,30 @@ public function validateByMode(App\Request $request) ]); $response->emit(); } + + /** + * Validate file. + * + * @param App\Request $request + * + * @return void + */ + public function validateFile(App\Request $request): void + { + $validate = false; + if ($request->has('base64')) { + $fileInstance = \App\Fields\File::loadFromBase($request->getByType('base64', 'base64'), ['validateAllowedFormat' => 'image']); + if ($fileInstance && $fileInstance->validate()) { + $validate = true; + } else { + $validateError = $fileInstance->validateError; + } + } + $response = new Vtiger_Response(); + $response->setResult([ + 'validate' => $validate, + 'validateError' => $validateError ?? null, + ]); + $response->emit(); + } } diff --git a/public_html/layouts/resources/libraries/ckeditor/base64image/dialogs/dialog.js b/public_html/layouts/resources/libraries/ckeditor/base64image/dialogs/dialog.js index faec7711b862..524fbaf205ac 100644 --- a/public_html/layouts/resources/libraries/ckeditor/base64image/dialogs/dialog.js +++ b/public_html/layouts/resources/libraries/ckeditor/base64image/dialogs/dialog.js @@ -1,21 +1,24 @@ -/* {[The file is published on the basis of YetiForce Public License 3.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */ +/* {[The file is published on the basis of YetiForce Public License 5.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */ +'use strict'; CKEDITOR.dialog.add('base64image-dialog', function (editor) { - var t = null, + let self = null, selectedImg = null, orgWidth = null, orgHeight = null, imgPreview = null, - imgScal = 1, - lock = true; + sourceElements = [], + imgScale = 1, + lock = true, + maxUploadSize = CONFIG['maxUploadLimit']; /* Check File Reader Support */ function fileSupport() { - var r = false, + let r = false, n = null; try { if (FileReader) { - var n = document.createElement('input'); + let n = document.createElement('input'); if (n && 'files' in n) r = true; } } catch (e) { @@ -24,7 +27,7 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { n = null; return r; } - var isFReaderSupported = fileSupport(); + let isFReaderSupported = fileSupport(); /* Load preview image */ function imagePreviewLoad(s) { @@ -35,10 +38,10 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { } /* Create image */ - var i = new Image(); + let i = new Image(); /* Display loading text in preview element */ - imgPreview.getElement().setHtml('Loading...'); + $(imgPreview.getElement().$).progressIndicator({ position: 'html' }); /* When image is loaded */ i.onload = function () { @@ -47,11 +50,11 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { /* Set attributes */ if (orgWidth == null || orgHeight == null) { - t.setValueOf('tab-properties', 'width', this.width); - t.setValueOf('tab-properties', 'height', this.height); - imgScal = 1; - if (this.height > 0 && this.width > 0) imgScal = this.width / this.height; - if (imgScal <= 0) imgScal = 1; + self.setValueOf('tab-properties', 'width', this.width); + self.setValueOf('tab-properties', 'height', this.height); + imgScale = 1; + if (this.height > 0 && this.width > 0) imgScale = this.width / this.height; + if (imgScale <= 0) imgScale = 1; } else { orgWidth = null; orgHeight = null; @@ -62,7 +65,7 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { /* Insert preview image */ try { - var p = imgPreview.getElement().$; + let p = imgPreview.getElement().$; if (p) p.appendChild(this); } catch (e) {} }; @@ -82,39 +85,61 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { function imagePreview(src) { imgPreview.getElement().setHtml(''); if (isFReaderSupported) { - var fileI = t.getContentElement('tab-source', 'file'); - var n = null; - try { - n = fileI.getInputElement().$; - } catch (e) { - n = null; - } - if (n && 'files' in n && n.files && n.files.length > 0 && n.files[0]) { - if ('type' in n.files[0] && !n.files[0].type.match('image.*')) return; - if (!FileReader) return; - imgPreview.getElement().setHtml('Loading...'); - var fr = new FileReader(); - fr.onload = (function (f) { - return function (e) { - imgPreview.getElement().setHtml(''); - imagePreviewLoad(e.target.result); - }; - })(n.files[0]); - fr.onerror = function () { + $(imgPreview.getElement().$).progressIndicator({ position: 'html' }); + readImageAsBase64() + .done(function (base) { imgPreview.getElement().setHtml(''); - }; - fr.onabort = function () { + imagePreviewLoad(base); + }) + .fail(function () { imgPreview.getElement().setHtml(''); - }; - fr.readAsDataURL(n.files[0]); + }); + } + } + + function readImageAsBase64() { + const aDeferred = jQuery.Deferred(); + let fileI = self.getContentElement('tab-source', 'file'), + n = null; + try { + n = fileI.getInputElement().$; + } catch (e) { + n = null; + } + if (n && 'files' in n && n.files && n.files.length > 0 && n.files[0]) { + if (('type' in n.files[0] && !n.files[0].type.match('image.*')) || !FileReader) { + aDeferred.reject(); + return aDeferred.promise(); + } + if (n.files[0].size > maxUploadSize) { + app.showNotify({ + text: app.vtranslate('JS_UPLOADED_FILE_SIZE_EXCEEDS'), + type: 'error' + }); + aDeferred.reject(); + return aDeferred.promise(); } + let fr = new FileReader(); + fr.onload = (function (f) { + return function (e) { + aDeferred.resolve(e.target.result); + }; + })(n.files[0]); + fr.onerror = function () { + aDeferred.reject(); + }; + fr.onabort = function () { + aDeferred.reject(); + }; + fr.readAsDataURL(n.files[0]); } + return aDeferred.promise(); } function getImageDimensions() { - var o = { - w: t.getContentElement('tab-properties', 'width').getValue(), - h: t.getContentElement('tab-properties', 'height').getValue(), + let o = { + w: self.getContentElement('tab-properties', 'width').getValue(), + h: self.getContentElement('tab-properties', 'height').getValue(), uw: 'px', uh: 'px' }; @@ -128,25 +153,25 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { } function imageDimensions(src) { - var o = getImageDimensions(); - var u = 'px'; + let o = getImageDimensions(); + let u = 'px'; if (src == 'width') { if (o.uw == '%') u = '%'; - o.h = Math.round(o.w / imgScal); + o.h = Math.round(o.w / imgScale); } else { if (o.uh == '%') u = '%'; - o.w = Math.round(o.h * imgScal); + o.w = Math.round(o.h * imgScale); } if (u == '%') { o.w += '%'; o.h += '%'; } - t.getContentElement('tab-properties', 'width').setValue(o.w), - t.getContentElement('tab-properties', 'height').setValue(o.h); + self.getContentElement('tab-properties', 'width').setValue(o.w), + self.getContentElement('tab-properties', 'height').setValue(o.h); } function integerValue(elem) { - var v = elem.getValue(), + let v = elem.getValue(), u = ''; if (v.indexOf('%') >= 0) u = '%'; v = parseInt(v, 10); @@ -154,8 +179,55 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { elem.setValue(v + u); } + function validateFile() { + const fieldInfo = $(editor.element.$).data('fieldinfo'); + let length = editor.getData().length, + selectedImg = editor.getSelection(); + if (selectedImg) selectedImg = selectedImg.getSelectedElement(); + if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null; + if (selectedImg) { + length = length - selectedImg.getOuterHtml().length; + } + const aDeferred = jQuery.Deferred(); + readImageAsBase64() + .done((base) => { + length += base.length; + if (length > fieldInfo['maximumlength']) { + app.showNotify({ + text: app.vtranslate('JS_MAXIMUM_TEXT_SIZE_IN_BYTES') + ' ' + fieldInfo['maximumlength'], + type: 'error' + }); + } + AppConnector.request({ + module: app.getModuleName(), + action: 'Fields', + mode: 'validateFile', + fieldName: fieldInfo['name'], + base64: base + }) + .done((data) => { + if (data.result.validate) { + aDeferred.resolve(); + } else { + app.showNotify({ + text: data.result.validateError, + type: 'error' + }); + aDeferred.reject(); + } + }) + .fail(function () { + aDeferred.resolve(); + }); + }) + .fail(() => { + aDeferred.reject(); + }); + return aDeferred.promise(); + } + if (isFReaderSupported) { - var sourceElements = [ + sourceElements = [ { type: 'hbox', widths: ['70px'], @@ -165,8 +237,16 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { type: 'file', id: 'file', label: '', - onChange: function () { - imagePreview('file'); + size: maxUploadSize, + onChange: function (a) { + validateFile() + .done(() => { + imagePreview('file'); + }) + .fail(function () { + self.getContentElement('tab-source', 'file').getInputElement().$.value = null; + imgPreview.getElement().setHtml(''); + }); } } ] @@ -240,10 +320,13 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { ); }, onShow: function () { + this.getContentElement('tab-source', 'file') + .getInputElement() + .$.setAttribute('accept', 'image/jpeg, image/png, image/gif'); /* Remove preview */ imgPreview.getElement().setHtml(''); - (t = this), (orgWidth = null), (orgHeight = null), (imgScal = 1), (lock = true); + (self = this), (orgWidth = null), (orgHeight = null), (imgScale = 1), (lock = true); /* selected image or null */ selectedImg = editor.getSelection(); @@ -251,11 +334,11 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null; /* Set input values */ - t.setValueOf('tab-properties', 'lock', lock); - t.setValueOf('tab-properties', 'vmargin', '0'); - t.setValueOf('tab-properties', 'hmargin', '0'); - t.setValueOf('tab-properties', 'border', '0'); - t.setValueOf('tab-properties', 'align', 'none'); + self.setValueOf('tab-properties', 'lock', lock); + self.setValueOf('tab-properties', 'vmargin', '0'); + self.setValueOf('tab-properties', 'hmargin', '0'); + self.setValueOf('tab-properties', 'border', '0'); + self.setValueOf('tab-properties', 'align', 'none'); if (selectedImg) { /* Set input values from selected image */ if (typeof selectedImg.getAttribute('width') == 'string') orgWidth = selectedImg.getAttribute('width'); @@ -265,13 +348,13 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { orgHeight = selectedImg.$.height; } if (orgWidth != null && orgHeight != null) { - t.setValueOf('tab-properties', 'width', orgWidth); - t.setValueOf('tab-properties', 'height', orgHeight); + self.setValueOf('tab-properties', 'width', orgWidth); + self.setValueOf('tab-properties', 'height', orgHeight); orgWidth = parseInt(orgWidth, 10); orgHeight = parseInt(orgHeight, 10); - imgScal = 1; - if (!isNaN(orgWidth) && !isNaN(orgHeight) && orgHeight > 0 && orgWidth > 0) imgScal = orgWidth / orgHeight; - if (imgScal <= 0) imgScal = 1; + imgScale = 1; + if (!isNaN(orgWidth) && !isNaN(orgHeight) && orgHeight > 0 && orgWidth > 0) imgScale = orgWidth / orgHeight; + if (imgScale <= 0) imgScale = 1; } if (typeof selectedImg.getAttribute('src') == 'string') { @@ -281,38 +364,38 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { } } if (typeof selectedImg.getAttribute('alt') == 'string') - t.setValueOf('tab-properties', 'alt', selectedImg.getAttribute('alt')); + self.setValueOf('tab-properties', 'alt', selectedImg.getAttribute('alt')); if (typeof selectedImg.getAttribute('hspace') == 'string') - t.setValueOf('tab-properties', 'hmargin', selectedImg.getAttribute('hspace')); + self.setValueOf('tab-properties', 'hmargin', selectedImg.getAttribute('hspace')); if (typeof selectedImg.getAttribute('vspace') == 'string') - t.setValueOf('tab-properties', 'vmargin', selectedImg.getAttribute('vspace')); + self.setValueOf('tab-properties', 'vmargin', selectedImg.getAttribute('vspace')); if (typeof selectedImg.getAttribute('border') == 'string') - t.setValueOf('tab-properties', 'border', selectedImg.getAttribute('border')); + self.setValueOf('tab-properties', 'border', selectedImg.getAttribute('border')); if (typeof selectedImg.getAttribute('align') == 'string') { switch (selectedImg.getAttribute('align')) { case 'top': case 'text-top': - t.setValueOf('tab-properties', 'align', 'top'); + self.setValueOf('tab-properties', 'align', 'top'); break; case 'baseline': case 'bottom': case 'text-bottom': - t.setValueOf('tab-properties', 'align', 'bottom'); + self.setValueOf('tab-properties', 'align', 'bottom'); break; case 'left': - t.setValueOf('tab-properties', 'align', 'left'); + self.setValueOf('tab-properties', 'align', 'left'); break; case 'right': - t.setValueOf('tab-properties', 'align', 'right'); + self.setValueOf('tab-properties', 'align', 'right'); break; } } - t.selectPage('tab-properties'); + self.selectPage('tab-properties'); } }, onOk: function () { /* Get image source */ - var src = ''; + let src = ''; try { src = CKEDITOR.document.getById(editor.id + 'previewimage').$.src; } catch (e) { @@ -320,77 +403,85 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) { } if (typeof src != 'string' || src == null || src === '') return; - /* selected image or new image */ - if (selectedImg) var newImg = selectedImg; - else var newImg = editor.document.createElement('img'); - newImg.setAttribute('src', src); - src = null; + validateFile().always(() => { + /* selected image or new image */ + if (selectedImg) { + var newImg = selectedImg; + } else { + var newImg = editor.document.createElement('img'); + } + newImg.setAttribute('src', src); + src = null; - /* Set attributes */ - newImg.setAttribute('alt', t.getValueOf('tab-properties', 'alt').replace(/^\s+/, '').replace(/\s+$/, '')); - var attr = { - width: ['width', 'width:#;', 'integer', 1], - height: ['height', 'height:#;', 'integer', 1], - vmargin: ['vspace', 'margin-top:#;margin-bottom:#;', 'integer', 0], - hmargin: ['hspace', 'margin-left:#;margin-right:#;', 'integer', 0], - align: ['align', ''], - border: ['border', 'border:# solid black;', 'integer', 0] - }, - css = [], - value, - cssvalue, - attrvalue, - k; - for (k in attr) { - value = t.getValueOf('tab-properties', k); - attrvalue = value; - cssvalue = value; - unit = 'px'; - - if (k == 'align') { - switch (value) { - case 'top': - case 'bottom': - attr[k][1] = 'vertical-align:#;'; - break; - case 'left': - case 'right': - attr[k][1] = 'float:#;'; - break; - default: - value = null; - break; + /* Set attributes */ + newImg.setAttribute('alt', self.getValueOf('tab-properties', 'alt').replace(/^\s+/, '').replace(/\s+$/, '')); + let attr = { + width: ['width', 'width:#;', 'integer', 1], + height: ['height', 'height:#;', 'integer', 1], + vmargin: ['vspace', 'margin-top:#;margin-bottom:#;', 'integer', 0], + hmargin: ['hspace', 'margin-left:#;margin-right:#;', 'integer', 0], + align: ['align', ''], + border: ['border', 'border:# solid black;', 'integer', 0] + }, + css = [], + value, + cssValue, + attrValue, + unit, + k; + for (k in attr) { + value = self.getValueOf('tab-properties', k); + attrValue = value; + cssValue = value; + unit = 'px'; + + if (k == 'align') { + switch (value) { + case 'top': + case 'bottom': + attr[k][1] = 'vertical-align:#;'; + break; + case 'left': + case 'right': + attr[k][1] = 'float:#;'; + break; + default: + value = null; + break; + } } - } - if (attr[k][2] == 'integer') { - if (value.indexOf('%') >= 0) unit = '%'; - value = parseInt(value, 10); - if (isNaN(value)) value = null; - else if (value < attr[k][3]) value = null; - if (value != null) { - if (unit == '%') { - attrvalue = value + '%'; - cssvalue = value + '%'; - } else { - attrvalue = value; - cssvalue = value + 'px'; + if (attr[k][2] == 'integer') { + if (value.indexOf('%') >= 0) unit = '%'; + value = parseInt(value, 10); + if (isNaN(value)) value = null; + else if (value < attr[k][3]) value = null; + if (value != null) { + if (unit == '%') { + attrValue = value + '%'; + cssValue = value + '%'; + } else { + attrValue = value; + cssValue = value + 'px'; + } } } - } - if (value != null) { - newImg.setAttribute(attr[k][0], attrvalue); - css.push(attr[k][1].replace(/#/g, cssvalue)); + if (value != null) { + newImg.setAttribute(attr[k][0], attrValue); + css.push(attr[k][1].replace(/#/g, cssValue)); + } } - } - if (css.length > 0) newImg.setAttribute('style', css.join('')); + if (css.length > 0) newImg.setAttribute('style', css.join('')); + + /* Insert new image */ + if (!selectedImg) editor.insertElement(newImg); - /* Insert new image */ - if (!selectedImg) editor.insertElement(newImg); + /* Resize image */ + if (editor.plugins.imageresize) editor.plugins.imageresize.resize(editor, newImg, 800, 800); - /* Resize image */ - if (editor.plugins.imageresize) editor.plugins.imageresize.resize(editor, newImg, 800, 800); + editor.updateElement(); + }); }, /* Dialog form */ diff --git a/public_html/layouts/resources/libraries/ckeditor/base64image/plugin.js b/public_html/layouts/resources/libraries/ckeditor/base64image/plugin.js index fd8d13d6bff0..9e47c40407dc 100644 --- a/public_html/layouts/resources/libraries/ckeditor/base64image/plugin.js +++ b/public_html/layouts/resources/libraries/ckeditor/base64image/plugin.js @@ -1,72 +1,28 @@ -/* {[The file is published on the basis of YetiForce Public License 3.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */ - -function initPasteEvent(editorInstance) { - if (editorInstance.addFeature) { - editorInstance.addFeature({ - allowedContent: 'img[alt,id,!src]{width,height};' - }); - } - - editorInstance.on('contentDom', function () { - var editableElement = editorInstance.editable ? editorInstance.editable() : editorInstance.document; - editableElement.on('paste', onPaste, null, { editor: editorInstance }); - }); -} -function onPaste(event) { - var editor = event.listenerData && event.listenerData.editor; - var $event = event.data.$; - var clipboardData = $event.clipboardData; - var found = false; - var imageType = /^image/; - - if (!clipboardData) { - return; - } - return Array.prototype.forEach.call(clipboardData.types, function (type, i) { - if (found) { - return; - } - if (type.match(imageType) || clipboardData.items[i].type.match(imageType)) { - readImageAsBase64(clipboardData.items[i], editor); - return (found = true); - } - }); -} - -function readImageAsBase64(item, editor) { - if (!item || typeof item.getAsFile !== 'function') { - return; - } - var file = item.getAsFile(); - var reader = new FileReader(); - reader.onload = function (evt) { - var element = editor.document.createElement('img', { - attributes: { - src: evt.target.result - } - }); - setTimeout(function () { - editor.insertElement(element); - }, 10); - }; - reader.readAsDataURL(file); -} +/* {[The file is published on the basis of YetiForce Public License 5.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */ +'use strict'; CKEDITOR.plugins.add('base64image', { requires: 'dialog', icons: 'base64image', hidpi: true, - init: function (editorInstance) { - initPasteEvent(editorInstance); - var pluginName = 'base64image-dialog'; - editorInstance.ui.addToolbarGroup('base64image', 'insert'); - editorInstance.ui.addButton('base64image', { - label: editorInstance.lang.common.image, + init: function (editor) { + if (editor.addFeature) { + editor.addFeature({ + allowedContent: 'img[alt,id,!src]{width,height};' + }); + } + editor.on('paste', (event, a, b) => { + this.onPaste(event); + }); + const pluginName = 'base64image-dialog'; + editor.ui.addToolbarGroup('base64image', 'insert'); + editor.ui.addButton('base64image', { + label: editor.lang.common.image, command: pluginName, toolbar: 'insert' }); CKEDITOR.dialog.add(pluginName, this.path + 'dialogs/dialog.js'); - editorInstance.addCommand( + editor.addCommand( pluginName, new CKEDITOR.dialogCommand(pluginName, { allowedContent: @@ -78,29 +34,119 @@ CKEDITOR.plugins.add('base64image', { ] }) ); - editorInstance.on('doubleclick', function (evt) { + editor.on('doubleclick', function (evt) { if (evt.data.element && !evt.data.element.isReadOnly() && evt.data.element.getName() === 'img') { evt.data.dialog = pluginName; - editorInstance.getSelection().selectElement(evt.data.element); + editor.getSelection().selectElement(evt.data.element); } }); - if (editorInstance.addMenuItem) { - editorInstance.addMenuGroup('imageToBase64Group'); - editorInstance.addMenuItem('imageToBase64Item', { - label: editorInstance.lang.common.image, + if (editor.addMenuItem) { + editor.addMenuGroup('imageToBase64Group'); + editor.addMenuItem('imageToBase64Item', { + label: editor.lang.common.image, icon: this.path + 'icons/base64image.png', command: pluginName, group: 'imageToBase64Group' }); } - if (editorInstance.contextMenu) { - editorInstance.contextMenu.addListener(function (element, selection) { + if (editor.contextMenu) { + editor.contextMenu.addListener(function (element) { if (element && element.getName() === 'img') { - editorInstance.getSelection().selectElement(element); + editor.getSelection().selectElement(element); return { imageToBase64Item: CKEDITOR.TRISTATE_ON }; } return null; }); } + }, + onPaste: function (event) { + const self = this, + allowedTypes = 'image/jpeg|image/png|image/gif', + dataTransfer = event.data.dataTransfer, + editor = event.editor, + count = dataTransfer.getFilesCount(); + for (let index = 0; index < count; index++) { + let file = dataTransfer.getFile(index); + if (file.type.match(allowedTypes)) { + self + .validateFile(file, editor) + .done(function (base) { + let image, + selectedImg = editor.getSelection(); + if (selectedImg) selectedImg = selectedImg.getSelectedElement(); + if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null; + if (selectedImg) { + image = selectedImg; + } else { + image = editor.document.createElement('img'); + } + image.setAttribute('src', base); + if (!selectedImg) editor.insertElement(image); + }) + .fail(function (error) { + editor.showNotification(error, 'warning'); + }); + } else { + editor.showNotification( + app.vtranslate('JS_INVALID_FILE_TYPE') + + '
' + + app.vtranslate('JS_AVAILABLE_FILE_TYPES') + + ': ' + + allowedTypes.replace(/\|/g, ', '), + 'warning' + ); + } + } + }, + validateFile: function (file, editor) { + const aDeferred = jQuery.Deferred(); + if (file.size > CONFIG['maxUploadLimit']) { + aDeferred.reject(app.vtranslate('JS_UPLOADED_FILE_SIZE_EXCEEDS')); + } + this.readAndValidate(file, editor) + .done(function (base) { + aDeferred.resolve(base); + }) + .fail(function (error) { + aDeferred.reject(error); + }); + return aDeferred.promise(); + }, + readAndValidate: function (file, editor) { + const aDeferred = jQuery.Deferred(), + fieldInfo = $(editor.element.$).data('fieldinfo'); + let length = editor.getData().length, + selectedImg = editor.getSelection(); + if (selectedImg) selectedImg = selectedImg.getSelectedElement(); + if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null; + if (selectedImg) { + length = length - selectedImg.getOuterHtml().length; + } + const fileReader = new FileReader(); + fileReader.onload = function (evt) { + length += evt.target.result.length; + if (length > fieldInfo['maximumlength']) { + return aDeferred.reject(app.vtranslate('JS_MAXIMUM_TEXT_SIZE_IN_BYTES') + ' ' + fieldInfo['maximumlength']); + } + AppConnector.request({ + module: app.getModuleName(), + action: 'Fields', + mode: 'validateFile', + fieldName: fieldInfo['name'], + base64: evt.target.result + }) + .done((data) => { + if (data.result.validate) { + aDeferred.resolve(evt.target.result); + } else { + aDeferred.reject(data.result.validateError); + } + }) + .fail(function () { + aDeferred.reject(); + }); + }; + fileReader.readAsDataURL(file); + return aDeferred.promise(); } });