diff --git a/application/Espo/Core/SelectManagers/Base.php b/application/Espo/Core/SelectManagers/Base.php index f5f1277c79..2e7f976f63 100644 --- a/application/Espo/Core/SelectManagers/Base.php +++ b/application/Espo/Core/SelectManagers/Base.php @@ -150,7 +150,7 @@ class Base } else { $orderPart = 'DESC'; } - $result['orderBy'] = [[$sortBy . 'Country', $orderPart], [$sortBy . 'City', $orderPart], [$sortBy . 'Street', $orderPart]]; + $result['orderBy'] = [[$sortBy . 'Country', $orderPart], [$sortBy . 'City', $orderPart], [$sortBy . '_eet', $orderPart]]; return; } else if ($type === 'enum') { $list = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'options']); @@ -1514,6 +1514,10 @@ class Base return null; } + if ($this->getConfig()->get('fullTextSearchDisabled')) { + return null; + } + $result = null; $fieldList = $this->getTextFilterFieldList(); @@ -1522,8 +1526,15 @@ class Base $textFilter = str_replace('%', '', $textFilter); } + $fullTextSearchColumnList = $this->getEntityManager()->getOrmMetadata()->get($this->getEntityType(), ['fullTextSearchColumnList']); + $useFullTextSearch = false; - if ($this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'collection', 'fullTextSearch'])) { + + if ( + $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'collection', 'fullTextSearch']) + && + !empty($fullTextSearchColumnList) + ) { $fullTextSearchMinLength = $this->getConfig()->get('fullTextSearchMinLength', self::MIN_LENGTH_FOR_FULL_TEXT_SEARCH); if (!$fullTextSearchMinLength) { $fullTextSearchMinLength = 0; @@ -1549,6 +1560,10 @@ class Base } } + if (empty($fullTextSearchColumnList)) { + $useFullTextSearch = false; + } + if ($useFullTextSearch) { if ( mb_strpos($textFilter, ' ') === false @@ -1564,15 +1579,18 @@ class Base $function = 'MATCH_BOOLEAN'; } - $fullTextSearchFieldSanitizedList = []; - foreach ($fullTextSearchFieldList as $i => $field) { - $fullTextSearchFieldSanitizedList[$i] = $this->getEntityManager()->getQuery()->sanitize($field); + $fullTextSearchColumnSanitizedList = []; + $query = $this->getEntityManager()->getQuery(); + foreach ($fullTextSearchColumnList as $i => $field) { + $fullTextSearchColumnSanitizedList[$i] = $query->sanitize($query->toDb($field)); } - $where = $function . ':' . implode(',', $fullTextSearchFieldSanitizedList) . ':' . $textFilter; + $where = $function . ':' . implode(',', $fullTextSearchColumnSanitizedList) . ':' . $textFilter; + $result = [ 'where' => $where, - 'fieldList' => $fullTextSearchFieldList + 'fieldList' => $fullTextSearchFieldList, + 'columnList' => $fullTextSearchColumnList ]; } @@ -1593,11 +1611,12 @@ class Base $forceFullTextSearch = false; - $useFullTextSearch = !empty($result['forceFullTextSearch']); + $useFullTextSearch = !empty($result['useFullTextSearch']); if (mb_strpos($textFilter, 'ft:') === 0) { $textFilter = mb_substr($textFilter, 3); $useFullTextSearch = true; + $forceFullTextSearch = true; } $skipWidlcards = false; diff --git a/application/Espo/Core/Utils/Database/Orm/Converter.php b/application/Espo/Core/Utils/Database/Orm/Converter.php index b401f1ca14..20dc18a086 100644 --- a/application/Espo/Core/Utils/Database/Orm/Converter.php +++ b/application/Espo/Core/Utils/Database/Orm/Converter.php @@ -186,6 +186,8 @@ class Converter $ormMetadata = Util::merge($ormMetadata, $convertedLinks); + $this->applyFullTextSearch($ormMetadata, $entityName); + if (!empty($entityMetadata['collection']) && is_array($entityMetadata['collection'])) { $collectionDefs = $entityMetadata['collection']; $ormMetadata[$entityName]['collection'] = array(); @@ -467,4 +469,46 @@ class Converter return $values; } + protected function applyFullTextSearch(&$ormMetadata, $entityType) + { + if (!$this->getMetadata()->get(['entityDefs', $entityType, 'collection', 'fullTextSearch'])) return; + + $fieldList = $this->getMetadata()->get(['entityDefs', $entityType, 'collection', 'textFilterFields'], ['name']); + + $fullTextSearchColumnList = []; + + foreach ($fieldList as $field) { + $defs = $this->getMetadata()->get(['entityDefs', $entityType, 'fields', $field], []); + if (empty($defs['type'])) continue; + $fieldType = $defs['type']; + if (!empty($defs['notStorable'])) continue; + if (!$this->getMetadata()->get(['fields', $fieldType, 'fullTextSearch'])) continue; + + $partList = $this->getMetadata()->get(['fields', $fieldType, 'fullTextSearchColumnList']); + if ($partList) { + if ($this->getMetadata()->get(['fields', $fieldType, 'naming']) === 'prefix') { + foreach ($partList as $part) { + $fullTextSearchColumnList[] = $part . ucfirst($field); + } + } else { + foreach ($partList as $part) { + $fullTextSearchColumnList[] = $field . ucfirst($part); + } + } + } else { + $fullTextSearchColumnList[] = $field; + } + } + + if (!empty($fullTextSearchColumnList)) { + $ormMetadata[$entityType]['fullTextSearchColumnList'] = $fullTextSearchColumnList; + if (!array_key_exists('indexes', $ormMetadata[$entityType])) { + $ormMetadata[$entityType]['indexes'] = []; + } + $ormMetadata[$entityType]['indexes']['system_fullTextSearch'] = [ + 'columns' => $fullTextSearchColumnList, + 'flags' => ['fulltext'] + ]; + } + } } diff --git a/application/Espo/ORM/EntityManager.php b/application/Espo/ORM/EntityManager.php index 7f7d508128..fe0d1ae76a 100644 --- a/application/Espo/ORM/EntityManager.php +++ b/application/Espo/ORM/EntityManager.php @@ -206,6 +206,11 @@ class EntityManager return $this->metadata; } + public function getOrmMetadata() + { + return $this->getMetadata(); + } + public function getPDO() { if (empty($this->pdo)) { diff --git a/application/Espo/Resources/i18n/en_US/EntityManager.json b/application/Espo/Resources/i18n/en_US/EntityManager.json index adbaee8a6c..0197aeb6fc 100644 --- a/application/Espo/Resources/i18n/en_US/EntityManager.json +++ b/application/Espo/Resources/i18n/en_US/EntityManager.json @@ -70,6 +70,6 @@ "linkAudited": "Creating related record and linking with existing record will be logged in Stream.", "linkMultipleField": "Link Multiple field provides a handy way to edit relations. Don't use it if you can have a large number of related records.", "entityType": "Base Plus - has Activities, History and Tasks panels.\n\nEvent - available in Calendar and Activities panel.", - "fullTextSearch": "Running rebuild required." + "fullTextSearch": "Running rebuild is required." } } diff --git a/application/Espo/Resources/metadata/entityDefs/Email.json b/application/Espo/Resources/metadata/entityDefs/Email.json index 5b1f347264..4333004ac2 100644 --- a/application/Espo/Resources/metadata/entityDefs/Email.json +++ b/application/Espo/Resources/metadata/entityDefs/Email.json @@ -433,7 +433,8 @@ "collection": { "sortBy": "dateSent", "asc": false, - "textFilterFields": ["name", "bodyPlain", "body"] + "textFilterFields": ["name", "bodyPlain", "body"], + "fullTextSearch": true }, "indexes": { "dateSent": { diff --git a/application/Espo/Resources/metadata/fields/personName.json b/application/Espo/Resources/metadata/fields/personName.json index 4003c1d8ec..467a38b329 100644 --- a/application/Espo/Resources/metadata/fields/personName.json +++ b/application/Espo/Resources/metadata/fields/personName.json @@ -29,5 +29,10 @@ "notCreatable":true, "filter":true, "skipOrmDefs": true, - "personalData": true + "personalData": true, + "fullTextSearch": true, + "fullTextSearchColumnList": [ + "first", + "last" + ] } \ No newline at end of file diff --git a/application/Espo/Services/GlobalSearch.php b/application/Espo/Services/GlobalSearch.php index 9bf72a167c..c29219d2a6 100644 --- a/application/Espo/Services/GlobalSearch.php +++ b/application/Espo/Services/GlobalSearch.php @@ -103,7 +103,7 @@ class GlobalSearch extends \Espo\Core\Services\Base } $selectManager->manageAccess($params); - $params['forceFullTextSearch'] = true; + $params['useFullTextSearch'] = true; $selectManager->applyTextFilter($query, $params); $sql = $this->getEntityManager()->getQuery()->createSelectQuery($entityType, $params);