From 0ab88534555ae4e8b19d77df9caf8ded907d20c0 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 1 Dec 2020 13:14:17 +0200 Subject: [PATCH 01/51] fix notification for portal user --- application/Espo/Services/Notification.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/application/Espo/Services/Notification.php b/application/Espo/Services/Notification.php index da8992444c..8a93ce0d4d 100644 --- a/application/Espo/Services/Notification.php +++ b/application/Espo/Services/Notification.php @@ -81,7 +81,7 @@ class Notification extends \Espo\Services\Record implements $collection = $this->entityManager->createCollection(); $userList = $this->getEntityManager()->getRepository('User') - ->select(['id']) + ->select(['id', 'type']) ->where([ 'isActive' => true, 'id' => $userIdList, @@ -90,7 +90,10 @@ class Notification extends \Espo\Services\Record implements foreach ($userList as $user) { $userId = $user->id; - if (!$this->checkUserNoteAccess($user, $note)) continue; + if (!$this->checkUserNoteAccess($user, $note)) { + continue; + } + if ($note->get('createdById') === $user->id) continue; if ($related && $related->getEntityType() == 'Email' && $related->get('sentById') == $user->id) continue; if ($related && $related->get('createdById') == $user->id) continue; From 0bc06f3cae9b4b3003983bbace7ac2f0c3f29b02 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 2 Dec 2020 11:04:33 +0200 Subject: [PATCH 02/51] fix autofollow --- application/Espo/Resources/metadata/entityDefs/Autofollow.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/Espo/Resources/metadata/entityDefs/Autofollow.json b/application/Espo/Resources/metadata/entityDefs/Autofollow.json index 977282fb23..da125c6e08 100644 --- a/application/Espo/Resources/metadata/entityDefs/Autofollow.json +++ b/application/Espo/Resources/metadata/entityDefs/Autofollow.json @@ -12,7 +12,7 @@ }, "userId": { "type": "varchar", - "maxLength": 11, + "maxLength": 24, "index": true } } From dd708778d6db957ef8ed4b985b5223cd6d265fbf Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 3 Dec 2020 11:56:15 +0200 Subject: [PATCH 03/51] field clone validations --- client/src/views/fields/base.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/views/fields/base.js b/client/src/views/fields/base.js index cd10aa3ec4..de431acd33 100644 --- a/client/src/views/fields/base.js +++ b/client/src/views/fields/base.js @@ -257,6 +257,8 @@ define('views/fields/base', 'view', function (Dep) { this.events = {}; } + this.validations = Espo.Utils.clone(this.validations); + this.defs = this.options.defs || {}; this.name = this.options.name || this.defs.name; this.params = this.options.params || this.defs.params || {}; From 6b3e81e3fd8a8e450d2ad7f92dd8a686f698b2d9 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 3 Dec 2020 11:56:44 +0200 Subject: [PATCH 04/51] settings currency validation --- .../Espo/Resources/i18n/en_US/Global.json | 1 + .../views/settings/fields/default-currency.js | 32 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/application/Espo/Resources/i18n/en_US/Global.json b/application/Espo/Resources/i18n/en_US/Global.json index 4c7f4b8df4..06d81ed5ef 100644 --- a/application/Espo/Resources/i18n/en_US/Global.json +++ b/application/Espo/Resources/i18n/en_US/Global.json @@ -270,6 +270,7 @@ "notModified": "You have not modified the record", "duplicate": "The record you are creating might already exist", "dropToAttach": "Drop to attach", + "fieldInvalid": "{field} is invalid", "fieldIsRequired": "{field} is required", "fieldShouldBeEmail": "{field} should be a valid email", "fieldShouldBeFloat": "{field} should be a valid float", diff --git a/client/src/views/settings/fields/default-currency.js b/client/src/views/settings/fields/default-currency.js index 5d9d7fd9d6..e11854bb51 100644 --- a/client/src/views/settings/fields/default-currency.js +++ b/client/src/views/settings/fields/default-currency.js @@ -25,14 +25,40 @@ * In accordance with Section 7(b) of the GNU General Public License version 3, * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -Espo.define('views/settings/fields/default-currency', 'views/fields/enum', function (Dep) { + +define('views/settings/fields/default-currency', 'views/fields/enum', function (Dep) { return Dep.extend({ + setup: function () { + Dep.prototype.setup.call(this); + + this.validations.push('existing'); + }, + setupOptions: function () { this.params.options = Espo.Utils.clone(this.getConfig().get('currencyList') || []); - } + }, + + validateExisting: function () { + var currencyList = this.model.get('currencyList'); + + if (!currencyList) { + return; + } + + var value = this.model.get(this.name); + + if (~currencyList.indexOf(value)) { + return; + } + + var msg = this.translate('fieldInvalid', 'messages').replace('{field}', this.getLabelText()); + + this.showValidationMessage(msg); + + return true; + }, }); - }); From ad2e977b7ce52662c5b9a5bdab9566610d9743dc Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 3 Dec 2020 15:52:14 +0200 Subject: [PATCH 05/51] cs fix --- client/src/views/record/kanban.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/views/record/kanban.js b/client/src/views/record/kanban.js index 80f38ba7ce..48f90fd19a 100644 --- a/client/src/views/record/kanban.js +++ b/client/src/views/record/kanban.js @@ -90,7 +90,7 @@ define('views/record/kanban', ['views/record/list'], function (Dep) { }, 'click .action': function (e) { Espo.Utils.handleAction(this, e); - } + }, }, showMore: true, @@ -521,7 +521,7 @@ define('views/record/kanban', ['views/record/list'], function (Dep) { var collection = this.seedCollection.clone(); collection.total = item.total; - //collection.url = this.scope; + collection.url = this.collection.url; collection.where = this.collection.where; From fad183e20afc231b2c47afa2e2a6390d2114825e Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 3 Dec 2020 16:28:03 +0200 Subject: [PATCH 06/51] kanban order fix --- client/src/views/record/kanban.js | 38 ++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/client/src/views/record/kanban.js b/client/src/views/record/kanban.js index 48f90fd19a..800ac93e02 100644 --- a/client/src/views/record/kanban.js +++ b/client/src/views/record/kanban.js @@ -381,10 +381,14 @@ define('views/record/kanban', ['views/record/list'], function (Dep) { this.initSortable(); + this.moveModelBetweenGroupCollections(model, draggedGroupFrom, group); + if (!orderDisabled) { this.reOrderGroup(group); this.storeGroupOrder(group); } + + this.rebuildGroupDataList(); }.bind(this)) .fail(function () { $list.sortable('cancel'); @@ -400,12 +404,13 @@ define('views/record/kanban', ['views/record/list'], function (Dep) { this.reOrderGroup(group); this.storeGroupOrder(group); + + this.rebuildGroupDataList(); } }.bind(this) }); }, - storeGroupOrder: function (group) { Espo.Ajax.postRequest('KanbanOrder/action/store', { entityType: this.entityType, @@ -452,6 +457,37 @@ define('views/record/kanban', ['views/record/list'], function (Dep) { }); }, + rebuildGroupDataList: function () { + this.groupDataList.forEach(function (item) { + item.dataList = []; + + for (var model of item.collection.models) { + item.dataList.push({ + key: model.id, + id: model.id, + }); + } + }, this); + }, + + moveModelBetweenGroupCollections: function (model, groupFrom, groupTo) { + var collection = this.getGroupCollection(groupFrom); + + if (!collection) { + return; + } + + collection.remove(model.id, {silent: true}); + + var collection = this.getGroupCollection(groupTo); + + if (!collection) { + return; + } + + collection.add(model, {silent: true}); + }, + handleAttributesOnGroupChange: function (model, attributes, group) {}, adjustMinHeight: function () { From 4426d81e77e22950903e9e1582f3fe6d605f6331 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 4 Dec 2020 14:28:04 +0200 Subject: [PATCH 07/51] fix category tree --- application/Espo/Core/Repositories/CategoryTree.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/Espo/Core/Repositories/CategoryTree.php b/application/Espo/Core/Repositories/CategoryTree.php index 6eda3f1d59..ea3c24152d 100644 --- a/application/Espo/Core/Repositories/CategoryTree.php +++ b/application/Espo/Core/Repositories/CategoryTree.php @@ -49,6 +49,9 @@ class CategoryTree extends Database ->select() ->from($pathEntityType) ->select(['ascendorId', "'" . $entity->id . "'"]) + ->where([ + 'descendorId' => $parentId, + ]) ->build(); $subSelect2 = $em->getQueryBuilder() From d3d710b2b998800cc1b8a86c245663b8c7968927 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 4 Dec 2020 15:04:36 +0200 Subject: [PATCH 08/51] v --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e6420ea9a..6d639c6e51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "espocrm", - "version": "6.0.7", + "version": "6.0.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 11f3f5848e..51d48902cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "espocrm", - "version": "6.0.7", + "version": "6.0.8", "description": "", "main": "index.php", "repository": { From 70d15cbac56b93869f22b0fd8c571655833fd8e0 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sat, 5 Dec 2020 15:37:14 +0200 Subject: [PATCH 09/51] pdf barcode tag --- application/Espo/Core/Htmlizer/Htmlizer.php | 66 +--------- .../Espo/Tools/Pdf/Tcpdf/EntityProcessor.php | 118 +++++++++++++++--- 2 files changed, 106 insertions(+), 78 deletions(-) diff --git a/application/Espo/Core/Htmlizer/Htmlizer.php b/application/Espo/Core/Htmlizer/Htmlizer.php index 42d8ca70b6..ac7d544988 100644 --- a/application/Espo/Core/Htmlizer/Htmlizer.php +++ b/application/Espo/Core/Htmlizer/Htmlizer.php @@ -428,72 +428,12 @@ class Htmlizer $context = $args[count($args) - 1]; $value = $args[0]; - $codeType = $context['hash']['type'] ?? 'CODE128'; - - $typeMap = [ - "CODE128" => 'C128', - "CODE128A" => 'C128A', - "CODE128B" => 'C128B', - "CODE128C" => 'C128C', - "EAN13" => 'EAN13', - "EAN8" => 'EAN8', - "EAN5" => 'EAN5', - "EAN2" => 'EAN2', - "UPC" => 'UPCA', - "UPCE" => 'UPCE', - "ITF14" => 'I25', - "pharmacode" => 'PHARMA', - "QRcode" => 'QRCODE,H', - ]; - - if ($codeType === 'QRcode') { - $function = 'write2DBarcode'; - - $params = [ - $value, - $typeMap[$codeType] ?? null, - '', '', - $context['hash']['width'] ?? 40, - $context['hash']['height'] ?? 40, - [ - 'border' => false, - 'vpadding' => $context['hash']['padding'] ?? 2, - 'hpadding' => $context['hash']['padding'] ?? 2, - 'fgcolor' => $context['hash']['color'] ?? [0,0,0], - 'bgcolor' => $context['hash']['bgcolor'] ?? false, - 'module_width' => 1, - 'module_height' => 1, - ], - 'N', - ]; - } else { - $function = 'write1DBarcode'; - - $params = [ - $value, - $typeMap[$codeType] ?? null, - '', '', - $context['hash']['width'] ?? 60, - $context['hash']['height'] ?? 30, - 0.4, - [ - 'position' => 'S', - 'border' => false, - 'padding' => $context['hash']['padding'] ?? 0, - 'fgcolor' => $context['hash']['color'] ?? [0,0,0], - 'bgcolor' => $context['hash']['bgcolor'] ?? [255,255,255], - 'text' => $context['hash']['text'] ?? true, - 'font' => 'helvetica', - 'fontsize' => $context['hash']['fontsize'] ?? 14, - 'stretchtext' => 4, - ], - 'N', - ]; - } + $params = $context['hash']; + $params['value'] = $value; $paramsString = urlencode(json_encode($params)); - return new LightnCandy\SafeString(""); + return new LightnCandy\SafeString(""); }, 'ifEqual' => function () { $args = func_get_args(); diff --git a/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php b/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php index aa3374c423..d457dabab0 100644 --- a/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php +++ b/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php @@ -32,6 +32,7 @@ namespace Espo\Tools\Pdf\Tcpdf; use Espo\Core\{ Exceptions\Error, Utils\Config, + Htmlizer\Htmlizer as Htmlizer, Htmlizer\Factory as HtmlizerFactory, Pdf\Tcpdf, }; @@ -80,15 +81,11 @@ class EntityProcessor ); if ($template->hasFooter()) { - $htmlFooter = $htmlizer->render( - $entity, - $template->getFooter(), - null, - $additionalData - ); + $htmlFooter = $this->render($htmlizer, $entity, $template->getFooter(), $additionalData); $pdf->setFooterFont([$fontFace, '', $this->fontSize]); $pdf->setFooterPosition($template->getFooterPosition()); + $pdf->setFooterHtml($htmlFooter); } else { @@ -112,16 +109,16 @@ class EntityProcessor $pageOrientationCode = 'L'; } - $htmlHeader = $htmlizer->render( - $entity, - $template->getHeader(), - null, - $additionalData - ); + $htmlHeader = ''; + + if ($template->getHeader() !== '') { + $htmlHeader = $this->render($htmlizer, $entity, $template->getHeader(), $additionalData); + } if ($template->hasHeader()) { $pdf->setHeaderFont([$fontFace, '', $this->fontSize]); $pdf->setHeaderPosition($template->getHeaderPosition()); + $pdf->setHeaderHtml($htmlHeader); $pdf->addPage($pageOrientationCode, $pageFormat); @@ -134,13 +131,104 @@ class EntityProcessor $pdf->writeHTML($htmlHeader, true, false, true, false, ''); } - $htmlBody = $htmlizer->render( + $htmlBody = $this->render($htmlizer, $entity, $template->getBody(), $additionalData); + + $pdf->writeHTML($htmlBody, true, false, true, false, ''); + } + + protected function render(Htmlizer $htmlizer, Entity $entity, string $template, array $additionalData) : string + { + $html = $htmlizer->render( $entity, - $template->getBody(), + $template, null, $additionalData ); - $pdf->writeHTML($htmlBody, true, false, true, false, ''); + $html = preg_replace_callback( + '//', + function ($matches) { + $dataString = $matches[1]; + + $data = json_decode(urldecode($dataString), true); + + return $this->composeBarcodeTag($data); + }, + $html + ); + + return $html; + } + + protected function composeBarcodeTag(array $data) : string + { + $value = $data['value'] ?? null; + + $codeType = $data['type'] ?? 'CODE128'; + + $typeMap = [ + "CODE128" => 'C128', + "CODE128A" => 'C128A', + "CODE128B" => 'C128B', + "CODE128C" => 'C128C', + "EAN13" => 'EAN13', + "EAN8" => 'EAN8', + "EAN5" => 'EAN5', + "EAN2" => 'EAN2', + "UPC" => 'UPCA', + "UPCE" => 'UPCE', + "ITF14" => 'I25', + "pharmacode" => 'PHARMA', + "QRcode" => 'QRCODE,H', + ]; + + if ($codeType === 'QRcode') { + $function = 'write2DBarcode'; + + $params = [ + $value, + $typeMap[$codeType] ?? null, + '', '', + $data['width'] ?? 40, + $data['height'] ?? 40, + [ + 'border' => false, + 'vpadding' => $data['padding'] ?? 2, + 'hpadding' => $data['padding'] ?? 2, + 'fgcolor' => $data['color'] ?? [0,0,0], + 'bgcolor' => $data['bgcolor'] ?? false, + 'module_width' => 1, + 'module_height' => 1, + ], + 'N', + ]; + } else { + $function = 'write1DBarcode'; + + $params = [ + $value, + $typeMap[$codeType] ?? null, + '', '', + $data['width'] ?? 60, + $data['height'] ?? 30, + 0.4, + [ + 'position' => 'S', + 'border' => false, + 'padding' => $data['padding'] ?? 0, + 'fgcolor' => $data['color'] ?? [0,0,0], + 'bgcolor' => $data['bgcolor'] ?? [255,255,255], + 'text' => $data['text'] ?? true, + 'font' => 'helvetica', + 'fontsize' => $data['fontsize'] ?? 14, + 'stretchtext' => 4, + ], + 'N', + ]; + } + + $paramsString = urlencode(json_encode($params)); + + return ""; } } From 07d63013aae35d141e08dcd2249efa9926c0997a Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sat, 5 Dec 2020 22:16:48 +0200 Subject: [PATCH 10/51] cleanup --- application/Espo/Tools/Pdf/Builder.php | 1 - 1 file changed, 1 deletion(-) diff --git a/application/Espo/Tools/Pdf/Builder.php b/application/Espo/Tools/Pdf/Builder.php index b7df79e5b9..e28e828c24 100644 --- a/application/Espo/Tools/Pdf/Builder.php +++ b/application/Espo/Tools/Pdf/Builder.php @@ -82,5 +82,4 @@ class Builder ] ); } - } From 55b7d81922413272e08012e6e538b9602f908d2e Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 7 Dec 2020 11:46:56 +0200 Subject: [PATCH 11/51] pdf font face list --- .../Espo/Resources/defaults/config.php | 1 + .../Resources/metadata/app/pdfEngines.json | 32 ++++++++++++- .../metadata/entityDefs/Template.json | 32 +------------ client/src/views/template/fields/font-face.js | 48 +++++++++++++++++++ upgrades/6.1/data.json | 4 ++ upgrades/6.1/scripts/AfterUpgrade.php | 42 ++++++++++++++++ 6 files changed, 127 insertions(+), 32 deletions(-) create mode 100644 client/src/views/template/fields/font-face.js create mode 100644 upgrades/6.1/data.json create mode 100644 upgrades/6.1/scripts/AfterUpgrade.php diff --git a/application/Espo/Resources/defaults/config.php b/application/Espo/Resources/defaults/config.php index 1ebbce38a3..6452b50d8d 100644 --- a/application/Espo/Resources/defaults/config.php +++ b/application/Espo/Resources/defaults/config.php @@ -156,5 +156,6 @@ return [ 'auth2FAMethodList' => ['Totp'], 'personNameFormat' => 'firstLast', 'newNotificationCountInTitle' => false, + 'pdfEngine' => 'Tcpdf', 'isInstalled' => false, ]; diff --git a/application/Espo/Resources/metadata/app/pdfEngines.json b/application/Espo/Resources/metadata/app/pdfEngines.json index 8a965a0a3d..f1a79e39a1 100644 --- a/application/Espo/Resources/metadata/app/pdfEngines.json +++ b/application/Espo/Resources/metadata/app/pdfEngines.json @@ -3,6 +3,36 @@ "implementationClassNameMap": { "entity": "Espo\\Tools\\Pdf\\Tcpdf\\TcpdfEntityPrinter", "collection": "Espo\\Tools\\Pdf\\Tcpdf\\TcpdfCollectionPrinter" - } + }, + "fontFaceList": [ + "aealarabiya", + "aefurat", + "cid0cs", + "cid0ct", + "cid0jp", + "cid0kr", + "courier", + "dejavusans", + "dejavusanscondensed", + "dejavusansextralight", + "dejavusansmono", + "dejavuserif", + "dejavuserifcondensed", + "freemono", + "freesans", + "freeserif", + "helvetica", + "hysmyeongjostdmedium", + "kozgopromedium", + "kozminproregular", + "msungstdlight", + "pdfacourier", + "pdfahelvetica", + "pdfasymbol", + "pdfatimes", + "stsongstdlight", + "symbol", + "times" + ] } } diff --git a/application/Espo/Resources/metadata/entityDefs/Template.json b/application/Espo/Resources/metadata/entityDefs/Template.json index a38b6ec163..5134374344 100644 --- a/application/Espo/Resources/metadata/entityDefs/Template.json +++ b/application/Espo/Resources/metadata/entityDefs/Template.json @@ -98,37 +98,7 @@ }, "fontFace": { "type": "enum", - "options": [ - "", - "aealarabiya", - "aefurat", - "cid0cs", - "cid0ct", - "cid0jp", - "cid0kr", - "courier", - "dejavusans", - "dejavusanscondensed", - "dejavusansextralight", - "dejavusansmono", - "dejavuserif", - "dejavuserifcondensed", - "freemono", - "freesans", - "freeserif", - "helvetica", - "hysmyeongjostdmedium", - "kozgopromedium", - "kozminproregular", - "msungstdlight", - "pdfacourier", - "pdfahelvetica", - "pdfasymbol", - "pdfatimes", - "stsongstdlight", - "symbol", - "times" - ], + "view": "views/template/fields/font-face", "default": "" } }, diff --git a/client/src/views/template/fields/font-face.js b/client/src/views/template/fields/font-face.js new file mode 100644 index 0000000000..c7179c2162 --- /dev/null +++ b/client/src/views/template/fields/font-face.js @@ -0,0 +1,48 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: https://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU General Public License version 3. + * + * In accordance with Section 7(b) of the GNU General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +define('views/template/fields/font-face', 'views/fields/enum', function (Dep) { + + return Dep.extend({ + + setupOptions: function () { + var engine = this.getConfig().get('pdfEngine') || 'Tcpdf'; + + var fontFaceList = this.getMetadata().get([ + 'app', 'pdfEngines', engine, 'fontFaceList', + ]) || []; + + fontFaceList = Espo.Utils.clone(fontFaceList); + + fontFaceList.unshift(''); + + this.params.options = fontFaceList; + }, + + }); +}); diff --git a/upgrades/6.1/data.json b/upgrades/6.1/data.json new file mode 100644 index 0000000000..1449b0403c --- /dev/null +++ b/upgrades/6.1/data.json @@ -0,0 +1,4 @@ +{ + "manifest": { + } +} diff --git a/upgrades/6.1/scripts/AfterUpgrade.php b/upgrades/6.1/scripts/AfterUpgrade.php new file mode 100644 index 0000000000..c34d0a5830 --- /dev/null +++ b/upgrades/6.1/scripts/AfterUpgrade.php @@ -0,0 +1,42 @@ +container = $container; + + $config = $container->get('config'); + + $config->set('pdfEngine', 'Tcpdf'); + + $config->save(); + } +} From 2f40830630eb9735176278b7e006e1c352ee4840 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 7 Dec 2020 12:04:09 +0200 Subject: [PATCH 12/51] update mail-mime-parser --- composer.json | 4 +-- composer.lock | 75 ++++++++++++++++++++++++++++----------------------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/composer.json b/composer.json index 04c3bc9167..3fc0b6e5fa 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,6 @@ "zordius/lightncandy": "1.*", "composer/semver": "^3", "tecnickcom/tcpdf": "^6.3", - "zbateson/mail-mime-parser": "1.2.*", "phpoffice/phpspreadsheet": "^1.10", "spatie/async": "dev-for-espocrm", "symfony/process": "4.1.7", @@ -38,7 +37,8 @@ "guzzlehttp/psr7": "^1.6", "michelf/php-markdown": "^1.8", "robthree/twofactorauth": "^1.6", - "nesbot/carbon": "^2.26" + "nesbot/carbon": "^2.26", + "zbateson/mail-mime-parser": "1.3.*" }, "require-dev": { "phpunit/phpunit": "^8" diff --git a/composer.lock b/composer.lock index c70241243f..1b38412511 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "c5eae37a091cd326f12eb8dc07b649eb", + "content-hash": "f4230248c7de33c9a74a7fc1c6592a5c", "packages": [ { "name": "cboden/ratchet", @@ -2988,20 +2988,20 @@ }, { "name": "symfony/polyfill-iconv", - "version": "v1.12.0", + "version": "v1.20.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "685968b11e61a347c18bf25db32effa478be610f" + "reference": "c536646fdb4f29104dd26effc2fdcb9a5b085024" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/685968b11e61a347c18bf25db32effa478be610f", - "reference": "685968b11e61a347c18bf25db32effa478be610f", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/c536646fdb4f29104dd26effc2fdcb9a5b085024", + "reference": "c536646fdb4f29104dd26effc2fdcb9a5b085024", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { "ext-iconv": "For best performance" @@ -3009,7 +3009,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -3043,7 +3047,7 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2020-10-23T14:02:19+00:00" }, { "name": "symfony/polyfill-intl-idn", @@ -3109,20 +3113,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.15.0", + "version": "v1.20.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + "reference": "39d483bdf39be819deabf04ec872eb0b2410b531" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/39d483bdf39be819deabf04ec872eb0b2410b531", + "reference": "39d483bdf39be819deabf04ec872eb0b2410b531", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { "ext-mbstring": "For best performance" @@ -3130,7 +3134,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -3164,7 +3172,7 @@ "portable", "shim" ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-10-23T14:02:19+00:00" }, { "name": "symfony/polyfill-php72", @@ -3638,23 +3646,23 @@ }, { "name": "zbateson/mail-mime-parser", - "version": "1.2.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/zbateson/mail-mime-parser.git", - "reference": "fd49e6f53184529a5ec4bc0b9edd98edeee53f5a" + "reference": "706964d904798b8c22d63f62f0ec5f5bc84e30d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/fd49e6f53184529a5ec4bc0b9edd98edeee53f5a", - "reference": "fd49e6f53184529a5ec4bc0b9edd98edeee53f5a", + "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/706964d904798b8c22d63f62f0ec5f5bc84e30d9", + "reference": "706964d904798b8c22d63f62f0ec5f5bc84e30d9", "shasum": "" }, "require": { "guzzlehttp/psr7": "^1.0", "php": ">=5.4", - "zbateson/mb-wrapper": "^1.0", - "zbateson/stream-decorators": "^1.0.2" + "zbateson/mb-wrapper": "^1.0.1", + "zbateson/stream-decorators": "^1.0.4" }, "require-dev": { "jms/serializer": "^1.1", @@ -3698,20 +3706,20 @@ "parser", "php-imap" ], - "time": "2020-01-09T04:49:13+00:00" + "time": "2020-12-02T21:55:45+00:00" }, { "name": "zbateson/mb-wrapper", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/zbateson/mb-wrapper.git", - "reference": "723f25a1ab0e4e662efa8d89f38da751c799134a" + "reference": "721b3dfbf7ab75fee5ac60a542d7923ffe59ef6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/723f25a1ab0e4e662efa8d89f38da751c799134a", - "reference": "723f25a1ab0e4e662efa8d89f38da751c799134a", + "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/721b3dfbf7ab75fee5ac60a542d7923ffe59ef6d", + "reference": "721b3dfbf7ab75fee5ac60a542d7923ffe59ef6d", "shasum": "" }, "require": { @@ -3742,7 +3750,6 @@ } ], "description": "Wrapper for mbstring with fallback to iconv for encoding conversion and string manipulation", - "homepage": "https://github.com/zbateson/StreamDecorators", "keywords": [ "charset", "encoding", @@ -3756,20 +3763,20 @@ "multibyte", "string" ], - "time": "2018-09-28T17:43:01+00:00" + "time": "2020-10-21T22:14:27+00:00" }, { "name": "zbateson/stream-decorators", - "version": "1.0.2", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/zbateson/stream-decorators.git", - "reference": "ec3c3ea91d26a7685db7a29a270464c5e1470786" + "reference": "6f54738dfecc65e1d5bfb855035836748083a6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/ec3c3ea91d26a7685db7a29a270464c5e1470786", - "reference": "ec3c3ea91d26a7685db7a29a270464c5e1470786", + "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/6f54738dfecc65e1d5bfb855035836748083a6dd", + "reference": "6f54738dfecc65e1d5bfb855035836748083a6dd", "shasum": "" }, "require": { @@ -3778,7 +3785,7 @@ "zbateson/mb-wrapper": "^1.0.0" }, "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5" }, "type": "library", "autoload": { @@ -3807,7 +3814,7 @@ "stream", "uuencode" ], - "time": "2019-03-21T06:13:00+00:00" + "time": "2020-08-10T18:59:43+00:00" }, { "name": "zordius/lightncandy", From 7041a9ca9d850451c2b992534e6f5c67b901994c Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 7 Dec 2020 12:13:20 +0200 Subject: [PATCH 13/51] mysql locker fix --- application/Espo/ORM/Locker/MysqlLocker.php | 96 ++++++++++++++-- tests/unit/Espo/ORM/BaseLockerTest.php | 120 ++++++++++++++++++++ tests/unit/Espo/ORM/MysqlLockerTest.php | 16 --- 3 files changed, 204 insertions(+), 28 deletions(-) create mode 100644 tests/unit/Espo/ORM/BaseLockerTest.php diff --git a/application/Espo/ORM/Locker/MysqlLocker.php b/application/Espo/ORM/Locker/MysqlLocker.php index eff1d9a7e0..759bf477f1 100644 --- a/application/Espo/ORM/Locker/MysqlLocker.php +++ b/application/Espo/ORM/Locker/MysqlLocker.php @@ -29,29 +29,101 @@ namespace Espo\ORM\Locker; +use Espo\ORM\{ + TransactionManager, + QueryComposer\QueryComposer, + QueryParams\LockTableBuilder, +}; + +use PDO; use RuntimeException; -class MysqlLocker extends BaseLocker +/** + * Transactions within locking is not supported for MySQL. + */ +class MysqlLocker implements Locker { - /** - * {@inheritdoc} - */ - public function commit() + protected $pdo; + protected $queryComposer; + protected $transactionManager; + + protected $isLocked = false; + + public function __construct(PDO $pdo, QueryComposer $queryComposer, TransactionManager $transactionManager) { - parent::commit(); - - $sql = $this->queryComposer->composeUnlockTables(); - - $this->pdo->exec($sql); - + $this->pdo = $pdo; + $this->queryComposer = $queryComposer; + $this->transactionManager = $transactionManager; } /** * {@inheritdoc} */ + public function isLocked() : bool + { + return $this->isLocked; + } + /** + * {@inheritdoc} + */ + public function lockExclusive(string $entityType) + { + $this->isLocked = true; + + $query = (new LockTableBuilder()) + ->table($entityType) + ->inExclusiveMode() + ->build(); + + $sql = $this->queryComposer->compose($query); + + $this->pdo->exec($sql); + } + + /** + * {@inheritdoc} + */ + public function lockShare(string $entityType) + { + $this->isLocked = true; + + $query = (new LockTableBuilder()) + ->table($entityType) + ->inShareMode() + ->build(); + + $sql = $this->queryComposer->compose($query); + + $this->pdo->exec($sql); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + if (!$this->isLocked) { + throw new RuntimeException("Can't commit, it was not locked."); + } + + $this->isLocked = false; + + $sql = $this->queryComposer->composeUnlockTables(); + + $this->pdo->exec($sql); + } + + /** + * Lift locking. + * Rolling back within locking is not supported for MySQL. + */ public function rollback() { - parent::rollback(); + if (!$this->isLocked) { + throw new RuntimeException("Can't rollback, it was not locked."); + } + + $this->isLocked = false; $sql = $this->queryComposer->composeUnlockTables(); diff --git a/tests/unit/Espo/ORM/BaseLockerTest.php b/tests/unit/Espo/ORM/BaseLockerTest.php new file mode 100644 index 0000000000..2260a6a840 --- /dev/null +++ b/tests/unit/Espo/ORM/BaseLockerTest.php @@ -0,0 +1,120 @@ +pdo = $this->getMockBuilder(PDO::class)->disableOriginalConstructor()->getMock(); + + $entityFactory = $this->getMockBuilder(EntityFactory::class)->disableOriginalConstructor()->getMock(); + + $metadata = $this->getMockBuilder(Metadata::class)->disableOriginalConstructor()->getMock(); + + $this->transactionManager = $this->getMockBuilder(TransactionManager::class)->disableOriginalConstructor()->getMock(); + + $composer = new MysqlQueryComposer($this->pdo, $entityFactory, $metadata); + + $this->locker = new BaseLocker($this->pdo, $composer, $this->transactionManager); + } + + public function testLockCommit() + { + $this->pdo + ->expects($this->at(0)) + ->method('exec') + ->with('LOCK TABLES `account` WRITE'); + + $this->transactionManager + ->expects($this->at(0)) + ->method('start'); + + $this->pdo + ->expects($this->at(1)) + ->method('exec') + ->with('LOCK TABLES `contact` READ'); + + $this->transactionManager + ->expects($this->at(1)) + ->method('commit'); + + $this->locker->lockExclusive('Account'); + $this->locker->lockShare('Contact'); + + $this->assertTrue($this->locker->isLocked()); + + $this->locker->commit(); + + $this->assertFalse($this->locker->isLocked()); + } + + public function testLockRollback() + { + $this->pdo + ->expects($this->at(0)) + ->method('exec') + ->with('LOCK TABLES `account` WRITE'); + + $this->transactionManager + ->expects($this->at(0)) + ->method('start'); + + $this->pdo + ->expects($this->at(1)) + ->method('exec') + ->with('LOCK TABLES `contact` READ'); + + $this->transactionManager + ->expects($this->at(1)) + ->method('rollback'); + + $this->locker->lockExclusive('Account'); + $this->locker->lockShare('Contact'); + + $this->assertTrue($this->locker->isLocked()); + + $this->locker->commit(); + + $this->assertFalse($this->locker->isLocked()); + } +} diff --git a/tests/unit/Espo/ORM/MysqlLockerTest.php b/tests/unit/Espo/ORM/MysqlLockerTest.php index 4cefed04f8..be8faacae4 100644 --- a/tests/unit/Espo/ORM/MysqlLockerTest.php +++ b/tests/unit/Espo/ORM/MysqlLockerTest.php @@ -65,19 +65,11 @@ class MysqlLockerTest extends \PHPUnit\Framework\TestCase ->method('exec') ->with('LOCK TABLES `account` WRITE'); - $this->transactionManager - ->expects($this->at(0)) - ->method('start'); - $this->pdo ->expects($this->at(1)) ->method('exec') ->with('LOCK TABLES `contact` READ'); - $this->transactionManager - ->expects($this->at(1)) - ->method('commit'); - $this->pdo ->expects($this->at(2)) ->method('exec') @@ -100,19 +92,11 @@ class MysqlLockerTest extends \PHPUnit\Framework\TestCase ->method('exec') ->with('LOCK TABLES `account` WRITE'); - $this->transactionManager - ->expects($this->at(0)) - ->method('start'); - $this->pdo ->expects($this->at(1)) ->method('exec') ->with('LOCK TABLES `contact` READ'); - $this->transactionManager - ->expects($this->at(1)) - ->method('rollback'); - $this->pdo ->expects($this->at(2)) ->method('exec') From b870025f88cc41913786ba488ba22db2ddb25f34 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 7 Dec 2020 13:39:57 +0200 Subject: [PATCH 14/51] cleanup --- application/Espo/Core/Utils/Log/Monolog/Logger.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/application/Espo/Core/Utils/Log/Monolog/Logger.php b/application/Espo/Core/Utils/Log/Monolog/Logger.php index b8c5561749..29ebbbe973 100644 --- a/application/Espo/Core/Utils/Log/Monolog/Logger.php +++ b/application/Espo/Core/Utils/Log/Monolog/Logger.php @@ -29,7 +29,9 @@ namespace Espo\Core\Utils\Log\Monolog; -class Logger extends \Monolog\Logger +use Monolog\Logger as MonologLogger; + +class Logger extends MonologLogger { protected $defaultLevelName = 'DEBUG'; @@ -41,7 +43,6 @@ class Logger extends \Monolog\Logger foreach ($handlers as $handler) { if ($handler->getLevel() > $level) { - $className = get_class($handler); $handler->setLevel($level); } } From 06ff48515cdcd81a9883701490ca83844b2224bb Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 7 Dec 2020 16:01:49 +0200 Subject: [PATCH 15/51] log cleanup --- .../Core/Upgrades/Actions/Base/Install.php | 2 - application/Espo/Core/Utils/Log.php | 4 +- .../Espo/Core/Utils/Log/Monolog/Logger.php | 50 ------------------- 3 files changed, 2 insertions(+), 54 deletions(-) delete mode 100644 application/Espo/Core/Utils/Log/Monolog/Logger.php diff --git a/application/Espo/Core/Upgrades/Actions/Base/Install.php b/application/Espo/Core/Upgrades/Actions/Base/Install.php index 77b4e20f59..8a684f676d 100644 --- a/application/Espo/Core/Upgrades/Actions/Base/Install.php +++ b/application/Espo/Core/Upgrades/Actions/Base/Install.php @@ -77,8 +77,6 @@ class Install extends \Espo\Core\Upgrades\Actions\Base protected function initPackage(array $data) { - $GLOBALS['log']->setLevel('info'); - $processId = $data['id']; if (empty($processId)) { diff --git a/application/Espo/Core/Utils/Log.php b/application/Espo/Core/Utils/Log.php index ba162f2f8e..63f9c89455 100644 --- a/application/Espo/Core/Utils/Log.php +++ b/application/Espo/Core/Utils/Log.php @@ -29,9 +29,9 @@ namespace Espo\Core\Utils; -use Espo\Core\Utils\Log\Monolog\Logger; +use Monolog\Logger as MonologLogger; -class Log extends Logger +class Log extends MonologLogger { } diff --git a/application/Espo/Core/Utils/Log/Monolog/Logger.php b/application/Espo/Core/Utils/Log/Monolog/Logger.php deleted file mode 100644 index 29ebbbe973..0000000000 --- a/application/Espo/Core/Utils/Log/Monolog/Logger.php +++ /dev/null @@ -1,50 +0,0 @@ -getHandlers(); - - foreach ($handlers as $handler) { - if ($handler->getLevel() > $level) { - $handler->setLevel($level); - } - } - } -} From 3a89b16b919b5e6792b500470d1456c9957347ad Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 7 Dec 2020 16:13:51 +0200 Subject: [PATCH 16/51] log naming change --- application/Espo/Core/Loaders/Log.php | 9 +++++---- .../Handler/{StreamHandler.php => EspoFileHandler.php} | 3 ++- ...tatingFileHandler.php => EspoRotatingFileHandler.php} | 5 +---- 3 files changed, 8 insertions(+), 9 deletions(-) rename application/Espo/Core/Utils/Log/Monolog/Handler/{StreamHandler.php => EspoFileHandler.php} (96%) rename application/Espo/Core/Utils/Log/Monolog/Handler/{RotatingFileHandler.php => EspoRotatingFileHandler.php} (97%) diff --git a/application/Espo/Core/Loaders/Log.php b/application/Espo/Core/Loaders/Log.php index e33e27c966..bba598ea51 100644 --- a/application/Espo/Core/Loaders/Log.php +++ b/application/Espo/Core/Loaders/Log.php @@ -35,8 +35,8 @@ use Espo\Core\{ }; use Espo\Core\Utils\Log\Monolog\Handler\{ - RotatingFileHandler, - StreamHandler, + EspoRotatingFileHandler, + EspoFileHandler, }; use Monolog\ErrorHandler as MonologErrorHandler; @@ -63,9 +63,10 @@ class Log implements Loader if ($rotation) { $maxFileNumber = $config->get('logger.maxFileNumber', 30); - $handler = new RotatingFileHandler($path, $maxFileNumber, $levelCode); + + $handler = new EspoRotatingFileHandler($path, $maxFileNumber, $levelCode); } else { - $handler = new StreamHandler($path, $levelCode); + $handler = new EspoFileHandler($path, $levelCode); } $log->pushHandler($handler); diff --git a/application/Espo/Core/Utils/Log/Monolog/Handler/StreamHandler.php b/application/Espo/Core/Utils/Log/Monolog/Handler/EspoFileHandler.php similarity index 96% rename from application/Espo/Core/Utils/Log/Monolog/Handler/StreamHandler.php rename to application/Espo/Core/Utils/Log/Monolog/Handler/EspoFileHandler.php index 1441854c04..8618760739 100644 --- a/application/Espo/Core/Utils/Log/Monolog/Handler/StreamHandler.php +++ b/application/Espo/Core/Utils/Log/Monolog/Handler/EspoFileHandler.php @@ -30,13 +30,14 @@ namespace Espo\Core\Utils\Log\Monolog\Handler; use Monolog\Logger; +use Monolog\Handler\StreamHandler as MonologStreamHandler; use Espo\Core\Utils\File\Manager as FileManager; use LogicException; use UnexpectedValueException; -class StreamHandler extends \Monolog\Handler\StreamHandler +class EspoFileHandler extends MonologStreamHandler { protected $fileManager; diff --git a/application/Espo/Core/Utils/Log/Monolog/Handler/RotatingFileHandler.php b/application/Espo/Core/Utils/Log/Monolog/Handler/EspoRotatingFileHandler.php similarity index 97% rename from application/Espo/Core/Utils/Log/Monolog/Handler/RotatingFileHandler.php rename to application/Espo/Core/Utils/Log/Monolog/Handler/EspoRotatingFileHandler.php index 6efdb4c90c..81b88373a0 100644 --- a/application/Espo/Core/Utils/Log/Monolog/Handler/RotatingFileHandler.php +++ b/application/Espo/Core/Utils/Log/Monolog/Handler/EspoRotatingFileHandler.php @@ -31,11 +31,8 @@ namespace Espo\Core\Utils\Log\Monolog\Handler; use Monolog\Logger; -class RotatingFileHandler extends StreamHandler +class EspoRotatingFileHandler extends EspoFileHandler { - /** - * Date format as a part of filename. - */ protected $dateFormat = 'Y-m-d'; protected $filenameFormat = '{filename}-{date}'; From b1e31c9999f10847e73d15d13c9f90f1666b2684 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 8 Dec 2020 12:53:16 +0200 Subject: [PATCH 17/51] fix tests --- tests/unit/Espo/Core/Utils/File/ManagerTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/Espo/Core/Utils/File/ManagerTest.php b/tests/unit/Espo/Core/Utils/File/ManagerTest.php index 2501138ced..bff0402398 100644 --- a/tests/unit/Espo/Core/Utils/File/ManagerTest.php +++ b/tests/unit/Espo/Core/Utils/File/ManagerTest.php @@ -233,7 +233,7 @@ class ManagerTest extends \PHPUnit\Framework\TestCase $this->assertEquals($result, $this->object->getParentDirName($input, true)); } - public function testGetSingeFileListAll() + public function testGetSingleFileListAll() { $input = array ( 'custom' => @@ -266,7 +266,7 @@ class ManagerTest extends \PHPUnit\Framework\TestCase ); $result = array_map('\Espo\Core\Utils\Util::fixPath', $result); - $this->assertEquals($result, $this->reflection->invokeMethod('getSingeFileList', array($input))); + $this->assertEquals($result, $this->reflection->invokeMethod('getSingleFileList', array($input))); } public function testGetSingeFileListOnlyFiles() @@ -297,7 +297,7 @@ class ManagerTest extends \PHPUnit\Framework\TestCase ); $result = array_map('\Espo\Core\Utils\Util::fixPath', $result); - $this->assertEquals($result, $this->reflection->invokeMethod('getSingeFileList', array($input, true))); + $this->assertEquals($result, $this->reflection->invokeMethod('getSingleFileList', array($input, true))); } public function testGetSingeFileListOnlyDirs() @@ -331,7 +331,7 @@ class ManagerTest extends \PHPUnit\Framework\TestCase ); $result = array_map('\Espo\Core\Utils\Util::fixPath', $result); - $this->assertEquals($result, $this->reflection->invokeMethod('getSingeFileList', array($input, false))); + $this->assertEquals($result, $this->reflection->invokeMethod('getSingleFileList', array($input, false))); } public function fileListSets() From d3dcaa92e5f6932d58ca5ea701aab06322d6fc3b Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 8 Dec 2020 14:24:56 +0200 Subject: [PATCH 18/51] log handlers --- application/Espo/Core/Loaders/Log.php | 39 +----- .../Espo/Core/Log/DefaultHandlerLoader.php | 115 ++++++++++++++++ .../Log/EspoRotatingFileHandlerLoader.php | 50 +++++++ .../Handler/EspoFileHandler.php | 29 ++-- .../Handler/EspoRotatingFileHandler.php | 19 +-- .../Espo/Core/Log/HandlerListLoader.php | 92 +++++++++++++ application/Espo/Core/Log/HandlerLoader.php | 39 ++++++ application/Espo/Core/Log/LogLoader.php | 125 ++++++++++++++++++ composer.json | 2 +- composer.lock | 122 +++++++++++++---- .../Espo/Core/Log/HandlerListLoaderTest.php | 125 ++++++++++++++++++ 11 files changed, 666 insertions(+), 91 deletions(-) create mode 100644 application/Espo/Core/Log/DefaultHandlerLoader.php create mode 100644 application/Espo/Core/Log/EspoRotatingFileHandlerLoader.php rename application/Espo/Core/{Utils/Log/Monolog => Log}/Handler/EspoFileHandler.php (77%) rename application/Espo/Core/{Utils/Log/Monolog => Log}/Handler/EspoRotatingFileHandler.php (84%) create mode 100644 application/Espo/Core/Log/HandlerListLoader.php create mode 100644 application/Espo/Core/Log/HandlerLoader.php create mode 100644 application/Espo/Core/Log/LogLoader.php create mode 100644 tests/unit/Espo/Core/Log/HandlerListLoaderTest.php diff --git a/application/Espo/Core/Loaders/Log.php b/application/Espo/Core/Loaders/Log.php index bba598ea51..a72bfef0a8 100644 --- a/application/Espo/Core/Loaders/Log.php +++ b/application/Espo/Core/Loaders/Log.php @@ -30,51 +30,22 @@ namespace Espo\Core\Loaders; use Espo\Core\{ - Utils\Config, + Log\LogLoader, Utils\Log as LogService, }; -use Espo\Core\Utils\Log\Monolog\Handler\{ - EspoRotatingFileHandler, - EspoFileHandler, -}; - -use Monolog\ErrorHandler as MonologErrorHandler; - class Log implements Loader { - protected $config; + protected $logLoader; - public function __construct(Config $config) + public function __construct(LogLoader $logLoader) { - $this->config = $config; + $this->logLoader = $logLoader; } public function load() : LogService { - $config = $this->config; - - $path = $config->get('logger.path', 'data/logs/espo.log'); - $rotation = $config->get('logger.rotation', true); - - $log = new LogService('Espo'); - - $levelCode = $log::toMonologLevel($config->get('logger.level', 'WARNING')); - - if ($rotation) { - $maxFileNumber = $config->get('logger.maxFileNumber', 30); - - $handler = new EspoRotatingFileHandler($path, $maxFileNumber, $levelCode); - } else { - $handler = new EspoFileHandler($path, $levelCode); - } - - $log->pushHandler($handler); - - $errorHandler = new MonologErrorHandler($log); - - $errorHandler->registerExceptionHandler(null, false); - $errorHandler->registerErrorHandler([], false); + $log = $this->logLoader->load(); $GLOBALS['log'] = $log; diff --git a/application/Espo/Core/Log/DefaultHandlerLoader.php b/application/Espo/Core/Log/DefaultHandlerLoader.php new file mode 100644 index 0000000000..17285b1a18 --- /dev/null +++ b/application/Espo/Core/Log/DefaultHandlerLoader.php @@ -0,0 +1,115 @@ +createInstance($className, $params); + + $formatter = $this->loadFormatter($data); + + if ($formatter) { + $handler->setFormatter($formatter); + } + + return $handler; + } + + protected function loadFormatter(array $data) : ?FormatterInterface + { + $formatterData = $data['formatter'] ?? null; + + if (!$formatterData || !is_array($formatterData)) { + return null; + } + + $className = $formatterData['className'] ?? null; + + if (!$className) { + return null; + } + + $params = $formatterData['params'] ?? []; + + return $this->createInstance($className, $params); + } + + protected function createInstance(string $className, array $params) : object + { + $class = new ReflectionClass($className); + + $constructor = $class->getConstructor(); + + $argumentList = []; + + foreach ($constructor->getParameters() as $parameter) { + $name = $parameter->getName(); + + if (array_key_exists($name, $params)) { + $value = $params[$name]; + } + else if ($parameter->isDefaultValueAvailable()) { + $value = $parameter->getDefaultValue(); + } else { + continue; + } + + $argumentList[] = $value; + } + + return $class->newInstanceArgs($argumentList); + } +} diff --git a/application/Espo/Core/Log/EspoRotatingFileHandlerLoader.php b/application/Espo/Core/Log/EspoRotatingFileHandlerLoader.php new file mode 100644 index 0000000000..1524770e18 --- /dev/null +++ b/application/Espo/Core/Log/EspoRotatingFileHandlerLoader.php @@ -0,0 +1,50 @@ +fileManager = new FileManager(); } - protected function getFileManager() - { - return $this->fileManager; - } - - protected function write(array $record) + protected function write(array $record): void { if (!$this->url) { throw new LogicException( - 'Missing logger path, the stream can not be opened. Please check logger options in the data/config.php.' + 'Missing logger path. Check logger params in the data/config.php.' ); } $this->errorMessage = null; if (!is_writable($this->url)) { - $this->getFileManager()->checkCreateFile($this->url); + $this->fileManager->checkCreateFile($this->url); } if (is_writable($this->url)) { set_error_handler([$this, 'customErrorHandler']); - $this->getFileManager()->appendContents($this->url, $this->pruneMessage($record)); + $this->fileManager->appendContents($this->url, $this->pruneMessage($record)); restore_error_handler(); } if (isset($this->errorMessage)) { throw new UnexpectedValueException( - sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url) + sprintf('File "%s" could not be opened: ' . $this->errorMessage, $this->url) ); } } @@ -100,4 +97,4 @@ class EspoFileHandler extends MonologStreamHandler return (string) $record['formatted']; } -} \ No newline at end of file +} diff --git a/application/Espo/Core/Utils/Log/Monolog/Handler/EspoRotatingFileHandler.php b/application/Espo/Core/Log/Handler/EspoRotatingFileHandler.php similarity index 84% rename from application/Espo/Core/Utils/Log/Monolog/Handler/EspoRotatingFileHandler.php rename to application/Espo/Core/Log/Handler/EspoRotatingFileHandler.php index 81b88373a0..e8a89f2a7a 100644 --- a/application/Espo/Core/Utils/Log/Monolog/Handler/EspoRotatingFileHandler.php +++ b/application/Espo/Core/Log/Handler/EspoRotatingFileHandler.php @@ -27,7 +27,7 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Core\Utils\Log\Monolog\Handler; +namespace Espo\Core\Log\Handler; use Monolog\Logger; @@ -41,7 +41,7 @@ class EspoRotatingFileHandler extends EspoFileHandler protected $maxFiles; - public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true) + public function __construct(string $filename, int $maxFiles = 0, $level = Logger::DEBUG, bool $bubble = true) { $this->filename = $filename; $this->maxFiles = (int) $maxFiles; @@ -51,12 +51,6 @@ class EspoRotatingFileHandler extends EspoFileHandler $this->rotate(); } - public function setFilenameFormat($filenameFormat, $dateFormat) - { - $this->filenameFormat = $filenameFormat; - $this->dateFormat = $dateFormat; - } - protected function rotate() { if (0 === $this->maxFiles) { @@ -64,18 +58,17 @@ class EspoRotatingFileHandler extends EspoFileHandler } $filePattern = $this->getFilePattern(); - $dirPath = $this->getFileManager()->getDirName($this->filename); - $logFiles = $this->getFileManager()->getFileList($dirPath, false, $filePattern, true); + $dirPath = $this->fileManager->getDirName($this->filename); + $logFiles = $this->fileManager->getFileList($dirPath, false, $filePattern, true); if (!empty($logFiles) && count($logFiles) > $this->maxFiles) { - usort($logFiles, function ($a, $b) { return strcmp($b, $a); }); $logFilesToBeRemoved = array_slice($logFiles, $this->maxFiles); - $this->getFileManager()->removeFile($logFilesToBeRemoved, $dirPath); + $this->fileManager->removeFile($logFilesToBeRemoved, $dirPath); } } @@ -114,4 +107,4 @@ class EspoRotatingFileHandler extends EspoFileHandler return $glob; } -} \ No newline at end of file +} diff --git a/application/Espo/Core/Log/HandlerListLoader.php b/application/Espo/Core/Log/HandlerListLoader.php new file mode 100644 index 0000000000..c5b666daf6 --- /dev/null +++ b/application/Espo/Core/Log/HandlerListLoader.php @@ -0,0 +1,92 @@ +injectableFactory = $injectableFactory; + $this->defaultLoader = $defaultLoader; + } + + public function load(array $dataList, ?string $defaultLevel = null) : array + { + $handlerList = []; + + foreach ($dataList as $item) { + $handler = $this->loadHandler($item, $defaultLevel); + + if (!$handler) { + continue; + } + + $handlerList[] = $handler; + } + + return $handlerList; + } + + protected function loadHandler(array $data, ?string $defaultLevel = null) : ?HandlerInterface + { + $params = $data['params'] ?? []; + + $level = $data['level'] ?? $defaultLevel; + + if ($level) { + $params['level'] = Logger::toMonologLevel($level); + } + + $loaderClassName = $data['loaderClassName'] ?? null; + + if ($loaderClassName) { + $loader = $this->injectableFactory->create($loaderClassName); + + $handler = $loader->load($params); + + return $handler; + } + + $handler = $this->defaultLoader->load($data, $defaultLevel); + + return $handler; + } +} diff --git a/application/Espo/Core/Log/HandlerLoader.php b/application/Espo/Core/Log/HandlerLoader.php new file mode 100644 index 0000000000..4c0fedd4f4 --- /dev/null +++ b/application/Espo/Core/Log/HandlerLoader.php @@ -0,0 +1,39 @@ +config = $config; + $this->injectableFactory = $injectableFactory; + } + + public function load() : Log + { + $log = new Log('Espo'); + + $handlerDataList = $this->config->get('logger.handlerList') ?? null; + + if ($handlerDataList) { + $level = $this->config->get('logger.level'); + + $loader = $this->injectableFactory->create(HandlerListLoader::class); + + $handlerList = $loader->load($handlerDataList, $level); + } + else { + $handler = $this->createDefaultHandler(); + + $handlerList = [$handler]; + } + + foreach ($handlerList as $handler) { + $log->pushHandler($handler); + } + + $errorHandler = new MonologErrorHandler($log); + + $errorHandler->registerExceptionHandler(null, false); + $errorHandler->registerErrorHandler([], false); + + return $log; + } + + protected function createDefaultHandler() : HandlerInterface + { + $path = $this->config->get('logger.path') ?? self::PATH; + $rotation = $this->config->get('logger.rotation') ?? true; + $level = $this->config->get('logger.level') ?? self::DEFAULT_LEVEL; + + $levelCode = Logger::toMonologLevel($level); + + if ($rotation) { + $maxFileNumber = $this->config->get('logger.maxFileNumber') ?? self::MAX_FILE_NUMBER; + + $handler = new EspoRotatingFileHandler($path, $maxFileNumber, $levelCode); + } else { + $handler = new EspoFileHandler($path, $levelCode); + } + + $formatter = new LineFormatter( + self::LINE_FORMAT, + self::DATE_FORMAT + ); + + $handler->setFormatter($formatter); + + return $handler; + } +} diff --git a/composer.json b/composer.json index 3fc0b6e5fa..8dd1374237 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "laminas/laminas-ldap": "2.*", "laminas/laminas-mime": "^2.7.1", "laminas/laminas-stdlib": "^3.1", - "monolog/monolog": "1.*", + "monolog/monolog": "2.*", "yzalis/identicon": "*", "zordius/lightncandy": "1.*", "composer/semver": "^3", diff --git a/composer.lock b/composer.lock index 1b38412511..d7a3ec7063 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f4230248c7de33c9a74a7fc1c6592a5c", + "content-hash": "e164d331463e4b99a826e2bab0006784", "packages": [ { "name": "cboden/ratchet", @@ -119,6 +119,20 @@ "validation", "versioning" ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], "time": "2020-05-26T18:22:04+00:00" }, { @@ -670,6 +684,12 @@ "cron", "schedule" ], + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], "time": "2020-08-21T02:30:13+00:00" }, { @@ -1453,55 +1473,58 @@ }, { "name": "monolog/monolog", - "version": "1.20.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "55841909e2bcde01b5318c35f2b74f8ecc86e037" + "reference": "f9eee5cec93dfb313a38b6b288741e84e53f02d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/55841909e2bcde01b5318c35f2b74f8ecc86e037", - "reference": "55841909e2bcde01b5318c35f2b74f8ecc86e037", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f9eee5cec93dfb313a38b6b288741e84e53f02d5", + "reference": "f9eee5cec93dfb313a38b6b288741e84e53f02d5", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "php": ">=7.2", + "psr/log": "^1.0.1" }, "provide": { "psr/log-implementation": "1.0.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9", + "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", + "elasticsearch/elasticsearch": "^6.0", + "graylog2/gelf-php": "^1.4.2", "php-amqplib/php-amqplib": "~2.4", "php-console/php-console": "^3.1.3", - "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", + "php-parallel-lint/php-parallel-lint": "^1.0", + "phpspec/prophecy": "^1.6.1", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "~5.3" + "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -1527,7 +1550,17 @@ "logging", "psr-3" ], - "time": "2016-07-02T14:02:10+00:00" + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2020-07-23T08:41:23+00:00" }, { "name": "nesbot/carbon", @@ -2813,6 +2846,16 @@ "micro", "router" ], + "funding": [ + { + "url": "https://opencollective.com/slimphp", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slim/slim", + "type": "tidelift" + } + ], "time": "2020-04-14T20:49:48+00:00" }, { @@ -2922,6 +2965,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-30T14:07:33+00:00" }, { @@ -4545,16 +4602,16 @@ }, { "name": "phpunit/phpunit", - "version": "8.5.3", + "version": "8.5.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "67750516bc02f300e2742fed2f50177f8f37bedf" + "reference": "34c18baa6a44f1d1fbf0338907139e9dce95b997" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/67750516bc02f300e2742fed2f50177f8f37bedf", - "reference": "67750516bc02f300e2742fed2f50177f8f37bedf", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/34c18baa6a44f1d1fbf0338907139e9dce95b997", + "reference": "34c18baa6a44f1d1fbf0338907139e9dce95b997", "shasum": "" }, "require": { @@ -4624,7 +4681,17 @@ "testing", "xunit" ], - "time": "2020-03-31T08:52:04+00:00" + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-22T07:06:58+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -5407,5 +5474,6 @@ "ext-curl": "*", "ext-exif": "*" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "1.1.0" } diff --git a/tests/unit/Espo/Core/Log/HandlerListLoaderTest.php b/tests/unit/Espo/Core/Log/HandlerListLoaderTest.php new file mode 100644 index 0000000000..a84b7ec9ea --- /dev/null +++ b/tests/unit/Espo/Core/Log/HandlerListLoaderTest.php @@ -0,0 +1,125 @@ +injectableFactory = $this->getMockBuilder(InjectableFactory::class) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testLoad1() + { + $defaultLoader = $this->getMockBuilder(DefaultHandlerLoader::class) + ->disableOriginalConstructor() + ->getMock(); + + $listLoader = new HandlerListLoader($this->injectableFactory, $defaultLoader); + + $dataList = [ + [ + 'className' => 'Espo\\Core\\Log\\Handler\\EspoRotatingFileHandler', + 'params' => [ + 'filename' => 'data/logs/test-1.log', + ], + 'level' => 'DEBUG', + 'formatter' => [ + 'className' => 'Monolog\\Formatter\\LineFormatter', + 'params' => [ + 'dateFormat' => 'Y-m-d H:i:s', + ], + ] + ], + [ + 'loaderClassName' => EspoRotatingFileHandlerLoader::class, + 'params' => [ + 'filename' => 'data/logs/test-2.log', + ], + 'level' => 'NOTICE', + ], + ]; + + $handler1 = $this->getMockBuilder(EspoRotatingFileHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $defaultLoader + ->expects($this->once()) + ->method('load') + ->with($dataList[0], 'NOTICE') + ->willReturn($handler1); + + $loader = $this->getMockBuilder(EspoRotatingFileHandlerLoader::class) + ->disableOriginalConstructor() + ->getMock(); + + $handler = $this->getMockBuilder(EspoRotatingFileHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->injectableFactory + ->expects($this->once()) + ->method('create') + ->with(EspoRotatingFileHandlerLoader::class) + ->willReturn($loader); + + $params = [ + 'filename' => 'data/logs/test-2.log', + 'level' => Logger::NOTICE, + ]; + + $loader + ->expects($this->once()) + ->method('load') + ->with($params) + ->willReturn($handler); + + $list = $listLoader->load($dataList, 'NOTICE'); + + $this->assertEquals(2, count($list)); + + $this->assertInstanceOf(EspoRotatingFileHandler::class, $list[0]); + } +} From df16f3791265cb0b65347e0f78725d7a2c0e82cd Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 9 Dec 2020 14:25:53 +0200 Subject: [PATCH 19/51] cleanup --- application/Espo/ORM/QueryParams/BaseBuilderTrait.php | 1 - application/Espo/ORM/QueryParams/SelectBuilder.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/application/Espo/ORM/QueryParams/BaseBuilderTrait.php b/application/Espo/ORM/QueryParams/BaseBuilderTrait.php index 71f1a70082..25dc9c672a 100644 --- a/application/Espo/ORM/QueryParams/BaseBuilderTrait.php +++ b/application/Espo/ORM/QueryParams/BaseBuilderTrait.php @@ -52,5 +52,4 @@ trait BaseBuilderTrait $this->params = $query->getRawParams(); } - } diff --git a/application/Espo/ORM/QueryParams/SelectBuilder.php b/application/Espo/ORM/QueryParams/SelectBuilder.php index 61f6ef89ba..f810ac9f84 100644 --- a/application/Espo/ORM/QueryParams/SelectBuilder.php +++ b/application/Espo/ORM/QueryParams/SelectBuilder.php @@ -251,6 +251,4 @@ class SelectBuilder implements Builder return $this; } - - } From e35ecf96ca9554329ed543f46200f8eab7dbd940 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Dec 2020 11:19:34 +0200 Subject: [PATCH 20/51] Bump ini from 1.3.5 to 1.3.7 (#1862) Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7. - [Release notes](https://github.com/isaacs/ini/releases) - [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e6420ea9a..851cfa3348 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1527,9 +1527,9 @@ "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", "dev": true }, "interpret": { From 105b494710ba630ea38b89552c60cdf10bf63d65 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 14 Dec 2020 12:49:16 +0200 Subject: [PATCH 21/51] fix binding for php 8 --- .../Espo/Core/Binding/BindingContainer.php | 12 +++++---- .../Core/Binding/BindingContainerTest.php | 27 ++++++++++++++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/application/Espo/Core/Binding/BindingContainer.php b/application/Espo/Core/Binding/BindingContainer.php index 98a2bebc93..e67cc02ffc 100644 --- a/application/Espo/Core/Binding/BindingContainer.php +++ b/application/Espo/Core/Binding/BindingContainer.php @@ -74,17 +74,19 @@ class BindingContainer return $this->data->getContext($className, $key); } - $dependencyClass = null; + $dependencyClassName = null; - if ($param->getType()) { - $dependencyClass = $param->getClass(); + $type = $param->getType(); + + if ($type && !$type->isBuiltin()) { + $dependencyClassName = $type->getName(); } $key = null; $keyWithParamName = null; - if ($dependencyClass) { - $key = $dependencyClass->getName(); + if ($dependencyClassName) { + $key = $dependencyClassName; if ($key) { $keyWithParamName = $key . ' $' . $param->getName(); diff --git a/tests/unit/Espo/Core/Binding/BindingContainerTest.php b/tests/unit/Espo/Core/Binding/BindingContainerTest.php index df26db7441..6618021438 100644 --- a/tests/unit/Espo/Core/Binding/BindingContainerTest.php +++ b/tests/unit/Espo/Core/Binding/BindingContainerTest.php @@ -39,7 +39,7 @@ use Espo\Core\{ use ReflectionClass; use ReflectionParameter; -use ReflectionType; +use ReflectionNamedType; class BindingContainerTest extends \PHPUnit\Framework\TestCase { @@ -75,17 +75,32 @@ class BindingContainerTest extends \PHPUnit\Framework\TestCase $class = null; + $type = $this->getMockBuilder(ReflectionNamedType::class)->disableOriginalConstructor()->getMock(); + if ($className) { $class = $this->createClassMock($className); - $type = $this->getMockBuilder(ReflectionType::class)->disableOriginalConstructor()->getMock(); - - $param + $type ->expects($this->any()) - ->method('getType') - ->willReturn($type); + ->method('isBuiltin') + ->willReturn(false); + + $type + ->expects($this->any()) + ->method('getName') + ->willReturn($className); } + $type + ->expects($this->any()) + ->method('isBuiltin') + ->willReturn(true); + + $param + ->expects($this->any()) + ->method('getType') + ->willReturn($type); + $param ->expects($this->any()) ->method('getName') From 8a23bba39ad2403708d2c72de72acd3cb70def1c Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 14 Dec 2020 13:01:37 +0200 Subject: [PATCH 22/51] fix authentication param order --- application/Espo/Core/Authentication/Authentication.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/Espo/Core/Authentication/Authentication.php b/application/Espo/Core/Authentication/Authentication.php index 5639037489..c09017fdd0 100644 --- a/application/Espo/Core/Authentication/Authentication.php +++ b/application/Espo/Core/Authentication/Authentication.php @@ -84,7 +84,6 @@ class Authentication protected $auth2FAFactory; public function __construct( - bool $allowAnyAccess = false, ApplicationUser $applicationUser, ApplicationState $applicationState, Config $config, @@ -92,7 +91,8 @@ class Authentication EntityManagerProxy $entityManagerProxy, LoginFactory $authLoginFactory, TwoFAFactory $auth2FAFactory, - AuthTokenManager $authTokenManager + AuthTokenManager $authTokenManager, + bool $allowAnyAccess = false ) { $this->allowAnyAccess = $allowAnyAccess; From e19462c045339e0707646d8c52ffb7b7ef8152e8 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 14 Dec 2020 13:10:07 +0200 Subject: [PATCH 23/51] fix query composer notice --- application/Espo/ORM/QueryComposer/BaseQueryComposer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/Espo/ORM/QueryComposer/BaseQueryComposer.php b/application/Espo/ORM/QueryComposer/BaseQueryComposer.php index 55fd97d6fa..7e2cb09d7d 100644 --- a/application/Espo/ORM/QueryComposer/BaseQueryComposer.php +++ b/application/Espo/ORM/QueryComposer/BaseQueryComposer.php @@ -1085,7 +1085,7 @@ abstract class BaseQueryComposer implements QueryComposer } protected function convertComplexExpression( - ?Entity $entity = null, string $attribute, bool $distinct = false, array &$params + ?Entity $entity = null, string $attribute, bool $distinct, array &$params ) : string { $function = null; From a2be80501a3f5450e5bc67978e37906f41331e00 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 14 Dec 2020 13:15:07 +0200 Subject: [PATCH 24/51] fix query composer notices --- .../Espo/ORM/QueryComposer/BaseQueryComposer.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/application/Espo/ORM/QueryComposer/BaseQueryComposer.php b/application/Espo/ORM/QueryComposer/BaseQueryComposer.php index 7e2cb09d7d..a166ffed54 100644 --- a/application/Espo/ORM/QueryComposer/BaseQueryComposer.php +++ b/application/Espo/ORM/QueryComposer/BaseQueryComposer.php @@ -1145,7 +1145,7 @@ abstract class BaseQueryComposer implements QueryComposer } protected function getFunctionArgumentPart( - Entity $entity, string $attribute, bool $distinct = false, array &$params + Entity $entity, string $attribute, bool $distinct, array &$params ) : string { $argument = $attribute; @@ -1767,7 +1767,7 @@ abstract class BaseQueryComposer implements QueryComposer return null; } - protected function getBelongsToJoinsPart(Entity $entity, ?array $select = null, array $skipList = [], array $params) : string + protected function getBelongsToJoinsPart(Entity $entity, ?array $select, array $skipList, array $params) : string { $joinsArr = []; @@ -1939,7 +1939,7 @@ abstract class BaseQueryComposer implements QueryComposer } protected function getAggregationSelectPart( - Entity $entity, string $aggregation, string $aggregationBy, bool $distinct = false, array $params + Entity $entity, string $aggregation, string $aggregationBy, bool $distinct, array $params ) : ?string { if (!isset($entity->getAttributes()[$aggregationBy])) { return null; @@ -2510,7 +2510,7 @@ abstract class BaseQueryComposer implements QueryComposer } protected function getJoinsTypePart( - Entity $entity, array $joins, bool $isLeft = false, $joinConditions = [], array $params + Entity $entity, array $joins, bool $isLeft, $joinConditions, array $params ) : string { $joinSqlList = []; @@ -2556,7 +2556,7 @@ abstract class BaseQueryComposer implements QueryComposer return implode(' ', $joinSqlList); } - protected function buildJoinConditionStatement(Entity $entity, $alias = null, $left, $right, array $params) + protected function buildJoinConditionStatement(Entity $entity, $alias, $left, $right, array $params) { $sql = ''; From 2d26d1a9d1656b848c7c679b5c7ee23a45419fde Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 14 Dec 2020 13:18:23 +0200 Subject: [PATCH 25/51] fix notice --- application/Espo/Modules/Crm/Services/Activities.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/Espo/Modules/Crm/Services/Activities.php b/application/Espo/Modules/Crm/Services/Activities.php index 6968ca6c5d..7f1c52cad3 100644 --- a/application/Espo/Modules/Crm/Services/Activities.php +++ b/application/Espo/Modules/Crm/Services/Activities.php @@ -1349,7 +1349,7 @@ class Activities implements return Select::fromRaw($selectParams); } - protected function getActivitiesSelectParams(Entity $entity, $scope, array $statusList = [], $isHistory) + protected function getActivitiesSelectParams(Entity $entity, $scope, array $statusList, $isHistory) { $selectManager = $this->getSelectManagerFactory()->create($scope); From c65b9d8cc2427c5677ed67487bd219f7742f86a8 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 14 Dec 2020 13:19:50 +0200 Subject: [PATCH 26/51] fix merge notice --- application/Espo/Core/Controllers/Record.php | 10 +++++++++- application/Espo/Services/Record.php | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/application/Espo/Core/Controllers/Record.php b/application/Espo/Core/Controllers/Record.php index 94d257b57f..0e9319c9be 100644 --- a/application/Espo/Core/Controllers/Record.php +++ b/application/Espo/Core/Controllers/Record.php @@ -43,6 +43,8 @@ use Espo\Core\{ Record\Collection as RecordCollection, }; +use StdClass; + class Record extends Base { const MAX_SIZE_LIMIT = 200; @@ -441,9 +443,15 @@ class Record extends Base throw new BadRequest(); } - if (empty($data->targetId) || empty($data->sourceIds) || !is_array($data->sourceIds) || !($data->attributes instanceof \StdClass)) { + if ( + empty($data->targetId) || + empty($data->sourceIds) || + !is_array($data->sourceIds) || + !($data->attributes instanceof StdClass) + ) { throw new BadRequest(); } + $targetId = $data->targetId; $sourceIds = $data->sourceIds; $attributes = $data->attributes; diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php index e4c982eede..b86f16631a 100644 --- a/application/Espo/Services/Record.php +++ b/application/Espo/Services/Record.php @@ -2205,7 +2205,7 @@ class Record implements Crud, } } - public function merge($id, array $sourceIdList = [], $attributes) + public function merge(string $id, array $sourceIdList, StdClass $attributes) { if (empty($id)) { throw new Error("No ID passed."); From 9dc050332e9b6a2140674a1f94c72ba7c6ae7c92 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 14 Dec 2020 13:48:25 +0200 Subject: [PATCH 27/51] controller manager notice fix --- application/Espo/Core/ControllerManager.php | 27 ++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/application/Espo/Core/ControllerManager.php b/application/Espo/Core/ControllerManager.php index 5797fde92e..026f1c8773 100644 --- a/application/Espo/Core/ControllerManager.php +++ b/application/Espo/Core/ControllerManager.php @@ -125,18 +125,23 @@ class ControllerManager $params = $method->getParameters(); - if (count($params) > 0) { - $firstParamClass = $params[0]->getClass(); + if (count($params) === 0) { + return false; + } - if ( - $firstParamClass - && - ( - $firstParamClass->getName() === Request::class || $firstParamClass->isSubclassOf(Request::class) - ) - ) { - return true; - } + $type = $params[0]->getType(); + + if (!$type || $type->isBuiltin()) { + return false; + } + + $firstParamClass = new ReflectionClass($type->getName()); + + if ( + $firstParamClass->getName() === Request::class || + $firstParamClass->isSubclassOf(Request::class) + ) { + return true; } return false; From bb10b0a266f2d796b134ecbcb8a99cfa8031f441 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 14 Dec 2020 13:48:32 +0200 Subject: [PATCH 28/51] controller manager test --- .../unit/Espo/Core/ControllerManagerTest.php | 82 +++++++++++++++++++ .../Controllers/TestController.php | 43 ++++++++++ 2 files changed, 125 insertions(+) create mode 100644 tests/unit/Espo/Core/ControllerManagerTest.php create mode 100644 tests/unit/testClasses/Controllers/TestController.php diff --git a/tests/unit/Espo/Core/ControllerManagerTest.php b/tests/unit/Espo/Core/ControllerManagerTest.php new file mode 100644 index 0000000000..8734b408e9 --- /dev/null +++ b/tests/unit/Espo/Core/ControllerManagerTest.php @@ -0,0 +1,82 @@ +classFinder = $this->getMockBuilder(ClassFinder::class)->disableOriginalConstructor()->getMock(); + $this->injectableFactory = $this->getMockBuilder(InjectableFactory::class)->disableOriginalConstructor()->getMock(); + $this->request = $this->getMockBuilder(RequestWrapper::class)->disableOriginalConstructor()->getMock(); + $this->response = $this->getMockBuilder(ResponseWrapper::class)->disableOriginalConstructor()->getMock(); + + $this->controllerManager = new ControllerManager($this->injectableFactory, $this->classFinder); + } + + public function testAction1() + { + $controller = $this->getMockBuilder(TestController::class)->disableOriginalConstructor()->getMock(); + + $this->classFinder + ->expects($this->once()) + ->method('find') + ->with('Controllers', 'Test') + ->willReturn(TestController::class); + + $this->request + ->expects($this->once()) + ->method('getMethod') + ->willReturn('POST'); + + $this->injectableFactory + ->expects($this->once()) + ->method('createWith') + ->with(TestController::class, ['name' => 'Test']) + ->willReturn($controller); + + $controller + ->expects($this->once()) + ->method('postActionHello') + ->with($this->request, $this->response); + + $this->controllerManager->process('Test', 'hello', $this->request, $this->response); + } +} diff --git a/tests/unit/testClasses/Controllers/TestController.php b/tests/unit/testClasses/Controllers/TestController.php new file mode 100644 index 0000000000..2beb657a1b --- /dev/null +++ b/tests/unit/testClasses/Controllers/TestController.php @@ -0,0 +1,43 @@ + Date: Mon, 14 Dec 2020 13:59:25 +0200 Subject: [PATCH 29/51] fix notices --- application/Espo/Services/EmailAccount.php | 14 ++++++++++++-- application/Espo/Services/InboundEmail.php | 22 ++++++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/application/Espo/Services/EmailAccount.php b/application/Espo/Services/EmailAccount.php index 914c01db09..c650cc0b99 100644 --- a/application/Espo/Services/EmailAccount.php +++ b/application/Espo/Services/EmailAccount.php @@ -442,7 +442,17 @@ class EmailAccount extends Record implements $flags = $message->getFlags(); } - $email = $this->importMessage($importer, $emailAccount, $message, $teamIdList, null, [$userId], $filterCollection, $fetchOnlyHeader, $folderData); + $email = $this->importMessage( + $importer, + $emailAccount, + $message, + $teamIdList, + null, + [$userId], + $filterCollection, + $fetchOnlyHeader, + $folderData + ); if ($emailAccount->get('keepFetchedEmailsUnread')) { if (is_array($flags) && empty($flags[Storage::FLAG_SEEN])) { @@ -529,7 +539,7 @@ class EmailAccount extends Record implements $emailAccount, $message, $teamIdList, - $userId = null, + $userId, $userIdList = [], $filterCollection, $fetchOnlyHeader, diff --git a/application/Espo/Services/InboundEmail.php b/application/Espo/Services/InboundEmail.php index 9c334d70e0..ea6ee98ce7 100644 --- a/application/Espo/Services/InboundEmail.php +++ b/application/Espo/Services/InboundEmail.php @@ -364,8 +364,15 @@ class InboundEmail extends RecordService implements } $email = $this->importMessage( - $importer, $emailAccount, $message, $teamIdList, $userId, $userIdList, - $filterCollection, $fetchOnlyHeader, null + $importer, + $emailAccount, + $message, + $teamIdList, + $userId, + $userIdList, + $filterCollection, + $fetchOnlyHeader, + null ); if ($emailAccount->get('keepFetchedEmailsUnread')) { @@ -481,8 +488,15 @@ class InboundEmail extends RecordService implements } protected function importMessage( - $importer, $emailAccount, $message, $teamIdList, $userId = null, $userIdList = [], - $filterCollection, $fetchOnlyHeader, $folderData = null + $importer, + $emailAccount, + $message, + $teamIdList, + $userId, + $userIdList, + $filterCollection, + $fetchOnlyHeader, + $folderData = null ) { $email = null; From 58d3929bee20e1dc6d8ff58eacc14d75b3e6162e Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 14 Dec 2020 13:59:52 +0200 Subject: [PATCH 30/51] fix notices and cs --- .../Espo/Modules/Crm/Services/Campaign.php | 293 ++++++++++++------ 1 file changed, 203 insertions(+), 90 deletions(-) diff --git a/application/Espo/Modules/Crm/Services/Campaign.php b/application/Espo/Modules/Crm/Services/Campaign.php index 593addfd5a..fb5d7bd901 100644 --- a/application/Espo/Modules/Crm/Services/Campaign.php +++ b/application/Espo/Modules/Crm/Services/Campaign.php @@ -57,21 +57,30 @@ class Campaign extends \Espo\Services\Record implements { parent::loadAdditionalFields($entity); - $sentCount = $this->getEntityManager()->getRepository('CampaignLogRecord')->where([ - 'campaignId' => $entity->id, - 'action' => 'Sent', - 'isTest' => false - ])->count(); + $sentCount = $this->getEntityManager() + ->getRepository('CampaignLogRecord') + ->where([ + 'campaignId' => $entity->id, + 'action' => 'Sent', + 'isTest' => false + ]) + ->count(); + if (!$sentCount) { $sentCount = null; } + $entity->set('sentCount', $sentCount); - $openedCount = $this->getEntityManager()->getRepository('CampaignLogRecord')->where([ - 'campaignId' => $entity->id, - 'action' => 'Opened', - 'isTest' => false - ])->count(); + $openedCount = $this->getEntityManager() + ->getRepository('CampaignLogRecord') + ->where([ + 'campaignId' => $entity->id, + 'action' => 'Opened', + 'isTest' => false + ]) + ->count(); + $entity->set('openedCount', $openedCount); $openedPercentage = null; @@ -80,23 +89,27 @@ class Campaign extends \Espo\Services\Record implements } $entity->set('openedPercentage', $openedPercentage); - $clickedCount = $this->getEntityManager()->getRepository('CampaignLogRecord')->where([ - 'campaignId' => $entity->id, - 'action' => 'Clicked', - 'isTest' => false, - 'id=s' => [ - 'entityType' => 'CampaignLogRecord', - 'selectParams' => [ - 'select' => ['MIN:id'], - 'whereClause' => [ - 'action' => 'Clicked', - 'isTest' => false, - 'campaignId' => $entity->id, + $clickedCount = $this->getEntityManager() + ->getRepository('CampaignLogRecord') + ->where([ + 'campaignId' => $entity->id, + 'action' => 'Clicked', + 'isTest' => false, + 'id=s' => [ + 'entityType' => 'CampaignLogRecord', + 'selectParams' => [ + 'select' => ['MIN:id'], + 'whereClause' => [ + 'action' => 'Clicked', + 'isTest' => false, + 'campaignId' => $entity->id, + ], + 'groupBy' => ['queueItemId'], ], - 'groupBy' => ['queueItemId'], ], - ], - ])->count(); + ]) + ->count(); + $entity->set('clickedCount', $clickedCount); $clickedPercentage = null; @@ -105,47 +118,71 @@ class Campaign extends \Espo\Services\Record implements } $entity->set('clickedPercentage', $clickedPercentage); - $optedInCount = $this->getEntityManager()->getRepository('CampaignLogRecord')->where([ - 'campaignId' => $entity->id, - 'action' => 'Opted In', - 'isTest' => false - ])->count(); - if (!$optedInCount) $optedInCount = null; + $optedInCount = $this->getEntityManager() + ->getRepository('CampaignLogRecord') + ->where([ + 'campaignId' => $entity->id, + 'action' => 'Opted In', + 'isTest' => false, + ]) + ->count(); + + if (!$optedInCount) { + $optedInCount = null; + } + $entity->set('optedInCount', $optedInCount); - $optedOutCount = $this->getEntityManager()->getRepository('CampaignLogRecord')->where([ - 'campaignId' => $entity->id, - 'action' => 'Opted Out', - 'isTest' => false - ])->count(); + $optedOutCount = $this->getEntityManager() + ->getRepository('CampaignLogRecord') + ->where([ + 'campaignId' => $entity->id, + 'action' => 'Opted Out', + 'isTest' => false, + ]) + ->count(); + $entity->set('optedOutCount', $optedOutCount); $optedOutPercentage = null; + if ($sentCount > 0) { $optedOutPercentage = round($optedOutCount / $sentCount * 100, 2, \PHP_ROUND_HALF_EVEN); } + $entity->set('optedOutPercentage', $optedOutPercentage); - $bouncedCount = $this->getEntityManager()->getRepository('CampaignLogRecord')->where([ - 'campaignId' => $entity->id, - 'action' => 'Bounced', - 'isTest' => false - ])->count(); + $bouncedCount = $this->getEntityManager() + ->getRepository('CampaignLogRecord') + ->where([ + 'campaignId' => $entity->id, + 'action' => 'Bounced', + 'isTest' => false, + ]) + ->count(); + $entity->set('bouncedCount', $bouncedCount); $bouncedPercentage = null; + if ($sentCount && $sentCount > 0) { $bouncedPercentage = round($bouncedCount / $sentCount * 100, 2, \PHP_ROUND_HALF_EVEN); } + $entity->set('bouncedPercentage', $bouncedPercentage); if ($this->getAcl()->check('Lead')) { - $leadCreatedCount = $this->getEntityManager()->getRepository('Lead')->where([ - 'campaignId' => $entity->id - ])->count(); + $leadCreatedCount = $this->getEntityManager() + ->getRepository('Lead') + ->where([ + 'campaignId' => $entity->id, + ]) + ->count(); + if (!$leadCreatedCount) { $leadCreatedCount = null; } + $entity->set('leadCreatedCount', $leadCreatedCount); } @@ -157,24 +194,28 @@ class Campaign extends \Espo\Services\Record implements 'select' => ['SUM:amountConverted'], 'whereClause' => [ 'stage' => 'Closed Won', - 'campaignId' => $entity->id + 'campaignId' => $entity->id, ], - 'groupBy' => ['opportunity.campaignId'] + 'groupBy' => ['opportunity.campaignId'], ]; $sql = $this->getEntityManager()->getQueryComposer()->compose(Select::fromRaw($params)); $pdo = $this->getEntityManager()->getPDO(); $sth = $pdo->prepare($sql); + $sth->execute(); $revenue = null; + if ($row = $sth->fetch(\PDO::FETCH_ASSOC)) { $revenue = floatval($row['SUM:amountConverted']); + if (!$revenue) { $revenue = null; } } + $entity->set('revenue', $revenue); } } @@ -191,7 +232,7 @@ class Campaign extends \Espo\Services\Record implements 'parentId' => $target->id, 'parentType' => $target->getEntityType(), 'action' => 'Lead Created', - 'isTest' => $isTest + 'isTest' => $isTest, ]); $this->getEntityManager()->saveEntity($logRecord); @@ -199,9 +240,9 @@ class Campaign extends \Espo\Services\Record implements public function logSent( string $campaignId, - ?string $queueItemId = null, + ?string $queueItemId, Entity $target, - Entity $emailOrEmailTemplate = null, + Entity $emailOrEmailTemplate, $emailAddress, $actionDate = null, $isTest = false @@ -218,7 +259,7 @@ class Campaign extends \Espo\Services\Record implements 'action' => 'Sent', 'stringData' => $emailAddress, 'queueItemId' => $queueItemId, - 'isTest' => $isTest + 'isTest' => $isTest, ]); if ($emailOrEmailTemplate) { @@ -230,19 +271,35 @@ class Campaign extends \Espo\Services\Record implements $this->getEntityManager()->saveEntity($logRecord); } - public function logBounced($campaignId, $queueItemId = null, Entity $target, $emailAddress, $isHard = false, $actionDate = null, $isTest = false) - { - if ($queueItemId && $this->getEntityManager()->getRepository('CampaignLogRecord')->where([ - 'queueItemId' => $queueItemId, - 'action' => 'Bounced', - 'isTest' => $isTest - ])->findOne()) { + public function logBounced( + $campaignId, + $queueItemId, + Entity $target, + $emailAddress, + $isHard = false, + $actionDate = null, + $isTest = false + ) { + if ( + $queueItemId && + $this->getEntityManager() + ->getRepository('CampaignLogRecord') + ->where([ + 'queueItemId' => $queueItemId, + 'action' => 'Bounced', + 'isTest' => $isTest, + ]) + ->findOne() + ) { return; } + if (empty($actionDate)) { $actionDate = date('Y-m-d H:i:s'); } + $logRecord = $this->getEntityManager()->getEntity('CampaignLogRecord'); + $logRecord->set([ 'campaignId' => $campaignId, 'actionDate' => $actionDate, @@ -251,8 +308,9 @@ class Campaign extends \Espo\Services\Record implements 'action' => 'Bounced', 'stringData' => $emailAddress, 'queueItemId' => $queueItemId, - 'isTest' => $isTest + 'isTest' => $isTest, ]); + if ($isHard) { $logRecord->set('stringAdditionalData', 'Hard'); } else { @@ -261,21 +319,38 @@ class Campaign extends \Espo\Services\Record implements $this->getEntityManager()->saveEntity($logRecord); } - public function logOptedIn($campaignId, $queueItemId = null, Entity $target, $emailAddress = null, $actionDate = null, $isTest = false) - { - if ($queueItemId && $this->getEntityManager()->getRepository('CampaignLogRecord')->where([ - 'queueItemId' => $queueItemId, - 'action' => 'Opted In', - 'isTest' => $isTest - ])->findOne()) return; + public function logOptedIn( + $campaignId, + $queueItemId, + Entity $target, + $emailAddress = null, + $actionDate = null, + $isTest = false + ) { + if ( + $queueItemId && + $this->getEntityManager() + ->getRepository('CampaignLogRecord') + ->where([ + 'queueItemId' => $queueItemId, + 'action' => 'Opted In', + 'isTest' => $isTest, + ]) + ->findOne() + ) { + return; + } if (empty($actionDate)) { $actionDate = date('Y-m-d H:i:s'); } + if (!$emailAddress) { $emailAddress = $target->get('emailAddress'); } + $logRecord = $this->getEntityManager()->getEntity('CampaignLogRecord'); + $logRecord->set([ 'campaignId' => $campaignId, 'actionDate' => $actionDate, @@ -284,23 +359,38 @@ class Campaign extends \Espo\Services\Record implements 'action' => 'Opted In', 'stringData' => $emailAddress, 'queueItemId' => $queueItemId, - 'isTest' => $isTest + 'isTest' => $isTest, ]); + $this->getEntityManager()->saveEntity($logRecord); } - public function logOptedOut($campaignId, $queueItemId = null, Entity $target, $emailAddress = null, $actionDate = null, $isTest = false) - { - if ($queueItemId && $this->getEntityManager()->getRepository('CampaignLogRecord')->where(array( - 'queueItemId' => $queueItemId, - 'action' => 'Opted Out', - 'isTest' => $isTest - ))->findOne()) { + public function logOptedOut( + $campaignId, + $queueItemId, + Entity $target, + $emailAddress = null, + $actionDate = null, + $isTest = false + ) { + if ( + $queueItemId && + $this->getEntityManager() + ->getRepository('CampaignLogRecord') + ->where([ + 'queueItemId' => $queueItemId, + 'action' => 'Opted Out', + 'isTest' => $isTest, + ]) + ->findOne() + ) { return; } + if (empty($actionDate)) { $actionDate = date('Y-m-d H:i:s'); } + $logRecord = $this->getEntityManager()->getEntity('CampaignLogRecord'); $logRecord->set(array( 'campaignId' => $campaignId, @@ -315,22 +405,31 @@ class Campaign extends \Espo\Services\Record implements $this->getEntityManager()->saveEntity($logRecord); } - public function logOpened($campaignId, $queueItemId = null, Entity $target, $actionDate = null, $isTest = false) + public function logOpened($campaignId, $queueItemId, Entity $target, $actionDate = null, $isTest = false) { if (empty($actionDate)) { $actionDate = date('Y-m-d H:i:s'); } - if ($queueItemId && $this->getEntityManager()->getRepository('CampaignLogRecord')->where(array( - 'queueItemId' => $queueItemId, - 'action' => 'Opened', - 'isTest' => $isTest - ))->findOne()) { + if ( + $queueItemId && + $this->getEntityManager() + ->getRepository('CampaignLogRecord') + ->where([ + 'queueItemId' => $queueItemId, + 'action' => 'Opened', + 'isTest' => $isTest, + ]) + ->findOne() + ) { return; } + $queueItem = $this->getEntityManager()->getEntity('EmailQueueItem', $queueItemId); + if ($queueItem) { $massEmail = $this->getEntityManager()->getEntity('MassEmail', $queueItem->get('massEmailId')); + if ($massEmail && $massEmail->id) { $logRecord = $this->getEntityManager()->getEntity('CampaignLogRecord'); $logRecord->set([ @@ -342,28 +441,42 @@ class Campaign extends \Espo\Services\Record implements 'objectId' => $massEmail->get('emailTemplateId'), 'objectType' => 'EmailTemplate', 'queueItemId' => $queueItemId, - 'isTest' => $isTest + 'isTest' => $isTest, ]); + $this->getEntityManager()->saveEntity($logRecord); } } } - public function logClicked($campaignId, $queueItemId = null, Entity $target, Entity $trackingUrl, $actionDate = null, $isTest = false) - { + public function logClicked( + $campaignId, + $queueItemId, + Entity $target, + Entity $trackingUrl, + $actionDate = null, + $isTest = false + ) { if ($this->getConfig()->get('massEmailOpenTracking')) { $this->logOpened($campaignId, $queueItemId, $target); } - if ($queueItemId && $this->getEntityManager()->getRepository('CampaignLogRecord')->where([ - 'queueItemId' => $queueItemId, - 'action' => 'Clicked', - 'objectId' => $trackingUrl->id, - 'objectType' => $trackingUrl->getEntityType(), - 'isTest' => $isTest - ])->findOne()) { + if ( + $queueItemId && + $this->getEntityManager() + ->getRepository('CampaignLogRecord') + ->where([ + 'queueItemId' => $queueItemId, + 'action' => 'Clicked', + 'objectId' => $trackingUrl->id, + 'objectType' => $trackingUrl->getEntityType(), + 'isTest' => $isTest, + ]) + ->findOne() + ) { return; } + if (empty($actionDate)) { $actionDate = date('Y-m-d H:i:s'); } @@ -378,7 +491,7 @@ class Campaign extends \Espo\Services\Record implements 'objectId' => $trackingUrl->id, 'objectType' => $trackingUrl->getEntityType(), 'queueItemId' => $queueItemId, - 'isTest' => $isTest + 'isTest' => $isTest, ]); $this->getEntityManager()->saveEntity($logRecord); } From 4ab0d765e33c531555cc62f3409f530b6f5fde20 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 14 Dec 2020 14:52:18 +0200 Subject: [PATCH 31/51] fix notices --- application/Espo/Core/ExternalAccount/OAuth2/Client.php | 2 +- application/Espo/Services/EmailAccount.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/Espo/Core/ExternalAccount/OAuth2/Client.php b/application/Espo/Core/ExternalAccount/OAuth2/Client.php index 618f411ffc..a954321260 100644 --- a/application/Espo/Core/ExternalAccount/OAuth2/Client.php +++ b/application/Espo/Core/ExternalAccount/OAuth2/Client.php @@ -155,7 +155,7 @@ class Client return $this->execute($url, $params, $httpMethod, $httpHeaders); } - private function execute($url, $params = null, $httpMethod, array $httpHeaders = []) + private function execute($url, $params, $httpMethod, array $httpHeaders = []) { $curlOptions = array( CURLOPT_RETURNTRANSFER => true, diff --git a/application/Espo/Services/EmailAccount.php b/application/Espo/Services/EmailAccount.php index c650cc0b99..b0c0d36b15 100644 --- a/application/Espo/Services/EmailAccount.php +++ b/application/Espo/Services/EmailAccount.php @@ -540,7 +540,7 @@ class EmailAccount extends Record implements $message, $teamIdList, $userId, - $userIdList = [], + $userIdList, $filterCollection, $fetchOnlyHeader, $folderData = null From b30df2f6e2c365d51333248021fd8fd75d58927b Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 15 Dec 2020 09:46:29 +0200 Subject: [PATCH 32/51] orm select builder check has join --- .../ORM/QueryParams/SelectingBuilderTrait.php | 42 +++++++++++++++++++ .../ORM/QueryParams/SelectBuilderTest.php | 26 ++++++++++++ 2 files changed, 68 insertions(+) diff --git a/application/Espo/ORM/QueryParams/SelectingBuilderTrait.php b/application/Espo/ORM/QueryParams/SelectingBuilderTrait.php index ed5e94207a..8867ec1108 100644 --- a/application/Espo/ORM/QueryParams/SelectingBuilderTrait.php +++ b/application/Espo/ORM/QueryParams/SelectingBuilderTrait.php @@ -136,6 +136,10 @@ trait SelectingBuilderTrait return $this; } + if (is_null($alias) && is_null($conditions) && $this->hasJoin($relationName)) { + return $this; + } + if (is_null($alias) && is_null($conditions)) { $this->params['joins'][] = $relationName; @@ -174,6 +178,10 @@ trait SelectingBuilderTrait return $this; } + if (is_null($alias) && is_null($conditions) && $this->hasLeftJoin($relationName)) { + return $this; + } + if (is_null($alias) && is_null($conditions)) { $this->params['leftJoins'][] = $relationName; @@ -190,4 +198,38 @@ trait SelectingBuilderTrait return $this; } + + protected function hasLeftJoin(string $alias) : bool + { + if (in_array($alias, $this->params['leftJoins'])) { + return true; + } + + foreach ($this->params['leftJoins'] as $item) { + if (is_array($item) && count($item) > 1) { + if ($item[1] === $alias) { + return true; + } + } + } + + return false; + } + + protected function hasJoin(string $alias) : bool + { + if (in_array($alias, $this->params['joins'])) { + return true; + } + + foreach ($this->params['joins'] as $item) { + if (is_array($item) && count($item) > 1) { + if ($item[1] === $alias) { + return true; + } + } + } + + return false; + } } diff --git a/tests/unit/Espo/ORM/QueryParams/SelectBuilderTest.php b/tests/unit/Espo/ORM/QueryParams/SelectBuilderTest.php index 5c13531f9f..6e9a1d403e 100644 --- a/tests/unit/Espo/ORM/QueryParams/SelectBuilderTest.php +++ b/tests/unit/Espo/ORM/QueryParams/SelectBuilderTest.php @@ -238,4 +238,30 @@ class SelectBuilderTest extends \PHPUnit\Framework\TestCase $this->assertEquals($expected, $raw['whereClause']); } + + public function testLeftJoin() + { + $params = $this->builder + ->from('Test') + ->leftJoin('link1') + ->leftJoin('link1') + ->leftJoin('link2') + ->build() + ->getRawParams(); + + $this->assertEquals(['link1', 'link2'], $params['leftJoins']); + } + + public function testJoin() + { + $params = $this->builder + ->from('Test') + ->join('link1') + ->join('link1') + ->join('link2') + ->build() + ->getRawParams(); + + $this->assertEquals(['link1', 'link2'], $params['joins']); + } } From 521a166c9232529340cb260dea09027bb98a08a3 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 16 Dec 2020 19:44:08 +0200 Subject: [PATCH 33/51] ceanup --- application/Espo/Tools/Pdf/Tcpdf/TcpdfCollectionPrinter.php | 1 - 1 file changed, 1 deletion(-) diff --git a/application/Espo/Tools/Pdf/Tcpdf/TcpdfCollectionPrinter.php b/application/Espo/Tools/Pdf/Tcpdf/TcpdfCollectionPrinter.php index bc3183cf43..8e7a4d3981 100644 --- a/application/Espo/Tools/Pdf/Tcpdf/TcpdfCollectionPrinter.php +++ b/application/Espo/Tools/Pdf/Tcpdf/TcpdfCollectionPrinter.php @@ -30,7 +30,6 @@ namespace Espo\Tools\Pdf\Tcpdf; use Espo\Core\{ - //Exceptions\Error, Utils\Config, Htmlizer\Factory as HtmlizerFactory, Pdf\Tcpdf, From 5ffd03ca855db64b497d0182170da73be2a4f0e1 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 17 Dec 2020 18:43:57 +0200 Subject: [PATCH 34/51] application runner refactoring --- application/Espo/Core/Application.php | 9 ++++++--- .../ApplicationRunners/ApplicationRunner.php | 1 + .../Espo/Core/ApplicationRunners/EntryPoint.php | 17 ++++++++++------- .../Espo/Core/ApplicationRunners/Job.php | 10 +++++++--- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/application/Espo/Core/Application.php b/application/Espo/Core/Application.php index f40f5d99de..243844d56d 100644 --- a/application/Espo/Core/Application.php +++ b/application/Espo/Core/Application.php @@ -43,6 +43,7 @@ use Espo\Core\{ }; use ReflectionClass; +use StdClass; /** * A central access point of the application. @@ -68,7 +69,7 @@ class Application /** * Run a specific application runner. */ - public function run(string $className, ?object $params = null) + public function run(string $className, ?StdClass $params = null) { if (!$className || !class_exists($className)) { $this->getLog()->error("Application runner '{$className}' does not exist."); @@ -88,9 +89,11 @@ class Application $this->setupSystemUser(); } - $runner = $this->getInjectableFactory()->create($className); + $runner = $this->getInjectableFactory()->createWith($className, [ + 'params' => $params, + ]); - $runner->run($params); + $runner->run(); } /** diff --git a/application/Espo/Core/ApplicationRunners/ApplicationRunner.php b/application/Espo/Core/ApplicationRunners/ApplicationRunner.php index 2dd9e125cd..d47c5e7cff 100644 --- a/application/Espo/Core/ApplicationRunners/ApplicationRunner.php +++ b/application/Espo/Core/ApplicationRunners/ApplicationRunner.php @@ -34,4 +34,5 @@ namespace Espo\Core\ApplicationRunners; */ interface ApplicationRunner { + public function run(); } diff --git a/application/Espo/Core/ApplicationRunners/EntryPoint.php b/application/Espo/Core/ApplicationRunners/EntryPoint.php index a04a8d5e29..c2156746d2 100644 --- a/application/Espo/Core/ApplicationRunners/EntryPoint.php +++ b/application/Espo/Core/ApplicationRunners/EntryPoint.php @@ -60,6 +60,8 @@ use Exception; */ class EntryPoint implements ApplicationRunner { + protected $params; + protected $injectableFactory; protected $entryPointManager; protected $entityManager; @@ -73,7 +75,8 @@ class EntryPoint implements ApplicationRunner EntityManager $entityManager, ClientManager $clientManager, ApplicationUser $applicationUser, - AuthTokenManager $authTokenManager + AuthTokenManager $authTokenManager, + ?StdClass $params = null ) { $this->injectableFactory = $injectableFactory; $this->entryPointManager = $entryPointManager; @@ -81,16 +84,16 @@ class EntryPoint implements ApplicationRunner $this->clientManager = $clientManager; $this->applicationUser = $applicationUser; $this->authTokenManager = $authTokenManager; + + $this->params = $params ?? (object) []; } - public function run(?StdClass $params = null) + public function run() { - $params = $params ?? (object) []; + $entryPoint = $this->params->entryPoint ?? $_GET['entryPoint']; - $entryPoint = $params->entryPoint ?? $_GET['entryPoint']; - - $final = $params->final ?? false; - $data = $params->data ?? null; + $final = $this->params->final ?? false; + $data = $this->params->data ?? null; if (!$entryPoint) { throw new Error(); diff --git a/application/Espo/Core/ApplicationRunners/Job.php b/application/Espo/Core/ApplicationRunners/Job.php index 18025a1472..8901446036 100644 --- a/application/Espo/Core/ApplicationRunners/Job.php +++ b/application/Espo/Core/ApplicationRunners/Job.php @@ -43,15 +43,19 @@ class Job implements ApplicationRunner use Cli; use SetupSystemUser; + protected $params; + protected $cronManager; - public function __construct(CronManager $cronManager) + public function __construct(CronManager $cronManager, StdClass $params) { $this->cronManager = $cronManager; + + $this->params = $params; } - public function run(StdClass $params) + public function run() { - $this->cronManager->runJobById($params->id); + $this->cronManager->runJobById($this->params->id); } } From d194eac5bc8879e893e033033ce39fb101537c4d Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 18 Dec 2020 15:06:00 +0200 Subject: [PATCH 35/51] laminas service manager dependency --- composer.json | 1 + composer.lock | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8dd1374237..56217f4094 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "laminas/laminas-ldap": "2.*", "laminas/laminas-mime": "^2.7.1", "laminas/laminas-stdlib": "^3.1", + "laminas/laminas-servicemanager": "^3.3.1", "monolog/monolog": "2.*", "yzalis/identicon": "*", "zordius/lightncandy": "1.*", diff --git a/composer.lock b/composer.lock index d7a3ec7063..26632d56cf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e164d331463e4b99a826e2bab0006784", + "content-hash": "d36f98ae23257bb02e7e08f682f2a906", "packages": [ { "name": "cboden/ratchet", @@ -1084,6 +1084,84 @@ ], "time": "2019-12-31T17:25:26+00:00" }, + { + "name": "laminas/laminas-servicemanager", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-servicemanager.git", + "reference": "0d4c8628a71fae9f7bd0b1b74b76382e5e9a04b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/0d4c8628a71fae9f7bd0b1b74b76382e5e9a04b1", + "reference": "0d4c8628a71fae9f7bd0b1b74b76382e5e9a04b1", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0", + "psr/container": "^1.0" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "replace": { + "zendframework/zend-servicemanager": "^3.4.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "mikey179/vfsstream": "^1.6.5", + "ocramius/proxy-manager": "^1.0 || ^2.0", + "phpbench/phpbench": "^0.13.0", + "phpunit/phpunit": "^5.7.25 || ^6.4.4" + }, + "suggest": { + "laminas/laminas-stdlib": "laminas-stdlib ^2.5 if you wish to use the MergeReplaceKey or MergeRemoveKey features in Config instances", + "ocramius/proxy-manager": "ProxyManager 1.* to handle lazy initialization of services" + }, + "bin": [ + "bin/generate-deps-for-config-factory", + "bin/generate-factory-for-class" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev", + "dev-develop": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\ServiceManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Factory-Driven Dependency Injection Container", + "homepage": "https://laminas.dev", + "keywords": [ + "PSR-11", + "dependency-injection", + "di", + "dic", + "laminas", + "service-manager", + "servicemanager" + ], + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-05-11T14:43:22+00:00" + }, { "name": "laminas/laminas-stdlib", "version": "3.2.1", @@ -5364,6 +5442,20 @@ "polyfill", "portable" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-02-27T09:26:54+00:00" }, { From 9fbc62837f8eb9f0cc3b654cc2043c3f4a6e3d45 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 21 Dec 2020 11:07:31 +0200 Subject: [PATCH 36/51] fix validation --- application/Espo/Services/Record.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php index acaeaf5f14..c9a0f23caf 100644 --- a/application/Espo/Services/Record.php +++ b/application/Espo/Services/Record.php @@ -706,7 +706,7 @@ class Record implements Crud, } } - protected function processValidationField(Entity $entity, $field, $data) + protected function processValidationField(Entity $entity, string $field, $data) { $fieldType = $this->fieldUtil->getEntityTypeFieldParam($this->entityType, $field, 'type'); @@ -724,10 +724,11 @@ class Record implements Crud, } $skipPropertyName = 'validate' . ucfirst($type) . 'SkipFieldList'; + if (property_exists($this, $skipPropertyName)) { $skipList = $this->$skipPropertyName; - if (in_array($type, $skipList)) { + if (in_array($field, $skipList)) { continue; } } From 543eb21a8b6ea2881804498a24c9ce74f357ff00 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 21 Dec 2020 11:27:48 +0200 Subject: [PATCH 37/51] fix flotr --- client/lib/flotr2.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/client/lib/flotr2.js b/client/lib/flotr2.js index 51621d7eaf..36ca2da338 100644 --- a/client/lib/flotr2.js +++ b/client/lib/flotr2.js @@ -5237,16 +5237,19 @@ Flotr.addPlugin('hit', { // EspoCRM fix start if (n.mouse.autoPositionHorizontal) { + if (n.xaxis.d2p(n.x) > this.plotWidth * 2 / 3) { p = 'w'; } else { p = 'e'; } - if (n.x < 0 && n.xaxis.d2p(n.x) < this.plotWidth * 1 / 4) { - p = 'e'; - } else { - p = 'w'; + if (n.x < 0) { + if (n.xaxis.d2p(n.x) < this.plotWidth * 1 / 4) { + p = 'e'; + } else { + p = 'w'; + } } } if (n.mouse.autoPositionVertical) { From 8f89fa4597f10e92f99b067c6b58e749c5b6a461 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 21 Dec 2020 11:33:51 +0200 Subject: [PATCH 38/51] cs fix --- client/src/views/list-with-categories.js | 42 +++++++++++++++++++----- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/client/src/views/list-with-categories.js b/client/src/views/list-with-categories.js index dd5f2d20d8..fbef782f47 100644 --- a/client/src/views/list-with-categories.js +++ b/client/src/views/list-with-categories.js @@ -58,9 +58,11 @@ define('views/list-with-categories', 'views/list', function (Dep) { data: function () { var data = {}; + data.categoriesDisabled = this.categoriesDisabled; data.isExpanded = this.isExpanded; data.hasExpandedToggler = this.hasExpandedToggler; + return data; }, @@ -101,7 +103,10 @@ define('views/list-with-categories', 'views/list', function (Dep) { this.applyCategoryToCollection(); this.listenTo(this.collection, 'sync', function (c, d, o) { - if (o && o.openCategory) return; + if (o && o.openCategory) { + return; + } + this.controlListVisibility(); }, this); }, @@ -120,6 +125,7 @@ define('views/list-with-categories', 'views/list', function (Dep) { this.openCategory(params.categoryId, params.categoryName); } } + this.selectCurrentCategory(); } }, @@ -129,13 +135,14 @@ define('views/list-with-categories', 'views/list', function (Dep) { for (var i = 0; i < this.collection.where.length; i++) { if (this.collection.where[i].type === 'textFilter') { return true; - break; } } } + if (this.collection.data && this.collection.data.textFilter) { return true; } + return false; }, @@ -161,9 +168,11 @@ define('views/list-with-categories', 'views/list', function (Dep) { } else { this.controlListVisibility(); } + if (!this.categoriesDisabled && !this.hasView('categories')) { this.loadCategories(); } + if (!this.isExpanded && !this.hasView('nestedCategories')) { this.loadNestedCategories(); } @@ -171,6 +180,7 @@ define('views/list-with-categories', 'views/list', function (Dep) { actionExpand: function () { this.isExpanded = true; + this.setIsExpandedStoredValue(true); this.applyCategoryToCollection(); @@ -307,12 +317,15 @@ define('views/list-with-categories', 'views/list', function (Dep) { this.listenToOnce(collection, 'sync', function () { callback.call(this, collection); }, this); + collection.fetch(); }, this); }, applyCategoryToNestedCategoriesCollection: function () { - if (!this.nestedCategoriesCollection) return; + if (!this.nestedCategoriesCollection) { + return; + } this.nestedCategoriesCollection.where = null; @@ -335,6 +348,7 @@ define('views/list-with-categories', 'views/list', function (Dep) { collection.maxDepth = 1; collection.data.checkIfEmpty = true; + if (!this.getAcl().checkScope(this.scope, 'create')) { collection.data.onlyNotEmpty = true; } @@ -344,6 +358,7 @@ define('views/list-with-categories', 'views/list', function (Dep) { collection.fetch().then(function () { this.controlListVisibility(); this.controlNestedCategoriesVisibility(); + callback.call(this, collection); }.bind(this)); }, this); @@ -353,7 +368,7 @@ define('views/list-with-categories', 'views/list', function (Dep) { this.getNestedCategoriesCollection(function (collection) { this.createView('nestedCategories', 'views/record/list-nested-categories', { collection: collection, - el: this.options.el + ' .nested-categories-container' + el: this.options.el + ' .nested-categories-container', }, function (view) { view.render(); }); @@ -378,18 +393,22 @@ define('views/list-with-categories', 'views/list', function (Dep) { if (this.currentCategoryId) { view.setSelected(this.currentCategoryId); } + view.render(); this.listenTo(view, 'select', function (model) { if (!this.isExpanded) { var id = null; var name = null; + if (model && model.id) { id = model.id; name = model.get('name'); } + this.openCategory(id, name); this.navigateToCurrentCategory(); + return; } this.currentCategoryId = null; @@ -409,6 +428,7 @@ define('views/list-with-categories', 'views/list', function (Dep) { this.listenToOnce(this.collection, 'sync', function () { this.notify(false); }, this); + this.collection.fetch(); }, this); @@ -417,9 +437,7 @@ define('views/list-with-categories', 'views/list', function (Dep) { }, this); }, - applyCategoryToCollection: function () { - this.collection.whereFunction = function () { var filter; var isExpanded = this.isExpanded; @@ -457,14 +475,14 @@ define('views/list-with-categories', 'views/list', function (Dep) { filter = { field: this.categoryField, type: this.categoryFilterType, - value: this.currentCategoryId + value: this.currentCategoryId, }; } } + if (filter) { return [filter]; } - }.bind(this); }, @@ -479,19 +497,26 @@ define('views/list-with-categories', 'views/list', function (Dep) { if (this.currentCategoryId) { var names = {}; names[this.currentCategoryId] = this.currentCategoryName; + var data = {}; + var idsAttribute = this.categoryField + 'Ids'; var namesAttribute = this.categoryField + 'Names'; + data[idsAttribute] = [this.currentCategoryId], data[namesAttribute] = names; + return data; } } else { var idAttribute = this.categoryField + 'Id'; var nameAttribute = this.categoryField + 'Name'; + var data = {}; + data[idAttribute] = this.currentCategoryId; data[nameAttribute] = this.currentCategoryName; + return data; } }, @@ -500,6 +525,5 @@ define('views/list-with-categories', 'views/list', function (Dep) { this.clearView('categories'); this.getRouter().navigate('#' + this.categoryScope, {trigger: true}); }, - }); }); From 641382dc66c1fe4adbcdaf987d8c027f2691f261 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 21 Dec 2020 11:50:07 +0200 Subject: [PATCH 39/51] cs fix and error throwing --- application/Espo/Modules/Crm/Services/Lead.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/application/Espo/Modules/Crm/Services/Lead.php b/application/Espo/Modules/Crm/Services/Lead.php index 4d52618405..48f1ecd83a 100644 --- a/application/Espo/Modules/Crm/Services/Lead.php +++ b/application/Espo/Modules/Crm/Services/Lead.php @@ -98,7 +98,7 @@ class Lead extends PersonService implements $ignoreAttributeList = ['createdAt', 'modifiedAt', 'modifiedById', 'modifiedByName', 'createdById', 'createdByName']; - $convertFieldsDefs = $this->getMetadata()->get('entityDefs.Lead.convertFields', array()); + $convertFieldsDefs = $this->getMetadata()->get('entityDefs.Lead.convertFields', []); foreach ($entityList as $entityType) { if (!$this->getAcl()->checkScope($entityType, 'edit')) { @@ -141,7 +141,10 @@ class Lead extends PersonService implements $attachment = $lead->get($field); if ($attachment) { - $attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment); + $attachment = $this->getEntityManager() + ->getRepository('Attachment') + ->getCopiedAttachment($attachment); + $idAttribute = $field . 'Id'; $nameAttribute = $field . 'Name'; @@ -189,7 +192,11 @@ class Lead extends PersonService implements continue; } - $leadAttribute = $leadAttributeList[$i]; + $leadAttribute = $leadAttributeList[$i] ?? null; + + if (!$leadAttribute) { + throw new Error("Not compatible fields in 'convertFields' map."); + } if (!$lead->has($leadAttribute)) { continue; From df179212b18928e37a901d66045f62b85f99fb34 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 21 Dec 2020 13:01:07 +0200 Subject: [PATCH 40/51] orm metadata refactoring --- .../Espo/Core/Binding/DefaultBinding.php | 5 ++ application/Espo/Core/DataManager.php | 12 ++--- .../Espo/Core/Loaders/EntityManager.php | 21 +++++--- application/Espo/Core/Loaders/OrmMetadata.php | 50 +++++++++++++++++++ application/Espo/Core/ORM/EntityManager.php | 10 ++-- .../Espo/Core/ORM/MetadataDataProvider.php | 48 ++++++++++++++++++ .../Core/Utils/Database/Schema/Schema.php | 10 ++-- .../{OrmMetadata.php => OrmMetadataData.php} | 2 +- application/Espo/ORM/EntityManager.php | 25 +++------- application/Espo/ORM/Metadata.php | 17 +++++-- application/Espo/ORM/MetadataDataProvider.php | 38 ++++++++++++++ .../metadata/app/containerServices.json | 4 +- 12 files changed, 197 insertions(+), 45 deletions(-) create mode 100644 application/Espo/Core/Loaders/OrmMetadata.php create mode 100644 application/Espo/Core/ORM/MetadataDataProvider.php rename application/Espo/Core/Utils/Metadata/{OrmMetadata.php => OrmMetadataData.php} (99%) create mode 100644 application/Espo/ORM/MetadataDataProvider.php diff --git a/application/Espo/Core/Binding/DefaultBinding.php b/application/Espo/Core/Binding/DefaultBinding.php index 665e1ceea0..90e5d151df 100644 --- a/application/Espo/Core/Binding/DefaultBinding.php +++ b/application/Espo/Core/Binding/DefaultBinding.php @@ -48,6 +48,11 @@ class DefaultBinding 'entityManager' ); + $binder->bindService( + 'Espo\\ORM\\Metadata', + 'ormMetadata' + ); + $binder->bindService( 'Espo\\Core\\ORM\\EntityManager', 'entityManager' diff --git a/application/Espo/Core/DataManager.php b/application/Espo/Core/DataManager.php index 55e2861c1b..270f7dc02f 100644 --- a/application/Espo/Core/DataManager.php +++ b/application/Espo/Core/DataManager.php @@ -36,7 +36,7 @@ use Espo\Core\{ Utils\Util, Utils\Config, Utils\File\Manager as FileManager, - Utils\Metadata\OrmMetadata, + Utils\Metadata\OrmMetadataData, HookManager, Utils\Database\Schema\SchemaProxy, }; @@ -53,7 +53,7 @@ class DataManager protected $entityManager; protected $fileManager; protected $metadata; - protected $ormMetadata; + protected $ormMetadataData; protected $hookManager; protected $schemaProxy; @@ -64,7 +64,7 @@ class DataManager Config $config, FileManager $fileManager, Metadata $metadata, - OrmMetadata $ormMetadata, + OrmMetadataData $ormMetadataData, HookManager $hookManager, SchemaProxy $schemaProxy ) { @@ -72,7 +72,7 @@ class DataManager $this->config = $config; $this->fileManager = $fileManager; $this->metadata = $metadata; - $this->ormMetadata = $ormMetadata; + $this->ormMetadataData = $ormMetadataData; $this->hookManager = $hookManager; $this->schemaProxy = $schemaProxy; } @@ -157,9 +157,9 @@ class DataManager $metadata->init(true); - $ormData = $this->ormMetadata->getData(true); + $ormData = $this->ormMetadataData->getData(true); - $this->entityManager->setMetadata($ormData); + $this->entityManager->getMetadata()->updateData(); $this->updateCacheTimestamp(); diff --git a/application/Espo/Core/Loaders/EntityManager.php b/application/Espo/Core/Loaders/EntityManager.php index 856fb7b860..a990e0616a 100644 --- a/application/Espo/Core/Loaders/EntityManager.php +++ b/application/Espo/Core/Loaders/EntityManager.php @@ -31,25 +31,32 @@ namespace Espo\Core\Loaders; use Espo\Core\{ Utils\Config, - Utils\Metadata\OrmMetadata, InjectableFactory, ORM\EntityManager as EntityManagerService, ORM\RepositoryFactory, ORM\EntityFactory, ORM\Helper, + ORM\MetadataDataProvider, +}; + +use Espo\{ + ORM\Metadata as OrmMetadata, }; class EntityManager implements Loader { protected $config; protected $injectableFactory; - protected $ormMetadata; + protected $metadataDataProvider; - public function __construct(Config $config, InjectableFactory $injectableFactory, OrmMetadata $ormMetadata) - { + public function __construct( + Config $config, + InjectableFactory $injectableFactory, + MetadataDataProvider $metadataDataProvider + ) { $this->config = $config; $this->injectableFactory = $injectableFactory; - $this->ormMetadata = $ormMetadata; + $this->metadataDataProvider = $metadataDataProvider; } public function load() : EntityManagerService @@ -65,7 +72,6 @@ class EntityManager implements Loader $config = $this->config; $params = [ - 'metadata' => $this->ormMetadata->getData(), 'host' => $config->get('database.host'), 'port' => $config->get('database.port'), 'dbname' => $config->get('database.dbname'), @@ -81,8 +87,11 @@ class EntityManager implements Loader 'sslCipher' => $config->get('database.sslCipher'), ]; + $metadata = new OrmMetadata($this->metadataDataProvider); + $entityManager = $this->injectableFactory->createWith(EntityManagerService::class, [ 'params' => $params, + 'metadata' => $metadata, 'repositoryFactory' => $repositoryFactory, 'entityFactory' => $entityFactory, 'helper' => $helper, diff --git a/application/Espo/Core/Loaders/OrmMetadata.php b/application/Espo/Core/Loaders/OrmMetadata.php new file mode 100644 index 0000000000..4623bb10df --- /dev/null +++ b/application/Espo/Core/Loaders/OrmMetadata.php @@ -0,0 +1,50 @@ +entityManager = $entityManager; + } + + public function load() : Metadata + { + return $this->entityManager->getMetadata(); + } +} diff --git a/application/Espo/Core/ORM/EntityManager.php b/application/Espo/Core/ORM/EntityManager.php index 89fbacc18e..2acd86b533 100644 --- a/application/Espo/Core/ORM/EntityManager.php +++ b/application/Espo/Core/ORM/EntityManager.php @@ -32,12 +32,13 @@ namespace Espo\Core\ORM; use Espo\Entities\User; use Espo\Core\{ - Utils\Metadata as EspoMetadata, - HookManager, Utils\Util, }; -use Espo\ORM\EntityManager as BaseEntityManager; +use Espo\ORM\{ + EntityManager as BaseEntityManager, + Metadata, +}; class EntityManager extends BaseEntityManager { @@ -45,11 +46,12 @@ class EntityManager extends BaseEntityManager public function __construct( array $params, + Metadata $metadata, RepositoryFactory $repositoryFactory, EntityFactory $entityFactory, Helper $helper ) { - parent::__construct($params, $repositoryFactory, $entityFactory); + parent::__construct($params, $metadata, $repositoryFactory, $entityFactory); $this->helper = $helper; } diff --git a/application/Espo/Core/ORM/MetadataDataProvider.php b/application/Espo/Core/ORM/MetadataDataProvider.php new file mode 100644 index 0000000000..753aebe54b --- /dev/null +++ b/application/Espo/Core/ORM/MetadataDataProvider.php @@ -0,0 +1,48 @@ +ormMetadataData = $ormMetadataData; + } + + public function get() : array + { + return $this->ormMetadataData->getData(); + } +} diff --git a/application/Espo/Core/Utils/Database/Schema/Schema.php b/application/Espo/Core/Utils/Database/Schema/Schema.php index 170d7c2255..abc481ab87 100644 --- a/application/Espo/Core/Utils/Database/Schema/Schema.php +++ b/application/Espo/Core/Utils/Database/Schema/Schema.php @@ -41,7 +41,7 @@ use Espo\Core\{ Utils\File\Manager as FileManager, ORM\EntityManager, Utils\File\ClassParser, - Utils\Metadata\OrmMetadata, + Utils\Metadata\OrmMetadataData, Utils\Util, Utils\Database\Helper, Utils\Database\DBAL\Schema\Comparator, @@ -64,6 +64,8 @@ class Schema private $databaseHelper; + protected $ormMetadataData; + protected $fieldTypePaths = [ 'application/Espo/Core/Utils/Database/DBAL/FieldTypes', 'custom/Espo/Custom/Core/Utils/Database/DBAL/FieldTypes', @@ -92,7 +94,7 @@ class Schema FileManager $fileManager, EntityManager $entityManager, ClassParser $classParser, - OrmMetadata $ormMetadata + OrmMetadataData $ormMetadataData ) { $this->config = $config; $this->metadata = $metadata; @@ -110,7 +112,7 @@ class Schema $this->schemaConverter = new Converter($this->metadata, $this->fileManager, $this, $this->config); - $this->ormMetadata = $ormMetadata; + $this->ormMetadataData = $ormMetadataData; } protected function getConfig() @@ -203,7 +205,7 @@ class Schema $currentSchema = $this->getCurrentSchema(); - $metadataSchema = $this->schemaConverter->process($this->ormMetadata->getData(), $entityList); + $metadataSchema = $this->schemaConverter->process($this->ormMetadataData->getData(), $entityList); $this->initRebuildActions($currentSchema, $metadataSchema); diff --git a/application/Espo/Core/Utils/Metadata/OrmMetadata.php b/application/Espo/Core/Utils/Metadata/OrmMetadataData.php similarity index 99% rename from application/Espo/Core/Utils/Metadata/OrmMetadata.php rename to application/Espo/Core/Utils/Metadata/OrmMetadataData.php index 67a1eb9372..25e355db01 100644 --- a/application/Espo/Core/Utils/Metadata/OrmMetadata.php +++ b/application/Espo/Core/Utils/Metadata/OrmMetadataData.php @@ -39,7 +39,7 @@ use Espo\Core\{ Exceptions\Error, }; -class OrmMetadata +class OrmMetadataData { protected $data = null; diff --git a/application/Espo/ORM/EntityManager.php b/application/Espo/ORM/EntityManager.php index 6b7abdb3a9..3ae4a633cf 100644 --- a/application/Espo/ORM/EntityManager.php +++ b/application/Espo/ORM/EntityManager.php @@ -82,11 +82,15 @@ class EntityManager 'mysqli' => 'Mysql', ]; - public function __construct(array $params, RepositoryFactory $repositoryFactory, EntityFactory $entityFactory) - { + public function __construct( + array $params, + Metadata $metadata, + RepositoryFactory $repositoryFactory, + EntityFactory $entityFactory + ) { $this->params = $params; - $this->metadata = new Metadata(); + $this->metadata = $metadata; if (empty($this->params['platform'])) { if (empty($this->params['driver'])) { @@ -102,10 +106,6 @@ class EntityManager $this->params['platform'] = $this->driverPlatformMap[$this->params['driver']]; } - if (!empty($params['metadata'])) { - $this->setMetadata($params['metadata']); - } - $this->entityFactory = $entityFactory; $this->entityFactory->setEntityManager($this); @@ -372,16 +372,7 @@ class EntityManager return $this->queryBuilder; } - /** - * @deprecated - * @todo Remove. - */ - public function setMetadata(array $data) - { - $this->metadata->setData($data); - } - - public function getMetadata() + public function getMetadata() : Metadata { return $this->metadata; } diff --git a/application/Espo/ORM/Metadata.php b/application/Espo/ORM/Metadata.php index 5d6f7c4799..0ffa792844 100644 --- a/application/Espo/ORM/Metadata.php +++ b/application/Espo/ORM/Metadata.php @@ -32,20 +32,27 @@ namespace Espo\ORM; use InvalidArgumentException; /** - * Metadata of all entities. + * Metadata. */ class Metadata { protected $data; - public function __construct(array $data = []) + protected $dataProvider; + + public function __construct(MetadataDataProvider $dataProvider) { - $this->data = $data; + $this->data = $dataProvider->get(); + + $this->dataProvider = $dataProvider; } - public function setData(array $data) + /** + * Update data from the data provider. + */ + public function updateData() { - $this->data = $data; + $this->data = $this->dataProvider->get(); } /** diff --git a/application/Espo/ORM/MetadataDataProvider.php b/application/Espo/ORM/MetadataDataProvider.php new file mode 100644 index 0000000000..385c3aa58d --- /dev/null +++ b/application/Espo/ORM/MetadataDataProvider.php @@ -0,0 +1,38 @@ + Date: Mon, 21 Dec 2020 13:07:14 +0200 Subject: [PATCH 41/51] fix tests --- tests/unit/Espo/ORM/MetadataTest.php | 23 +++++++++++++++---- .../unit/Espo/ORM/MysqlQueryComposerTest.php | 10 +++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/tests/unit/Espo/ORM/MetadataTest.php b/tests/unit/Espo/ORM/MetadataTest.php index 6ff33cb4ff..c83a1fcf46 100644 --- a/tests/unit/Espo/ORM/MetadataTest.php +++ b/tests/unit/Espo/ORM/MetadataTest.php @@ -31,6 +31,7 @@ namespace tests\unit\Espo\ORM; use Espo\ORM\{ Metadata, + MetadataDataProvider, }; class MetadataTest extends \PHPUnit\Framework\TestCase @@ -42,7 +43,7 @@ class MetadataTest extends \PHPUnit\Framework\TestCase public function testHas1() { - $metadata = new Metadata([ + $metadata = $this->createMetadata([ 'Test' => [], ]); @@ -51,7 +52,7 @@ class MetadataTest extends \PHPUnit\Framework\TestCase public function testHas2() { - $metadata = new Metadata([ + $metadata = $this->createMetadata([ 'Test' => [], ]); @@ -60,7 +61,7 @@ class MetadataTest extends \PHPUnit\Framework\TestCase public function testGet1() { - $metadata = new Metadata([ + $metadata = $this->createMetadata([ 'Test' => [ 'indexes' => [], ], @@ -71,7 +72,7 @@ class MetadataTest extends \PHPUnit\Framework\TestCase public function testGet2() { - $metadata = new Metadata([ + $metadata = $this->createMetadata([ 'Test' => [ 'relations' => [ 'test' => [ @@ -86,7 +87,7 @@ class MetadataTest extends \PHPUnit\Framework\TestCase public function testGet3() { - $metadata = new Metadata([ + $metadata = $this->createMetadata([ 'Test' => [ 'relations' => [ 'test' => [ @@ -98,4 +99,16 @@ class MetadataTest extends \PHPUnit\Framework\TestCase $this->assertEquals('hasMany', $metadata->get('Test', ['relations', 'test', 'type'])); } + + protected function createMetadata(array $data) : Metadata + { + $metadataDataProvider = $this->createMock(MetadataDataProvider::class); + + $metadataDataProvider + ->expects($this->any()) + ->method('get') + ->willReturn($data); + + return new Metadata($metadataDataProvider); + } } diff --git a/tests/unit/Espo/ORM/MysqlQueryComposerTest.php b/tests/unit/Espo/ORM/MysqlQueryComposerTest.php index cd97ea971b..b6f02a1bb8 100644 --- a/tests/unit/Espo/ORM/MysqlQueryComposerTest.php +++ b/tests/unit/Espo/ORM/MysqlQueryComposerTest.php @@ -35,6 +35,7 @@ use Espo\ORM\{ QueryComposer\MysqlQueryComposer as QueryComposer, QueryBuilder, EntityManager, + MetadataDataProvider, }; use Espo\ORM\QueryParams\{ @@ -92,7 +93,14 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase $ormMetadata = include('tests/unit/testData/DB/ormMetadata.php'); - $this->metadata = new Metadata($ormMetadata); + $metadataDataProvider = $this->createMock(MetadataDataProvider::class); + + $metadataDataProvider + ->expects($this->any()) + ->method('get') + ->willReturn($ormMetadata); + + $this->metadata = new Metadata($metadataDataProvider); $this->getMockBuilder('Espo\\ORM\\Metadata')->disableOriginalConstructor()->getMock(); From 99dc6b1623cdf3f93883ccff78c078a692c4ef78 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 21 Dec 2020 13:13:30 +0200 Subject: [PATCH 42/51] phpunit config --- phpunit.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 phpunit.xml diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000000..180b4cd607 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,10 @@ + + + + tests/unit + + + tests/integration + + + \ No newline at end of file From 1b517a6eb17fb8340f97d5776e52a0262325b2d0 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 21 Dec 2020 13:16:12 +0200 Subject: [PATCH 43/51] cleanup --- application/Espo/Core/Utils/Config.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/application/Espo/Core/Utils/Config.php b/application/Espo/Core/Utils/Config.php index f73b587289..4c574ca56d 100644 --- a/application/Espo/Core/Utils/Config.php +++ b/application/Espo/Core/Utils/Config.php @@ -49,8 +49,6 @@ class Config private $cacheTimestamp = 'cacheTimestamp'; - protected $adminItems = []; - protected $associativeArrayAttributeList = [ 'currencyRates', 'database', From facb5c747d46b7ed6094c1e06ef11feb1c9ac7c2 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 21 Dec 2020 13:58:23 +0200 Subject: [PATCH 44/51] fix test --- tests/integration/Espo/Webhook/AclTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/integration/Espo/Webhook/AclTest.php b/tests/integration/Espo/Webhook/AclTest.php index 9e159ef943..dfe3d6b2e9 100644 --- a/tests/integration/Espo/Webhook/AclTest.php +++ b/tests/integration/Espo/Webhook/AclTest.php @@ -90,8 +90,6 @@ class AclTest extends \tests\integration\Core\BaseTestCase $this->auth(null, null, null, 'ApiKey', $request); - $data = json_decode(); - $app = $this->createApplication(); $controllerManager = $app->getContainer()->get('injectableFactory')->create(ControllerManager::class); From 85c199d563c8682ef0776d473237a1c7eaa37819 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 21 Dec 2020 14:15:10 +0200 Subject: [PATCH 45/51] cd fix --- .../admin/layouts/bottom-panels-detail.js | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/client/src/views/admin/layouts/bottom-panels-detail.js b/client/src/views/admin/layouts/bottom-panels-detail.js index 3d730c8781..7dc02b1c19 100644 --- a/client/src/views/admin/layouts/bottom-panels-detail.js +++ b/client/src/views/admin/layouts/bottom-panels-detail.js @@ -46,19 +46,27 @@ define('views/admin/layouts/bottom-panels-detail', 'views/admin/layouts/side-pan this.getMetadata().get(['scopes', this.scope, 'stream']) ) { panelListAll.push('stream'); + labels['stream'] = this.translate('Stream'); + params['stream'] = { name: 'stream', sticked: true, index: 2, }; } + (this.getMetadata().get(['clientDefs', this.scope, 'bottomPanels', this.viewType]) || []).forEach(function (item) { - if (!item.name) return; + if (!item.name) { + return; + } + panelListAll.push(item.name); + if (item.label) { labels[item.name] = item.label; } + params[item.name] = Espo.Utils.clone(item); if ('order' in item) { @@ -71,8 +79,14 @@ define('views/admin/layouts/bottom-panels-detail', 'views/admin/layouts/side-pan if (this.hasRelationships) { var linkDefs = this.getMetadata().get(['entityDefs', this.scope, 'links']) || {}; Object.keys(linkDefs).forEach(function (link) { - if (linkDefs[link].disabled || linkDefs[link].layoutRelationshipsDisabled) return; - if (!~['hasMany', 'hasChildren'].indexOf(linkDefs[link].type)) return; + if (linkDefs[link].disabled || linkDefs[link].layoutRelationshipsDisabled) { + return; + } + + if (!~['hasMany', 'hasChildren'].indexOf(linkDefs[link].type)) { + return; + } + panelListAll.push(link); labels[link] = this.translate(link, 'links', this.scope); @@ -82,11 +96,18 @@ define('views/admin/layouts/bottom-panels-detail', 'views/admin/layouts/side-pan index: 5, }; this.dataAttributeList.forEach(function (attribute) { - if (attribute in item) return; + if (attribute in item) { + return; + } + var value = this.getMetadata().get( ['clientDefs', this.scope, 'relationshipPanels', item.name, attribute] ); - if (value === null) return; + + if (value === null) { + return; + } + item[attribute] = value; }, this); @@ -146,8 +167,10 @@ define('views/admin/layouts/bottom-panels-detail', 'views/admin/layouts/side-pan name: item, label: labelText, }; + if (o.name[0] === '_') { o.notEditable = true; + if (o.name == '_delimiter_') { o.label = '. . .'; } @@ -158,25 +181,34 @@ define('views/admin/layouts/bottom-panels-detail', 'views/admin/layouts/side-pan name: item, label: labelText, }; + if (o.name[0] === '_') { o.notEditable = true; if (o.name == '_delimiter_') { o.label = '. . .'; } } + if (o.name in params) { this.dataAttributeList.forEach(function (attribute) { - if (attribute === 'name') return; + if (attribute === 'name') { + return; + } + var itemParams = params[o.name] || {}; + if (attribute in itemParams) { o[attribute] = itemParams[attribute]; } }, this); } + for (var i in itemData) { o[i] = itemData[i]; } + o.index = ('index' in itemData) ? itemData.index : index; + this.rowLayout.push(o); this.itemsData[o.name] = Espo.Utils.cloneDeep(o); @@ -194,12 +226,14 @@ define('views/admin/layouts/bottom-panels-detail', 'views/admin/layouts/side-pan var newLayout = {}; for (var i in layout) { - if (layout[i].disabled && this.links[i]) continue; + if (layout[i].disabled && this.links[i]) { + continue; + } + newLayout[i] = layout[i]; } return newLayout; }, - }); }); From 6fecf6164830c65649e98d8dc7da1c3e755bf8e5 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 21 Dec 2020 15:35:11 +0200 Subject: [PATCH 46/51] fix notice --- application/Espo/Jobs/Cleanup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/Espo/Jobs/Cleanup.php b/application/Espo/Jobs/Cleanup.php index 36132882f5..6ba70b4c15 100644 --- a/application/Espo/Jobs/Cleanup.php +++ b/application/Espo/Jobs/Cleanup.php @@ -107,7 +107,7 @@ class Cleanup implements Job $o1 = $a['order'] ?? 0; $o2 = $b['order'] ?? 0; - return $o1 > $o2; + return $o1 <=> $o2; }); $injectableFactory = $this->injectableFactory; From 5f95859bcd2d0bd56400b8cd304ca2e93df9775f Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 23 Dec 2020 09:21:15 +0200 Subject: [PATCH 47/51] delete orm metadata service --- application/Espo/Core/Loaders/OrmMetadata.php | 50 ------------------- 1 file changed, 50 deletions(-) delete mode 100644 application/Espo/Core/Loaders/OrmMetadata.php diff --git a/application/Espo/Core/Loaders/OrmMetadata.php b/application/Espo/Core/Loaders/OrmMetadata.php deleted file mode 100644 index 4623bb10df..0000000000 --- a/application/Espo/Core/Loaders/OrmMetadata.php +++ /dev/null @@ -1,50 +0,0 @@ -entityManager = $entityManager; - } - - public function load() : Metadata - { - return $this->entityManager->getMetadata(); - } -} From 9642f9ae41e3798ecd24238269c3d16ae9ee9e78 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 23 Dec 2020 09:32:51 +0200 Subject: [PATCH 48/51] entity manager factory --- .../Espo/Core/Loaders/EntityManager.php | 64 ++---------- .../Espo/Core/ORM/EntityManagerFactory.php | 97 +++++++++++++++++++ 2 files changed, 103 insertions(+), 58 deletions(-) create mode 100644 application/Espo/Core/ORM/EntityManagerFactory.php diff --git a/application/Espo/Core/Loaders/EntityManager.php b/application/Espo/Core/Loaders/EntityManager.php index a990e0616a..d9dc496d27 100644 --- a/application/Espo/Core/Loaders/EntityManager.php +++ b/application/Espo/Core/Loaders/EntityManager.php @@ -30,73 +30,21 @@ namespace Espo\Core\Loaders; use Espo\Core\{ - Utils\Config, - InjectableFactory, + ORM\EntityManagerFactory, ORM\EntityManager as EntityManagerService, - ORM\RepositoryFactory, - ORM\EntityFactory, - ORM\Helper, - ORM\MetadataDataProvider, -}; - -use Espo\{ - ORM\Metadata as OrmMetadata, }; class EntityManager implements Loader { - protected $config; - protected $injectableFactory; - protected $metadataDataProvider; + protected $entityManagerFactory; - public function __construct( - Config $config, - InjectableFactory $injectableFactory, - MetadataDataProvider $metadataDataProvider - ) { - $this->config = $config; - $this->injectableFactory = $injectableFactory; - $this->metadataDataProvider = $metadataDataProvider; + public function __construct(EntityManagerFactory $entityManagerFactory) + { + $this->entityManagerFactory = $entityManagerFactory; } public function load() : EntityManagerService { - $entityFactory = $this->injectableFactory->create(EntityFactory::class); - - $repositoryFactory = $this->injectableFactory->createWith(RepositoryFactory::class, [ - 'entityFactory' => $entityFactory, - ]); - - $helper = $this->injectableFactory->create(Helper::class); - - $config = $this->config; - - $params = [ - 'host' => $config->get('database.host'), - 'port' => $config->get('database.port'), - 'dbname' => $config->get('database.dbname'), - 'user' => $config->get('database.user'), - 'charset' => $config->get('database.charset', 'utf8'), - 'password' => $config->get('database.password'), - 'driver' => $config->get('database.driver'), - 'platform' => $config->get('database.platform'), - 'sslCA' => $config->get('database.sslCA'), - 'sslCert' => $config->get('database.sslCert'), - 'sslKey' => $config->get('database.sslKey'), - 'sslCAPath' => $config->get('database.sslCAPath'), - 'sslCipher' => $config->get('database.sslCipher'), - ]; - - $metadata = new OrmMetadata($this->metadataDataProvider); - - $entityManager = $this->injectableFactory->createWith(EntityManagerService::class, [ - 'params' => $params, - 'metadata' => $metadata, - 'repositoryFactory' => $repositoryFactory, - 'entityFactory' => $entityFactory, - 'helper' => $helper, - ]); - - return $entityManager; + return $this->entityManagerFactory->create(); } } diff --git a/application/Espo/Core/ORM/EntityManagerFactory.php b/application/Espo/Core/ORM/EntityManagerFactory.php new file mode 100644 index 0000000000..67ef69d876 --- /dev/null +++ b/application/Espo/Core/ORM/EntityManagerFactory.php @@ -0,0 +1,97 @@ +config = $config; + $this->injectableFactory = $injectableFactory; + $this->metadataDataProvider = $metadataDataProvider; + } + + public function create() : EntityManager + { + $entityFactory = $this->injectableFactory->create(EntityFactory::class); + + $repositoryFactory = $this->injectableFactory->createWith(RepositoryFactory::class, [ + 'entityFactory' => $entityFactory, + ]); + + $helper = $this->injectableFactory->create(Helper::class); + + $config = $this->config; + + $params = [ + 'host' => $config->get('database.host'), + 'port' => $config->get('database.port'), + 'dbname' => $config->get('database.dbname'), + 'user' => $config->get('database.user'), + 'charset' => $config->get('database.charset', 'utf8'), + 'password' => $config->get('database.password'), + 'driver' => $config->get('database.driver'), + 'platform' => $config->get('database.platform'), + 'sslCA' => $config->get('database.sslCA'), + 'sslCert' => $config->get('database.sslCert'), + 'sslKey' => $config->get('database.sslKey'), + 'sslCAPath' => $config->get('database.sslCAPath'), + 'sslCipher' => $config->get('database.sslCipher'), + ]; + + $metadata = new Metadata($this->metadataDataProvider); + + $entityManager = $this->injectableFactory->createWith(EntityManager::class, [ + 'params' => $params, + 'metadata' => $metadata, + 'repositoryFactory' => $repositoryFactory, + 'entityFactory' => $entityFactory, + 'helper' => $helper, + ]); + + return $entityManager; + } +} From e43265fe57e691032f9cdd3dc8bf463224312385 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 23 Dec 2020 09:33:19 +0200 Subject: [PATCH 49/51] gruntfile fix --- Gruntfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 90be173ab4..1ea77f9f43 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -315,11 +315,11 @@ module.exports = function (grunt) { }); grunt.registerTask("unit-tests-run", function() { - cp.execSync("vendor/bin/phpunit --bootstrap=./vendor/autoload.php ./tests/unit", {stdio: 'inherit'}); + cp.execSync("vendor/bin/phpunit ./tests/unit", {stdio: 'inherit'}); }); grunt.registerTask("integration-tests-run", function() { - cp.execSync("vendor/bin/phpunit --bootstrap=./vendor/autoload.php ./tests/integration", {stdio: 'inherit'}); + cp.execSync("vendor/bin/phpunit ./tests/integration", {stdio: 'inherit'}); }); grunt.registerTask("zip", function() { From 713ecb3be7e6041fc8a5bc2e38aa4e66a51ef003 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 25 Dec 2020 14:02:42 +0200 Subject: [PATCH 50/51] fix orm select builder --- application/Espo/ORM/QueryParams/SelectBuilder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/Espo/ORM/QueryParams/SelectBuilder.php b/application/Espo/ORM/QueryParams/SelectBuilder.php index f810ac9f84..b9cea30cb5 100644 --- a/application/Espo/ORM/QueryParams/SelectBuilder.php +++ b/application/Espo/ORM/QueryParams/SelectBuilder.php @@ -30,6 +30,7 @@ namespace Espo\ORM\QueryParams; use InvalidArgumentException; +use RuntimeException; class SelectBuilder implements Builder { From ca17a5453a80a6bedf2af30bf1dfd068a5b77442 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 28 Dec 2020 11:51:56 +0200 Subject: [PATCH 51/51] fix warning --- application/Espo/Tools/LeadCapture/LeadCapture.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/Espo/Tools/LeadCapture/LeadCapture.php b/application/Espo/Tools/LeadCapture/LeadCapture.php index 46e619c515..b38c3b6a26 100644 --- a/application/Espo/Tools/LeadCapture/LeadCapture.php +++ b/application/Espo/Tools/LeadCapture/LeadCapture.php @@ -201,6 +201,8 @@ class LeadCapture $lead = $this->getLeadWithPopulatedData($leadCapture, $data); } + $campaign = null; + $campaingService = $this->serviceFactory->create('Campaign'); if ($leadCapture->get('campaignId')) {