diff --git a/application/Espo/Core/Htmlizer/Htmlizer.php b/application/Espo/Core/Htmlizer/Htmlizer.php index b1d7a61586..b377815fcc 100644 --- a/application/Espo/Core/Htmlizer/Htmlizer.php +++ b/application/Espo/Core/Htmlizer/Htmlizer.php @@ -100,10 +100,10 @@ class Htmlizer $forbidenAttributeList = $this->getAcl()->getScopeForbiddenAttributeList($entity->getEntityType(), 'read'); } + foreach ($fieldList as $field) { if (in_array($field, $forbidenAttributeList)) continue; - $type = $entity->getAttributeType($field); if ($type == Entity::DATETIME) { @@ -219,6 +219,14 @@ class Htmlizer return number_format($number, $decimals, $decimalPoint, $thousandsSeparator); } return ''; + }, + 'var' => function ($context, $options) { + if ($context && isset($context[0]) && isset($context[1])) { + if (isset($context[1][$context[0]])) { + return $context[1][$context[0]]; + } + } + return; } ], 'hbhelpers' => [ diff --git a/application/Espo/Core/Mail/Importer.php b/application/Espo/Core/Mail/Importer.php index f935a65f70..730ceffade 100644 --- a/application/Espo/Core/Mail/Importer.php +++ b/application/Espo/Core/Mail/Importer.php @@ -205,6 +205,10 @@ class Importer ))->findOne(); if ($replied) { $email->set('repliedId', $replied->id); + $repliedTeamIdList = $replied->getLinkMultipleIdList('teams'); + foreach ($repliedTeamIdList as $repliedTeamId) { + $email->addLinkMultipleId('teams', $repliedTeamId); + } } } diff --git a/application/Espo/Core/SelectManagers/Base.php b/application/Espo/Core/SelectManagers/Base.php index 28b5f2f5a8..7b7d904bbb 100644 --- a/application/Espo/Core/SelectManagers/Base.php +++ b/application/Espo/Core/SelectManagers/Base.php @@ -1504,7 +1504,7 @@ class Base ); } - public function getFullTextSearchDataForTextFilter($textFilter, $stripWildcard = false) + public function getFullTextSearchDataForTextFilter($textFilter, $isAuxiliaryUse = false) { if (array_key_exists($textFilter, $this->fullTextSearchDataCacheHash)) { return $this->fullTextSearchDataCacheHash[$textFilter]; @@ -1518,7 +1518,7 @@ class Base $fieldList = $this->getTextFilterFieldList(); - if ($stripWildcard) { + if ($isAuxiliaryUse) { $textFilter = str_replace('%', '', $textFilter); } @@ -1562,6 +1562,8 @@ class Base if ($useFullTextSearch) { if ( + $isAuxiliaryUse + || mb_strpos($textFilter, ' ') === false && mb_strpos($textFilter, '+') === false @@ -1644,14 +1646,21 @@ class Base $attributeType = $fieldDefs[$field]['type']; } + if ($attributeType === 'int') { + if (is_numeric($textFilter)) { + $group[$field] = intval($textFilter); + } + continue; + } + if (!$skipWidlcards) { if ( - strlen($textFilter) >= $textFilterContainsMinLength + mb_strlen($textFilter) >= $textFilterContainsMinLength && ( $attributeType == 'text' || - !empty($this->textFilterUseContainsAttributeList[$field]) + in_array($field, $this->textFilterUseContainsAttributeList) || $attributeType == 'varchar' && $this->getConfig()->get('textFilterUseContainsForVarchar') ) @@ -1679,6 +1688,10 @@ class Base $group[$field . '*'] = $expression; } + if (!$forceFullTextSearch) { + $this->applyAdditionalToTextFilterGroup($textFilter, $group); + } + if (!empty($fullTextGroup)) { $group['AND'] = $fullTextGroup; } @@ -1694,6 +1707,10 @@ class Base ]; } + protected function applyAdditionalToTextFilterGroup($textFilter, &$group) + { + } + public function applyAccess(&$result) { $this->prepareResult($result); diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/HasMany.php b/application/Espo/Core/Utils/Database/Orm/Relations/HasMany.php index 3b3fd2b8ea..1e6e866753 100644 --- a/application/Espo/Core/Utils/Database/Orm/Relations/HasMany.php +++ b/application/Espo/Core/Utils/Database/Orm/Relations/HasMany.php @@ -43,11 +43,11 @@ class HasMany extends Base $entityName => array ( 'fields' => array( $linkName.'Ids' => array( - 'type' => 'varchar', + 'type' => 'jsonArray', 'notStorable' => true, ), $linkName.'Names' => array( - 'type' => 'varchar', + 'type' => 'jsonObject', 'notStorable' => true, ), ), diff --git a/application/Espo/Core/Utils/Database/Orm/Relations/ManyMany.php b/application/Espo/Core/Utils/Database/Orm/Relations/ManyMany.php index 41bb50daa4..7e75aef7fd 100644 --- a/application/Espo/Core/Utils/Database/Orm/Relations/ManyMany.php +++ b/application/Espo/Core/Utils/Database/Orm/Relations/ManyMany.php @@ -50,11 +50,11 @@ class ManyMany extends Base $entityName => array( 'fields' => array( $linkName.'Ids' => array( - 'type' => 'varchar', + 'type' => 'jsonArray', 'notStorable' => true, ), $linkName.'Names' => array( - 'type' => 'varchar', + 'type' => 'jsonObject', 'notStorable' => true, ), ), diff --git a/application/Espo/Core/defaults/config.php b/application/Espo/Core/defaults/config.php index 87401abadb..480e6510e5 100644 --- a/application/Espo/Core/defaults/config.php +++ b/application/Espo/Core/defaults/config.php @@ -170,7 +170,7 @@ return array ( 'inlineAttachmentUploadMaxSize' => 20, 'textFilterUseContainsForVarchar' => false, 'tabColorsDisabled' => false, - 'massPrintPdfMaxCount' => 100, + 'massPrintPdfMaxCount' => 50, 'isInstalled' => false ); diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Case.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Case.json index cd2845ecf1..b518ed124e 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Case.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Case.json @@ -166,7 +166,8 @@ }, "collection": { "sortBy": "number", - "asc": false + "asc": false, + "textFilterFields": ["name", "number"] }, "indexes": { "status": { diff --git a/application/Espo/ORM/DB/Query/Base.php b/application/Espo/ORM/DB/Query/Base.php index 54be559fe3..dd6300c2c4 100644 --- a/application/Espo/ORM/DB/Query/Base.php +++ b/application/Espo/ORM/DB/Query/Base.php @@ -336,7 +336,7 @@ abstract class Base } $columns = substr($rest, 0, $delimiterPosition); - $query = substr($rest, $delimiterPosition + 1); + $query = mb_substr($rest, $delimiterPosition + 1); $columnList = explode(',', $columns); @@ -837,6 +837,7 @@ abstract class Base if (empty($isComplex)) { if (!isset($entity->fields[$field])) { + $whereParts[] = '0'; continue; } diff --git a/application/Espo/Resources/i18n/en_US/Global.json b/application/Espo/Resources/i18n/en_US/Global.json index f88250e58f..45ac09eb91 100644 --- a/application/Espo/Resources/i18n/en_US/Global.json +++ b/application/Espo/Resources/i18n/en_US/Global.json @@ -340,6 +340,7 @@ "children": "Children", "id": "ID", "ids": "IDs", + "type": "Type", "names": "Names", "targetListIsOptedOut": "Is Opted Out (Target List)" }, diff --git a/application/Espo/Resources/metadata/fields/address.json b/application/Espo/Resources/metadata/fields/address.json index 1b615fab68..9f086d0706 100644 --- a/application/Espo/Resources/metadata/fields/address.json +++ b/application/Espo/Resources/metadata/fields/address.json @@ -35,7 +35,8 @@ "layoutListDisabled": true, "provider": "Google", "height": 300, - "exportDisabled": true + "exportDisabled": true, + "importDisabled": true } }, "notMergeable":true, diff --git a/application/Espo/Resources/metadata/fields/autoincrement.json b/application/Espo/Resources/metadata/fields/autoincrement.json index ecaf2e266f..aa71f4ee6d 100644 --- a/application/Espo/Resources/metadata/fields/autoincrement.json +++ b/application/Espo/Resources/metadata/fields/autoincrement.json @@ -8,5 +8,6 @@ "autoincrement":true, "unique":true }, + "textFilter": true, "readOnly": true } diff --git a/application/Espo/Resources/metadata/fields/currency.json b/application/Espo/Resources/metadata/fields/currency.json index 8e3da26b3b..9cae1988f1 100644 --- a/application/Espo/Resources/metadata/fields/currency.json +++ b/application/Espo/Resources/metadata/fields/currency.json @@ -33,7 +33,8 @@ }, "converted":{ "type":"currencyConverted", - "readOnly": true + "readOnly": true, + "importDisabled": true } }, "filter": true, diff --git a/application/Espo/Resources/metadata/fields/email.json b/application/Espo/Resources/metadata/fields/email.json index 18f67274c8..95bd7fa265 100644 --- a/application/Espo/Resources/metadata/fields/email.json +++ b/application/Espo/Resources/metadata/fields/email.json @@ -27,5 +27,6 @@ "fieldDefs":{ "notStorable":true }, + "textFilter": true, "personalData": true } diff --git a/application/Espo/Resources/metadata/fields/int.json b/application/Espo/Resources/metadata/fields/int.json index 6a10dabfff..fc5c232544 100644 --- a/application/Espo/Resources/metadata/fields/int.json +++ b/application/Espo/Resources/metadata/fields/int.json @@ -31,5 +31,6 @@ } ], "filter": true, + "textFilter": true, "personalData": true } diff --git a/application/Espo/Resources/metadata/fields/number.json b/application/Espo/Resources/metadata/fields/number.json index ede2d5e9c2..ef7cbf4a79 100644 --- a/application/Espo/Resources/metadata/fields/number.json +++ b/application/Espo/Resources/metadata/fields/number.json @@ -29,5 +29,6 @@ "unique": false }, "hookClassName": "\\Espo\\Core\\Utils\\FieldManager\\Hooks\\NumberType", + "textFilter": true, "readOnly": true } diff --git a/application/Espo/Resources/metadata/fields/personName.json b/application/Espo/Resources/metadata/fields/personName.json index 467a38b329..b95d2d0c90 100644 --- a/application/Espo/Resources/metadata/fields/personName.json +++ b/application/Espo/Resources/metadata/fields/personName.json @@ -30,6 +30,7 @@ "filter":true, "skipOrmDefs": true, "personalData": true, + "textFilter": true, "fullTextSearch": true, "fullTextSearchColumnList": [ "first", diff --git a/application/Espo/Resources/metadata/fields/phone.json b/application/Espo/Resources/metadata/fields/phone.json index 4d03d3fcfb..96878f2628 100644 --- a/application/Espo/Resources/metadata/fields/phone.json +++ b/application/Espo/Resources/metadata/fields/phone.json @@ -26,5 +26,6 @@ "notStorable":true }, "translatedOptions": true, + "textFilter": true, "personalData": true } diff --git a/application/Espo/Resources/metadata/fields/text.json b/application/Espo/Resources/metadata/fields/text.json index 9e62616d47..a9ecf830f2 100644 --- a/application/Espo/Resources/metadata/fields/text.json +++ b/application/Espo/Resources/metadata/fields/text.json @@ -38,5 +38,6 @@ ], "filter": true, "personalData": true, + "textFilter": true, "fullTextSearch": true } diff --git a/application/Espo/Resources/metadata/fields/varchar.json b/application/Espo/Resources/metadata/fields/varchar.json index 5502b12c07..338488ee9a 100644 --- a/application/Espo/Resources/metadata/fields/varchar.json +++ b/application/Espo/Resources/metadata/fields/varchar.json @@ -29,5 +29,6 @@ ], "filter": true, "personalData": true, + "textFilter": true, "fullTextSearch": true } diff --git a/application/Espo/Resources/metadata/fields/wysiwyg.json b/application/Espo/Resources/metadata/fields/wysiwyg.json index 5b482abda2..22ec56d166 100644 --- a/application/Espo/Resources/metadata/fields/wysiwyg.json +++ b/application/Espo/Resources/metadata/fields/wysiwyg.json @@ -36,5 +36,6 @@ "type":"text" }, "personalData": true, + "textFilter": true, "fullTextSearch": true } diff --git a/application/Espo/SelectManagers/Email.php b/application/Espo/SelectManagers/Email.php index 0ad1153d8f..f44a095139 100644 --- a/application/Espo/SelectManagers/Email.php +++ b/application/Espo/SelectManagers/Email.php @@ -270,28 +270,16 @@ class Email extends \Espo\Core\SelectManagers\Base ); } - protected function textFilter($textFilter, &$result) + protected function applyAdditionalToTextFilterGroup($textFilter, &$group) { - $d = array(); - - $d['name*'] = '%' . $textFilter . '%'; - if (strlen($textFilter) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH) { - $d['bodyPlain*'] = '%' . $textFilter . '%'; - $d['body*'] = '%' . $textFilter . '%'; - $emailAddressId = $this->getEmailAddressIdByValue($textFilter); if ($emailAddressId) { $this->leftJoinEmailAddress($result); - $d['fromEmailAddressId'] = $emailAddressId; - $d['emailEmailAddress.emailAddressId'] = $emailAddressId; + $group['fromEmailAddressId'] = $emailAddressId; + $group['emailEmailAddress.emailAddressId'] = $emailAddressId; } } - - $result['whereClause'][] = array( - 'OR' => $d - ); - } protected function getEmailAddressIdByValue($value) diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php index ec2844b13e..108237be10 100644 --- a/application/Espo/Services/Record.php +++ b/application/Espo/Services/Record.php @@ -2168,12 +2168,19 @@ class Record extends \Espo\Core\Services\Base if (!empty($params['sortBy'])) { $sortByField = $params['sortBy']; $sortByFieldType = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortByField, 'type']); + if ($sortByFieldType === 'currency') { if (!in_array($sortByField . 'Converted', $attributeList)) { $attributeList[] = $sortByField . 'Converted'; } } + $sortByAttributeList = $this->getFieldManagerUtil()->getAttributeList($this->getEntityType(), $sortByField); + foreach ($sortByAttributeList as $attribute) { + if (!in_array($attribute, $attributeList) && $seed->hasAttribute($attribute)) { + $attributeList[] = $attribute; + } + } } foreach ($this->mandatorySelectAttributeList as $attribute) { diff --git a/client/modules/crm/src/views/record/row-actions/activities.js b/client/modules/crm/src/views/record/row-actions/activities.js index c58a3eb00a..62eb1bac2a 100644 --- a/client/modules/crm/src/views/record/row-actions/activities.js +++ b/client/modules/crm/src/views/record/row-actions/activities.js @@ -36,7 +36,8 @@ Espo.define('crm:views/record/row-actions/activities', 'views/record/row-actions label: 'View', data: { id: this.model.id - } + }, + link: '#' + this.model.name + '/view/' + this.model.id }]; if (this.options.acl.edit) { list.push({ @@ -44,7 +45,8 @@ Espo.define('crm:views/record/row-actions/activities', 'views/record/row-actions label: 'Edit', data: { id: this.model.id - } + }, + link: '#' + this.model.name + '/edit/' + this.model.id }); if (this.model.name == 'Meeting' || this.model.name == 'Call') { list.push({ diff --git a/client/modules/crm/src/views/record/row-actions/history.js b/client/modules/crm/src/views/record/row-actions/history.js index 465f4e0729..4c1b21a514 100644 --- a/client/modules/crm/src/views/record/row-actions/history.js +++ b/client/modules/crm/src/views/record/row-actions/history.js @@ -36,7 +36,8 @@ Espo.define('crm:views/record/row-actions/history', 'views/record/row-actions/re label: 'View', data: { id: this.model.id - } + }, + link: '#' + this.model.name + '/view/' + this.model.id }]; if (this.model.name == 'Email') { list.push({ @@ -54,7 +55,8 @@ Espo.define('crm:views/record/row-actions/history', 'views/record/row-actions/re label: 'Edit', data: { id: this.model.id - } + }, + link: '#' + this.model.name + '/edit/' + this.model.id } ]); } diff --git a/client/modules/crm/src/views/record/row-actions/tasks.js b/client/modules/crm/src/views/record/row-actions/tasks.js index 9c4132dce1..61a56f67ec 100644 --- a/client/modules/crm/src/views/record/row-actions/tasks.js +++ b/client/modules/crm/src/views/record/row-actions/tasks.js @@ -36,7 +36,8 @@ Espo.define('crm:views/record/row-actions/tasks', 'views/record/row-actions/rela label: 'View', data: { id: this.model.id - } + }, + link: '#' + this.model.name + '/view/' + this.model.id }]; if (this.options.acl.edit) { list.push({ @@ -44,7 +45,8 @@ Espo.define('crm:views/record/row-actions/tasks', 'views/record/row-actions/rela label: 'Edit', data: { id: this.model.id - } + }, + link: '#' + this.model.name + '/edit/' + this.model.id }); if (!~['Completed', 'Canceled'].indexOf(this.model.get('status'))) { diff --git a/client/src/email-helper.js b/client/src/email-helper.js index 95e284dce8..d5036fd1bf 100644 --- a/client/src/email-helper.js +++ b/client/src/email-helper.js @@ -161,6 +161,16 @@ Espo.define('email-helper', [], function () { attributes['parentType'] = model.get('parentType'); } + if (model.get('teamsIds') && model.get('teamsIds').length) { + attributes.teamsIds = Espo.Utils.clone(model.get('teamsIds')); + attributes.teamsNames = Espo.Utils.clone(model.get('teamsNames') || {}); + + if (this.user.get('defaultTeamId')) { + attributes.teamsIds.push(this.user.get('defaultTeamId')); + attributes.teamsNames[this.user.get('defaultTeamId')] = this.user.get('defaultTeamName'); + } + } + attributes.nameHash = nameHash; attributes.repliedId = model.id; diff --git a/client/src/views/admin/entity-manager/modals/edit-entity.js b/client/src/views/admin/entity-manager/modals/edit-entity.js index bb9e0f0a42..127d6ac625 100644 --- a/client/src/views/admin/entity-manager/modals/edit-entity.js +++ b/client/src/views/admin/entity-manager/modals/edit-entity.js @@ -280,9 +280,8 @@ Espo.define('views/admin/entity-manager/modals/edit-entity', ['views/modal', 'mo }); var optionList = Object.keys(fieldDefs).filter(function (item) { - if (!~['varchar', 'wysiwyg', 'text', 'phone', 'email', 'personName', 'number'].indexOf(this.getMetadata().get(['entityDefs', scope, 'fields', item, 'type']))) { - return false; - } + var fieldType = fieldDefs[item].type; + if (!this.getMetadata().get(['fields', fieldType, 'textFilter'])) return false if (this.getMetadata().get(['entityDefs', scope, 'fields', item, 'disabled'])) { return false; } diff --git a/client/src/views/import/step2.js b/client/src/views/import/step2.js index 39f3e73f58..beb0e737b8 100644 --- a/client/src/views/import/step2.js +++ b/client/src/views/import/step2.js @@ -166,7 +166,7 @@ Espo.define('views/import/step2', 'view', function (Dep) { for (var field in defs) { var d = defs[field]; - if (!~this.allowedFieldList.indexOf(field) && (d.readOnly || d.disabled || d.importDisabled)) { + if (!~this.allowedFieldList.indexOf(field) && (d.disabled || d.importDisabled)) { continue; } fieldList.push(field); @@ -187,7 +187,7 @@ Espo.define('views/import/step2', 'view', function (Dep) { for (var field in fields) { var d = fields[field]; - if (!~this.allowedFieldList.indexOf(field) && (((d.readOnly || d.disabled) && !d.importNotDisabled) || d.importDisabled)) { + if (!~this.allowedFieldList.indexOf(field) && (((d.disabled) && !d.importNotDisabled) || d.importDisabled)) { continue; } @@ -260,6 +260,11 @@ Espo.define('views/import/step2', 'view', function (Dep) { if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) { label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('name', 'fields') + ')'; } + } else if (field.indexOf('Type') === field.length - 4) { + var baseField = field.substr(0, field.length - 4); + if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) { + label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('type', 'fields') + ')'; + } } else if (field.indexOf('phoneNumber') === 0) { var phoneNumberType = field.substr(11); var phoneNumberTypeLabel = this.getLanguage().translateOption(phoneNumberType, 'phoneNumber', scope); @@ -300,7 +305,13 @@ Espo.define('views/import/step2', 'view', function (Dep) { $('#default-values-container').append(html); var type = Espo.Utils.upperCaseFirst(this.model.getFieldParam(name, 'type')); - this.createView(name, this.getFieldManager().getViewName(type), { + + var viewName = + this.getMetadata().get(['entityDefs', this.scope, 'fields', name, 'view']) + || + this.getFieldManager().getViewName(type); + + this.createView(name, viewName, { model: this.model, el: this.getSelector() + ' .field[data-name="' + name + '"]', defs: { diff --git a/client/src/views/record/list.js b/client/src/views/record/list.js index 54342d0662..309a4e69b5 100644 --- a/client/src/views/record/list.js +++ b/client/src/views/record/list.js @@ -143,26 +143,7 @@ Espo.define('views/record/list', 'view', function (Dep) { } }, 'click .select-all': function (e) { - this.checkedList = []; - - if (e.currentTarget.checked) { - this.$el.find('input.record-checkbox').prop('checked', true); - this.$el.find('.actions-button').removeAttr('disabled'); - this.collection.models.forEach(function (model) { - this.checkedList.push(model.id); - }, this); - - this.$el.find('.list > table tbody tr').addClass('active'); - } else { - if (this.allResultIsChecked) { - this.unselectAllResult(); - } - this.$el.find('input.record-checkbox').prop('checked', false); - this.$el.find('.actions-button').attr('disabled', true); - this.$el.find('.list > table tbody tr').removeClass('active'); - } - - this.trigger('check'); + this.selectAllHandler(e.currentTarget.checked); }, 'click .action': function (e) { var $el = $(e.currentTarget); @@ -190,6 +171,30 @@ Espo.define('views/record/list', 'view', function (Dep) { } }, + selectAllHandler: function (isChecked) { + this.checkedList = []; + + var $actionsButton = this.$el.find('.actions-button'); + + if (isChecked) { + this.$el.find('input.record-checkbox').prop('checked', true); + $actionsButton.removeAttr('disabled'); + this.collection.models.forEach(function (model) { + this.checkedList.push(model.id); + }, this); + this.$el.find('.list > table tbody tr').addClass('active'); + } else { + if (this.allResultIsChecked) { + this.unselectAllResult(); + } + this.$el.find('input.record-checkbox').prop('checked', false); + $actionsButton.attr('disabled', true); + this.$el.find('.list > table tbody tr').removeClass('active'); + } + + this.trigger('check'); + }, + /** * @param {string} or {bool} ['both', 'top', 'bottom', false, true] Where to display paginations. */ @@ -304,7 +309,10 @@ Espo.define('views/record/list', 'view', function (Dep) { } }, this); - this.$el.find('.actions-button').removeAttr('disabled'); + if (this.checkAllResultMassActionList.length) { + this.$el.find('.actions-button').removeAttr('disabled'); + } + this.$el.find('.list > table tbody tr').removeClass('active'); },