From 39624e1ddb27032e8d7b884a849f7b6060617a9e Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 11:07:24 +0200 Subject: [PATCH 01/19] fix logo image access --- application/Espo/EntryPoints/Image.php | 15 +++++++++++++++ application/Espo/EntryPoints/LogoImage.php | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/application/Espo/EntryPoints/Image.php b/application/Espo/EntryPoints/Image.php index 3b7ec020ed..5a79c30830 100644 --- a/application/Espo/EntryPoints/Image.php +++ b/application/Espo/EntryPoints/Image.php @@ -55,6 +55,9 @@ class Image extends \Espo\Core\EntryPoints\Base 'xx-large' => array(1024, 1024), ); + protected $allowedRelatedTypeList = null; + + protected $allowedFieldList = null; public function run() { @@ -97,6 +100,18 @@ class Image extends \Espo\Core\EntryPoints\Base throw new Error(); } + if ($this->allowedRelatedTypeList) { + if (!in_array($attachment->get('relatedType'), $this->allowedRelatedTypeList)) { + throw new NotFound(); + } + } + + if ($this->allowedFieldList) { + if (!in_array($attachment->get('field'), $this->allowedFieldList)) { + throw new NotFound(); + } + } + if (!empty($size)) { if (!empty($this->imageSizes[$size])) { $thumbFilePath = "data/upload/thumbs/{$sourceId}_{$size}"; diff --git a/application/Espo/EntryPoints/LogoImage.php b/application/Espo/EntryPoints/LogoImage.php index 8b7b52e01b..ccdcd89cd6 100644 --- a/application/Espo/EntryPoints/LogoImage.php +++ b/application/Espo/EntryPoints/LogoImage.php @@ -38,6 +38,10 @@ class LogoImage extends Image { public static $authRequired = false; + protected $allowedRelatedTypeList = ['Settings', 'Portal']; + + protected $allowedFieldList = ['companyLogo']; + public function run() { $this->imageSizes['small-logo'] = array(181, 44); From 32f8a93021fbca6f7388adf8748e87c8011d3ba9 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 11:17:51 +0200 Subject: [PATCH 02/19] fix image --- application/Espo/EntryPoints/Image.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/application/Espo/EntryPoints/Image.php b/application/Espo/EntryPoints/Image.php index 5a79c30830..ff10a62da9 100644 --- a/application/Espo/EntryPoints/Image.php +++ b/application/Espo/EntryPoints/Image.php @@ -55,6 +55,10 @@ class Image extends \Espo\Core\EntryPoints\Base 'xx-large' => array(1024, 1024), ); + protected $fixOrientationFileTypeList = [ + 'image/jpeg', + ]; + protected $allowedRelatedTypeList = null; protected $allowedFieldList = null; @@ -212,8 +216,21 @@ class Image extends \Espo\Core\EntryPoints\Base break; } + if (in_array($fileType, $this->fixOrientationFileTypeList)) { + $targetImage = $this->fixOrientation($targetImage, $filePath); + } + + return $targetImage; + } + + protected function fixOrientation($targetImage, $filePath) + { if (function_exists('exif_read_data')) { - $targetImage = imagerotate($targetImage, array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[@exif_read_data($filePath)['Orientation'] ?: 0], 0); + $orientation = @exif_read_data($filePath)['Orientation']; + if ($orientation) { + $angle = array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[$orientation]; + $targetImage = imagerotate($targetImage, $angle, 0); + } } return $targetImage; From 3ee28f8d48058dbb2c39056055b85359bb16a90e Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 11:22:20 +0200 Subject: [PATCH 03/19] cs fix --- application/Espo/EntryPoints/Image.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/application/Espo/EntryPoints/Image.php b/application/Espo/EntryPoints/Image.php index ff10a62da9..e8551e7762 100644 --- a/application/Espo/EntryPoints/Image.php +++ b/application/Espo/EntryPoints/Image.php @@ -38,22 +38,22 @@ class Image extends \Espo\Core\EntryPoints\Base { public static $authRequired = true; - protected $allowedFileTypes = array( + protected $allowedFileTypes = [ 'image/jpeg', 'image/png', 'image/gif', - ); + ]; - protected $imageSizes = array( - 'xxx-small' => array(18, 18), - 'xx-small' => array(32, 32), - 'x-small' => array(64, 64), - 'small' => array(128, 128), - 'medium' => array(256, 256), - 'large' => array(512, 512), - 'x-large' => array(864, 864), - 'xx-large' => array(1024, 1024), - ); + 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', From 54ac4b5308ce4d5bf78991575bfb95a54b6feaa7 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 11:35:05 +0200 Subject: [PATCH 04/19] scriptList in metadata --- application/Espo/Core/Container.php | 3 +- application/Espo/Core/Utils/ClientManager.php | 40 +++++-------------- application/Espo/Core/Utils/Metadata.php | 3 +- .../Espo/Resources/metadata/app/client.json | 26 ++++++++++++ 4 files changed, 41 insertions(+), 31 deletions(-) create mode 100644 application/Espo/Resources/metadata/app/client.json diff --git a/application/Espo/Core/Container.php b/application/Espo/Core/Container.php index 85883dcd72..08fe9435d8 100644 --- a/application/Espo/Core/Container.php +++ b/application/Espo/Core/Container.php @@ -378,7 +378,8 @@ class Container { return new \Espo\Core\Utils\ClientManager( $this->get('config'), - $this->get('themeManager') + $this->get('themeManager'), + $this->get('metadata') ); } diff --git a/application/Espo/Core/Utils/ClientManager.php b/application/Espo/Core/Utils/ClientManager.php index 08833aeaee..9500679f32 100644 --- a/application/Espo/Core/Utils/ClientManager.php +++ b/application/Espo/Core/Utils/ClientManager.php @@ -35,42 +35,19 @@ class ClientManager private $config; + private $metadata; + protected $mainHtmlFilePath = 'html/main.html'; protected $runScript = "app.start();"; protected $basePath = ''; - protected $jsFileList = [ - 'client/espo.min.js' - ]; - - protected $developerModeJsFileList = [ - 'client/lib/jquery-2.1.4.min.js', - 'client/lib/underscore-min.js', - 'client/lib/es6-promise.min.js', - 'client/lib/backbone-min.js', - 'client/lib/handlebars.js', - 'client/lib/base64.js', - 'client/lib/jquery-ui.min.js', - 'client/lib/jquery.ui.touch-punch.min.js', - 'client/lib/moment.min.js', - 'client/lib/moment-timezone-with-data.min.js', - 'client/lib/jquery.timepicker.min.js', - 'client/lib/jquery.autocomplete.js', - 'client/lib/bootstrap.min.js', - 'client/lib/bootstrap-datepicker.js', - 'client/lib/bull.js', - 'client/lib/marked.min.js', - 'client/src/loader.js', - 'client/src/utils.js', - 'client/src/exceptions.js', - ]; - - public function __construct(Config $config, ThemeManager $themeManager) + public function __construct(Config $config, ThemeManager $themeManager, Metadata $metadata) { $this->config = $config; $this->themeManager = $themeManager; + $this->metadata = $metadata; } protected function getThemeManager() @@ -83,6 +60,11 @@ class ClientManager return $this->config; } + protected function getMetadata() + { + return $this->metadata; + } + public function setBasePath($basePath) { $this->basePath = $basePath; @@ -116,11 +98,11 @@ class ClientManager if ($isDeveloperMode) { $useCache = $this->getConfig()->get('useCacheInDeveloperMode'); - $jsFileList = $this->developerModeJsFileList; + $jsFileList = $this->getMetadata()->get(['app', 'client', 'developerModeScriptList']); $loaderCacheTimestamp = 'null'; } else { $useCache = $this->getConfig()->get('useCache'); - $jsFileList = $this->jsFileList; + $jsFileList = $this->getMetadata()->get(['app', 'client', 'scriptList']); $loaderCacheTimestamp = $cacheTimestamp; } diff --git a/application/Espo/Core/Utils/Metadata.php b/application/Espo/Core/Utils/Metadata.php index 10b14e3632..94e1382327 100644 --- a/application/Espo/Core/Utils/Metadata.php +++ b/application/Espo/Core/Utils/Metadata.php @@ -66,7 +66,8 @@ class Metadata protected $frontendHiddenPathList = [ ['app', 'formula', 'functionClassNameMap'], ['app', 'fileStorage', 'implementationClassNameMap'], - ['app', 'emailNotifications', 'handlerClassNameMap'] + ['app', 'emailNotifications', 'handlerClassNameMap'], + ['app', 'client'], ]; /** diff --git a/application/Espo/Resources/metadata/app/client.json b/application/Espo/Resources/metadata/app/client.json new file mode 100644 index 0000000000..54777e8945 --- /dev/null +++ b/application/Espo/Resources/metadata/app/client.json @@ -0,0 +1,26 @@ +{ + "scriptList": [ + "client/espo.min.js" + ], + "developerModeScriptList": [ + "client/lib/jquery-2.1.4.min.js", + "client/lib/underscore-min.js", + "client/lib/es6-promise.min.js", + "client/lib/backbone-min.js", + "client/lib/handlebars.js", + "client/lib/base64.js", + "client/lib/jquery-ui.min.js", + "client/lib/jquery.ui.touch-punch.min.js", + "client/lib/moment.min.js", + "client/lib/moment-timezone-with-data.min.js", + "client/lib/jquery.timepicker.min.js", + "client/lib/jquery.autocomplete.js", + "client/lib/bootstrap.min.js", + "client/lib/bootstrap-datepicker.js", + "client/lib/bull.js", + "client/lib/marked.min.js", + "client/src/loader.js", + "client/src/utils.js", + "client/src/exceptions.js" + ] +} From d40b7aef11ce6287418d608b6256c7bb5e323ba6 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 12:10:28 +0200 Subject: [PATCH 05/19] charts no data --- .../crm/src/views/dashlets/abstract/chart.js | 33 +++++++++++++++++++ .../dashlets/opportunities-by-lead-source.js | 4 +++ .../crm/src/views/dashlets/sales-by-month.js | 4 +++ 3 files changed, 41 insertions(+) diff --git a/client/modules/crm/src/views/dashlets/abstract/chart.js b/client/modules/crm/src/views/dashlets/abstract/chart.js index 9b9368b18d..4caa34a987 100644 --- a/client/modules/crm/src/views/dashlets/abstract/chart.js +++ b/client/modules/crm/src/views/dashlets/abstract/chart.js @@ -212,6 +212,11 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base' this.fetch(function (data) { this.chartData = this.prepareData(data); + if (this.isNoData()) { + this.showNoData(); + return; + } + this.adjustContainer(); setTimeout(function () { @@ -221,6 +226,10 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base' }); }, + isNoData: function () { + return false; + }, + url: function () {}, prepareData: function (response) { @@ -239,6 +248,30 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base' getDateFilter: function () { return this.getOption('dateFilter') || 'currentYear'; + }, + + showNoData: function () { + var fontSize = this.getThemeManager().getParam('fontSize') || 14; + this.$container.empty(); + var textFontSize = fontSize * 1.2; + + var $text = $('').html(this.translate('No Data')).addClass('text-muted'); + + var $div = $('
').css('text-align', 'center') + .css('font-size', textFontSize + 'px') + .css('display', 'table') + .css('width', '100%') + .css('height', '100%'); + + $text + .css('display', 'table-cell') + .css('vertical-align', 'middle') + .css('padding-bottom', fontSize * 1.5 + 'px'); + + + $div.append($text); + + this.$container.append($div); } }); diff --git a/client/modules/crm/src/views/dashlets/opportunities-by-lead-source.js b/client/modules/crm/src/views/dashlets/opportunities-by-lead-source.js index eea5492a3b..5fcc83f1fb 100644 --- a/client/modules/crm/src/views/dashlets/opportunities-by-lead-source.js +++ b/client/modules/crm/src/views/dashlets/opportunities-by-lead-source.js @@ -53,6 +53,10 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle return data; }, + isNoData: function () { + return !this.chartData.length; + }, + setupDefaultOptions: function () { this.defaultOptions['dateFrom'] = this.defaultOptions['dateFrom'] || moment().format('YYYY') + '-01-01'; this.defaultOptions['dateTo'] = this.defaultOptions['dateTo'] || moment().format('YYYY') + '-12-31'; diff --git a/client/modules/crm/src/views/dashlets/sales-by-month.js b/client/modules/crm/src/views/dashlets/sales-by-month.js index e19a31c52a..a1192f673b 100644 --- a/client/modules/crm/src/views/dashlets/sales-by-month.js +++ b/client/modules/crm/src/views/dashlets/sales-by-month.js @@ -52,6 +52,10 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch return 0; }, + isNoData: function () { + return !this.monthList.length; + }, + prepareData: function (response) { var monthList = this.monthList = response.keyList; From af9718951fc1ca60fe5988e52efb32cf575513e2 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 12:54:58 +0200 Subject: [PATCH 06/19] record service naming change --- application/Espo/Core/Controllers/Record.php | 24 ++++++++++---------- application/Espo/Services/Attachment.php | 4 ++-- application/Espo/Services/Email.php | 4 ++-- application/Espo/Services/EmailAccount.php | 4 ++-- application/Espo/Services/InboundEmail.php | 18 --------------- application/Espo/Services/Note.php | 12 +++++----- application/Espo/Services/Record.php | 21 ++++++++++------- application/Espo/Services/RecordTree.php | 8 +++---- application/Espo/Services/User.php | 12 +++++----- 9 files changed, 47 insertions(+), 60 deletions(-) diff --git a/application/Espo/Core/Controllers/Record.php b/application/Espo/Core/Controllers/Record.php index 6d20849c26..594ea8096b 100644 --- a/application/Espo/Core/Controllers/Record.php +++ b/application/Espo/Core/Controllers/Record.php @@ -67,7 +67,7 @@ class Record extends Base public function actionRead($params, $data, $request) { $id = $params['id']; - $entity = $this->getRecordService()->readEntity($id); + $entity = $this->getRecordService()->read($id); if (empty($entity)) { throw new NotFound(); @@ -95,7 +95,7 @@ class Record extends Base $service = $this->getRecordService(); - if ($entity = $service->createEntity($data)) { + if ($entity = $service->create($data)) { return $entity->getValueMap(); } @@ -116,7 +116,7 @@ class Record extends Base $id = $params['id']; - if ($entity = $this->getRecordService()->updateEntity($id, $data)) { + if ($entity = $this->getRecordService()->update($id, $data)) { return $entity->getValueMap(); } @@ -140,12 +140,12 @@ class Record extends Base throw new Forbidden("Max size should should not exceed " . $maxSizeLimit . ". Use offset and limit."); } - $result = $this->getRecordService()->findEntities($params); + $result = $this->getRecordService()->find($params); - return array( + return [ 'total' => $result['total'], 'list' => isset($result['collection']) ? $result['collection']->getValueMapList() : $result['list'] - ); + ]; } public function getActionListKanban($params, $data, $request) @@ -195,7 +195,7 @@ class Record extends Base throw new Forbidden("Max size should should not exceed " . $maxSizeLimit . ". Use offset and limit."); } - $result = $this->getRecordService()->findLinkedEntities($id, $link, $params); + $result = $this->getRecordService()->findLinked($id, $link, $params); return array( 'total' => $result['total'], @@ -211,7 +211,7 @@ class Record extends Base $id = $params['id']; - if ($this->getRecordService()->deleteEntity($id)) { + if ($this->getRecordService()->delete($id)) { return true; } throw new Error(); @@ -262,9 +262,9 @@ class Record extends Base $params['format'] = $data->format; } - return array( + return [ 'id' => $this->getRecordService()->export($params) - ); + ]; } public function actionMassUpdate($params, $data, $request) @@ -363,7 +363,7 @@ class Record extends Base $result = false; foreach ($foreignIdList as $foreignId) { - if ($this->getRecordService()->linkEntity($id, $link, $foreignId)) { + if ($this->getRecordService()->link($id, $link, $foreignId)) { $result = true; } } @@ -400,7 +400,7 @@ class Record extends Base $result = false; foreach ($foreignIdList as $foreignId) { - if ($this->getRecordService()->unlinkEntity($id, $link, $foreignId)) { + if ($this->getRecordService()->unlink($id, $link, $foreignId)) { $result = $result || true; } } diff --git a/application/Espo/Services/Attachment.php b/application/Espo/Services/Attachment.php index 0bdef87886..d0457608f3 100644 --- a/application/Espo/Services/Attachment.php +++ b/application/Espo/Services/Attachment.php @@ -65,7 +65,7 @@ class Attachment extends Record return $attachment; } - public function createEntity($data) + public function create($data) { if (!empty($data->file)) { $arr = explode(',', $data->file); @@ -127,7 +127,7 @@ class Attachment extends Record } } - $entity = parent::createEntity($data); + $entity = parent::create($data); if (!empty($data->file)) { $entity->clear('contents'); diff --git a/application/Espo/Services/Email.php b/application/Espo/Services/Email.php index 2acf23a4be..016deaf346 100644 --- a/application/Espo/Services/Email.php +++ b/application/Espo/Services/Email.php @@ -262,9 +262,9 @@ class Email extends Record return $this->streamService; } - public function createEntity($data) + public function create($data) { - $entity = parent::createEntity($data); + $entity = parent::create($data); if ($entity && $entity->get('status') == 'Sending') { $this->send($entity); diff --git a/application/Espo/Services/EmailAccount.php b/application/Espo/Services/EmailAccount.php index 1c6e91f94c..c1d70cfc3e 100644 --- a/application/Espo/Services/EmailAccount.php +++ b/application/Espo/Services/EmailAccount.php @@ -116,7 +116,7 @@ class EmailAccount extends Record throw new Error(); } - public function createEntity($data) + public function create($data) { if (!$this->getUser()->isAdmin()) { $count = $this->getEntityManager()->getRepository('EmailAccount')->where(array( @@ -127,7 +127,7 @@ class EmailAccount extends Record } } - $entity = parent::createEntity($data); + $entity = parent::create($data); if ($entity) { if (!$this->getUser()->isAdmin()) { $entity->set('assignedUserId', $this->getUser()->id); diff --git a/application/Espo/Services/InboundEmail.php b/application/Espo/Services/InboundEmail.php index c9d1d72089..a2ed498046 100644 --- a/application/Espo/Services/InboundEmail.php +++ b/application/Espo/Services/InboundEmail.php @@ -41,24 +41,6 @@ class InboundEmail extends \Espo\Services\Record const PORTION_LIMIT = 20; - public function createEntity($data) - { - $entity = parent::createEntity($data); - return $entity; - } - - public function getEntity($id = null) - { - $entity = parent::getEntity($id); - return $entity; - } - - public function updateEntity($id, $data) - { - $entity = parent::updateEntity($id, $data); - return $entity; - } - protected function init() { parent::init(); diff --git a/application/Espo/Services/Note.php b/application/Espo/Services/Note.php index f42a73ee04..1fc24279f2 100644 --- a/application/Espo/Services/Note.php +++ b/application/Espo/Services/Note.php @@ -48,7 +48,7 @@ class Note extends Record return $entity; } - public function createEntity($data) + public function create($data) { if (!empty($data->parentType) && !empty($data->parentId)) { $entity = $this->getEntityManager()->getEntity($data->parentType, $data->parentId); @@ -59,7 +59,7 @@ class Note extends Record } } - return parent::createEntity($data); + return parent::create($data); } protected function afterCreateEntity(Entity $entity, $data) @@ -197,20 +197,20 @@ class Note extends Record return true; } - public function linkEntity($id, $link, $foreignId) + public function link($id, $link, $foreignId) { if ($link === 'teams' || $link === 'users') { throw new Forbidden(); } - return parant::linkEntity($id, $link, $foreignId); + return parant::link($id, $link, $foreignId); } - public function unlinkEntity($id, $link, $foreignId) + public function unlink($id, $link, $foreignId) { if ($link === 'teams' || $link === 'users') { throw new Forbidden(); } - return parant::unlinkEntity($id, $link, $foreignId); + return parant::unlink($id, $link, $foreignId); } public function processNoteAclJob($data) diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php index 8d47aea173..ba159253cc 100644 --- a/application/Espo/Services/Record.php +++ b/application/Espo/Services/Record.php @@ -261,7 +261,12 @@ class Record extends \Espo\Core\Services\Base $this->getEntityManager()->saveEntity($historyRecord); } - public function readEntity($id) + public function readEntity($id) //TODO Remove in 5.8 + { + return $this->read($id); + } + + public function read($id) { if (empty($id)) { throw new Error(); @@ -787,7 +792,7 @@ class Record extends \Espo\Core\Services\Base } } - public function createEntity($data) + public function createEntity($data) //TODO Remove in 5.8 { return $this->create($data); } @@ -845,7 +850,7 @@ class Record extends \Espo\Core\Services\Base throw new Error(); } - public function updateEntity($id, $data) + public function updateEntity($id, $data) //TODO Remove in 5.8 { return $this->update($id, $data); } @@ -944,7 +949,7 @@ class Record extends \Espo\Core\Services\Base { } - public function deleteEntity($id) + public function deleteEntity($id) //TODO Remove in 5.8 { return $this->delete($id); } @@ -1285,7 +1290,7 @@ class Record extends \Espo\Core\Services\Base ]; } - public function linkEntity($id, $link, $foreignId) + public function linkEntity($id, $link, $foreignId) //TODO Remove in 5.8 { return $this->link($id, $link, $foreignId); } @@ -1342,7 +1347,7 @@ class Record extends \Espo\Core\Services\Base return true; } - public function unlinkEntity($id, $link, $foreignId) + public function unlinkEntity($id, $link, $foreignId) //TODO Remove in 5.8 { return $this->unlink($id, $link, $foreignId); } @@ -1403,7 +1408,7 @@ class Record extends \Espo\Core\Services\Base return true; } - public function linkEntityMass($id, $link, $where, $selectData = null) + public function linkEntityMass($id, $link, $where, $selectData = null) //TODO Remove in 5.8 { return $this->linkMass($id, $link, $where, $selectData); } @@ -2238,7 +2243,7 @@ class Record extends \Espo\Core\Services\Base { } - protected function findLinkedEntitiesFollowers($id, $params) + protected function findLinkedFollowers($id, $params) { $maxSize = 0; diff --git a/application/Espo/Services/RecordTree.php b/application/Espo/Services/RecordTree.php index 5c7479a4c5..720142e3f9 100644 --- a/application/Espo/Services/RecordTree.php +++ b/application/Espo/Services/RecordTree.php @@ -174,21 +174,21 @@ class RecordTree extends Record } } - public function updateEntity($id, $data) + public function update($id, $data) { if (!empty($data->parentId) && $data->parentId == $id) { throw new Forbidden(); } - return parent::updateEntity($id, $data); + return parent::update($id, $data); } - public function linkEntity($id, $link, $foreignId) + public function link($id, $link, $foreignId) { if ($id == $foreignId ) { throw new Forbidden(); } - return parent::linkEntity($id, $link, $foreignId); + return parent::link($id, $link, $foreignId); } public function getLastChildrenIdList($parentId = null) diff --git a/application/Espo/Services/User.php b/application/Espo/Services/User.php index 6e29e3fcbc..1ece8f7c83 100644 --- a/application/Espo/Services/User.php +++ b/application/Espo/Services/User.php @@ -242,7 +242,7 @@ class User extends Record } } - public function createEntity($data) + public function create($data) { $newPassword = null; if (property_exists($data, 'password')) { @@ -250,7 +250,7 @@ class User extends Record $data->password = $this->hashPassword($data->password); } - $user = parent::createEntity($data); + $user = parent::create($data); if (!is_null($newPassword) && !empty($data->sendAccessInfo)) { if ($user->isActive()) { @@ -263,7 +263,7 @@ class User extends Record return $user; } - public function updateEntity($id, $data) + public function update($id, $data) { if ($id == 'system') { throw new Forbidden(); @@ -280,7 +280,7 @@ class User extends Record unset($data->type); } - $user = parent::updateEntity($id, $data); + $user = parent::update($id, $data); if (!is_null($newPassword)) { try { @@ -576,7 +576,7 @@ class User extends Record $this->getMailSender()->send($email); } - public function deleteEntity($id) + public function delete($id) { if ($id == 'system') { throw new Forbidden(); @@ -584,7 +584,7 @@ class User extends Record if ($id == $this->getUser()->id) { throw new Forbidden(); } - return parent::deleteEntity($id); + return parent::delete($id); } protected function checkEntityForMassRemove(Entity $entity) From 3fb57a1dbe9fe633b17886706ec1579c8b6d7fad Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 13:48:13 +0200 Subject: [PATCH 07/19] webp images support --- application/Espo/EntryPoints/Image.php | 8 ++++++++ application/Espo/Services/Attachment.php | 12 ++++++++++-- client/src/views/fields/attachment-multiple.js | 15 +++++---------- client/src/views/fields/file.js | 15 +++++---------- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/application/Espo/EntryPoints/Image.php b/application/Espo/EntryPoints/Image.php index e8551e7762..2cd986d976 100644 --- a/application/Espo/EntryPoints/Image.php +++ b/application/Espo/EntryPoints/Image.php @@ -42,6 +42,7 @@ class Image extends \Espo\Core\EntryPoints\Base 'image/jpeg', 'image/png', 'image/gif', + 'image/webp', ]; protected $imageSizes = [ @@ -134,6 +135,9 @@ class Image extends \Espo\Core\EntryPoints\Base case 'image/gif': imagegif($targetImage); break; + case 'image/webp': + imagewebp($targetImage); + break; } $contents = ob_get_contents(); ob_end_clean(); @@ -214,6 +218,10 @@ class Image extends \Espo\Core\EntryPoints\Base $sourceImage = imagecreatefromgif($filePath); imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight); break; + case 'image/webp': + $sourceImage = imagecreatefromwebp($filePath); + imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight); + break; } if (in_array($fileType, $this->fixOrientationFileTypeList)) { diff --git a/application/Espo/Services/Attachment.php b/application/Espo/Services/Attachment.php index d0457608f3..a6bf5f5855 100644 --- a/application/Espo/Services/Attachment.php +++ b/application/Espo/Services/Attachment.php @@ -44,6 +44,13 @@ class Attachment extends Record protected $inlineAttachmentFieldTypeList = ['wysiwyg']; + protected $imageTypeList = [ + 'image/png', + 'image/jpeg', + 'image/gif', + 'image/webp', + ]; + public function upload($fileData) { if (!$this->getAcl()->checkScope('Attachment', 'create')) { @@ -322,7 +329,8 @@ class Attachment extends Record 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', - 'gif' => 'image/gif' + 'gif' => 'image/gif', + 'webp' => 'image/webp', ]; $extension = preg_replace('#\?.*#', '', pathinfo($url, \PATHINFO_EXTENSION)); @@ -334,7 +342,7 @@ class Attachment extends Record if (!$type) return; - if (!in_array($type, ['image/png', 'image/jpeg', 'image/gif'])) { + if (!in_array($type, $this->imageTypeList)) { return; } diff --git a/client/src/views/fields/attachment-multiple.js b/client/src/views/fields/attachment-multiple.js index b89fc1a655..220ec3a7ee 100644 --- a/client/src/views/fields/attachment-multiple.js +++ b/client/src/views/fields/attachment-multiple.js @@ -56,6 +56,7 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D 'image/jpeg', 'image/png', 'image/gif', + 'image/webp', ], validations: ['ready', 'required'], @@ -284,11 +285,8 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D var preview = name; - switch (type) { - case 'image/png': - case 'image/jpeg': - case 'image/gif': - preview = ''; + if (~this.previewTypeList.indexOf(type)) { + preview = ''; } return preview; @@ -490,11 +488,8 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D }, isTypeIsImage: function (type) { - switch (type) { - case 'image/png': - case 'image/jpeg': - case 'image/gif': - return true; + if (~this.previewTypeList.indexOf(type)) { + return true; } return false }, diff --git a/client/src/views/fields/file.js b/client/src/views/fields/file.js index 1e0085aeb4..a73bacd963 100644 --- a/client/src/views/fields/file.js +++ b/client/src/views/fields/file.js @@ -46,6 +46,7 @@ Espo.define('views/fields/file', 'views/fields/link', function (Dep) { 'image/jpeg', 'image/png', 'image/gif', + 'image/webp', ], defaultType: false, @@ -247,11 +248,8 @@ Espo.define('views/fields/file', 'views/fields/link', function (Dep) { name = Handlebars.Utils.escapeExpression(name); var preview = name; - switch (type) { - case 'image/png': - case 'image/jpeg': - case 'image/gif': - preview = ''; + if (~this.previewTypeList.indexOf(type)) { + preview = ''; } return preview; }, @@ -260,11 +258,8 @@ Espo.define('views/fields/file', 'views/fields/link', function (Dep) { name = Handlebars.Utils.escapeExpression(name); var preview = name; - switch (type) { - case 'image/png': - case 'image/jpeg': - case 'image/gif': - preview = ''; + if (~this.previewTypeList.indexOf(type)) { + preview = ''; } return preview; From 73ac717c4d5dcf5c18b39213165ff15376dab33c Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 15:20:14 +0200 Subject: [PATCH 08/19] remove thumbs with attachment --- application/Espo/Repositories/Attachment.php | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/application/Espo/Repositories/Attachment.php b/application/Espo/Repositories/Attachment.php index 07d2f2f5e2..92fdc25e9a 100644 --- a/application/Espo/Repositories/Attachment.php +++ b/application/Espo/Repositories/Attachment.php @@ -35,6 +35,24 @@ use Espo\Core\Utils\Util; class Attachment extends \Espo\Core\ORM\Repositories\RDB { + protected $imageTypeList = [ + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + ]; + + protected $imageThumbList = [ + 'xxx-small', + 'xx-small', + 'x-small', + 'small', + 'medium', + 'large', + 'x-large', + 'xx-large', + ]; + protected function init() { parent::init(); @@ -42,6 +60,11 @@ class Attachment extends \Espo\Core\ORM\Repositories\RDB $this->addDependency('config'); } + protected function getFileManager() + { + return $this->getInjection('container')->get('fileManager'); + } + protected function getFileStorageManager() { return $this->getInjection('container')->get('fileStorageManager'); @@ -106,6 +129,20 @@ class Attachment extends \Espo\Core\ORM\Repositories\RDB if ($duplicateCount === 0) { $this->getFileStorageManager()->unlink($entity); + + if (in_array($entity->get('type'), $this->imageTypeList)) { + $this->removeImageThumbs($entity); + } + } + } + + public function removeImageThumbs($entity) + { + foreach ($this->imageThumbList as $suffix) { + $filePath = "data/upload/thumbs/".$entity->getSourceId()."_{$suffix}"; + if ($this->getFileManager()->isFile($filePath)) { + $this->getFileManager()->removeFile($filePath); + } } } From 8fbfce086cfc09c193a650014dbe7b2b0b80af31 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 16:29:05 +0200 Subject: [PATCH 09/19] refactoring image --- application/Espo/EntryPoints/Image.php | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/application/Espo/EntryPoints/Image.php b/application/Espo/EntryPoints/Image.php index 2cd986d976..0f27b8789c 100644 --- a/application/Espo/EntryPoints/Image.php +++ b/application/Espo/EntryPoints/Image.php @@ -76,7 +76,7 @@ class Image extends \Espo\Core\EntryPoints\Base $size = $_GET['size']; } - $this->show($id, $size); + $this->show($id, $size, false); } protected function show($id, $size, $disableAccessCheck = false) @@ -204,7 +204,7 @@ class Image extends \Espo\Core\EntryPoints\Base switch ($fileType) { case 'image/jpeg': $sourceImage = imagecreatefromjpeg($filePath); - imagecopyresampled ($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight); + imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight); break; case 'image/png': $sourceImage = imagecreatefrompng($filePath); @@ -231,14 +231,21 @@ class Image extends \Espo\Core\EntryPoints\Base return $targetImage; } - protected function fixOrientation($targetImage, $filePath) + protected function getOrientation($filePath) { + $orientation = 0; if (function_exists('exif_read_data')) { $orientation = @exif_read_data($filePath)['Orientation']; - if ($orientation) { - $angle = array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[$orientation]; - $targetImage = imagerotate($targetImage, $angle, 0); - } + } + return $orientation; + } + + protected function fixOrientation($targetImage, $filePath) + { + $orientation = $this->getOrientation($filePath); + if ($orientation) { + $angle = array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[$orientation]; + $targetImage = imagerotate($targetImage, $angle, 0); } return $targetImage; From 94a6c8d525e9e5dac9fc67abead6078bae0604b2 Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 16:29:15 +0200 Subject: [PATCH 10/19] image preview orientation --- client/lib/exif-js.js | 8 ++++ client/src/views/modals/image-preview.js | 57 ++++++++++++++++++++++-- frontend/less/espo/custom.less | 29 ++++++++++++ 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 client/lib/exif-js.js diff --git a/client/lib/exif-js.js b/client/lib/exif-js.js new file mode 100644 index 0000000000..d7cb31593c --- /dev/null +++ b/client/lib/exif-js.js @@ -0,0 +1,8 @@ +/** + * Minified by jsDelivr using UglifyJS v3.3.25. + * Original file: /npm/exif-js@2.3.0/exif.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +(function(){var d=!1,l=function(e){return e instanceof l?e:this instanceof l?void(this.EXIFwrapped=e):new l(e)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=l),exports.EXIF=l):this.EXIF=l;var u=l.Tags={36864:"ExifVersion",40960:"FlashpixVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",37121:"ComponentsConfiguration",37122:"CompressedBitsPerPixel",37500:"MakerNote",37510:"UserComment",40964:"RelatedSoundFile",36867:"DateTimeOriginal",36868:"DateTimeDigitized",37520:"SubsecTime",37521:"SubsecTimeOriginal",37522:"SubsecTimeDigitized",33434:"ExposureTime",33437:"FNumber",34850:"ExposureProgram",34852:"SpectralSensitivity",34855:"ISOSpeedRatings",34856:"OECF",37377:"ShutterSpeedValue",37378:"ApertureValue",37379:"BrightnessValue",37380:"ExposureBias",37381:"MaxApertureValue",37382:"SubjectDistance",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37396:"SubjectArea",37386:"FocalLength",41483:"FlashEnergy",41484:"SpatialFrequencyResponse",41486:"FocalPlaneXResolution",41487:"FocalPlaneYResolution",41488:"FocalPlaneResolutionUnit",41492:"SubjectLocation",41493:"ExposureIndex",41495:"SensingMethod",41728:"FileSource",41729:"SceneType",41730:"CFAPattern",41985:"CustomRendered",41986:"ExposureMode",41987:"WhiteBalance",41988:"DigitalZoomRation",41989:"FocalLengthIn35mmFilm",41990:"SceneCaptureType",41991:"GainControl",41992:"Contrast",41993:"Saturation",41994:"Sharpness",41995:"DeviceSettingDescription",41996:"SubjectDistanceRange",40965:"InteroperabilityIFDPointer",42016:"ImageUniqueID"},c=l.TiffTags={256:"ImageWidth",257:"ImageHeight",34665:"ExifIFDPointer",34853:"GPSInfoIFDPointer",40965:"InteroperabilityIFDPointer",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",274:"Orientation",277:"SamplesPerPixel",284:"PlanarConfiguration",530:"YCbCrSubSampling",531:"YCbCrPositioning",282:"XResolution",283:"YResolution",296:"ResolutionUnit",273:"StripOffsets",278:"RowsPerStrip",279:"StripByteCounts",513:"JPEGInterchangeFormat",514:"JPEGInterchangeFormatLength",301:"TransferFunction",318:"WhitePoint",319:"PrimaryChromaticities",529:"YCbCrCoefficients",532:"ReferenceBlackWhite",306:"DateTime",270:"ImageDescription",271:"Make",272:"Model",305:"Software",315:"Artist",33432:"Copyright"},f=l.GPSTags={0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude",5:"GPSAltitudeRef",6:"GPSAltitude",7:"GPSTimeStamp",8:"GPSSatellites",9:"GPSStatus",10:"GPSMeasureMode",11:"GPSDOP",12:"GPSSpeedRef",13:"GPSSpeed",14:"GPSTrackRef",15:"GPSTrack",16:"GPSImgDirectionRef",17:"GPSImgDirection",18:"GPSMapDatum",19:"GPSDestLatitudeRef",20:"GPSDestLatitude",21:"GPSDestLongitudeRef",22:"GPSDestLongitude",23:"GPSDestBearingRef",24:"GPSDestBearing",25:"GPSDestDistanceRef",26:"GPSDestDistance",27:"GPSProcessingMethod",28:"GPSAreaInformation",29:"GPSDateStamp",30:"GPSDifferential"},g=l.IFD1Tags={256:"ImageWidth",257:"ImageHeight",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",273:"StripOffsets",274:"Orientation",277:"SamplesPerPixel",278:"RowsPerStrip",279:"StripByteCounts",282:"XResolution",283:"YResolution",284:"PlanarConfiguration",296:"ResolutionUnit",513:"JpegIFOffset",514:"JpegIFByteCount",529:"YCbCrCoefficients",530:"YCbCrSubSampling",531:"YCbCrPositioning",532:"ReferenceBlackWhite"},m=l.StringValues={ExposureProgram:{0:"Not defined",1:"Manual",2:"Normal program",3:"Aperture priority",4:"Shutter priority",5:"Creative program",6:"Action program",7:"Portrait mode",8:"Landscape mode"},MeteringMode:{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"},LightSource:{0:"Unknown",1:"Daylight",2:"Fluorescent",3:"Tungsten (incandescent light)",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 - 5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"},Flash:{0:"Flash did not fire",1:"Flash fired",5:"Strobe return light not detected",7:"Strobe return light detected",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"},SensingMethod:{1:"Not defined",2:"One-chip color area sensor",3:"Two-chip color area sensor",4:"Three-chip color area sensor",5:"Color sequential area sensor",7:"Trilinear sensor",8:"Color sequential linear sensor"},SceneCaptureType:{0:"Standard",1:"Landscape",2:"Portrait",3:"Night scene"},SceneType:{1:"Directly photographed"},CustomRendered:{0:"Normal process",1:"Custom process"},WhiteBalance:{0:"Auto white balance",1:"Manual white balance"},GainControl:{0:"None",1:"Low gain up",2:"High gain up",3:"Low gain down",4:"High gain down"},Contrast:{0:"Normal",1:"Soft",2:"Hard"},Saturation:{0:"Normal",1:"Low saturation",2:"High saturation"},Sharpness:{0:"Normal",1:"Soft",2:"Hard"},SubjectDistanceRange:{0:"Unknown",1:"Macro",2:"Close view",3:"Distant view"},FileSource:{3:"DSC"},Components:{0:"",1:"Y",2:"Cb",3:"Cr",4:"R",5:"G",6:"B"}};function i(e){return!!e.exifdata}function r(i,o){function t(e){var t=p(e);i.exifdata=t||{};var n=function(e){var t=new DataView(e);d&&console.log("Got file of length "+e.byteLength);if(255!=t.getUint8(0)||216!=t.getUint8(1))return d&&console.log("Not a valid JPEG"),!1;var n=2,r=e.byteLength;for(;n")+8,u=(s=s.substring(s.indexOf("e.byteLength)return{};var u=P(e,t,t+l,g,r);if(u.Compression)switch(u.Compression){case 6:if(u.JpegIFOffset&&u.JpegIFByteCount){var c=t+u.JpegIFOffset,d=u.JpegIFByteCount;u.blob=new Blob([new Uint8Array(e.buffer,c,d)],{type:"image/jpeg"})}break;case 1:console.log("Thumbnail image format is TIFF, which is not implemented.");break;default:console.log("Unknown thumbnail image format '%s'",u.Compression)}else 2==u.PhotometricInterpretation&&console.log("Thumbnail image format is RGB, which is not implemented.");return u}(e,s,l,n),r}function b(e){var t={};if(1==e.nodeType){if(0 span.fa-info-circle { .grid-auto-fill-md { grid-template-columns: repeat(auto-fill, minmax(@grid-column-width-medium, 1fr)); } +} + +.transform-flip { + -webkit-transform: scaleX(-1); + transform: scaleX(-1); +} + +.transform-rotate-90 { + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -o-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} + +.transform-rotate-180 { + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -o-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} + +.transform-rotate-270 { + -webkit-transform: rotate(270deg); + -moz-transform: rotate(270deg); + -o-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); } \ No newline at end of file From 28da462c4e245038786aca92a6ae100897df7e9a Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 16:32:19 +0200 Subject: [PATCH 11/19] exif js in about --- client/res/templates/about.tpl | 1 + 1 file changed, 1 insertion(+) diff --git a/client/res/templates/about.tpl b/client/res/templates/about.tpl index 46279cb14c..8d05151a04 100644 --- a/client/res/templates/about.tpl +++ b/client/res/templates/about.tpl @@ -69,6 +69,7 @@
  • vis.js by Almende B.V.
  • Ace
  • Marked by Christopher Jeffrey
  • +
  • Exif.js by Jacob Seidelin
  • From 3d2d54aafa39d05622a3911150266f24fa1a31bf Mon Sep 17 00:00:00 2001 From: yuri Date: Wed, 26 Dec 2018 16:37:14 +0200 Subject: [PATCH 12/19] add exif to jsLibs --- application/Espo/Resources/metadata/app/jsLibs.json | 5 +++++ client/src/views/modals/image-preview.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/application/Espo/Resources/metadata/app/jsLibs.json b/application/Espo/Resources/metadata/app/jsLibs.json index d5fe45a417..76de11d633 100644 --- a/application/Espo/Resources/metadata/app/jsLibs.json +++ b/application/Espo/Resources/metadata/app/jsLibs.json @@ -38,5 +38,10 @@ "path": "client/lib/bootstrap-colorpicker.js", "exportsTo": "$", "exportsAs": "colorpicker" + }, + "exif": { + "path": "client/lib/exif-js.js", + "exportsTo": "window", + "exportsAs": "EXIF" } } diff --git a/client/src/views/modals/image-preview.js b/client/src/views/modals/image-preview.js index 7b22b0624e..4a02d64cec 100644 --- a/client/src/views/modals/image-preview.js +++ b/client/src/views/modals/image-preview.js @@ -26,7 +26,7 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -Espo.define('views/modals/image-preview', ['views/modal', 'lib!client/lib/exif-js.js'], function (Dep) { +Espo.define('views/modals/image-preview', ['views/modal', 'lib!exif'], function (Dep) { return Dep.extend({ From a361b124d61edc4049c23aa1c02c7c006cf99b03 Mon Sep 17 00:00:00 2001 From: yuri Date: Thu, 27 Dec 2018 12:00:14 +0200 Subject: [PATCH 13/19] more functions --- .../Espo/Resources/metadata/app/formula.json | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/application/Espo/Resources/metadata/app/formula.json b/application/Espo/Resources/metadata/app/formula.json index e9dd6df846..f260ceb72c 100644 --- a/application/Espo/Resources/metadata/app/formula.json +++ b/application/Espo/Resources/metadata/app/formula.json @@ -6,7 +6,7 @@ }, { "name": "ifThen", - "insertText": "ifThenElse(CONDITION, CONSEQUENT)" + "insertText": "ifThen(CONDITION, CONSEQUENT)" }, { "name": "string\\concatenate", @@ -16,10 +16,30 @@ "name": "string\\substring", "insertText": "string\\substring(STRING, START, LENGTH)" }, + { + "name": "string\\contains", + "insertText": "string\\contains(STRING, NEEDLE)" + }, + { + "name": "string\\test", + "insertText": "string\\test(STRING, REGULAR_EXPRESSION)" + }, + { + "name": "string\\length", + "insertText": "string\\length(STRING)" + }, { "name": "string\\trim", "insertText": "string\\trim(STRING)" }, + { + "name": "string\\lowerCase", + "insertText": "string\\lowerCase(STRING)" + }, + { + "name": "string\\upperCase", + "insertText": "string\\upperCase(STRING)" + }, { "name": "datetime\\today", "insertText": "datetime\\today()" @@ -32,6 +52,30 @@ "name": "datetime\\format", "insertText": "datetime\\format(VALUE)" }, + { + "name": "datetime\\date", + "insertText": "datetime\\date(VALUE)" + }, + { + "name": "datetime\\month", + "insertText": "datetime\\month(VALUE)" + }, + { + "name": "datetime\\year", + "insertText": "datetime\\year(VALUE)" + }, + { + "name": "datetime\\hour", + "insertText": "datetime\\hour(VALUE)" + }, + { + "name": "datetime\\minute", + "insertText": "datetime\\minute(VALUE)" + }, + { + "name": "datetime\\dayOfWeek", + "insertText": "datetime\\dayOfWeek(VALUE)" + }, { "name": "datetime\\addMinutes", "insertText": "datetime\\addMinutes(VALUE, MINUTES)" @@ -72,6 +116,14 @@ "name": "number\\round", "insertText": "number\\round(VALUE, PRECISION)" }, + { + "name": "number\\floor", + "insertText": "number\\floor(VALUE)" + }, + { + "name": "number\\ceil", + "insertText": "number\\ceil(VALUE)" + }, { "name": "entity\\isNew", "insertText": "entity\\isNew()" @@ -108,6 +160,14 @@ "name": "entity\\isRelated", "insertText": "entity\\isRelated(LINK, ID)" }, + { + "name": "entity\\sumRelated", + "insertText": "entity\\sumRelated(LINK, FIELD, FILTER)" + }, + { + "name": "entity\\countRelated", + "insertText": "entity\\countRelated(LINK, FILTER)" + }, { "name": "env\\userAttribute", "insertText": "env\\userAttribute(ATTRIBUTE)" From 8da0ac369c2cfa9980525cbc2be951a193283474 Mon Sep 17 00:00:00 2001 From: yuri Date: Thu, 27 Dec 2018 15:29:35 +0200 Subject: [PATCH 14/19] address textaread height adjust --- client/src/views/fields/address.js | 31 ++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/client/src/views/fields/address.js b/client/src/views/fields/address.js index 3ed0d2d5c4..316f72ec5c 100644 --- a/client/src/views/fields/address.js +++ b/client/src/views/fields/address.js @@ -334,15 +334,9 @@ Espo.define('views/fields/address', 'views/fields/base', function (Dep) { this.$country.attr('autocomplete', 'espo-country'); } + this.controlStreetTextareaHeight(); this.$street.on('input', function (e) { - var numberOfLines = e.currentTarget.value.split('\n').length; - var numberOfRows = this.$street.prop('rows'); - - if (numberOfRows < numberOfLines) { - this.$street.prop('rows', numberOfLines); - } else if (numberOfRows > numberOfLines) { - this.$street.prop('rows', numberOfLines); - } + this.controlStreetTextareaHeight(); }.bind(this)); var numberOfLines = this.$street.val().split('\n').length; @@ -350,6 +344,27 @@ Espo.define('views/fields/address', 'views/fields/base', function (Dep) { } }, + controlStreetTextareaHeight: function (lastHeight) { + var scrollHeight = this.$street.prop('scrollHeight'); + var clientHeight = this.$street.prop('clientHeight'); + + if (typeof lastHeight === 'undefined' && clientHeight === 0) { + setTimeout(this.controlStreetTextareaHeight.bind(this), 10); + return; + } + + if (clientHeight === lastHeight) return; + + if (scrollHeight > clientHeight + 1) { + var rows = this.$street.prop('rows'); + this.$street.attr('rows', rows + 1); + this.controlStreetTextareaHeight(clientHeight); + } + if (this.$street.val().length === 0) { + this.$street.attr('rows', 1); + } + }, + setup: function () { Dep.prototype.setup.call(this); From f4c59e70b55ec16f783737d0c3b5cf97fdb5b4b4 Mon Sep 17 00:00:00 2001 From: yuri Date: Thu, 27 Dec 2018 17:38:04 +0200 Subject: [PATCH 15/19] email has attachment title --- client/res/templates/email/fields/has-attachment/detail.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/res/templates/email/fields/has-attachment/detail.tpl b/client/res/templates/email/fields/has-attachment/detail.tpl index c036df771b..bd76c8ec78 100644 --- a/client/res/templates/email/fields/has-attachment/detail.tpl +++ b/client/res/templates/email/fields/has-attachment/detail.tpl @@ -1 +1 @@ -{{#if value}}{{/if}} \ No newline at end of file +{{#if value}}{{/if}} \ No newline at end of file From 8dd675a275588c3746f9a9a2468678f10b4aee9a Mon Sep 17 00:00:00 2001 From: yuri Date: Fri, 28 Dec 2018 12:34:45 +0200 Subject: [PATCH 16/19] fix and refactor email address and phone number repositoties --- .../Espo/Repositories/EmailAddress.php | 603 +++++++++--------- application/Espo/Repositories/PhoneNumber.php | 478 +++++++------- 2 files changed, 568 insertions(+), 513 deletions(-) diff --git a/application/Espo/Repositories/EmailAddress.php b/application/Espo/Repositories/EmailAddress.php index 48ffbf2e93..c9b2a71f96 100644 --- a/application/Espo/Repositories/EmailAddress.php +++ b/application/Espo/Repositories/EmailAddress.php @@ -59,7 +59,7 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB public function getIds(array $addressList = []) { - $ids = array(); + $ids = []; if (!empty($addressList)) { $lowerAddressList = []; foreach ($addressList as $address) { @@ -72,8 +72,8 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB ] ])->find(); - $ids = array(); - $exist = array(); + $ids = []; + $exist = []; foreach ($eaCollection as $ea) { $ids[] = $ea->id; $exist[] = $ea->get('lower'); @@ -96,7 +96,7 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB public function getEmailAddressData(Entity $entity) { - $data = array(); + $data = []; $pdo = $this->getEntityManager()->getPDO(); $sql = " @@ -105,7 +105,7 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB JOIN email_address ON email_address.id = entity_email_address.email_address_id AND email_address.deleted = 0 WHERE entity_email_address.entity_id = ".$pdo->quote($entity->id)." AND - entity_email_address.entity_type = ".$pdo->quote($entity->getEntityName())." AND + entity_email_address.entity_type = ".$pdo->quote($entity->getEntityType())." AND entity_email_address.deleted = 0 ORDER BY entity_email_address.primary DESC "; @@ -249,40 +249,302 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB } } - public function storeEntityEmailAddress(Entity $entity) + public function storeEntityEmailAddressData(Entity $entity) { - $emailAddressValue = $entity->get('emailAddress'); - if (is_string($emailAddressValue)) { - $emailAddressValue = trim($emailAddressValue); - } - $emailAddressData = null; + $pdo = $this->getEntityManager()->getPDO(); - if ($entity->has('emailAddressData')) { - $emailAddressData = $entity->get('emailAddressData'); - } + $emailAddressValue = $entity->get('emailAddress'); + if (is_string($emailAddressValue)) { + $emailAddressValue = trim($emailAddressValue); + } - $pdo = $this->getEntityManager()->getPDO(); + $emailAddressData = null; + if ($entity->has('emailAddressData')) { + $emailAddressData = $entity->get('emailAddressData'); + } - if ($emailAddressData !== null && is_array($emailAddressData)) { - $previousEmailAddressData = array(); - if (!$entity->isNew()) { - $previousEmailAddressData = $this->getEmailAddressData($entity); + if (is_null($emailAddressData)) return; + if (!is_array($emailAddressData)) return; + + $keyList = []; + $keyPreviousList = []; + + $previousEmailAddressData = []; + if (!$entity->isNew()) { + $previousEmailAddressData = $this->getEmailAddressData($entity); + } + + $hash = (object) []; + $hashPrevious = (object) []; + + foreach ($emailAddressData as $row) { + $key = trim($row->emailAddress); + if (empty($key)) continue; + $key = strtolower($key); + $hash->$key = [ + 'primary' => !empty($row->primary) ? true : false, + 'optOut' => !empty($row->optOut) ? true : false, + 'invalid' => !empty($row->invalid) ? true : false, + 'emailAddress' => trim($row->emailAddress) + ]; + $keyList[] = $key; + } + + if ( + $entity->has('emailAddressIsOptedOut') + && + ( + $entity->isNew() + || + ( + $entity->hasFetched('emailAddressIsOptedOut') + && + $entity->get('emailAddressIsOptedOut') !== $entity->getFetched('emailAddressIsOptedOut') + ) + ) + ) { + if ($emailAddressValue) { + $key = strtolower($emailAddressValue); + if ($key && isset($hash->$key)) { + $hash->$key['optOut'] = $entity->get('emailAddressIsOptedOut'); } + } + } - $hash = array(); - foreach ($emailAddressData as $row) { - $key = trim($row->emailAddress); - if (!empty($key)) { - $key = strtolower($key); - $hash[$key] = [ - 'primary' => !empty($row->primary) ? true : false, - 'optOut' => !empty($row->optOut) ? true : false, - 'invalid' => !empty($row->invalid) ? true : false, - 'emailAddress' => trim($row->emailAddress) - ]; + foreach ($previousEmailAddressData as $row) { + $key = $row->lower; + if (empty($key)) continue; + $hashPrevious->$key = [ + 'primary' => $row->primary ? true : false, + 'optOut' => $row->optOut ? true : false, + 'invalid' => $row->invalid ? true : false, + 'emailAddress' => $row->emailAddress + ]; + $keyPreviousList[] = $key; + } + + $primary = false; + + $toCreateList = []; + $toUpdateList = []; + $toRemoveList = []; + + $revertData = []; + + foreach ($keyList as $key) { + $data = $hash->$key; + + $new = true; + $changed = false; + + if ($hash->$key['primary']) { + $primary = $key; + } + + if (property_exists($hashPrevious, $key)) { + $new = false; + $changed = + $hash->$key['optOut'] != $hashPrevious->$key['optOut'] || + $hash->$key['invalid'] != $hashPrevious->$key['invalid'] || + $hash->$key['emailAddress'] !== $hashPrevious->$key['emailAddress']; + + if ($hash->$key['primary']) { + if ($hash->$key['primary'] == $hashPrevious->$key['primary']) { + $primary = false; } } + } + if ($new) { + $toCreateList[] = $key; + } + if ($changed) { + $toUpdateList[] = $key; + } + } + + foreach ($keyPreviousList as $key) { + if (!property_exists($hash, $key)) { + $toRemoveList[] = $key; + } + } + + foreach ($toRemoveList as $address) { + $emailAddress = $this->getByAddress($address); + if ($emailAddress) { + $query = " + DELETE FROM entity_email_address + WHERE + entity_id = ".$pdo->quote($entity->id)." AND + entity_type = ".$pdo->quote($entity->getEntityType())." AND + email_address_id = ".$pdo->quote($emailAddress->id)." + "; + $sth = $pdo->prepare($query); + $sth->execute(); + } + } + + foreach ($toUpdateList as $address) { + $emailAddress = $this->getByAddress($address); + if ($emailAddress) { + $skipSave = $this->checkChangeIsForbidden($emailAddress, $entity); + if (!$skipSave) { + $emailAddress->set([ + 'optOut' => $hash->$address['optOut'], + 'invalid' => $hash->$address['invalid'], + 'name' => $hash->$address['emailAddress'] + ]); + $this->save($emailAddress); + } else { + $revertData[$address] = [ + 'optOut' => $emailAddress->get('optOut'), + 'invalid' => $emailAddress->get('invalid') + ]; + } + } + } + + foreach ($toCreateList as $address) { + $emailAddress = $this->getByAddress($address); + if (!$emailAddress) { + $emailAddress = $this->get(); + + $emailAddress->set([ + 'name' => $hash->$address['emailAddress'], + 'optOut' => $hash->$address['optOut'], + 'invalid' => $hash->$address['invalid'], + ]); + $this->save($emailAddress); + } else { + $skipSave = $this->checkChangeIsForbidden($emailAddress, $entity); + if (!$skipSave) { + if ( + $emailAddress->get('optOut') != $hash->$address['optOut'] || + $emailAddress->get('invalid') != $hash->$address['invalid'] || + $emailAddress->get('emailAddress') != $hash->$address['emailAddress'] + ) { + $emailAddress->set([ + 'optOut' => $hash->$address['optOut'], + 'invalid' => $hash->$address['invalid'], + 'name' => $hash->$address['emailAddress'] + ]); + $this->save($emailAddress); + } + } else { + $revertData[$address] = [ + 'optOut' => $emailAddress->get('optOut'), + 'invalid' => $emailAddress->get('invalid') + ]; + } + } + + $query = " + INSERT entity_email_address + (entity_id, entity_type, email_address_id, `primary`) + VALUES + ( + ".$pdo->quote($entity->id).", + ".$pdo->quote($entity->getEntityType()).", + ".$pdo->quote($emailAddress->id).", + ".$pdo->quote((int)($address === $primary))." + ) + ON DUPLICATE KEY UPDATE deleted = 0, `primary` = ".$pdo->quote((int)($address === $primary))." + "; + $sth = $pdo->prepare($query); + $sth->execute(); + } + + if ($primary) { + $emailAddress = $this->getByAddress($primary); + if ($emailAddress) { + $query = " + UPDATE entity_email_address + SET `primary` = 0 + WHERE + entity_id = ".$pdo->quote($entity->id)." AND + entity_type = ".$pdo->quote($entity->getEntityType())." AND + `primary` = 1 AND + deleted = 0 + "; + $sth = $pdo->prepare($query); + $sth->execute(); + + $query = " + UPDATE entity_email_address + SET `primary` = 1 + WHERE + entity_id = ".$pdo->quote($entity->id)." AND + entity_type = ".$pdo->quote($entity->getEntityType())." AND + email_address_id = ".$pdo->quote($emailAddress->id)." AND + deleted = 0 + "; + $sth = $pdo->prepare($query); + $sth->execute(); + } + } + + if (!empty($revertData)) { + foreach ($emailAddressData as $row) { + if (!empty($revertData[$row->emailAddress])) { + $row->optOut = $revertData[$row->emailAddress]['optOut']; + $row->invalid = $revertData[$row->emailAddress]['invalid']; + } + } + $entity->set('emailAddressData', $emailAddressData); + } + } + + protected function storeEntityEmailAddressPrimary(Entity $entity) + { + if (!$entity->has('emailAddress')) return; + + $pdo = $this->getEntityManager()->getPDO(); + + $emailAddressValue = $entity->get('emailAddress'); + if (is_string($emailAddressValue)) { + $emailAddressValue = trim($emailAddressValue); + } + + $entityRepository = $this->getEntityManager()->getRepository($entity->getEntityType()); + if (!empty($emailAddressValue)) { + if ($emailAddressValue != $entity->getFetched('emailAddress')) { + + $emailAddressNew = $this->where(['lower' => strtolower($emailAddressValue)])->findOne(); + $isNewEmailAddress = false; + if (!$emailAddressNew) { + $emailAddressNew = $this->get(); + $emailAddressNew->set('name', $emailAddressValue); + if ($entity->has('emailAddressIsOptedOut')) { + $emailAddressNew->set('optOut', !!$entity->get('emailAddressIsOptedOut')); + } + $this->save($emailAddressNew); + $isNewEmailAddress = true; + } + + $emailAddressValueOld = $entity->getFetched('emailAddress'); + if (!empty($emailAddressValueOld)) { + $emailAddressOld = $this->getByAddress($emailAddressValueOld); + if ($emailAddressOld) { + $entityRepository->unrelate($entity, 'emailAddresses', $emailAddressOld); + } + } + $entityRepository->relate($entity, 'emailAddresses', $emailAddressNew); + + if ($entity->has('emailAddressIsOptedOut')) { + $this->markAddressOptedOut($emailAddressValue, !!$entity->get('emailAddressIsOptedOut')); + } + + $query = " + UPDATE entity_email_address + SET `primary` = 1 + WHERE + entity_id = ".$pdo->quote($entity->id)." AND + entity_type = ".$pdo->quote($entity->getEntityType())." AND + email_address_id = ".$pdo->quote($emailAddressNew->id)." + "; + $sth = $pdo->prepare($query); + $sth->execute(); + } else { if ( $entity->has('emailAddressIsOptedOut') && @@ -296,264 +558,33 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB ) ) ) { - if ($emailAddressValue) { - $key = strtolower($emailAddressValue); - if ($key && isset($hash[$key])) { - $hash[$key]['optOut'] = $entity->get('emailAddressIsOptedOut'); - } - } - } - - $hashPrev = array(); - foreach ($previousEmailAddressData as $row) { - $key = $row->lower; - if (!empty($key)) { - $hashPrev[$key] = array( - 'primary' => $row->primary ? true : false, - 'optOut' => $row->optOut ? true : false, - 'invalid' => $row->invalid ? true : false, - 'emailAddress' => $row->emailAddress - ); - } - } - - $primary = false; - $toCreate = array(); - $toUpdate = array(); - $toRemove = array(); - - $revertData = []; - - foreach ($hash as $key => $data) { - $new = true; - $changed = false; - - if ($hash[$key]['primary']) { - $primary = $key; - } - - if (array_key_exists($key, $hashPrev)) { - $new = false; - $changed = - $hash[$key]['optOut'] != $hashPrev[$key]['optOut'] || - $hash[$key]['invalid'] != $hashPrev[$key]['invalid'] || - $hash[$key]['emailAddress'] !== $hashPrev[$key]['emailAddress']; - - if ($hash[$key]['primary']) { - if ($hash[$key]['primary'] == $hashPrev[$key]['primary']) { - $primary = false; - } - } - } - - if ($new) { - $toCreate[] = $key; - } - if ($changed) { - $toUpdate[] = $key; - } - } - - foreach ($hashPrev as $key => $data) { - if (!array_key_exists($key, $hash)) { - $toRemove[] = $key; - } - } - - foreach ($toRemove as $address) { - $emailAddress = $this->getByAddress($address); - if ($emailAddress) { - $query = " - DELETE FROM entity_email_address - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityName())." AND - email_address_id = ".$pdo->quote($emailAddress->id)." - "; - $sth = $pdo->prepare($query); - $sth->execute(); - } - } - - foreach ($toUpdate as $address) { - $emailAddress = $this->getByAddress($address); - if ($emailAddress) { - $skipSave = $this->checkChangeIsForbidden($emailAddress, $entity); - if (!$skipSave) { - $emailAddress->set(array( - 'optOut' => $hash[$address]['optOut'], - 'invalid' => $hash[$address]['invalid'], - 'name' => $hash[$address]['emailAddress'] - )); - $this->save($emailAddress); - } else { - $revertData[$address] = [ - 'optOut' => $emailAddress->get('optOut'), - 'invalid' => $emailAddress->get('invalid') - ]; - } - } - } - - foreach ($toCreate as $address) { - $emailAddress = $this->getByAddress($address); - if (!$emailAddress) { - $emailAddress = $this->get(); - - $emailAddress->set(array( - 'name' => $hash[$address]['emailAddress'], - 'optOut' => $hash[$address]['optOut'], - 'invalid' => $hash[$address]['invalid'], - )); - $this->save($emailAddress); - } else { - $skipSave = $this->checkChangeIsForbidden($emailAddress, $entity); - if (!$skipSave) { - if ( - $emailAddress->get('optOut') != $hash[$address]['optOut'] || - $emailAddress->get('invalid') != $hash[$address]['invalid'] || - $emailAddress->get('emailAddress') != $hash[$address]['emailAddress'] - ) { - $emailAddress->set(array( - 'optOut' => $hash[$address]['optOut'], - 'invalid' => $hash[$address]['invalid'], - 'name' => $hash[$address]['emailAddress'] - )); - $this->save($emailAddress); - } - } else { - $revertData[$address] = [ - 'optOut' => $emailAddress->get('optOut'), - 'invalid' => $emailAddress->get('invalid') - ]; - } - } - - $query = " - INSERT entity_email_address - (entity_id, entity_type, email_address_id, `primary`) - VALUES - ( - ".$pdo->quote($entity->id).", - ".$pdo->quote($entity->getEntityName()).", - ".$pdo->quote($emailAddress->id).", - ".$pdo->quote((int)($address === $primary))." - ) - ON DUPLICATE KEY UPDATE deleted = 0, `primary` = ".$pdo->quote((int)($address === $primary))." - "; - $sth = $pdo->prepare($query); - $sth->execute(); - } - - if ($primary) { - $emailAddress = $this->getByAddress($primary); - if ($emailAddress) { - $query = " - UPDATE entity_email_address - SET `primary` = 0 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityName())." AND - `primary` = 1 AND - deleted = 0 - "; - $sth = $pdo->prepare($query); - $sth->execute(); - - $query = " - UPDATE entity_email_address - SET `primary` = 1 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityName())." AND - email_address_id = ".$pdo->quote($emailAddress->id)." AND - deleted = 0 - "; - $sth = $pdo->prepare($query); - $sth->execute(); - } - } - - if (!empty($revertData)) { - foreach ($emailAddressData as $row) { - if (!empty($revertData[$row->emailAddress])) { - $row->optOut = $revertData[$row->emailAddress]['optOut']; - $row->invalid = $revertData[$row->emailAddress]['invalid']; - } - } - $entity->set('emailAddressData', $emailAddressData); - } - - } else { - if (!$entity->has('emailAddress')) { - return; - } - $entityRepository = $this->getEntityManager()->getRepository($entity->getEntityName()); - if (!empty($emailAddressValue)) { - if ($emailAddressValue != $entity->getFetched('emailAddress')) { - - $emailAddressNew = $this->where(array('lower' => strtolower($emailAddressValue)))->findOne(); - $isNewEmailAddress = false; - if (!$emailAddressNew) { - $emailAddressNew = $this->get(); - $emailAddressNew->set('name', $emailAddressValue); - if ($entity->has('emailAddressIsOptedOut')) { - $emailAddressNew->set('optOut', !!$entity->get('emailAddressIsOptedOut')); - } - $this->save($emailAddressNew); - $isNewEmailAddress = true; - } - - $emailAddressValueOld = $entity->getFetched('emailAddress'); - if (!empty($emailAddressValueOld)) { - $emailAddressOld = $this->getByAddress($emailAddressValueOld); - if ($emailAddressOld) { - $entityRepository->unrelate($entity, 'emailAddresses', $emailAddressOld); - } - } - $entityRepository->relate($entity, 'emailAddresses', $emailAddressNew); - - if ($entity->has('emailAddressIsOptedOut')) { - $this->markAddressOptedOut($emailAddressValue, !!$entity->get('emailAddressIsOptedOut')); - } - - $query = " - UPDATE entity_email_address - SET `primary` = 1 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityName())." AND - email_address_id = ".$pdo->quote($emailAddressNew->id)." - "; - $sth = $pdo->prepare($query); - $sth->execute(); - } else { - if ( - $entity->has('emailAddressIsOptedOut') - && - ( - $entity->isNew() - || - ( - $entity->hasFetched('emailAddressIsOptedOut') - && - $entity->get('emailAddressIsOptedOut') !== $entity->getFetched('emailAddressIsOptedOut') - ) - ) - ) { - $this->markAddressOptedOut($emailAddressValue, !!$entity->get('emailAddressIsOptedOut')); - } - } - } else { - $emailAddressValueOld = $entity->getFetched('emailAddress'); - if (!empty($emailAddressValueOld)) { - $emailAddressOld = $this->getByAddress($emailAddressValueOld); - if ($emailAddressOld) { - $entityRepository->unrelate($entity, 'emailAddresses', $emailAddressOld); - } - } + $this->markAddressOptedOut($emailAddressValue, !!$entity->get('emailAddressIsOptedOut')); } } + } else { + $emailAddressValueOld = $entity->getFetched('emailAddress'); + if (!empty($emailAddressValueOld)) { + $emailAddressOld = $this->getByAddress($emailAddressValueOld); + if ($emailAddressOld) { + $entityRepository->unrelate($entity, 'emailAddresses', $emailAddressOld); + } + } + } + + } + + public function storeEntityEmailAddress(Entity $entity) + { + $emailAddressData = null; + if ($entity->has('emailAddressData')) { + $emailAddressData = $entity->get('emailAddressData'); + } + + if ($emailAddressData !== null) { + $this->storeEntityEmailAddressData($entity); + } else if ($entity->has('emailAddress')) { + $this->storeEntityEmailAddressPrimary($entity); + } } protected function checkChangeIsForbidden($entity, $excludeEntity) diff --git a/application/Espo/Repositories/PhoneNumber.php b/application/Espo/Repositories/PhoneNumber.php index 604d986948..05e4eceb7b 100644 --- a/application/Espo/Repositories/PhoneNumber.php +++ b/application/Espo/Repositories/PhoneNumber.php @@ -96,7 +96,7 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB JOIN phone_number ON phone_number.id = entity_phone_number.phone_number_id AND phone_number.deleted = 0 WHERE entity_phone_number.entity_id = ".$pdo->quote($entity->id)." AND - entity_phone_number.entity_type = ".$pdo->quote($entity->getEntityName())." AND + entity_phone_number.entity_type = ".$pdo->quote($entity->getEntityType())." AND entity_phone_number.deleted = 0 ORDER BY entity_phone_number.primary DESC "; @@ -193,247 +193,271 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB } } - public function storeEntityPhoneNumber(Entity $entity) + protected function storeEntityPhoneNumberData(Entity $entity) { - $phoneNumberValue = trim($entity->get('phoneNumber')); - $phoneNumberData = null; + $pdo = $this->getEntityManager()->getPDO(); - if ($entity->has('phoneNumberData')) { - $phoneNumberData = $entity->get('phoneNumberData'); + $phoneNumberData = null; + if ($entity->has('phoneNumberData')) { + $phoneNumberData = $entity->get('phoneNumberData'); + } + + if (is_null($phoneNumberData)) return; + if (!is_array($phoneNumberData)) return; + + $keyList = []; + $keyPreviousList = []; + + $previousPhoneNumberData = []; + if (!$entity->isNew()) { + $previousPhoneNumberData = $this->getPhoneNumberData($entity); + } + + $hash = (object) []; + $hashPrevious = (object) []; + + foreach ($phoneNumberData as $row) { + $key = trim($row->phoneNumber); + if (empty($key)) continue; + $hash->$key = [ + 'primary' => $row->primary ? true : false, + 'type' => $row->type + ]; + $keyList[] = $key; + } + + foreach ($previousPhoneNumberData as $row) { + $key = $row->phoneNumber; + if (empty($key)) continue; + $hashPrevious->$key = [ + 'primary' => $row->primary ? true : false, + 'type' => $row->type + ]; + $keyPreviousList[] = $key; + } + + $primary = false; + + $toCreateList = []; + $toUpdateList = []; + $toRemoveList = []; + + $revertData = []; + + foreach ($keyList as $key) { + $data = $hash->$key; + + $new = true; + $changed = false; + + if ($hash->$key['primary']) { + $primary = $key; } - $pdo = $this->getEntityManager()->getPDO(); - - if ($phoneNumberData !== null && is_array($phoneNumberData)) { - $previousPhoneNumberData = array(); - if (!$entity->isNew()) { - $previousPhoneNumberData = $this->getPhoneNumberData($entity); - } - - $hash = array(); - foreach ($phoneNumberData as $row) { - $key = trim($row->phoneNumber); - if (!empty($key)) { - $hash[$key] = array( - 'primary' => $row->primary ? true : false, - 'type' => $row->type - ); + if (property_exists($hashPrevious, $key)) { + $new = false; + $changed = $hash->$key['type'] != $hashPrevious->$key['type']; + if ($hash->$key['primary']) { + if ($hash->$key['primary'] == $hashPrevious->$key['primary']) { + $primary = false; } } + } - $hashPrev = array(); - foreach ($previousPhoneNumberData as $row) { - $key = $row->phoneNumber; - if (!empty($key)) { - $hashPrev[$key] = array( - 'primary' => $row->primary ? true : false, - 'type' => $row->type - ); - } + if ($new) { + $toCreateList[] = $key; + } + if ($changed) { + $toUpdateList[] = $key; + } + } + + foreach ($keyPreviousList as $key) { + if (!property_exists($hash, $key)) { + $toRemoveList[] = $key; + } + } + + foreach ($toRemoveList as $number) { + $phoneNumber = $this->getByNumber($number); + if ($phoneNumber) { + $query = " + DELETE FROM entity_phone_number + WHERE + entity_id = ".$pdo->quote($entity->id)." AND + entity_type = ".$pdo->quote($entity->getEntityType())." AND + phone_number_id = ".$pdo->quote($phoneNumber->id)." + "; + $sth = $pdo->prepare($query); + $sth->execute(); + } + } + + foreach ($toUpdateList as $number) { + $phoneNumber = $this->getByNumber($number); + if ($phoneNumber) { + $skipSave = $this->checkChangeIsForbidden($phoneNumber, $entity); + if (!$skipSave) { + $phoneNumber->set([ + 'type' => $hash->$number['type'], + ]); + $this->save($phoneNumber); + } else { + $revertData[$number] = [ + 'type' => $phoneNumber->get('type') + ]; } + } + } - $primary = false; - $toCreate = array(); - $toUpdate = array(); - $toRemove = array(); - - $revertData = []; - - foreach ($hash as $key => $data) { - $new = true; - $changed = false; - - if ($hash[$key]['primary']) { - $primary = $key; - } - - if (array_key_exists($key, $hashPrev)) { - $new = false; - $changed = $hash[$key]['type'] != $hashPrev[$key]['type']; - if ($hash[$key]['primary']) { - if ($hash[$key]['primary'] == $hashPrev[$key]['primary']) { - $primary = false; - } - } - } - - if ($new) { - $toCreate[] = $key; - } - if ($changed) { - $toUpdate[] = $key; - } - } - - foreach ($hashPrev as $key => $data) { - if (!array_key_exists($key, $hash)) { - $toRemove[] = $key; - } - } - - foreach ($toRemove as $number) { - $phoneNumber = $this->getByNumber($number); - if ($phoneNumber) { - $query = " - DELETE FROM entity_phone_number - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityName())." AND - phone_number_id = ".$pdo->quote($phoneNumber->id)." - "; - $sth = $pdo->prepare($query); - $sth->execute(); - } - } - - foreach ($toUpdate as $number) { - $phoneNumber = $this->getByNumber($number); - if ($phoneNumber) { - $skipSave = $this->checkChangeIsForbidden($phoneNumber, $entity); - if (!$skipSave) { - $phoneNumber->set(array( - 'type' => $hash[$number]['type'], - )); - $this->save($phoneNumber); - } else { - $revertData[$number] = [ - 'type' => $phoneNumber->get('type') - ]; - } - } - } - - foreach ($toCreate as $number) { - $phoneNumber = $this->getByNumber($number); - if (!$phoneNumber) { - $phoneNumber = $this->get(); - - $phoneNumber->set(array( - 'name' => $number, - 'type' => $hash[$number]['type'], - )); - $this->save($phoneNumber); - } else { - $skipSave = $this->checkChangeIsForbidden($phoneNumber, $entity); - if (!$skipSave) { - if ($phoneNumber->get('type') != $hash[$number]['type']) { - $phoneNumber->set(array( - 'type' => $hash[$number]['type'], - )); - $this->save($phoneNumber); - } - } else { - $revertData[$number] = [ - 'type' => $phoneNumber->get('type') - ]; - } - } - - $query = " - INSERT entity_phone_number - (entity_id, entity_type, phone_number_id, `primary`) - VALUES - ( - ".$pdo->quote($entity->id).", - ".$pdo->quote($entity->getEntityName()).", - ".$pdo->quote($phoneNumber->id).", - ".$pdo->quote((int)($number === $primary))." - ) - ON DUPLICATE KEY UPDATE deleted = 0, `primary` = ".$pdo->quote((int)($number === $primary))." - "; - $sth = $pdo->prepare($query); - $sth->execute(); - } - - if ($primary) { - $phoneNumber = $this->getByNumber($primary); - if ($phoneNumber) { - $query = " - UPDATE entity_phone_number - SET `primary` = 0 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityName())." AND - `primary` = 1 AND - deleted = 0 - "; - $sth = $pdo->prepare($query); - $sth->execute(); - - $query = " - UPDATE entity_phone_number - SET `primary` = 1 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityName())." AND - phone_number_id = ".$pdo->quote($phoneNumber->id)." AND - deleted = 0 - "; - $sth = $pdo->prepare($query); - $sth->execute(); - } - } - - if (!empty($revertData)) { - foreach ($phoneNumberData as $row) { - if (!empty($revertData[$row->phoneNumber])) { - $row->type = $revertData[$row->phoneNumber]['type']; - } - } - $entity->set('phoneNumberData', $phoneNumberData); - } + foreach ($toCreateList as $number) { + $phoneNumber = $this->getByNumber($number); + if (!$phoneNumber) { + $phoneNumber = $this->get(); + $phoneNumber->set([ + 'name' => $number, + 'type' => $hash->$number['type'], + ]); + $this->save($phoneNumber); } else { - if (!$entity->has('phoneNumber')) { - return; - } - $entityRepository = $this->getEntityManager()->getRepository($entity->getEntityName()); - if (!empty($phoneNumberValue)) { - if ($phoneNumberValue !== $entity->getFetched('phoneNumber')) { - - $phoneNumberNew = $this->where(array('name' => $phoneNumberValue))->findOne(); - $isNewPhoneNumber = false; - if (!$phoneNumberNew) { - $phoneNumberNew = $this->get(); - $phoneNumberNew->set('name', $phoneNumberValue); - $defaultType = $this->getEntityManager()->getEspoMetadata()->get('entityDefs.' . $entity->getEntityName() . '.fields.phoneNumber.defaultType'); - - $phoneNumberNew->set('type', $defaultType); - - $this->save($phoneNumberNew); - $isNewPhoneNumber = true; - } - - $phoneNumberValueOld = $entity->getFetched('phoneNumber'); - if (!empty($phoneNumberValueOld)) { - $phoneNumberOld = $this->getByNumber($phoneNumberValueOld); - if ($phoneNumberOld) { - $entityRepository->unrelate($entity, 'phoneNumbers', $phoneNumberOld); - } - } - $entityRepository->relate($entity, 'phoneNumbers', $phoneNumberNew); - - $query = " - UPDATE entity_phone_number - SET `primary` = 1 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityName())." AND - phone_number_id = ".$pdo->quote($phoneNumberNew->id)." - "; - $sth = $pdo->prepare($query); - $sth->execute(); + $skipSave = $this->checkChangeIsForbidden($phoneNumber, $entity); + if (!$skipSave) { + if ($phoneNumber->get('type') != $hash->$number['type']) { + $phoneNumber->set([ + 'type' => $hash->$number['type'], + ]); + $this->save($phoneNumber); } } else { - $phoneNumberValueOld = $entity->getFetched('phoneNumber'); - if (!empty($phoneNumberValueOld)) { - $phoneNumberOld = $this->getByNumber($phoneNumberValueOld); - if ($phoneNumberOld) { - $entityRepository->unrelate($entity, 'phoneNumbers', $phoneNumberOld); - } - } + $revertData[$number] = [ + 'type' => $phoneNumber->get('type') + ]; } } + + $query = " + INSERT entity_phone_number + (entity_id, entity_type, phone_number_id, `primary`) + VALUES + ( + ".$pdo->quote($entity->id).", + ".$pdo->quote($entity->getEntityType()).", + ".$pdo->quote($phoneNumber->id).", + ".$pdo->quote((int)($number === $primary))." + ) + ON DUPLICATE KEY UPDATE deleted = 0, `primary` = ".$pdo->quote((int)($number === $primary))." + "; + $sth = $pdo->prepare($query); + $sth->execute(); + } + + if ($primary) { + $phoneNumber = $this->getByNumber($primary); + if ($phoneNumber) { + $query = " + UPDATE entity_phone_number + SET `primary` = 0 + WHERE + entity_id = ".$pdo->quote($entity->id)." AND + entity_type = ".$pdo->quote($entity->getEntityType())." AND + `primary` = 1 AND + deleted = 0 + "; + $sth = $pdo->prepare($query); + $sth->execute(); + + $query = " + UPDATE entity_phone_number + SET `primary` = 1 + WHERE + entity_id = ".$pdo->quote($entity->id)." AND + entity_type = ".$pdo->quote($entity->getEntityType())." AND + phone_number_id = ".$pdo->quote($phoneNumber->id)." AND + deleted = 0 + "; + $sth = $pdo->prepare($query); + $sth->execute(); + } + } + + if (!empty($revertData)) { + foreach ($phoneNumberData as $row) { + if (!empty($revertData[$row->phoneNumber])) { + $row->type = $revertData[$row->phoneNumber]['type']; + } + } + $entity->set('phoneNumberData', $phoneNumberData); + } + } + + protected function storeEntityPhoneNumberPrimary(Entity $entity) + { + $pdo = $this->getEntityManager()->getPDO(); + + if (!$entity->has('phoneNumber')) return; + $phoneNumberValue = trim($entity->get('phoneNumber')); + + $entityRepository = $this->getEntityManager()->getRepository($entity->getEntityType()); + if (!empty($phoneNumberValue)) { + if ($phoneNumberValue !== $entity->getFetched('phoneNumber')) { + + $phoneNumberNew = $this->where(['name' => $phoneNumberValue])->findOne(); + $isNewPhoneNumber = false; + if (!$phoneNumberNew) { + $phoneNumberNew = $this->get(); + $phoneNumberNew->set('name', $phoneNumberValue); + $defaultType = $this->getEntityManager()->getEspoMetadata()->get('entityDefs.' . $entity->getEntityType() . '.fields.phoneNumber.defaultType'); + + $phoneNumberNew->set('type', $defaultType); + + $this->save($phoneNumberNew); + $isNewPhoneNumber = true; + } + + $phoneNumberValueOld = $entity->getFetched('phoneNumber'); + if (!empty($phoneNumberValueOld)) { + $phoneNumberOld = $this->getByNumber($phoneNumberValueOld); + if ($phoneNumberOld) { + $entityRepository->unrelate($entity, 'phoneNumbers', $phoneNumberOld); + } + } + $entityRepository->relate($entity, 'phoneNumbers', $phoneNumberNew); + + $query = " + UPDATE entity_phone_number + SET `primary` = 1 + WHERE + entity_id = ".$pdo->quote($entity->id)." AND + entity_type = ".$pdo->quote($entity->getEntityType())." AND + phone_number_id = ".$pdo->quote($phoneNumberNew->id)." + "; + $sth = $pdo->prepare($query); + $sth->execute(); + } + } else { + $phoneNumberValueOld = $entity->getFetched('phoneNumber'); + if (!empty($phoneNumberValueOld)) { + $phoneNumberOld = $this->getByNumber($phoneNumberValueOld); + if ($phoneNumberOld) { + $entityRepository->unrelate($entity, 'phoneNumbers', $phoneNumberOld); + } + } + } + } + + public function storeEntityPhoneNumber(Entity $entity) + { + $phoneNumberData = null; + if ($entity->has('phoneNumberData')) { + $phoneNumberData = $entity->get('phoneNumberData'); + } + + if ($phoneNumberData !== null) { + $this->storeEntityPhoneNumberData($entity); + } else if ($entity->has('phoneNumber')) { + $this->storeEntityPhoneNumberPrimary($entity); + } } protected function checkChangeIsForbidden($entity, $excludeEntity) From f4b4f6ff89e6626a65bb94d7a9f018418557d644 Mon Sep 17 00:00:00 2001 From: yuri Date: Fri, 28 Dec 2018 13:10:16 +0200 Subject: [PATCH 17/19] load followers acl check --- application/Espo/Services/Record.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php index ba159253cc..d100ca4e50 100644 --- a/application/Espo/Services/Record.php +++ b/application/Espo/Services/Record.php @@ -318,6 +318,8 @@ class Record extends \Espo\Core\Services\Base if ($this->getUser()->isPortal()) return; if (!$this->getMetadata()->get(['scopes', $entity->getEntityType(), 'stream'])) return; + if (!$this->getAcl()->check($entity, 'stream')) return; + $data = $this->getStreamService()->getEntityFollowers($entity, 0, self::FOLLOWERS_LIMIT); if ($data) { $entity->set('followersIds', $data['idList']); @@ -1676,10 +1678,6 @@ class Record extends \Espo\Core\Services\Base { $entity = $this->getRepository()->get($id); - if (!$this->getAcl()->check($entity, 'read')) { - throw new Forbidden(); - } - if (empty($userId)) { $userId = $this->getUser()->id; } @@ -1729,7 +1727,7 @@ class Record extends \Espo\Core\Services\Base $idList = $params['ids']; foreach ($idList as $id) { $entity = $this->getEntity($id); - if ($entity && $this->getAcl()->check($entity, 'stream')) { + if ($entity) { if ($streamService->unfollowEntity($entity, $userId)) { $resultIdList[] = $entity->id; } From 17bd2f3324f31c9570f98eaf6940dd002a1877e3 Mon Sep 17 00:00:00 2001 From: yuri Date: Fri, 28 Dec 2018 13:57:03 +0200 Subject: [PATCH 18/19] fix text cut --- client/src/views/fields/text.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/views/fields/text.js b/client/src/views/fields/text.js index d9d808a397..1b1d2489e5 100644 --- a/client/src/views/fields/text.js +++ b/client/src/views/fields/text.js @@ -181,7 +181,7 @@ Espo.define('views/fields/text', 'views/fields/base', function (Dep) { afterRender: function () { Dep.prototype.afterRender.call(this); - if (this.mode === 'detail' || this.mode === 'list') { + if (this.isReadMode()) { $(window).off('resize.see-more-' + this.cid); this.$textContainer = this.$el.find('> .complex-text-container'); @@ -190,6 +190,10 @@ Espo.define('views/fields/text', 'views/fields/base', function (Dep) { if (this.isCut()) { this.controlSeeMore(); + if (this.model.get(this.name) && this.$text.height() === 0) { + this.$textContainer.addClass('cut'); + setTimeout(this.controlSeeMore.bind(this), 50); + } $(window).on('resize.see-more-' + this.cid, function () { this.controlSeeMore(); From 79dcec028f6b08a4cb31055561c02247900695dd Mon Sep 17 00:00:00 2001 From: yuri Date: Fri, 28 Dec 2018 14:33:00 +0200 Subject: [PATCH 19/19] v --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ab6c47a681..5adc780e1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "espocrm", - "version": "5.5.3", + "version": "5.5.4", "description": "", "main": "index.php", "repository": {