From 7e5a7546f2b7c388d563c5cacc006fc1c6187fee Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 12 Apr 2022 12:49:51 +0300 Subject: [PATCH] fulltext search in email address lookup --- application/Espo/Controllers/EmailAddress.php | 6 +- .../Resources/metadata/entityDefs/User.json | 2 +- application/Espo/Services/EmailAddress.php | 104 ++++++++++++------ 3 files changed, 75 insertions(+), 37 deletions(-) diff --git a/application/Espo/Controllers/EmailAddress.php b/application/Espo/Controllers/EmailAddress.php index c8e365fe4a..3efefa6ebf 100644 --- a/application/Espo/Controllers/EmailAddress.php +++ b/application/Espo/Controllers/EmailAddress.php @@ -41,6 +41,8 @@ use Espo\Core\{ class EmailAddress extends RecordBase { + private const ADDRESS_MAX_SIZE = 50; + /** * @return array> */ @@ -62,8 +64,8 @@ class EmailAddress extends RecordBase $maxSize = intval($request->getQueryParam('maxSize')); - if (empty($maxSize) || $maxSize > 50) { - $maxSize = $this->config->get('recordsPerPage', 20); + if (!$maxSize || $maxSize > self::ADDRESS_MAX_SIZE) { + $maxSize = (int) $this->config->get('recordsPerPage'); } $onlyActual = $request->getQueryParam('onlyActual') === 'true'; diff --git a/application/Espo/Resources/metadata/entityDefs/User.json b/application/Espo/Resources/metadata/entityDefs/User.json index aa5b3285a0..6b1a05f530 100644 --- a/application/Espo/Resources/metadata/entityDefs/User.json +++ b/application/Espo/Resources/metadata/entityDefs/User.json @@ -436,6 +436,6 @@ "collection": { "orderBy": "userName", "order": "asc", - "textFilterFields": ["name", "userName"] + "textFilterFields": ["name", "userName", "emailAddress"] } } diff --git a/application/Espo/Services/EmailAddress.php b/application/Espo/Services/EmailAddress.php index 3afb98a416..8df7c89016 100644 --- a/application/Espo/Services/EmailAddress.php +++ b/application/Espo/Services/EmailAddress.php @@ -34,12 +34,16 @@ use Espo\Entities\EmailAddress as EmailAddressEntity; use Espo\ORM\Query\SelectBuilder as QueryBuilder; +use Espo\Core\Select\Text\MetadataProvider as TextMetadataProvider; + /** * @extends Record<\Espo\Entities\EmailAddress> */ class EmailAddress extends Record { - const ERASED_PREFIX = 'ERASED:'; + private const ERASED_PREFIX = 'ERASED:'; + + private ?TextMetadataProvider $textMetadataProvider = null; /** * @param array> $result @@ -52,35 +56,56 @@ class EmailAddress extends Record bool $onlyActual = false ): void { - $whereClause = [ - 'OR' => [ - [ - 'name*' => $filter . '%', - ], - [ - 'emailAddress*' => $filter . '%', - ], - ], - [ - 'emailAddress!=' => null, - ], - ]; + $textFilter = null; + $whereClause = []; - $builder = $this->selectBuilderFactory + $byEmailAddress = false; + + if (strpos($filter, '@') !== false) { + $byEmailAddress = true; + } + + if ( + !$byEmailAddress && + mb_strlen($filter) < (int) $this->config->get('fullTextSearchMinLength') && + $this->hasFullTextSearch($entityType) + ) { + $byEmailAddress = true; + } + + if ($byEmailAddress) { + $whereClause = [ + 'emailAddress*' => $filter . '%', + ]; + } + else { + $textFilter = $filter; + } + + $selectBuilder = $this->selectBuilderFactory ->create() ->from($entityType) - ->withAccessControlFilter() + ->withAccessControlFilter(); + + if ($textFilter) { + $selectBuilder->withTextFilter($textFilter); + } + + $builder = $selectBuilder ->buildQueryBuilder() ->where($whereClause) ->order('name') ->limit(0, $limit); - $handleMethodName = 'handleQueryBuilder' . $entityType; - - if (method_exists($this, $handleMethodName)) { - $this->$handleMethodName($filter, $builder); + if ($textFilter) { + $builder + ->join('emailAddresses', 'emailAddressesJoin') + ->distinct(); } + if ($entityType === 'User') { + $this->handleQueryBuilderUser($filter, $builder); + } $select = ['id', 'emailAddress', 'name']; @@ -107,24 +132,26 @@ class EmailAddress extends Record $skipPrimaryEmailAddress = false; - if ($emailAddress) { - if (strpos($emailAddress, self::ERASED_PREFIX) === 0) { + if (!$emailAddress) { + continue; + } + + if (strpos($emailAddress, self::ERASED_PREFIX) === 0) { + $skipPrimaryEmailAddress = true; + } + + if ($onlyActual) { + if ($entity->get('emailAddressIsOptedOut')) { $skipPrimaryEmailAddress = true; } - if ($onlyActual) { - if ($entity->get('emailAddressIsOptedOut')) { - $skipPrimaryEmailAddress = true; + foreach ($emailAddressData as $item) { + if ($emailAddress !== $item->emailAddress) { + continue; } - foreach ($emailAddressData as $item) { - if ($emailAddress !== $item->emailAddress) { - continue; - } - - if (!empty($item->invalid)) { - $skipPrimaryEmailAddress = true; - } + if (!empty($item->invalid)) { + $skipPrimaryEmailAddress = true; } } } @@ -167,7 +194,7 @@ class EmailAddress extends Record } } - protected function handleQueryBuilderUser(string $filter, QueryBuilder $queryBuilder): void + private function handleQueryBuilderUser(string $filter, QueryBuilder $queryBuilder): void { if ($this->acl->get('portalPermission') === 'no') { $queryBuilder->where([ @@ -298,4 +325,13 @@ class EmailAddress extends Record /** @var Repository */ return $this->entityManager->getRepository(EmailAddressEntity::ENTITY_TYPE); } + + private function hasFullTextSearch(string $entityType): bool + { + if ($this->textMetadataProvider === null) { + $this->textMetadataProvider = $this->injectableFactory->create(TextMetadataProvider::class); + } + + return $this->textMetadataProvider->hasFullTextSearch($entityType); + } }