diff --git a/application/Espo/Core/SelectManagers/Base.php b/application/Espo/Core/SelectManagers/Base.php index c8437bbe39..50cef2b098 100644 --- a/application/Espo/Core/SelectManagers/Base.php +++ b/application/Espo/Core/SelectManagers/Base.php @@ -148,7 +148,7 @@ class Base } } - protected function order(string $sortBy, bool $desc = false, array &$result) + protected function order(string $sortBy, $desc, array &$result) { if (is_string($desc)) { $desc = $desc === strtolower('desc'); @@ -1666,19 +1666,19 @@ class Base return $part; } - public function applyOrder($sortBy, $desc, array &$result) + public function applyOrder(string $sortBy, $desc, array &$result) { $this->prepareResult($result); $this->order($sortBy, $desc, $result); } - public function applyLimit($offset, $maxSize, array &$result) + public function applyLimit(?int $offset, ?int $maxSize, array &$result) { $this->prepareResult($result); $this->limit($offset, $maxSize, $result); } - public function applyPrimaryFilter($filterName, array &$result) + public function applyPrimaryFilter(string $filterName, array &$result) { $this->prepareResult($result); @@ -1702,12 +1702,12 @@ class Base } } - public function applyFilter($filterName, array &$result) + public function applyFilter(string $filterName, array &$result) { $this->applyPrimaryFilter($filterName, $result); } - public function applyBoolFilter($filterName, array &$result) + public function applyBoolFilter(string $filterName, array &$result) { $this->prepareResult($result); @@ -1717,7 +1717,7 @@ class Base } } - public function applyTextFilter($textFilter, array &$result) + public function applyTextFilter(string $textFilter, array &$result) { $this->prepareResult($result); $this->textFilter($textFilter, $result); @@ -1824,22 +1824,22 @@ class Base $result['leftJoins'][] = $leftJoin; } - public function setJoinCondition($join, $condition, array &$result) + public function setJoinCondition(string $join, $condition, array &$result) { $result['joinConditions'][$join] = $condition; } - public function setDistinct($distinct, array &$result) + public function setDistinct(bool $distinct, array &$result) { $result['distinct'] = (bool) $distinct; } - public function addAndWhere($whereClause, array &$result) + public function addAndWhere(array $whereClause, array &$result) { $result['whereClause'][] = $whereClause; } - public function addOrWhere($whereClause, array &$result) + public function addOrWhere(array $whereClause, array &$result) { $result['whereClause'][] = [ 'OR' => $whereClause @@ -1887,6 +1887,10 @@ class Base if ($useFullTextSearch) { foreach ($fieldList as $field) { + if (strpos($item, '.') !== false) { + continue; + } + $defs = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $field], []); if (empty($defs['type'])) continue; $fieldType = $defs['type']; @@ -2024,9 +2028,21 @@ class Base } if ($forceFullTextSearch) continue; + $seed = $this->getSeed(); + $attributeType = null; - if (!empty($fieldDefs[$field]['type'])) { - $attributeType = $fieldDefs[$field]['type']; + + if (strpos($field, '.') !== false) { + list($link, $foreignField) = explode('.', $field); + $foreignEntityType = $seed->getRelationParam($link, 'entity'); + $seed = $this->getEntityManager()->getEntity($foreignEntityType); + $this->addLeftJoin($link, $result); + if ($seed->getRelationParam($link, 'type') === $seed::HAS_MANY) { + $this->setDistinct(true, $result); + } + $attributeType = $seed->getAttributeType($foreignField); + } else { + $attributeType = $seed->getAttributeType($field); } if ($attributeType === 'int') { diff --git a/application/Espo/Resources/metadata/fields/int.json b/application/Espo/Resources/metadata/fields/int.json index fc5c232544..b7750d1d84 100644 --- a/application/Espo/Resources/metadata/fields/int.json +++ b/application/Espo/Resources/metadata/fields/int.json @@ -32,5 +32,6 @@ ], "filter": true, "textFilter": true, + "textFilterForeign": true, "personalData": true } diff --git a/application/Espo/Resources/metadata/fields/text.json b/application/Espo/Resources/metadata/fields/text.json index d63eaaa173..e9792c7c8f 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, + "textFilterForeign": true, "fullTextSearch": true } diff --git a/application/Espo/Resources/metadata/fields/varchar.json b/application/Espo/Resources/metadata/fields/varchar.json index 2f3ba63880..36e844c28f 100644 --- a/application/Espo/Resources/metadata/fields/varchar.json +++ b/application/Espo/Resources/metadata/fields/varchar.json @@ -34,5 +34,6 @@ "filter": true, "personalData": true, "textFilter": true, + "textFilterForeign": true, "fullTextSearch": true } diff --git a/client/src/field-manager.js b/client/src/field-manager.js index 9500cae312..df733b0620 100644 --- a/client/src/field-manager.js +++ b/client/src/field-manager.js @@ -177,12 +177,22 @@ getNotActualAttributes: function (fieldType, fieldName) { return this.getNotActualAttributeList(fieldType, fieldName); - } + }, + + isScopeFieldAvailable: function (enittyType, field) { + if (this.metadata.get(['entityDefs', enittyType, 'fields', field, 'disabled'])) return false; + if ( + this.metadata.get(['entityAcl', enittyType, 'fields', field, 'onlyAdmin']) + || + this.metadata.get(['entityAcl', enittyType, 'fields', field, 'forbidden']) + || + this.metadata.get(['entityAcl', enittyType, 'fields', field, 'internal']) + ) return false; + + return true; + }, }); return FieldManager; - }); - - 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 21005ba545..1d34b80654 100644 --- a/client/src/views/admin/entity-manager/modals/edit-entity.js +++ b/client/src/views/admin/entity-manager/modals/edit-entity.js @@ -223,6 +223,7 @@ Espo.define('views/admin/entity-manager/modals/edit-entity', ['views/modal', 'mo var fieldDefs = this.getMetadata().get('entityDefs.' + scope + '.fields') || {}; var orderableFieldList = Object.keys(fieldDefs).filter(function (item) { + if (!this.getFieldManager().isScopeFieldAvailable(scope, item)) return false; if (fieldDefs[item].notStorable) { return false; } @@ -280,20 +281,17 @@ Espo.define('views/admin/entity-manager/modals/edit-entity', ['views/modal', 'mo } }); - var optionList = Object.keys(fieldDefs).filter(function (item) { - 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; - } - if (this.getMetadata().get(['entityDefs', scope, 'fields', item, 'textFilterDisabled'])) { - return false; - } - return true; - }, this); + var filtersOptionList = this.getTextFiltersOptionList(scope); var textFilterFieldsTranslation = {}; - optionList.forEach(function (item) { + filtersOptionList.forEach(function (item) { + if (~item.indexOf('.')) { + var link = item.split('.')[0]; + var foreignField = item.split('.')[1]; + var foreignEntityType = this.getMetadata().get(['entityDefs', scope, 'links', link, 'entity']); + textFilterFieldsTranslation[item] = this.translate(link, 'links', scope) + '.' + this.translate(foreignField, 'fields', foreignEntityType); + return; + } textFilterFieldsTranslation[item] = this.translate(item, 'fields', scope); }, this); @@ -304,7 +302,7 @@ Espo.define('views/admin/entity-manager/modals/edit-entity', ['views/modal', 'mo defs: { name: 'textFilterFields', params: { - options: optionList + options: filtersOptionList } }, tooltip: true, @@ -621,7 +619,48 @@ Espo.define('views/admin/entity-manager/modals/edit-entity', ['views/modal', 'mo }.bind(this)); }.bind(this)); }, this); - } + }, + + getTextFiltersOptionList: function (scope) { + var fieldDefs = this.getMetadata().get(['entityDefs', scope, 'fields']) || {}; + + var filtersOptionList = Object.keys(fieldDefs).filter(function (item) { + var fieldType = fieldDefs[item].type; + if (!this.getMetadata().get(['fields', fieldType, 'textFilter'])) return false + if (!this.getFieldManager().isScopeFieldAvailable(scope, item)) return false; + if (this.getMetadata().get(['entityDefs', scope, 'fields', item, 'textFilterDisabled'])) return false; + return true; + }, this); + + var linkList = Object.keys(this.getMetadata().get(['entityDefs', scope, 'links']) || {}); + linkList.sort(function (v1, v2) { + return this.translate(v1, 'links', scope).localeCompare(this.translate(v2, 'links', scope)); + }.bind(this)); + linkList.forEach(function (link) { + var linkType = this.getMetadata().get(['entityDefs', scope, 'links', link, 'type']); + if (linkType != 'belongsTo') return; + var foreignEntityType = this.getMetadata().get(['entityDefs', scope, 'links', link, 'entity']); + if (!foreignEntityType) return; + var fields = this.getMetadata().get(['entityDefs', foreignEntityType, 'fields']) || {}; + var fieldList = Object.keys(fields); + fieldList.sort(function (v1, v2) { + return this.translate(v1, 'fields', foreignEntityType).localeCompare(this.translate(v2, 'fields', foreignEntityType)); + }.bind(this)); + fieldList.filter(function (item) { + var fieldType = this.getMetadata().get(['entityDefs', foreignEntityType, 'fields', item, 'type']); + if (!this.getMetadata().get(['fields', fieldType, 'textFilter'])) return false; + if (!this.getMetadata().get(['fields', fieldType, 'textFilterForeign'])) return false; + if (!this.getFieldManager().isScopeFieldAvailable(foreignEntityType, item)) return false; + + if (this.getMetadata().get(['entityDefs', foreignEntityType, 'fields', item, 'textFilterDisabled'])) return false; + return true; + }, this).forEach(function (item) { + filtersOptionList.push(link + '.' + item); + }, this); + }, this); + + return filtersOptionList; + }, }); });