Initial commit
This commit is contained in:
104
application/Espo/Core/Select/AccessControl/Applier.php
Normal file
104
application/Espo/Core/Select/AccessControl/Applier.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl;
|
||||
|
||||
use Espo\Core\Select\OrmSelectBuilder;
|
||||
use Espo\Core\Select\AccessControl\FilterFactory as AccessControlFilterFactory;
|
||||
use Espo\Core\Select\AccessControl\FilterResolverFactory as AccessControlFilterResolverFactory;
|
||||
use Espo\Core\Select\SelectManager;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class Applier
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private AccessControlFilterFactory $accessControlFilterFactory,
|
||||
private AccessControlFilterResolverFactory $accessControlFilterResolverFactory,
|
||||
private SelectManager $selectManager
|
||||
) {}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
// For backward compatibility.
|
||||
if (
|
||||
$this->selectManager->hasInheritedAccessMethod() &&
|
||||
$queryBuilder instanceof OrmSelectBuilder
|
||||
) {
|
||||
$this->selectManager->applyAccessToQueryBuilder($queryBuilder);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->applyMandatoryFilter($queryBuilder);
|
||||
|
||||
$accessControlFilterResolver = $this->accessControlFilterResolverFactory
|
||||
->create($this->entityType, $this->user);
|
||||
|
||||
$filterName = $accessControlFilterResolver->resolve();
|
||||
|
||||
if (!$filterName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For backward compatibility.
|
||||
if (
|
||||
$this->selectManager->hasInheritedAccessFilterMethod($filterName) &&
|
||||
$queryBuilder instanceof OrmSelectBuilder
|
||||
) {
|
||||
$this->selectManager->applyAccessFilterToQueryBuilder($queryBuilder, $filterName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->accessControlFilterFactory->has($this->entityType, $filterName)) {
|
||||
$filter = $this->accessControlFilterFactory
|
||||
->create($this->entityType, $this->user, $filterName);
|
||||
|
||||
$filter->apply($queryBuilder);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new RuntimeException("No access filter '{$filterName}' for '{$this->entityType}'.");
|
||||
}
|
||||
|
||||
private function applyMandatoryFilter(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$filter = $this->accessControlFilterFactory
|
||||
->create($this->entityType, $this->user, 'mandatory');
|
||||
|
||||
$filter->apply($queryBuilder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
|
||||
class DefaultFilterResolver implements FilterResolver
|
||||
{
|
||||
public function __construct(private string $entityType, private Acl $acl)
|
||||
{}
|
||||
|
||||
public function resolve(): ?string
|
||||
{
|
||||
if ($this->acl->checkReadNo($this->entityType)) {
|
||||
return 'no';
|
||||
}
|
||||
|
||||
if ($this->acl->checkReadOnlyOwn($this->entityType)) {
|
||||
return 'onlyOwn';
|
||||
}
|
||||
|
||||
if ($this->acl->checkReadOnlyTeam($this->entityType)) {
|
||||
return 'onlyTeam';
|
||||
}
|
||||
|
||||
if ($this->acl->checkReadAll($this->entityType)) {
|
||||
return 'all';
|
||||
}
|
||||
|
||||
return 'no';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl;
|
||||
|
||||
use Espo\Core\Portal\Acl;
|
||||
|
||||
class DefaultPortalFilterResolver implements FilterResolver
|
||||
{
|
||||
public function __construct(private string $entityType, private Acl $acl)
|
||||
{}
|
||||
|
||||
public function resolve(): ?string
|
||||
{
|
||||
if ($this->acl->checkReadNo($this->entityType)) {
|
||||
return 'no';
|
||||
}
|
||||
|
||||
if ($this->acl->checkReadOnlyOwn($this->entityType)) {
|
||||
return 'portalOnlyOwn';
|
||||
}
|
||||
|
||||
if ($this->acl->checkReadOnlyAccount($this->entityType)) {
|
||||
return 'portalOnlyAccount';
|
||||
}
|
||||
|
||||
if ($this->acl->checkReadOnlyContact($this->entityType)) {
|
||||
return 'portalOnlyContact';
|
||||
}
|
||||
|
||||
if ($this->acl->checkReadAll($this->entityType)) {
|
||||
return 'portalAll';
|
||||
}
|
||||
|
||||
return 'no';
|
||||
}
|
||||
}
|
||||
46
application/Espo/Core/Select/AccessControl/Filter.php
Normal file
46
application/Espo/Core/Select/AccessControl/Filter.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl;
|
||||
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* An access filter.
|
||||
*
|
||||
* Bindings:
|
||||
* - `$entityType`
|
||||
* - `Espo\Entities\User`
|
||||
* - `Espo\Core\AclManager` – as of v9.1.
|
||||
* - `Espo\Core\Acl`
|
||||
*/
|
||||
interface Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void;
|
||||
}
|
||||
134
application/Espo/Core/Select/AccessControl/FilterFactory.php
Normal file
134
application/Espo/Core/Select/AccessControl/FilterFactory.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Core\Binding\Binder;
|
||||
use Espo\Core\Binding\BindingContainer;
|
||||
use Espo\Core\Binding\BindingData;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Portal\Acl as PortalAcl;
|
||||
use Espo\Core\Portal\AclManager as PortalAclManager;
|
||||
use Espo\Core\Select\Helpers\FieldHelper;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\User;
|
||||
use RuntimeException;
|
||||
|
||||
class FilterFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata,
|
||||
private AclManager $aclManager,
|
||||
private Acl $acl,
|
||||
) {}
|
||||
|
||||
public function create(string $entityType, User $user, string $name): Filter
|
||||
{
|
||||
$className = $this->getClassName($entityType, $name);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("Access control filter '$name' for '$entityType' does not exist.");
|
||||
}
|
||||
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder
|
||||
->bindInstance(User::class, $user)
|
||||
->bindInstance(AclManager::class, $this->aclManager)
|
||||
->bindInstance(Acl::class, $this->acl);
|
||||
|
||||
if ($user->isPortal()) {
|
||||
$binder->bindInstance(PortalAcl::class, $this->acl);
|
||||
$binder->bindInstance(PortalAclManager::class, $this->aclManager);
|
||||
}
|
||||
|
||||
$binder
|
||||
->for($className)
|
||||
->bindValue('$entityType', $entityType);
|
||||
|
||||
$binder
|
||||
->for(FieldHelper::class)
|
||||
->bindValue('$entityType', $entityType);
|
||||
|
||||
$bindingContainer = new BindingContainer($bindingData);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
public function has(string $entityType, string $name): bool
|
||||
{
|
||||
return (bool) $this->getClassName($entityType, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Filter>
|
||||
*/
|
||||
private function getClassName(string $entityType, string $name): ?string
|
||||
{
|
||||
if (!$name) {
|
||||
throw new RuntimeException("Empty access control filter name.");
|
||||
}
|
||||
|
||||
/** @var ?class-string<Filter> $className */
|
||||
$className = $this->metadata->get(
|
||||
[
|
||||
'selectDefs',
|
||||
$entityType,
|
||||
'accessControlFilterClassNameMap',
|
||||
$name,
|
||||
]
|
||||
);
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
return $this->getDefaultClassName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Filter>
|
||||
*/
|
||||
private function getDefaultClassName(string $name): ?string
|
||||
{
|
||||
$className = 'Espo\\Core\\Select\\AccessControl\\Filters\\' . ucfirst($name);
|
||||
|
||||
if (!class_exists($className)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var class-string<Filter> */
|
||||
return $className;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl;
|
||||
|
||||
/**
|
||||
* Resolves an access filter. An entity type, acl and user to be passed to the constructor.
|
||||
*
|
||||
* Bindings:
|
||||
* - `$entityType`
|
||||
* - `Espo\Entities\User`
|
||||
* - `Espo\Core\AclManager` – as of v9.1.
|
||||
* - `Espo\Core\Acl`
|
||||
*/
|
||||
interface FilterResolver
|
||||
{
|
||||
public function resolve(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl;
|
||||
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Portal\Acl as PortalAcl;
|
||||
use Espo\Core\Portal\AclManager as PortalAclManager;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Binding\BindingContainer;
|
||||
use Espo\Core\Binding\Binder;
|
||||
use Espo\Core\Binding\BindingData;
|
||||
use Espo\Entities\User;
|
||||
|
||||
class FilterResolverFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata,
|
||||
private AclManager $aclManager,
|
||||
private Acl $acl,
|
||||
) {}
|
||||
|
||||
public function create(string $entityType, User $user): FilterResolver
|
||||
{
|
||||
$className = !$user->isPortal() ?
|
||||
$this->getClassName($entityType) :
|
||||
$this->getPortalClassName($entityType);
|
||||
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder
|
||||
->bindInstance(User::class, $user)
|
||||
->bindInstance(AclManager::class, $this->aclManager)
|
||||
->bindInstance(Acl::class, $this->acl);
|
||||
|
||||
if ($user->isPortal()) {
|
||||
$binder->bindInstance(PortalAcl::class, $this->acl);
|
||||
$binder->bindInstance(PortalAclManager::class, $this->aclManager);
|
||||
}
|
||||
|
||||
$binder
|
||||
->for($className)
|
||||
->bindValue('$entityType', $entityType);
|
||||
|
||||
$bindingContainer = new BindingContainer($bindingData);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<FilterResolver>
|
||||
*/
|
||||
private function getClassName(string $entityType): string
|
||||
{
|
||||
/** @var class-string<FilterResolver> */
|
||||
return $this->metadata->get(['selectDefs', $entityType, 'accessControlFilterResolverClassName']) ??
|
||||
DefaultFilterResolver::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<FilterResolver>
|
||||
*/
|
||||
private function getPortalClassName(string $entityType): string
|
||||
{
|
||||
/** @var class-string<FilterResolver> */
|
||||
return $this->metadata->get(['selectDefs', $entityType, 'portalAccessControlFilterResolverClassName']) ??
|
||||
DefaultPortalFilterResolver::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\FilterResolvers;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Select\AccessControl\FilterResolver;
|
||||
|
||||
class Boolean implements FilterResolver
|
||||
{
|
||||
private string $entityType;
|
||||
|
||||
private Acl $acl;
|
||||
|
||||
public function __construct(string $entityType, Acl $acl)
|
||||
{
|
||||
$this->entityType = $entityType;
|
||||
$this->acl = $acl;
|
||||
}
|
||||
|
||||
public function resolve(): ?string
|
||||
{
|
||||
if ($this->acl->checkScope($this->entityType)) {
|
||||
return 'all';
|
||||
}
|
||||
|
||||
return 'no';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\FilterResolvers;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Select\AccessControl\FilterResolver;
|
||||
use Espo\Entities\User;
|
||||
|
||||
class BooleanOwn implements FilterResolver
|
||||
{
|
||||
private string $entityType;
|
||||
private Acl $acl;
|
||||
private User $user;
|
||||
|
||||
public function __construct(string $entityType, Acl $acl, User $user)
|
||||
{
|
||||
$this->entityType = $entityType;
|
||||
$this->acl = $acl;
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function resolve(): ?string
|
||||
{
|
||||
if (!$this->acl->checkScope($this->entityType)) {
|
||||
return 'no';
|
||||
}
|
||||
|
||||
if ($this->user->isAdmin()) {
|
||||
return 'all';
|
||||
}
|
||||
|
||||
return 'onlyOwn';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\FilterResolvers;
|
||||
|
||||
use Espo\Core\Select\AccessControl\FilterResolver;
|
||||
|
||||
class Bypass implements FilterResolver
|
||||
{
|
||||
public function resolve(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
39
application/Espo/Core/Select/AccessControl/Filters/All.php
Normal file
39
application/Espo/Core/Select/AccessControl/Filters/All.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\Filters;
|
||||
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class All implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\Filters;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use LogicException;
|
||||
|
||||
class ForeignOnlyOwn implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private Metadata $metadata,
|
||||
private Defs $defs
|
||||
) {}
|
||||
|
||||
public function apply(SelectBuilder $queryBuilder): void
|
||||
{
|
||||
$link = $this->metadata->get(['aclDefs', $this->entityType, 'link']);
|
||||
|
||||
if (!$link) {
|
||||
throw new LogicException("No `link` in aclDefs for {$this->entityType}.");
|
||||
}
|
||||
|
||||
$alias = $link . 'Access';
|
||||
|
||||
$queryBuilder->leftJoin($link, $alias);
|
||||
|
||||
$foreignEntityType = $this->defs
|
||||
->getEntity($this->entityType)
|
||||
->getRelation($link)
|
||||
->getForeignEntityType();
|
||||
|
||||
$foreignEntityDefs = $this->defs->getEntity($foreignEntityType);
|
||||
|
||||
if ($foreignEntityDefs->hasField(Field::ASSIGNED_USER)) {
|
||||
$queryBuilder->where([
|
||||
"{$alias}.assignedUserId" => $this->user->getId(),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($foreignEntityDefs->hasField(Field::CREATED_BY)) {
|
||||
$queryBuilder->where([
|
||||
"{$alias}.createdById" => $this->user->getId(),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$queryBuilder->where([Attribute::ID => null]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\Filters;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Condition;
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use Espo\ORM\Query\Part\Where\OrGroup;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class ForeignOnlyTeam implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private Metadata $metadata,
|
||||
private Defs $defs
|
||||
) {}
|
||||
|
||||
public function apply(SelectBuilder $queryBuilder): void
|
||||
{
|
||||
$link = $this->metadata->get(['aclDefs', $this->entityType, 'link']);
|
||||
|
||||
if (!$link) {
|
||||
throw new LogicException("No `link` in aclDefs for $this->entityType.");
|
||||
}
|
||||
|
||||
$alias = "{$link}Access";
|
||||
|
||||
$ownerAttribute = $this->getOwnerAttribute($link);
|
||||
|
||||
if (!$ownerAttribute) {
|
||||
$queryBuilder->where([Attribute::ID => null]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$teamIdList = $this->user->getTeamIdList();
|
||||
|
||||
if (count($teamIdList) === 0) {
|
||||
$queryBuilder
|
||||
->leftJoin($link, $alias)
|
||||
->where(["$alias.$ownerAttribute" => $this->user->getId()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$foreignEntityType = $this->getForeignEntityType($link);
|
||||
|
||||
$orGroup = OrGroup::create(
|
||||
Condition::equal(
|
||||
Expression::column("$alias.$ownerAttribute"),
|
||||
$this->user->getId()
|
||||
),
|
||||
Condition::in(
|
||||
Expression::column("$alias.id"),
|
||||
SelectBuilder::create()
|
||||
->from(Team::RELATIONSHIP_ENTITY_TEAM)
|
||||
->select('entityId')
|
||||
->where([
|
||||
'teamId' => $teamIdList,
|
||||
'entityType' => $foreignEntityType,
|
||||
])
|
||||
->build()
|
||||
)
|
||||
);
|
||||
|
||||
$queryBuilder
|
||||
->leftJoin($link, $alias)
|
||||
->where($orGroup)
|
||||
->where(["$alias.id!=" => null]);
|
||||
}
|
||||
|
||||
private function getOwnerAttribute(string $link): ?string
|
||||
{
|
||||
$foreignEntityType = $this->getForeignEntityType($link);
|
||||
|
||||
$foreignEntityDefs = $this->defs->getEntity($foreignEntityType);
|
||||
|
||||
if ($foreignEntityDefs->hasField(Field::ASSIGNED_USER)) {
|
||||
return 'assignedUserId';
|
||||
}
|
||||
|
||||
if ($foreignEntityDefs->hasField(Field::CREATED_BY)) {
|
||||
return 'createdById';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getForeignEntityType(string $link): string
|
||||
{
|
||||
return $this->defs
|
||||
->getEntity($this->entityType)
|
||||
->getRelation($link)
|
||||
->getForeignEntityType();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\Filters;
|
||||
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Mandatory implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{}
|
||||
}
|
||||
42
application/Espo/Core/Select/AccessControl/Filters/No.php
Normal file
42
application/Espo/Core/Select/AccessControl/Filters/No.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\Filters;
|
||||
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class No implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where([Attribute::ID => null]);
|
||||
}
|
||||
}
|
||||
101
application/Espo/Core/Select/AccessControl/Filters/OnlyOwn.php
Normal file
101
application/Espo/Core/Select/AccessControl/Filters/OnlyOwn.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\Filters;
|
||||
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\Core\Select\Helpers\RelationQueryHelper;
|
||||
use Espo\Core\Select\Helpers\FieldHelper;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Where\OrGroup;
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class OnlyOwn implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private FieldHelper $fieldHelper,
|
||||
private string $entityType,
|
||||
private RelationQueryHelper $relationQueryHelper,
|
||||
) {}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$ownItem = $this->getOwnWhereItem();
|
||||
|
||||
if ($this->fieldHelper->hasCollaboratorsField()) {
|
||||
$this->applyCollaborators($queryBuilder, $ownItem);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$ownItem) {
|
||||
$queryBuilder->where([Attribute::ID => null]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$queryBuilder->where($ownItem);
|
||||
}
|
||||
|
||||
private function applyCollaborators(QueryBuilder $queryBuilder, ?WhereItem $ownItem): void
|
||||
{
|
||||
$sharedItem = $this->relationQueryHelper->prepareCollaboratorsWhere($this->entityType, $this->user->getId());
|
||||
|
||||
$orBuilder = OrGroup::createBuilder();
|
||||
|
||||
if ($ownItem) {
|
||||
$orBuilder->add($ownItem);
|
||||
}
|
||||
|
||||
$orBuilder->add($sharedItem);
|
||||
|
||||
$queryBuilder->where($orBuilder->build());
|
||||
}
|
||||
|
||||
private function getOwnWhereItem(): ?WhereItem
|
||||
{
|
||||
if ($this->fieldHelper->hasAssignedUsersField()) {
|
||||
return $this->relationQueryHelper->prepareAssignedUsersWhere($this->entityType, $this->user->getId());
|
||||
}
|
||||
|
||||
if ($this->fieldHelper->hasAssignedUserField()) {
|
||||
return WhereClause::fromRaw(['assignedUserId' => $this->user->getId()]);
|
||||
}
|
||||
|
||||
if ($this->fieldHelper->hasCreatedByField()) {
|
||||
return WhereClause::fromRaw(['createdById' => $this->user->getId()]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
117
application/Espo/Core/Select/AccessControl/Filters/OnlyTeam.php
Normal file
117
application/Espo/Core/Select/AccessControl/Filters/OnlyTeam.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\Filters;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\Core\Select\Helpers\FieldHelper;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class OnlyTeam implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private FieldHelper $fieldHelper,
|
||||
private string $entityType,
|
||||
private Defs $defs
|
||||
) {}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
if (!$this->fieldHelper->hasTeamsField()) {
|
||||
$queryBuilder->where([Attribute::ID => null]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$subQueryBuilder = QueryBuilder::create()
|
||||
->select(Attribute::ID)
|
||||
->from($this->entityType)
|
||||
->leftJoin(Team::RELATIONSHIP_ENTITY_TEAM, 'entityTeam', [
|
||||
'entityTeam.entityId:' => Attribute::ID,
|
||||
'entityTeam.entityType' => $this->entityType,
|
||||
'entityTeam.deleted' => false,
|
||||
]);
|
||||
|
||||
// Empty list is converted to false statement by ORM.
|
||||
$orGroup = ['entityTeam.teamId' => $this->user->getTeamIdList()];
|
||||
|
||||
if ($this->fieldHelper->hasAssignedUsersField()) {
|
||||
$relationDefs = $this->defs
|
||||
->getEntity($this->entityType)
|
||||
->getRelation(Field::ASSIGNED_USERS);
|
||||
|
||||
$middleEntityType = ucfirst($relationDefs->getRelationshipName());
|
||||
$key1 = $relationDefs->getMidKey();
|
||||
$key2 = $relationDefs->getForeignMidKey();
|
||||
|
||||
$subQueryBuilder->leftJoin($middleEntityType, 'assignedUsersMiddle', [
|
||||
"assignedUsersMiddle.$key1:" => Attribute::ID,
|
||||
'assignedUsersMiddle.deleted' => false,
|
||||
]);
|
||||
|
||||
$orGroup["assignedUsersMiddle.$key2"] = $this->user->getId();
|
||||
} else if ($this->fieldHelper->hasAssignedUserField()) {
|
||||
$orGroup['assignedUserId'] = $this->user->getId();
|
||||
} else if ($this->fieldHelper->hasCreatedByField()) {
|
||||
$orGroup['createdById'] = $this->user->getId();
|
||||
}
|
||||
|
||||
if ($this->fieldHelper->hasCollaboratorsField()) {
|
||||
$relationDefs = $this->defs
|
||||
->getEntity($this->entityType)
|
||||
->getRelation(Field::COLLABORATORS);
|
||||
|
||||
$middleEntityType = ucfirst($relationDefs->getRelationshipName());
|
||||
$key1 = $relationDefs->getMidKey();
|
||||
$key2 = $relationDefs->getForeignMidKey();
|
||||
|
||||
$subQueryBuilder->leftJoin($middleEntityType, 'collaboratorsMiddle', [
|
||||
"collaboratorsMiddle.$key1:" => Attribute::ID,
|
||||
'collaboratorsMiddle.deleted' => false,
|
||||
]);
|
||||
|
||||
$orGroup["collaboratorsMiddle.$key2"] = $this->user->getId();
|
||||
}
|
||||
|
||||
$subQuery = $subQueryBuilder
|
||||
->where(['OR' => $orGroup])
|
||||
->build();
|
||||
|
||||
$queryBuilder->where(['id=s' => $subQuery]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\Filters;
|
||||
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class PortalAll implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\Filters;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Portal\Acl\OwnershipChecker\MetadataProvider;
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\Core\Select\Helpers\FieldHelper;
|
||||
use Espo\Core\Select\Helpers\RelationQueryHelper;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Crm\Entities\Account;
|
||||
use Espo\Modules\Crm\Entities\Contact;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Where\OrGroup;
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class PortalOnlyAccount implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private FieldHelper $fieldHelper,
|
||||
private MetadataProvider $metadataProvider,
|
||||
private RelationQueryHelper $relationQueryHelper,
|
||||
) {}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$orBuilder = OrGroup::createBuilder();
|
||||
|
||||
$accountIds = $this->user->getAccounts()->getIdList();
|
||||
$contactId = $this->user->getContactId();
|
||||
|
||||
if ($accountIds !== []) {
|
||||
$or = $this->prepareAccountWhere($queryBuilder, $accountIds);
|
||||
|
||||
if ($or) {
|
||||
$orBuilder->add($or);
|
||||
}
|
||||
}
|
||||
|
||||
if ($contactId) {
|
||||
$or = $this->prepareContactWhere($queryBuilder, $contactId);
|
||||
|
||||
if ($or) {
|
||||
$orBuilder->add($or);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->fieldHelper->hasCreatedByField()) {
|
||||
$orBuilder->add(
|
||||
WhereClause::fromRaw([Field::CREATED_BY . 'Id' => $this->user->getId()])
|
||||
);
|
||||
}
|
||||
|
||||
$orGroup = $orBuilder->build();
|
||||
|
||||
if ($orGroup->getItemCount() === 0) {
|
||||
$queryBuilder->where([Attribute::ID => null]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$queryBuilder->where($orGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $ids
|
||||
*/
|
||||
private function prepareAccountWhere(QueryBuilder $queryBuilder, array $ids): ?WhereItem
|
||||
{
|
||||
$defs = $this->metadataProvider->getAccountLink($this->entityType);
|
||||
|
||||
if (!$defs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->relationQueryHelper->prepareLinkWhere($defs, Account::ENTITY_TYPE, $ids, $queryBuilder);
|
||||
}
|
||||
|
||||
private function prepareContactWhere(QueryBuilder $queryBuilder, string $id): ?WhereItem
|
||||
{
|
||||
$defs = $this->metadataProvider->getContactLink($this->entityType);
|
||||
|
||||
if (!$defs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->relationQueryHelper->prepareLinkWhere($defs, Contact::ENTITY_TYPE, $id, $queryBuilder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\Filters;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Portal\Acl\OwnershipChecker\MetadataProvider;
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\Core\Select\Helpers\FieldHelper;
|
||||
use Espo\Core\Select\Helpers\RelationQueryHelper;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Crm\Entities\Contact;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Where\OrGroup;
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class PortalOnlyContact implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private FieldHelper $fieldHelper,
|
||||
private MetadataProvider $metadataProvider,
|
||||
private RelationQueryHelper $relationQueryHelper,
|
||||
) {}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$orBuilder = OrGroup::createBuilder();
|
||||
|
||||
$contactId = $this->user->getContactId();
|
||||
|
||||
if ($contactId) {
|
||||
$or = $this->prepareContactWhere($queryBuilder, $contactId);
|
||||
|
||||
if ($or) {
|
||||
$orBuilder->add($or);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->fieldHelper->hasCreatedByField()) {
|
||||
$orBuilder->add(
|
||||
WhereClause::fromRaw([Field::CREATED_BY . 'Id' => $this->user->getId()])
|
||||
);
|
||||
}
|
||||
|
||||
$orGroup = $orBuilder->build();
|
||||
|
||||
if ($orGroup->getItemCount() === 0) {
|
||||
$queryBuilder->where([Attribute::ID => null]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$queryBuilder->where($orGroup);
|
||||
}
|
||||
|
||||
private function prepareContactWhere(QueryBuilder $queryBuilder, string $id): ?WhereItem
|
||||
{
|
||||
$defs = $this->metadataProvider->getContactLink($this->entityType);
|
||||
|
||||
if (!$defs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->relationQueryHelper->prepareLinkWhere($defs, Contact::ENTITY_TYPE, $id, $queryBuilder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\AccessControl\Filters;
|
||||
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\Core\Select\Helpers\FieldHelper;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class PortalOnlyOwn implements Filter
|
||||
{
|
||||
public function __construct(private User $user, private FieldHelper $fieldHelper)
|
||||
{}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
if ($this->fieldHelper->hasCreatedByField()) {
|
||||
$queryBuilder->where([
|
||||
'createdById' => $this->user->getId(),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$queryBuilder->where([Attribute::ID => null]);
|
||||
}
|
||||
}
|
||||
38
application/Espo/Core/Select/Applier/AdditionalApplier.php
Normal file
38
application/Espo/Core/Select/Applier/AdditionalApplier.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Applier;
|
||||
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
use Espo\Core\Select\SearchParams;
|
||||
|
||||
interface AdditionalApplier
|
||||
{
|
||||
public function apply(SelectBuilder $queryBuilder, SearchParams $searchParams): void;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Applier\AdditionalAppliers;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Select\Applier\AdditionalApplier;
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\StarSubscription;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Query\Part\Expression as Expr;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class IsStarred implements AdditionalApplier
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private Metadata $metadata,
|
||||
) {}
|
||||
|
||||
public function apply(SelectBuilder $queryBuilder, SearchParams $searchParams): void
|
||||
{
|
||||
if (!$this->metadata->get("scopes.$this->entityType.stars")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$queryBuilder
|
||||
->select(Expr::isNotNull(Expr::column('starSubscription.id')), Field::IS_STARRED)
|
||||
->leftJoin(StarSubscription::ENTITY_TYPE, 'starSubscription', [
|
||||
'userId' => $this->user->getId(),
|
||||
'entityType' => $this->entityType,
|
||||
'entityId:' => 'id',
|
||||
]);
|
||||
}
|
||||
}
|
||||
88
application/Espo/Core/Select/Applier/Appliers/Additional.php
Normal file
88
application/Espo/Core/Select/Applier/Appliers/Additional.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Applier\Appliers;
|
||||
|
||||
use Espo\Core\Binding\ContextualBinder;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Select\Applier\AdditionalApplier;
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Entities\User;
|
||||
|
||||
class Additional
|
||||
{
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private string $entityType,
|
||||
private Metadata $metadata,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param class-string<AdditionalApplier>[] $classNameList
|
||||
*/
|
||||
public function apply(array $classNameList, QueryBuilder $queryBuilder, SearchParams $searchParams): void
|
||||
{
|
||||
$classNameList = array_merge($this->getMandatoryClassNameList(), $classNameList);
|
||||
|
||||
foreach ($classNameList as $className) {
|
||||
$applier = $this->createApplier($className);
|
||||
|
||||
$applier->apply($queryBuilder, $searchParams);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<AdditionalApplier> $className
|
||||
*/
|
||||
private function createApplier(string $className): AdditionalApplier
|
||||
{
|
||||
return $this->injectableFactory->createWithBinding(
|
||||
$className,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(User::class, $this->user)
|
||||
->inContext($className, function (ContextualBinder $binder) {
|
||||
$binder->bindValue('$entityType', $this->entityType);
|
||||
})
|
||||
->build()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<AdditionalApplier>[]
|
||||
*/
|
||||
private function getMandatoryClassNameList(): array
|
||||
{
|
||||
/** @var class-string<AdditionalApplier>[] */
|
||||
return $this->metadata->get("selectDefs.$this->entityType.additionalApplierClassNameList") ?? [];
|
||||
}
|
||||
}
|
||||
40
application/Espo/Core/Select/Applier/Appliers/Limit.php
Normal file
40
application/Espo/Core/Select/Applier/Appliers/Limit.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Applier\Appliers;
|
||||
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Limit
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder, ?int $offset, ?int $maxSize): void
|
||||
{
|
||||
$queryBuilder->limit($offset, $maxSize);
|
||||
}
|
||||
}
|
||||
182
application/Espo/Core/Select/Applier/Factory.php
Normal file
182
application/Espo/Core/Select/Applier/Factory.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Applier;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Core\Select\Text\Applier as TextFilterApplier;
|
||||
use Espo\Core\Select\AccessControl\Applier as AccessControlFilterApplier;
|
||||
use Espo\Core\Select\Where\Applier as WhereApplier;
|
||||
use Espo\Core\Select\Select\Applier as SelectApplier;
|
||||
use Espo\Core\Select\Primary\Applier as PrimaryFilterApplier;
|
||||
use Espo\Core\Select\Order\Applier as OrderApplier;
|
||||
use Espo\Core\Select\Bool\Applier as BoolFilterListApplier;
|
||||
use Espo\Core\Select\Applier\Appliers\Additional as AdditionalApplier;
|
||||
use Espo\Core\Select\Applier\Appliers\Limit as LimitApplier;
|
||||
use Espo\Core\Binding\Binder;
|
||||
use Espo\Core\Binding\BindingContainer;
|
||||
use Espo\Core\Binding\BindingData;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Select\SelectManager;
|
||||
use Espo\Core\Select\SelectManagerFactory;
|
||||
|
||||
use Espo\Core\Utils\Acl\UserAclManagerProvider;
|
||||
use Espo\Entities\User;
|
||||
use RuntimeException;
|
||||
|
||||
class Factory
|
||||
{
|
||||
public const SELECT = 'select';
|
||||
public const WHERE = 'where';
|
||||
public const ORDER = 'order';
|
||||
public const LIMIT = 'limit';
|
||||
public const ACCESS_CONTROL_FILTER = 'accessControlFilter';
|
||||
public const TEXT_FILTER = 'textFilter';
|
||||
public const PRIMARY_FILTER = 'primaryFilter';
|
||||
public const BOOL_FILTER_LIST = 'boolFilterList';
|
||||
public const ADDITIONAL = 'additional';
|
||||
|
||||
/**
|
||||
* @var array<string, class-string<object>>
|
||||
*/
|
||||
private array $defaultClassNameMap = [
|
||||
self::TEXT_FILTER => TextFilterApplier::class,
|
||||
self::ACCESS_CONTROL_FILTER => AccessControlFilterApplier::class,
|
||||
self::WHERE => WhereApplier::class,
|
||||
self::SELECT => SelectApplier::class,
|
||||
self::PRIMARY_FILTER => PrimaryFilterApplier::class,
|
||||
self::ORDER => OrderApplier::class,
|
||||
self::BOOL_FILTER_LIST => BoolFilterListApplier::class,
|
||||
self::ADDITIONAL => AdditionalApplier::class,
|
||||
self::LIMIT => LimitApplier::class,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private UserAclManagerProvider $userAclManagerProvider,
|
||||
private SelectManagerFactory $selectManagerFactory,
|
||||
) {}
|
||||
|
||||
private function create(string $entityType, User $user, string $type): object
|
||||
{
|
||||
$className = $this->getDefaultClassName($type);
|
||||
|
||||
// SelectManager is used for backward compatibility.
|
||||
$selectManager = $this->selectManagerFactory->create($entityType, $user);
|
||||
|
||||
$aclManager = $this->userAclManagerProvider->get($user);
|
||||
$acl = $aclManager->createUserAcl($user);
|
||||
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder
|
||||
->bindInstance(User::class, $user)
|
||||
->bindInstance(AclManager::class, $aclManager)
|
||||
->bindInstance(Acl::class, $acl)
|
||||
->bindInstance(SelectManager::class, $selectManager);
|
||||
|
||||
$binder
|
||||
->for($className)
|
||||
->bindValue('$entityType', $entityType)
|
||||
->bindValue('$selectManager', $selectManager);
|
||||
|
||||
$bindingContainer = new BindingContainer($bindingData);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
public function createWhere(string $entityType, User $user): WhereApplier
|
||||
{
|
||||
/** @var WhereApplier */
|
||||
return $this->create($entityType, $user, self::WHERE);
|
||||
}
|
||||
|
||||
public function createSelect(string $entityType, User $user): SelectApplier
|
||||
{
|
||||
/** @var SelectApplier */
|
||||
return $this->create($entityType, $user, self::SELECT);
|
||||
}
|
||||
|
||||
public function createOrder(string $entityType, User $user): OrderApplier
|
||||
{
|
||||
/** @var OrderApplier */
|
||||
return $this->create($entityType, $user, self::ORDER);
|
||||
}
|
||||
|
||||
public function createLimit(string $entityType, User $user): LimitApplier
|
||||
{
|
||||
/** @var LimitApplier */
|
||||
return $this->create($entityType, $user, self::LIMIT);
|
||||
}
|
||||
|
||||
public function createAccessControlFilter(string $entityType, User $user): AccessControlFilterApplier
|
||||
{
|
||||
/** @var AccessControlFilterApplier */
|
||||
return $this->create($entityType, $user, self::ACCESS_CONTROL_FILTER);
|
||||
}
|
||||
|
||||
public function createTextFilter(string $entityType, User $user): TextFilterApplier
|
||||
{
|
||||
/** @var TextFilterApplier */
|
||||
return $this->create($entityType, $user, self::TEXT_FILTER);
|
||||
}
|
||||
|
||||
public function createPrimaryFilter(string $entityType, User $user): PrimaryFilterApplier
|
||||
{
|
||||
/** @var PrimaryFilterApplier */
|
||||
return $this->create($entityType, $user, self::PRIMARY_FILTER);
|
||||
}
|
||||
|
||||
public function createBoolFilterList(string $entityType, User $user): BoolFilterListApplier
|
||||
{
|
||||
/** @var BoolFilterListApplier */
|
||||
return $this->create($entityType, $user, self::BOOL_FILTER_LIST);
|
||||
}
|
||||
|
||||
public function createAdditional(string $entityType, User $user): AdditionalApplier
|
||||
{
|
||||
/** @var AdditionalApplier */
|
||||
return $this->create($entityType, $user, self::ADDITIONAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<object>
|
||||
*/
|
||||
private function getDefaultClassName(string $type): string
|
||||
{
|
||||
if (array_key_exists($type, $this->defaultClassNameMap)) {
|
||||
return $this->defaultClassNameMap[$type];
|
||||
}
|
||||
|
||||
throw new RuntimeException("Applier `$type` does not exist.");
|
||||
}
|
||||
}
|
||||
124
application/Espo/Core/Select/Bool/Applier.php
Normal file
124
application/Espo/Core/Select/Bool/Applier.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Bool;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Select\OrmSelectBuilder;
|
||||
use Espo\Core\Select\SelectManager;
|
||||
use Espo\Core\Select\Bool\FilterFactory as BoolFilterFactory;
|
||||
use Espo\ORM\Query\Select;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
class Applier
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private BoolFilterFactory $boolFilterFactory,
|
||||
private SelectManager $selectManager
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param string[] $boolFilterNameList
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function apply(QueryBuilder $queryBuilder, array $boolFilterNameList): void
|
||||
{
|
||||
$orGroupBuilder = new OrGroupBuilder();
|
||||
|
||||
$isMultiple = count($boolFilterNameList) > 1;
|
||||
|
||||
if ($isMultiple) {
|
||||
$queryBefore = $queryBuilder->build();
|
||||
}
|
||||
|
||||
foreach ($boolFilterNameList as $filterName) {
|
||||
$this->applyBoolFilter($queryBuilder, $orGroupBuilder, $filterName);
|
||||
}
|
||||
|
||||
if ($isMultiple) {
|
||||
$this->handleMultiple($queryBefore, $queryBuilder);
|
||||
}
|
||||
|
||||
$queryBuilder->where(
|
||||
$orGroupBuilder->build()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function applyBoolFilter(
|
||||
QueryBuilder $queryBuilder,
|
||||
OrGroupBuilder $orGroupBuilder,
|
||||
string $filterName
|
||||
): void {
|
||||
|
||||
if ($this->boolFilterFactory->has($this->entityType, $filterName)) {
|
||||
$filter = $this->boolFilterFactory->create($this->entityType, $this->user, $filterName);
|
||||
|
||||
$filter->apply($queryBuilder, $orGroupBuilder);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// For backward compatibility.
|
||||
if (
|
||||
$this->selectManager->hasBoolFilter($filterName) &&
|
||||
$queryBuilder instanceof OrmSelectBuilder
|
||||
) {
|
||||
$rawWhereClause = $this->selectManager->applyBoolFilterToQueryBuilder($queryBuilder, $filterName);
|
||||
|
||||
$whereItem = WhereClause::fromRaw($rawWhereClause);
|
||||
|
||||
$orGroupBuilder->add($whereItem);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new BadRequest("No bool filter '$filterName' for '$this->entityType'.");
|
||||
}
|
||||
|
||||
private function handleMultiple(Select $queryBefore, QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryAfter = $queryBuilder->build();
|
||||
|
||||
$joinCountBefore = count($queryBefore->getJoins());
|
||||
$joinCountAfter = count($queryAfter->getJoins());
|
||||
|
||||
if ($joinCountBefore < $joinCountAfter) {
|
||||
$queryBuilder->distinct();
|
||||
}
|
||||
}
|
||||
}
|
||||
41
application/Espo/Core/Select/Bool/Filter.php
Normal file
41
application/Espo/Core/Select/Bool/Filter.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Bool;
|
||||
|
||||
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* Applies a bool filter. A where item should be added to OrGroupBuilder.
|
||||
*/
|
||||
interface Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder, OrGroupBuilder $orGroupBuilder): void;
|
||||
}
|
||||
127
application/Espo/Core/Select/Bool/FilterFactory.php
Normal file
127
application/Espo/Core/Select/Bool/FilterFactory.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Bool;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Select\Helpers\FieldHelper;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Binding\BindingContainer;
|
||||
use Espo\Core\Binding\Binder;
|
||||
use Espo\Core\Binding\BindingData;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class FilterFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function create(string $entityType, User $user, string $name): Filter
|
||||
{
|
||||
$className = $this->getClassName($entityType, $name);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("Bool filter '$name' for '$entityType' does not exist.");
|
||||
}
|
||||
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder
|
||||
->bindInstance(User::class, $user)
|
||||
->for($className)
|
||||
->bindValue('$entityType', $entityType);
|
||||
|
||||
$binder
|
||||
->for(FieldHelper::class)
|
||||
->bindValue('$entityType', $entityType);
|
||||
|
||||
$bindingContainer = new BindingContainer($bindingData);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
public function has(string $entityType, string $name): bool
|
||||
{
|
||||
return (bool) $this->getClassName($entityType, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<Filter>
|
||||
*/
|
||||
protected function getClassName(string $entityType, string $name): ?string
|
||||
{
|
||||
if (!$name) {
|
||||
throw new RuntimeException("Empty bool filter name.");
|
||||
}
|
||||
|
||||
$className = $this->metadata->get(
|
||||
[
|
||||
'selectDefs',
|
||||
$entityType,
|
||||
'boolFilterClassNameMap',
|
||||
$name,
|
||||
]
|
||||
);
|
||||
|
||||
if ($className) {
|
||||
/** @var ?class-string<Filter> */
|
||||
return $className;
|
||||
}
|
||||
|
||||
return $this->getDefaultClassName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<Filter>
|
||||
*/
|
||||
protected function getDefaultClassName(string $name): ?string
|
||||
{
|
||||
$className1 = $this->metadata->get(['app', 'select', 'boolFilterClassNameMap', $name]);
|
||||
|
||||
if ($className1) {
|
||||
/** @var class-string<Filter> */
|
||||
return $className1;
|
||||
}
|
||||
|
||||
$className = 'Espo\\Core\\Select\\Bool\\Filters\\' . ucfirst($name);
|
||||
|
||||
if (!class_exists($className)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var class-string<Filter> */
|
||||
return $className;
|
||||
}
|
||||
}
|
||||
65
application/Espo/Core/Select/Bool/Filters/Followed.php
Normal file
65
application/Espo/Core/Select/Bool/Filters/Followed.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Bool\Filters;
|
||||
|
||||
use Espo\Core\Select\Bool\Filter;
|
||||
use Espo\Entities\StreamSubscription;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Followed implements Filter
|
||||
{
|
||||
public function __construct(private string $entityType, private User $user)
|
||||
{}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder, OrGroupBuilder $orGroupBuilder): void
|
||||
{
|
||||
$alias = 'subscriptionFollowedBoolFilter';
|
||||
|
||||
$queryBuilder->leftJoin(
|
||||
StreamSubscription::ENTITY_TYPE,
|
||||
$alias,
|
||||
[
|
||||
$alias . '.entityType' => $this->entityType,
|
||||
$alias . '.entityId=:' => Attribute::ID,
|
||||
$alias . '.userId' => $this->user->getId(),
|
||||
]
|
||||
);
|
||||
|
||||
$orGroupBuilder->add(
|
||||
WhereClause::fromRaw([
|
||||
$alias . '.id!=' => null,
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
116
application/Espo/Core/Select/Bool/Filters/OnlyMy.php
Normal file
116
application/Espo/Core/Select/Bool/Filters/OnlyMy.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Bool\Filters;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Select\Bool\Filter;
|
||||
use Espo\Core\Select\Helpers\FieldHelper;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Condition as Cond;
|
||||
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class OnlyMy implements Filter
|
||||
{
|
||||
public const NAME = 'onlyMy';
|
||||
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private FieldHelper $fieldHelper,
|
||||
private Defs $defs
|
||||
) {}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder, OrGroupBuilder $orGroupBuilder): void
|
||||
{
|
||||
if ($this->user->isPortal()) {
|
||||
$orGroupBuilder->add(
|
||||
Cond::equal(
|
||||
Cond::column('createdById'),
|
||||
$this->user->getId()
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->fieldHelper->hasAssignedUsersField()) {
|
||||
$relationDefs = $this->defs
|
||||
->getEntity($this->entityType)
|
||||
->getRelation(Field::ASSIGNED_USERS);
|
||||
|
||||
$middleEntityType = ucfirst($relationDefs->getRelationshipName());
|
||||
$key1 = $relationDefs->getMidKey();
|
||||
$key2 = $relationDefs->getForeignMidKey();
|
||||
|
||||
$subQuery = QueryBuilder::create()
|
||||
->select(Attribute::ID)
|
||||
->from($this->entityType)
|
||||
->leftJoin($middleEntityType, 'assignedUsersMiddle', [
|
||||
"assignedUsersMiddle.$key1:" => Attribute::ID,
|
||||
'assignedUsersMiddle.deleted' => false,
|
||||
])
|
||||
->where(["assignedUsersMiddle.$key2" => $this->user->getId()])
|
||||
->build();
|
||||
|
||||
$orGroupBuilder->add(
|
||||
Cond::in(
|
||||
Cond::column(Attribute::ID),
|
||||
$subQuery
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->fieldHelper->hasAssignedUserField()) {
|
||||
$orGroupBuilder->add(
|
||||
Cond::equal(
|
||||
Cond::column('assignedUserId'),
|
||||
$this->user->getId()
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$orGroupBuilder->add(
|
||||
Cond::equal(
|
||||
Cond::column('createdById'),
|
||||
$this->user->getId()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
87
application/Espo/Core/Select/Bool/Filters/Shared.php
Normal file
87
application/Espo/Core/Select/Bool/Filters/Shared.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Bool\Filters;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Select\Bool\Filter;
|
||||
use Espo\Core\Select\Helpers\FieldHelper;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Condition as Cond;
|
||||
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class Shared implements Filter
|
||||
{
|
||||
public const NAME = 'shared';
|
||||
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private FieldHelper $fieldHelper,
|
||||
private Defs $defs
|
||||
) {}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder, OrGroupBuilder $orGroupBuilder): void
|
||||
{
|
||||
if (!$this->fieldHelper->hasCollaboratorsField()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$relationDefs = $this->defs
|
||||
->getEntity($this->entityType)
|
||||
->getRelation(Field::COLLABORATORS);
|
||||
|
||||
$middleEntityType = ucfirst($relationDefs->getRelationshipName());
|
||||
$key1 = $relationDefs->getMidKey();
|
||||
$key2 = $relationDefs->getForeignMidKey();
|
||||
|
||||
$subQuery = QueryBuilder::create()
|
||||
->select(Attribute::ID)
|
||||
->from($this->entityType)
|
||||
->leftJoin($middleEntityType, 'collaboratorsMiddle', [
|
||||
"collaboratorsMiddle.$key1:" => Attribute::ID,
|
||||
'collaboratorsMiddle.deleted' => false,
|
||||
])
|
||||
->where(["collaboratorsMiddle.$key2" => $this->user->getId()])
|
||||
->build();
|
||||
|
||||
$orGroupBuilder->add(
|
||||
Cond::in(
|
||||
Cond::column('id'),
|
||||
$subQuery
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
210
application/Espo/Core/Select/Helpers/FieldHelper.php
Normal file
210
application/Espo/Core/Select/Helpers/FieldHelper.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Helpers;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Crm\Entities\Account;
|
||||
use Espo\Modules\Crm\Entities\Contact;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Defs\RelationDefs;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\BaseEntity;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
/**
|
||||
* @todo Rewrite using EntityDefs class. Then write unit tests.
|
||||
*/
|
||||
class FieldHelper
|
||||
{
|
||||
private ?Entity $seed = null;
|
||||
|
||||
private const LINK_CONTACTS = 'contacts';
|
||||
private const LINK_CONTACT = 'contact';
|
||||
private const LINK_ACCOUNTS = 'accounts';
|
||||
private const LINK_ACCOUNT = 'account';
|
||||
private const LINK_PARENT = Field::PARENT;
|
||||
private const LINK_TEAMS = Field::TEAMS;
|
||||
private const LINK_ASSIGNED_USERS = Field::ASSIGNED_USERS;
|
||||
private const LINK_ASSIGNED_USER = Field::ASSIGNED_USER;
|
||||
private const LINK_CREATED_BY = Field::CREATED_BY;
|
||||
private const LINK_COLLABORATORS = Field::COLLABORATORS;
|
||||
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private EntityManager $entityManager,
|
||||
private Metadata $metadata,
|
||||
) {}
|
||||
|
||||
private function getSeed(): Entity
|
||||
{
|
||||
$this->seed ??= $this->entityManager->getNewEntity($this->entityType);
|
||||
|
||||
return $this->seed;
|
||||
}
|
||||
|
||||
public function hasAssignedUsersField(): bool
|
||||
{
|
||||
if (
|
||||
$this->getSeed()->hasRelation(self::LINK_ASSIGNED_USERS) &&
|
||||
$this->getSeed()->hasAttribute(self::LINK_ASSIGNED_USERS . 'Ids') &&
|
||||
$this->getRelationEntityType(self::LINK_ASSIGNED_USERS) === User::ENTITY_TYPE
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasCollaboratorsField(): bool
|
||||
{
|
||||
if (
|
||||
$this->metadata->get("scopes.$this->entityType.collaborators") &&
|
||||
$this->getSeed()->hasRelation(self::LINK_COLLABORATORS) &&
|
||||
$this->getSeed()->hasAttribute(self::LINK_COLLABORATORS . 'Ids') &&
|
||||
$this->getRelationEntityType(self::LINK_COLLABORATORS) === User::ENTITY_TYPE
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasAssignedUserField(): bool
|
||||
{
|
||||
if (
|
||||
$this->getSeed()->hasAttribute(self::LINK_ASSIGNED_USER . 'Id') &&
|
||||
$this->getSeed()->hasRelation(self::LINK_ASSIGNED_USER) &&
|
||||
$this->getRelationEntityType(self::LINK_ASSIGNED_USER) === User::ENTITY_TYPE
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasCreatedByField(): bool
|
||||
{
|
||||
if (
|
||||
$this->getSeed()->hasAttribute(self::LINK_CREATED_BY . 'Id') &&
|
||||
$this->getSeed()->hasRelation(self::LINK_CREATED_BY) &&
|
||||
$this->getRelationEntityType(self::LINK_CREATED_BY) === User::ENTITY_TYPE
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasTeamsField(): bool
|
||||
{
|
||||
if (
|
||||
$this->getSeed()->hasRelation(self::LINK_TEAMS) &&
|
||||
$this->getSeed()->hasAttribute(self::LINK_TEAMS . 'Ids') &&
|
||||
$this->getRelationEntityType(self::LINK_TEAMS) === Team::ENTITY_TYPE
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasContactField(): bool
|
||||
{
|
||||
return
|
||||
$this->getSeed()->hasAttribute(self::LINK_CONTACT . 'Id') &&
|
||||
$this->getRelationEntityType(self::LINK_CONTACT) === Contact::ENTITY_TYPE;
|
||||
}
|
||||
|
||||
public function hasContactsRelation(): bool
|
||||
{
|
||||
return
|
||||
$this->getSeed()->hasRelation(self::LINK_CONTACTS) &&
|
||||
$this->getRelationEntityType(self::LINK_CONTACTS) === Contact::ENTITY_TYPE;
|
||||
}
|
||||
|
||||
public function hasParentField(): bool
|
||||
{
|
||||
return
|
||||
$this->getSeed()->hasAttribute(self::LINK_PARENT . 'Id') &&
|
||||
$this->getSeed()->hasRelation(self::LINK_PARENT) &&
|
||||
$this->getSeed()->getRelationType(self::LINK_PARENT) === RelationType::BELONGS_TO_PARENT;
|
||||
}
|
||||
|
||||
public function hasAccountField(): bool
|
||||
{
|
||||
return
|
||||
$this->getSeed()->hasAttribute(self::LINK_ACCOUNT . 'Id') &&
|
||||
$this->getRelationEntityType(self::LINK_ACCOUNT) === Account::ENTITY_TYPE;
|
||||
}
|
||||
|
||||
public function hasAccountsRelation(): bool
|
||||
{
|
||||
return
|
||||
$this->getSeed()->hasRelation(self::LINK_ACCOUNTS) &&
|
||||
$this->getRelationEntityType(self::LINK_ACCOUNTS) === Account::ENTITY_TYPE;
|
||||
}
|
||||
|
||||
public function getRelationDefs(string $name): RelationDefs
|
||||
{
|
||||
return $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($this->entityType)
|
||||
->getRelation($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpSameParameterValueInspection
|
||||
*/
|
||||
private function getRelationParam(Entity $entity, string $relation, string $param): mixed
|
||||
{
|
||||
if ($entity instanceof BaseEntity) {
|
||||
return $entity->getRelationParam($relation, $param);
|
||||
}
|
||||
|
||||
$entityDefs = $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($entity->getEntityType());
|
||||
|
||||
if (!$entityDefs->hasRelation($relation)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $entityDefs->getRelation($relation)->getParam($param);
|
||||
}
|
||||
|
||||
private function getRelationEntityType(string $relation): ?string
|
||||
{
|
||||
return $this->getRelationParam($this->getSeed(), $relation, RelationParam::ENTITY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Helpers;
|
||||
|
||||
class RandomStringGenerator
|
||||
{
|
||||
public function generate(): string
|
||||
{
|
||||
return strval(rand(10000, 99999));
|
||||
}
|
||||
}
|
||||
197
application/Espo/Core/Select/Helpers/RelationQueryHelper.php
Normal file
197
application/Espo/Core/Select/Helpers/RelationQueryHelper.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Helpers;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Condition;
|
||||
use Espo\ORM\Query\Part\Condition as Cond;
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use Espo\ORM\Query\Part\Expression as Expr;
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @since 9.0.0
|
||||
*/
|
||||
class RelationQueryHelper
|
||||
{
|
||||
public function __construct(
|
||||
private Defs $defs,
|
||||
) {}
|
||||
|
||||
public function prepareAssignedUsersWhere(string $entityType, string $userId): WhereItem
|
||||
{
|
||||
return $this->prepareRelatedUsersWhere(
|
||||
$entityType,
|
||||
$userId,
|
||||
Field::ASSIGNED_USERS,
|
||||
User::RELATIONSHIP_ENTITY_USER
|
||||
);
|
||||
}
|
||||
|
||||
public function prepareCollaboratorsWhere(string $entityType, string $userId): WhereItem
|
||||
{
|
||||
return $this->prepareRelatedUsersWhere(
|
||||
$entityType,
|
||||
$userId,
|
||||
Field::COLLABORATORS,
|
||||
User::RELATIONSHIP_ENTITY_COLLABORATOR
|
||||
);
|
||||
}
|
||||
|
||||
private function prepareRelatedUsersWhere(
|
||||
string $entityType,
|
||||
string $userId,
|
||||
string $field,
|
||||
string $relationship
|
||||
): WhereItem {
|
||||
|
||||
$relationDefs = $this->defs
|
||||
->getEntity($entityType)
|
||||
->getRelation($field);
|
||||
|
||||
$middleEntityType = ucfirst($relationDefs->getRelationshipName());
|
||||
$key1 = $relationDefs->getMidKey();
|
||||
$key2 = $relationDefs->getForeignMidKey();
|
||||
|
||||
$joinWhere = [
|
||||
"m.$key1:" => Attribute::ID,
|
||||
'm.deleted' => false,
|
||||
];
|
||||
|
||||
if ($middleEntityType === $relationship) {
|
||||
$joinWhere['m.entityType'] = $entityType;
|
||||
}
|
||||
|
||||
$subQuery = QueryBuilder::create()
|
||||
->select(Attribute::ID)
|
||||
->from($entityType)
|
||||
->leftJoin($middleEntityType, 'm', $joinWhere)
|
||||
->where(["m.$key2" => $userId])
|
||||
->build();
|
||||
|
||||
return Condition::in(
|
||||
Expression::column('id'),
|
||||
$subQuery
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|string[] $id
|
||||
*
|
||||
* @since 9.1.6
|
||||
*/
|
||||
public function prepareLinkWhereMany(string $entityType, string $link, string|array $id): WhereItem
|
||||
{
|
||||
$defs = $this->defs
|
||||
->getEntity($entityType)
|
||||
->getRelation($link);
|
||||
|
||||
if (!in_array($defs->getType(), [RelationType::HAS_MANY, RelationType::MANY_MANY])) {
|
||||
throw new LogicException("Only many-many and has-many allowed.");
|
||||
}
|
||||
|
||||
$builder = SelectBuilder::create()->from($entityType);
|
||||
|
||||
$whereItem = $this->prepareLinkWhere($defs, $entityType, $id, $builder);
|
||||
|
||||
if (!$whereItem) {
|
||||
throw new RuntimeException("Not supported relationship.");
|
||||
}
|
||||
|
||||
return $whereItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Signature can be changed in future.
|
||||
*
|
||||
* @param string|string[] $id
|
||||
*/
|
||||
public function prepareLinkWhere(
|
||||
Defs\RelationDefs $defs,
|
||||
string $entityType,
|
||||
string|array $id,
|
||||
QueryBuilder $queryBuilder
|
||||
): ?WhereItem {
|
||||
|
||||
$type = $defs->getType();
|
||||
$link = $defs->getName();
|
||||
|
||||
if (
|
||||
$type === RelationType::BELONGS_TO ||
|
||||
$type === RelationType::HAS_ONE
|
||||
) {
|
||||
if ($type === RelationType::HAS_ONE) {
|
||||
$queryBuilder->leftJoin($link);
|
||||
}
|
||||
|
||||
return WhereClause::fromRaw([$link . 'Id' => $id]);
|
||||
}
|
||||
|
||||
if ($type === RelationType::BELONGS_TO_PARENT) {
|
||||
return WhereClause::fromRaw([
|
||||
'parentType' => $entityType,
|
||||
'parentId' => $id,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($type === RelationType::MANY_MANY) {
|
||||
return Cond::in(
|
||||
Expr::column(Attribute::ID),
|
||||
QueryBuilder::create()
|
||||
->from(ucfirst($defs->getRelationshipName()), 'm')
|
||||
->select($defs->getMidKey())
|
||||
->where([$defs->getForeignMidKey() => $id])
|
||||
->build()
|
||||
);
|
||||
}
|
||||
|
||||
if ($type === RelationType::HAS_MANY) {
|
||||
return Cond::in(
|
||||
Expr::column(Attribute::ID),
|
||||
QueryBuilder::create()
|
||||
->from($entityType, 's')
|
||||
->select($defs->getForeignKey())
|
||||
->where([Attribute::ID => $id])
|
||||
->build()
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Helpers;
|
||||
|
||||
use Espo\Core\Utils\Config\ApplicationConfig;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Entities\Preferences;
|
||||
|
||||
class UserTimeZoneProvider
|
||||
{
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private EntityManager $entityManager,
|
||||
private ApplicationConfig $applicationConfig,
|
||||
) {}
|
||||
|
||||
public function get(): string
|
||||
{
|
||||
$preferences = $this->entityManager->getEntityById(Preferences::ENTITY_TYPE, $this->user->getId());
|
||||
|
||||
if (!$preferences) {
|
||||
return 'UTC';
|
||||
}
|
||||
|
||||
if ($preferences->get('timeZone') === null || $preferences->get('timeZone') === '') {
|
||||
return $this->applicationConfig->getTimeZone();
|
||||
}
|
||||
|
||||
return $preferences->get('timeZone');
|
||||
}
|
||||
}
|
||||
233
application/Espo/Core/Select/Order/Applier.php
Normal file
233
application/Espo/Core/Select/Order/Applier.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Order;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\OrderList;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Select\Order\Item as OrderItem;
|
||||
use Espo\Core\Select\Order\Params as OrderParams;
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class Applier
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private MetadataProvider $metadataProvider,
|
||||
private ItemConverterFactory $itemConverterFactory,
|
||||
private OrdererFactory $ordererFactory,
|
||||
private AclManager $aclManager,
|
||||
private User $user,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function apply(QueryBuilder $queryBuilder, OrderParams $params): void
|
||||
{
|
||||
if ($params->forceDefault()) {
|
||||
$this->applyDefaultOrder($queryBuilder, $params->getOrder());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$orderBy = $params->getOrderBy();
|
||||
|
||||
if ($orderBy) {
|
||||
if (
|
||||
str_contains($orderBy, '.') ||
|
||||
str_contains($orderBy, ':')
|
||||
) {
|
||||
throw new Forbidden("Complex expressions are forbidden in 'orderBy'.");
|
||||
}
|
||||
|
||||
if ($this->metadataProvider->isFieldOrderDisabled($this->entityType, $orderBy)) {
|
||||
throw new Forbidden("Order by the field '$orderBy' is disabled.");
|
||||
}
|
||||
|
||||
if ($this->metadataProvider->getFieldType($this->entityType, $orderBy) === FieldType::PASSWORD) {
|
||||
throw new Forbidden("Order by field '$orderBy' is not allowed.");
|
||||
}
|
||||
|
||||
if (
|
||||
$params->applyPermissionCheck() &&
|
||||
!$this->aclManager->checkField($this->user, $this->entityType, $orderBy)
|
||||
) {
|
||||
throw new Forbidden("Not access to order by field '$orderBy'.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($orderBy === null) {
|
||||
$orderBy = $this->metadataProvider->getDefaultOrderBy($this->entityType);
|
||||
}
|
||||
|
||||
if (!$orderBy) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->applyOrder($queryBuilder, $orderBy, $params->getOrder());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchParams::ORDER_ASC|SearchParams::ORDER_DESC|null $order
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function applyDefaultOrder(QueryBuilder $queryBuilder, ?string $order): void
|
||||
{
|
||||
$orderBy = $this->metadataProvider->getDefaultOrderBy($this->entityType);
|
||||
|
||||
if (!$orderBy) {
|
||||
$queryBuilder->order(Attribute::ID, $order);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$order) {
|
||||
$order = $this->metadataProvider->getDefaultOrder($this->entityType);
|
||||
|
||||
if ($order && strtolower($order) === 'desc') {
|
||||
$order = SearchParams::ORDER_DESC;
|
||||
} else if ($order && strtolower($order) === 'asc') {
|
||||
$order = SearchParams::ORDER_ASC;
|
||||
} else if ($order !== null) {
|
||||
throw new RuntimeException("Bad default order.");
|
||||
}
|
||||
}
|
||||
|
||||
/** @var SearchParams::ORDER_ASC|SearchParams::ORDER_DESC|null $order */
|
||||
|
||||
$this->applyOrder($queryBuilder, $orderBy, $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchParams::ORDER_ASC|SearchParams::ORDER_DESC|null $order
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function applyOrder(QueryBuilder $queryBuilder, string $orderBy, ?string $order): void
|
||||
{
|
||||
if (!$orderBy) {
|
||||
throw new RuntimeException("Could not apply empty order.");
|
||||
}
|
||||
|
||||
if ($order === null) {
|
||||
$order = SearchParams::ORDER_ASC;
|
||||
}
|
||||
|
||||
$hasOrderer = $this->ordererFactory->has($this->entityType, $orderBy);
|
||||
|
||||
if ($hasOrderer) {
|
||||
$orderer = $this->ordererFactory->create($this->entityType, $orderBy);
|
||||
|
||||
$orderer->apply(
|
||||
$queryBuilder,
|
||||
OrderItem::create($orderBy, $order)
|
||||
);
|
||||
|
||||
if ($order !== Attribute::ID) {
|
||||
$queryBuilder->order(Attribute::ID, $order);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$resultOrderBy = $orderBy;
|
||||
|
||||
$type = $this->metadataProvider->getFieldType($this->entityType, $orderBy);
|
||||
|
||||
$hasItemConverter = $this->itemConverterFactory->has($this->entityType, $orderBy);
|
||||
|
||||
if ($hasItemConverter) {
|
||||
$converter = $this->itemConverterFactory->create($this->entityType, $orderBy);
|
||||
|
||||
$resultOrderBy = $this->orderListToArray(
|
||||
$converter->convert(
|
||||
OrderItem::create($orderBy, $order)
|
||||
)
|
||||
);
|
||||
} else if (in_array($type, [FieldType::LINK, FieldType::FILE, FieldType::IMAGE, FieldType::LINK_ONE])) {
|
||||
$resultOrderBy .= 'Name';
|
||||
} else if ($type === FieldType::LINK_PARENT) {
|
||||
$resultOrderBy .= 'Type';
|
||||
} else if (
|
||||
!$this->metadataProvider->hasAttribute($this->entityType, $orderBy)
|
||||
) {
|
||||
throw new BadRequest("Order by non-existing field '$orderBy'.");
|
||||
}
|
||||
|
||||
$orderByAttribute = null;
|
||||
|
||||
if (!is_array($resultOrderBy)) {
|
||||
$orderByAttribute = $resultOrderBy;
|
||||
|
||||
$resultOrderBy = [
|
||||
[$resultOrderBy, $order]
|
||||
];
|
||||
}
|
||||
|
||||
if (
|
||||
$orderBy !== Attribute::ID &&
|
||||
(
|
||||
!$orderByAttribute ||
|
||||
!$this->metadataProvider->isAttributeParamUniqueTrue($this->entityType, $orderByAttribute)
|
||||
) &&
|
||||
$this->metadataProvider->hasAttribute($this->entityType, Attribute::ID)
|
||||
) {
|
||||
$resultOrderBy[] = [Attribute::ID, $order];
|
||||
}
|
||||
|
||||
$queryBuilder->order($resultOrderBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{string, string}>
|
||||
*/
|
||||
private function orderListToArray(OrderList $orderList): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($orderList as $order) {
|
||||
$list[] = [
|
||||
$order->getExpression()->getValue(),
|
||||
$order->getDirection(),
|
||||
];
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
90
application/Espo/Core/Select/Order/Item.php
Normal file
90
application/Espo/Core/Select/Order/Item.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Order;
|
||||
|
||||
use Espo\Core\Select\SearchParams;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class Item
|
||||
{
|
||||
private string $orderBy;
|
||||
/** @var SearchParams::ORDER_ASC|SearchParams::ORDER_DESC */
|
||||
private string $order;
|
||||
|
||||
/**
|
||||
* @param SearchParams::ORDER_ASC|SearchParams::ORDER_DESC $order
|
||||
*/
|
||||
private function __construct(string $orderBy, string $order)
|
||||
{
|
||||
if (
|
||||
$order !== SearchParams::ORDER_ASC &&
|
||||
$order !== SearchParams::ORDER_DESC
|
||||
) {
|
||||
throw new InvalidArgumentException("Bad order.");
|
||||
}
|
||||
|
||||
$this->orderBy = $orderBy;
|
||||
$this->order = $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchParams::ORDER_ASC|SearchParams::ORDER_DESC|null $order
|
||||
*/
|
||||
public static function create(string $orderBy, ?string $order = null): self
|
||||
{
|
||||
if ($order === null) {
|
||||
$order = SearchParams::ORDER_ASC;
|
||||
}
|
||||
|
||||
return new self($orderBy, $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a field.
|
||||
*/
|
||||
public function getOrderBy(): string
|
||||
{
|
||||
return $this->orderBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a direction.
|
||||
*
|
||||
* @return SearchParams::ORDER_ASC|SearchParams::ORDER_DESC
|
||||
*/
|
||||
public function getOrder(): string
|
||||
{
|
||||
return $this->order;
|
||||
}
|
||||
}
|
||||
40
application/Espo/Core/Select/Order/ItemConverter.php
Normal file
40
application/Espo/Core/Select/Order/ItemConverter.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Order;
|
||||
|
||||
use Espo\ORM\Query\Part\OrderList;
|
||||
|
||||
/**
|
||||
* Converts an order item (passed from front-end) to an ORM order list.
|
||||
*/
|
||||
interface ItemConverter
|
||||
{
|
||||
public function convert(Item $item): OrderList;
|
||||
}
|
||||
113
application/Espo/Core/Select/Order/ItemConverterFactory.php
Normal file
113
application/Espo/Core/Select/Order/ItemConverterFactory.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Order;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\Binding\ContextualBinder;
|
||||
|
||||
use Espo\ORM\Defs\Params\FieldParam;
|
||||
use RuntimeException;
|
||||
|
||||
class ItemConverterFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata,
|
||||
private User $user
|
||||
) {}
|
||||
|
||||
public function has(string $entityType, string $field): bool
|
||||
{
|
||||
return (bool) $this->getClassName($entityType, $field);
|
||||
}
|
||||
|
||||
public function create(string $entityType, string $field): ItemConverter
|
||||
{
|
||||
$className = $this->getClassName($entityType, $field);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("Order item converter class name is not defined.");
|
||||
}
|
||||
|
||||
$container = BindingContainerBuilder::create()
|
||||
->bindInstance(User::class, $this->user)
|
||||
->inContext($className, function (ContextualBinder $binder) use ($entityType) {
|
||||
$binder->bindValue('$entityType', $entityType);
|
||||
})
|
||||
->build();
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<ItemConverter>
|
||||
*/
|
||||
private function getClassName(string $entityType, string $field): ?string
|
||||
{
|
||||
/** @var ?class-string<ItemConverter> $className1 */
|
||||
$className1 = $this->metadata->get([
|
||||
'selectDefs', $entityType, 'orderItemConverterClassNameMap', $field
|
||||
]);
|
||||
|
||||
if ($className1) {
|
||||
return $className1;
|
||||
}
|
||||
|
||||
$type = $this->metadata->get([
|
||||
'entityDefs', $entityType, 'fields', $field, FieldParam::TYPE
|
||||
]);
|
||||
|
||||
if (!$type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var ?class-string<ItemConverter> $className2 */
|
||||
$className2 = $this->metadata->get([
|
||||
'app', 'select', 'orderItemConverterClassNameMap', $type
|
||||
]);
|
||||
|
||||
if ($className2) {
|
||||
return $className2;
|
||||
}
|
||||
|
||||
$className3 = 'Espo\\Core\\Select\\Order\\ItemConverters\\' . ucfirst($type) . 'Type';
|
||||
|
||||
if (class_exists($className3)) {
|
||||
/** @var class-string<ItemConverter> */
|
||||
return $className3;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Order\ItemConverters;
|
||||
|
||||
use Espo\ORM\Query\Part\OrderList;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
|
||||
use Espo\Core\Select\Order\Item;
|
||||
use Espo\Core\Select\Order\ItemConverter;
|
||||
|
||||
class AddressType implements ItemConverter
|
||||
{
|
||||
public function convert(Item $item): OrderList
|
||||
{
|
||||
$orderBy = $item->getOrderBy();
|
||||
$order = $item->getOrder();
|
||||
|
||||
return OrderList::create([
|
||||
Order::fromString($orderBy . 'Country')->withDirection($order),
|
||||
Order::fromString($orderBy . 'City')->withDirection($order),
|
||||
Order::fromString($orderBy . 'Street')->withDirection($order),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Order\ItemConverters;
|
||||
|
||||
use Espo\ORM\Query\Part\OrderList;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use Espo\Core\Select\Order\Item;
|
||||
use Espo\Core\Select\Order\ItemConverter;
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
class EnumType implements ItemConverter
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function convert(Item $item): OrderList
|
||||
{
|
||||
$orderBy = $item->getOrderBy();
|
||||
$order = $item->getOrder();
|
||||
|
||||
/** @var ?string[] $list */
|
||||
$list = $this->metadata->get([
|
||||
'entityDefs', $this->entityType, 'fields', $orderBy, 'options'
|
||||
]);
|
||||
|
||||
if (!is_array($list) || !count($list)) {
|
||||
return OrderList::create([
|
||||
Order::fromString($orderBy)->withDirection($order)
|
||||
]);
|
||||
}
|
||||
|
||||
$isSorted = $this->metadata->get([
|
||||
'entityDefs', $this->entityType, 'fields', $orderBy, 'isSorted'
|
||||
]);
|
||||
|
||||
if ($isSorted) {
|
||||
asort($list);
|
||||
}
|
||||
|
||||
if ($order === SearchParams::ORDER_DESC) {
|
||||
$list = array_reverse($list);
|
||||
}
|
||||
|
||||
return OrderList::create([
|
||||
Order::createByPositionInList(Expression::column($orderBy), $list),
|
||||
]);
|
||||
}
|
||||
}
|
||||
85
application/Espo/Core/Select/Order/MetadataProvider.php
Normal file
85
application/Espo/Core/Select/Order/MetadataProvider.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Order;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Defs\Params\FieldParam;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
class MetadataProvider
|
||||
{
|
||||
public function __construct(private Metadata $metadata, private EntityManager $entityManager)
|
||||
{}
|
||||
|
||||
public function getFieldType(string $entityType, string $field): ?string
|
||||
{
|
||||
return $this->metadata->get([
|
||||
'entityDefs', $entityType, 'fields', $field, FieldParam::TYPE
|
||||
]) ?? null;
|
||||
}
|
||||
|
||||
public function getDefaultOrderBy(string $entityType): ?string
|
||||
{
|
||||
return $this->metadata->get([
|
||||
'entityDefs', $entityType, 'collection', 'orderBy'
|
||||
]) ?? null;
|
||||
}
|
||||
|
||||
public function getDefaultOrder(string $entityType): ?string
|
||||
{
|
||||
return $this->metadata->get([
|
||||
'entityDefs', $entityType, 'collection', 'order'
|
||||
]) ?? null;
|
||||
}
|
||||
|
||||
public function isFieldOrderDisabled(string $entityType, string $field): bool
|
||||
{
|
||||
return $this->metadata->get("entityDefs.$entityType.fields.$field.orderDisabled") ?? false;
|
||||
}
|
||||
|
||||
public function hasAttribute(string $entityType, string $attribute): bool
|
||||
{
|
||||
return $this->entityManager
|
||||
->getMetadata()
|
||||
->getDefs()
|
||||
->getEntity($entityType)
|
||||
->hasAttribute($attribute);
|
||||
}
|
||||
|
||||
public function isAttributeParamUniqueTrue(string $entityType, string $attribute): bool
|
||||
{
|
||||
return (bool) $this->entityManager
|
||||
->getMetadata()
|
||||
->getDefs()
|
||||
->getEntity($entityType)
|
||||
->getAttribute($attribute)
|
||||
->getParam('unique');
|
||||
}
|
||||
}
|
||||
40
application/Espo/Core/Select/Order/Orderer.php
Normal file
40
application/Espo/Core/Select/Order/Orderer.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Order;
|
||||
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
|
||||
/**
|
||||
* A custom orderer. Should call SelectBuilder::order.
|
||||
*/
|
||||
interface Orderer
|
||||
{
|
||||
public function apply(SelectBuilder $queryBuilder, Item $item): void;
|
||||
}
|
||||
81
application/Espo/Core/Select/Order/OrdererFactory.php
Normal file
81
application/Espo/Core/Select/Order/OrdererFactory.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Order;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\Binding\ContextualBinder;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use RuntimeException;
|
||||
|
||||
class OrdererFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata,
|
||||
private User $user
|
||||
) {}
|
||||
|
||||
public function has(string $entityType, string $field): bool
|
||||
{
|
||||
return (bool) $this->getClassName($entityType, $field);
|
||||
}
|
||||
|
||||
public function create(string $entityType, string $field): Orderer
|
||||
{
|
||||
$className = $this->getClassName($entityType, $field);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("Orderer class name is not defined.");
|
||||
}
|
||||
|
||||
$container = BindingContainerBuilder::create()
|
||||
->bindInstance(User::class, $this->user)
|
||||
->inContext($className, function (ContextualBinder $binder) use ($entityType) {
|
||||
$binder->bindValue('$entityType', $entityType);
|
||||
})
|
||||
->build();
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<Orderer>
|
||||
*/
|
||||
private function getClassName(string $entityType, string $field): ?string
|
||||
{
|
||||
/** @var ?class-string<Orderer> */
|
||||
return $this->metadata->get([
|
||||
'selectDefs', $entityType, 'ordererClassNameMap', $field
|
||||
]);
|
||||
}
|
||||
}
|
||||
126
application/Espo/Core/Select/Order/Params.php
Normal file
126
application/Espo/Core/Select/Order/Params.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Order;
|
||||
|
||||
use Espo\Core\Select\SearchParams;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Order parameters.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
private bool $forceDefault = false;
|
||||
private mixed $orderBy = null;
|
||||
/** @var SearchParams::ORDER_ASC|SearchParams::ORDER_DESC|null */
|
||||
private $order = null;
|
||||
private bool $applyPermissionCheck = false;
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* forceDefault?: bool,
|
||||
* orderBy?: ?string,
|
||||
* order?: SearchParams::ORDER_ASC|SearchParams::ORDER_DESC|null,
|
||||
* applyPermissionCheck?: bool,
|
||||
* } $params
|
||||
*/
|
||||
public static function fromAssoc(array $params): self
|
||||
{
|
||||
$object = new self();
|
||||
|
||||
$object->forceDefault = $params['forceDefault'] ?? false;
|
||||
$object->orderBy = $params['orderBy'] ?? null;
|
||||
$object->order = $params['order'] ?? null;
|
||||
$object->applyPermissionCheck = $params['applyPermissionCheck'] ?? false;
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
if (!property_exists($object, $key)) {
|
||||
throw new InvalidArgumentException("Unknown parameter '{$key}'.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($object->orderBy && !is_string($object->orderBy)) {
|
||||
throw new InvalidArgumentException("Bad orderBy.");
|
||||
}
|
||||
|
||||
/** @var ?string $order */
|
||||
$order = $object->order;
|
||||
|
||||
if (
|
||||
$order &&
|
||||
$order !== SearchParams::ORDER_ASC &&
|
||||
$order !== SearchParams::ORDER_DESC
|
||||
) {
|
||||
throw new InvalidArgumentException("Bad order.");
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force default order.
|
||||
*/
|
||||
public function forceDefault(): bool
|
||||
{
|
||||
return $this->forceDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* An order-By field.
|
||||
*/
|
||||
public function getOrderBy(): ?string
|
||||
{
|
||||
/** @var ?string */
|
||||
return $this->orderBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* An order direction.
|
||||
*
|
||||
* @return SearchParams::ORDER_ASC|SearchParams::ORDER_DESC|null
|
||||
*/
|
||||
public function getOrder(): ?string
|
||||
{
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply permission check.
|
||||
*/
|
||||
public function applyPermissionCheck(): bool
|
||||
{
|
||||
return $this->applyPermissionCheck;
|
||||
}
|
||||
}
|
||||
47
application/Espo/Core/Select/OrmSelectBuilder.php
Normal file
47
application/Espo/Core/Select/OrmSelectBuilder.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select;
|
||||
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* Need access to raw params for backward compatibility.
|
||||
* The legacy select manager operates with raw params.
|
||||
*/
|
||||
class OrmSelectBuilder extends QueryBuilder
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public function setRawParams(array $params): void
|
||||
{
|
||||
$this->params = $params;
|
||||
}
|
||||
}
|
||||
72
application/Espo/Core/Select/Primary/Applier.php
Normal file
72
application/Espo/Core/Select/Primary/Applier.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Primary;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Select\SelectManager;
|
||||
use Espo\Core\Select\OrmSelectBuilder;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
use Espo\Entities\User;
|
||||
|
||||
class Applier
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private FilterFactory $primaryFilterFactory,
|
||||
private SelectManager $selectManager
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function apply(QueryBuilder $queryBuilder, string $filterName): void
|
||||
{
|
||||
if ($this->primaryFilterFactory->has($this->entityType, $filterName)) {
|
||||
$filter = $this->primaryFilterFactory->create($this->entityType, $this->user, $filterName);
|
||||
|
||||
$filter->apply($queryBuilder);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// For backward compatibility.
|
||||
if (
|
||||
$this->selectManager->hasPrimaryFilter($filterName) &&
|
||||
$queryBuilder instanceof OrmSelectBuilder
|
||||
) {
|
||||
$this->selectManager->applyPrimaryFilterToQueryBuilder($queryBuilder, $filterName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new BadRequest("No primary filter '$filterName' for '$this->entityType'.");
|
||||
}
|
||||
}
|
||||
40
application/Espo/Core/Select/Primary/Filter.php
Normal file
40
application/Espo/Core/Select/Primary/Filter.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Primary;
|
||||
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* A primary filter.
|
||||
*/
|
||||
interface Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void;
|
||||
}
|
||||
118
application/Espo/Core/Select/Primary/FilterFactory.php
Normal file
118
application/Espo/Core/Select/Primary/FilterFactory.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Primary;
|
||||
|
||||
use Espo\Core\Binding\Binder;
|
||||
use Espo\Core\Binding\BindingContainer;
|
||||
use Espo\Core\Binding\BindingData;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Select\Helpers\FieldHelper;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class FilterFactory
|
||||
{
|
||||
public function __construct(private InjectableFactory $injectableFactory, private Metadata $metadata)
|
||||
{}
|
||||
|
||||
public function create(string $entityType, User $user, string $name): Filter
|
||||
{
|
||||
$className = $this->getClassName($entityType, $name);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("Primary filter '{$name}' for '{$entityType}' does not exist.");
|
||||
}
|
||||
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder
|
||||
->bindInstance(User::class, $user)
|
||||
->for($className)
|
||||
->bindValue('$entityType', $entityType)
|
||||
->bindValue('$name', $name);
|
||||
|
||||
$binder
|
||||
->for(FieldHelper::class)
|
||||
->bindValue('$entityType', $entityType);
|
||||
|
||||
$bindingContainer = new BindingContainer($bindingData);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
public function has(string $entityType, string $name): bool
|
||||
{
|
||||
return (bool) $this->getClassName($entityType, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<Filter>
|
||||
*/
|
||||
protected function getClassName(string $entityType, string $name): ?string
|
||||
{
|
||||
if (!$name) {
|
||||
throw new RuntimeException("Empty primary filter name.");
|
||||
}
|
||||
|
||||
$className = $this->metadata->get(
|
||||
[
|
||||
'selectDefs',
|
||||
$entityType,
|
||||
'primaryFilterClassNameMap',
|
||||
$name,
|
||||
]
|
||||
);
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
return $this->getDefaultClassName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<Filter>
|
||||
*/
|
||||
protected function getDefaultClassName(string $name): ?string
|
||||
{
|
||||
$className = 'Espo\\Core\\Select\\Primary\\Filters\\' . ucfirst($name);
|
||||
|
||||
if (!class_exists($className)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var class-string<Filter> */
|
||||
return $className;
|
||||
}
|
||||
}
|
||||
48
application/Espo/Core/Select/Primary/Filters/All.php
Normal file
48
application/Espo/Core/Select/Primary/Filters/All.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Primary\Filters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* A dummy filter 'all'. Can be detected in a custom AdditionalApplier to instruct that the filter needs to be
|
||||
* bypassed. Use this filter for special cases from code. Users can pass this filter too. Do not rely on this filter
|
||||
* when dealing with access control logic.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*/
|
||||
class All implements Filter
|
||||
{
|
||||
public const NAME = 'all';
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{}
|
||||
}
|
||||
57
application/Espo/Core/Select/Primary/Filters/Followed.php
Normal file
57
application/Espo/Core/Select/Primary/Filters/Followed.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Primary\Filters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Entities\StreamSubscription;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Followed implements Filter
|
||||
{
|
||||
public function __construct(private string $entityType, private User $user)
|
||||
{}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$alias = 'subscriptionFollowedPrimaryFilter';
|
||||
|
||||
$queryBuilder->join(
|
||||
StreamSubscription::ENTITY_TYPE,
|
||||
$alias,
|
||||
[
|
||||
$alias . '.entityType' => $this->entityType,
|
||||
$alias . '.entityId=:' => Attribute::ID,
|
||||
$alias . '.userId' => $this->user->getId(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
45
application/Espo/Core/Select/Primary/Filters/One.php
Normal file
45
application/Espo/Core/Select/Primary/Filters/One.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Primary\Filters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* A dummy filter 'one'. Applied only when reading a single record (from the detail view).
|
||||
* Can be detected in a custom AdditionalApplier to distinguish a read request from a find request.
|
||||
*/
|
||||
class One implements Filter
|
||||
{
|
||||
public const NAME = 'one';
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{}
|
||||
}
|
||||
60
application/Espo/Core/Select/Primary/Filters/Starred.php
Normal file
60
application/Espo/Core/Select/Primary/Filters/Starred.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Primary\Filters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Entities\StarSubscription;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class Starred implements Filter
|
||||
{
|
||||
public function __construct(private string $entityType, private User $user)
|
||||
{}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$alias = 'starredPrimaryFilter';
|
||||
|
||||
$queryBuilder->join(
|
||||
StarSubscription::ENTITY_TYPE,
|
||||
$alias,
|
||||
[
|
||||
"$alias.entityType" => $this->entityType,
|
||||
"$alias.entityId=:" => Attribute::ID,
|
||||
"$alias.userId" => $this->user->getId(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
540
application/Espo/Core/Select/SearchParams.php
Normal file
540
application/Espo/Core/Select/SearchParams.php
Normal file
@@ -0,0 +1,540 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select;
|
||||
|
||||
use Espo\Core\Select\Where\Item as WhereItem;
|
||||
use Espo\Core\Utils\Json;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Search parameters.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class SearchParams
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $rawParams = [
|
||||
'select' => null,
|
||||
'boolFilterList' => [],
|
||||
'orderBy' => null,
|
||||
'order' => null,
|
||||
'maxSize' => null,
|
||||
'where' => null,
|
||||
'primaryFilter' => null,
|
||||
'textFilter' => null,
|
||||
];
|
||||
|
||||
public const ORDER_ASC = 'ASC';
|
||||
public const ORDER_DESC = 'DESC';
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getRaw(): array
|
||||
{
|
||||
return $this->rawParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attributes to be selected.
|
||||
*
|
||||
* @return ?string[]
|
||||
*/
|
||||
public function getSelect(): ?array
|
||||
{
|
||||
return $this->rawParams['select'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An order-by field.
|
||||
*/
|
||||
public function getOrderBy(): ?string
|
||||
{
|
||||
return $this->rawParams['orderBy'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An order direction.
|
||||
*
|
||||
* @return self::ORDER_ASC|self::ORDER_DESC
|
||||
*/
|
||||
public function getOrder(): ?string
|
||||
{
|
||||
return $this->rawParams['order'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An offset.
|
||||
*/
|
||||
public function getOffset(): ?int
|
||||
{
|
||||
return $this->rawParams['offset'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A max-size.
|
||||
*/
|
||||
public function getMaxSize(): ?int
|
||||
{
|
||||
return $this->rawParams['maxSize'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A text filter.
|
||||
*/
|
||||
public function getTextFilter(): ?string
|
||||
{
|
||||
return $this->rawParams['textFilter'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A primary filter.
|
||||
*/
|
||||
public function getPrimaryFilter(): ?string
|
||||
{
|
||||
return $this->rawParams['primaryFilter'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A bool filter list.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getBoolFilterList(): array
|
||||
{
|
||||
return $this->rawParams['boolFilterList'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* A where.
|
||||
*/
|
||||
public function getWhere(): ?WhereItem
|
||||
{
|
||||
$raw = $this->rawParams['where'] ?? null;
|
||||
|
||||
if ($raw === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return WhereItem::fromRaw([
|
||||
'type' => 'and',
|
||||
'value' => $raw,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* A max text attribute length.
|
||||
*/
|
||||
public function getMaxTextAttributeLength(): ?int
|
||||
{
|
||||
return $this->rawParams['maxTextAttributeLength'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* With attributes to be selected. NULL means to select all attributes.
|
||||
*
|
||||
* @param string[]|null $select
|
||||
*/
|
||||
public function withSelect(?array $select): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->rawParams['select'] = $select;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With an order-by field.
|
||||
*/
|
||||
public function withOrderBy(?string $orderBy): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->rawParams['orderBy'] = $orderBy;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With an order direction.
|
||||
*
|
||||
* @param self::ORDER_ASC|self::ORDER_DESC|null $order
|
||||
*/
|
||||
public function withOrder(?string $order): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->rawParams['order'] = $order;
|
||||
|
||||
if (
|
||||
$order !== null &&
|
||||
$order !== self::ORDER_ASC &&
|
||||
$order !== self::ORDER_DESC
|
||||
) {
|
||||
throw new InvalidArgumentException("order value is bad.");
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With an offset.
|
||||
*/
|
||||
public function withOffset(?int $offset): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->rawParams['offset'] = $offset;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With a mix size.
|
||||
*/
|
||||
public function withMaxSize(?int $maxSize): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->rawParams['maxSize'] = $maxSize;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With a text filter.
|
||||
*/
|
||||
public function withTextFilter(?string $filter): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->rawParams['textFilter'] = $filter;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With a primary filter.
|
||||
*
|
||||
* @param string|null $primaryFilter
|
||||
* @return $this
|
||||
*/
|
||||
public function withPrimaryFilter(?string $primaryFilter): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->rawParams['primaryFilter'] = $primaryFilter;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With a bool filter list. Previously set bool filter will be unset.
|
||||
*
|
||||
* @param string[] $boolFilterList
|
||||
*/
|
||||
public function withBoolFilterList(array $boolFilterList): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->rawParams['boolFilterList'] = $boolFilterList;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withBoolFilterAdded(string $boolFilter): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->rawParams['boolFilterList'] ??= [];
|
||||
$obj->rawParams['boolFilterList'][] = $boolFilter;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With a where. The previously set where will be unset.
|
||||
*/
|
||||
public function withWhere(WhereItem $where): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
if ($where->getType() === WhereItem\Type::AND) {
|
||||
$obj->rawParams['where'] = $where->getValue() ?? [];
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
$obj->rawParams['where'] = [$where->getRaw()];
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With a where added.
|
||||
*/
|
||||
public function withWhereAdded(WhereItem $whereItem): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$rawWhere = $obj->rawParams['where'] ?? [];
|
||||
|
||||
$rawWhere[] = $whereItem->getRaw();
|
||||
|
||||
$obj->rawParams['where'] = $rawWhere;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With max text attribute length (long texts will be cut to avoid fetching too much data).
|
||||
*/
|
||||
public function withMaxTextAttributeLength(?int $value): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->rawParams['maxTextAttributeLength'] = $value;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty instance.
|
||||
*/
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from a raw.
|
||||
*
|
||||
* @param stdClass|array<string, mixed> $params
|
||||
*/
|
||||
public static function fromRaw($params): self
|
||||
{
|
||||
if (!is_array($params) && !$params instanceof stdClass) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
if ($params instanceof stdClass) {
|
||||
$params = json_decode(Json::encode($params), true);
|
||||
}
|
||||
|
||||
$object = new self();
|
||||
|
||||
$rawParams = [];
|
||||
|
||||
$select = $params['select'] ?? null;
|
||||
$orderBy = $params['orderBy'] ?? null;
|
||||
$order = $params['order'] ?? null;
|
||||
|
||||
$offset = $params['offset'] ?? null;
|
||||
$maxSize = $params['maxSize'] ?? null;
|
||||
|
||||
// For bc.
|
||||
if (is_string($offset) && is_numeric($offset)) {
|
||||
$offset = (int) $offset;
|
||||
}
|
||||
|
||||
// For bc.
|
||||
if (is_string($maxSize) && is_numeric($maxSize)) {
|
||||
$maxSize = (int) $maxSize;
|
||||
}
|
||||
|
||||
$boolFilterList = $params['boolFilterList'] ?? [];
|
||||
$primaryFilter = $params['primaryFilter'] ?? null;
|
||||
$textFilter = $params['textFilter'] ?? $params['q'] ?? null;
|
||||
|
||||
$where = $params['where'] ?? null;
|
||||
|
||||
$maxTextAttributeLength = $params['maxTextAttributeLength'] ?? null;
|
||||
|
||||
if ($select !== null && !is_array($select)) {
|
||||
throw new InvalidArgumentException("select should be array.");
|
||||
}
|
||||
|
||||
if (is_array($select)) {
|
||||
foreach ($select as $item) {
|
||||
if (!is_string($item)) {
|
||||
throw new InvalidArgumentException("select has non-string item.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($orderBy !== null && !is_string($orderBy)) {
|
||||
throw new InvalidArgumentException("orderBy should be string.");
|
||||
}
|
||||
|
||||
if ($order !== null && !is_string($order)) {
|
||||
throw new InvalidArgumentException("order should be string.");
|
||||
}
|
||||
|
||||
if (!is_array($boolFilterList)) {
|
||||
throw new InvalidArgumentException("boolFilterList should be array.");
|
||||
}
|
||||
|
||||
foreach ($boolFilterList as $item) {
|
||||
if (!is_string($item)) {
|
||||
throw new InvalidArgumentException("boolFilterList has non-string item.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($primaryFilter !== null && !is_string($primaryFilter)) {
|
||||
throw new InvalidArgumentException("primaryFilter should be string.");
|
||||
}
|
||||
|
||||
if ($textFilter !== null && !is_string($textFilter)) {
|
||||
throw new InvalidArgumentException("textFilter should be string.");
|
||||
}
|
||||
|
||||
if ($where !== null && !is_array($where)) {
|
||||
throw new InvalidArgumentException("where should be array.");
|
||||
}
|
||||
|
||||
if ($offset !== null && !is_int($offset)) {
|
||||
throw new InvalidArgumentException("offset should be int.");
|
||||
}
|
||||
|
||||
if ($maxSize !== null && !is_int($maxSize)) {
|
||||
throw new InvalidArgumentException("maxSize should be int.");
|
||||
}
|
||||
|
||||
if ($maxTextAttributeLength && !is_int($maxTextAttributeLength)) {
|
||||
throw new InvalidArgumentException("maxTextAttributeLength should be int.");
|
||||
}
|
||||
|
||||
if ($order) {
|
||||
$order = strtoupper($order);
|
||||
|
||||
if ($order !== self::ORDER_ASC && $order !== self::ORDER_DESC) {
|
||||
throw new InvalidArgumentException("order value is bad.");
|
||||
}
|
||||
}
|
||||
|
||||
$rawParams['select'] = $select;
|
||||
$rawParams['orderBy'] = $orderBy;
|
||||
$rawParams['order'] = $order;
|
||||
$rawParams['offset'] = $offset;
|
||||
$rawParams['maxSize'] = $maxSize;
|
||||
$rawParams['boolFilterList'] = $boolFilterList;
|
||||
$rawParams['primaryFilter'] = $primaryFilter;
|
||||
$rawParams['textFilter'] = $textFilter;
|
||||
$rawParams['where'] = $where;
|
||||
$rawParams['maxTextAttributeLength'] = $maxTextAttributeLength;
|
||||
|
||||
if ($where) {
|
||||
$object->adjustParams($rawParams);
|
||||
}
|
||||
|
||||
$object->rawParams = $rawParams;
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two SelectParams instances.
|
||||
*/
|
||||
public static function merge(self $searchParams1, self $searchParams2): self
|
||||
{
|
||||
$paramList = [
|
||||
'select',
|
||||
'orderBy',
|
||||
'order',
|
||||
'maxSize',
|
||||
'primaryFilter',
|
||||
'textFilter',
|
||||
];
|
||||
|
||||
$params = $searchParams2->getRaw();
|
||||
|
||||
$leftParams = $searchParams1->getRaw();
|
||||
|
||||
foreach ($paramList as $name) {
|
||||
if (!is_null($leftParams[$name])) {
|
||||
$params[$name] = $leftParams[$name];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($leftParams['boolFilterList'] as $item) {
|
||||
if (in_array($item, $params['boolFilterList'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$params['boolFilterList'][] = $item;
|
||||
}
|
||||
|
||||
$params['where'] = $params['where'] ?? [];
|
||||
|
||||
if (!is_null($leftParams['where'])) {
|
||||
foreach ($leftParams['where'] as $item) {
|
||||
$params['where'][] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($params['where']) === 0) {
|
||||
$params['where'] = null;
|
||||
}
|
||||
|
||||
return self::fromRaw($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* For compatibility with the legacy definition.
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
private function adjustParams(array &$params): void
|
||||
{
|
||||
if (!$params['where']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$where = $params['where'];
|
||||
|
||||
foreach ($where as $i => $item) {
|
||||
$type = $item['type'] ?? null;
|
||||
$value = $item['value'] ?? null;
|
||||
|
||||
if ($type == 'bool' && !empty($value) && is_array($value)) {
|
||||
$params['boolFilterList'] = $value;
|
||||
|
||||
unset($where[$i]);
|
||||
} else if ($type === 'textFilter') {
|
||||
$params['textFilter'] = $value;
|
||||
|
||||
unset($where[$i]);
|
||||
} else if ($type == 'primary' && $value) {
|
||||
$params['primaryFilter'] = $value;
|
||||
|
||||
unset($where[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
$params['where'] = array_values($where);
|
||||
}
|
||||
}
|
||||
206
application/Espo/Core/Select/Select/Applier.php
Normal file
206
application/Espo/Core/Select/Select/Applier.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Select;
|
||||
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\Core\Utils\FieldUtil;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Applier
|
||||
{
|
||||
/** @var string[] */
|
||||
private $aclAttributeList = [
|
||||
'assignedUserId',
|
||||
'createdById',
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private $aclPortalAttributeList = [
|
||||
'assignedUserId',
|
||||
'createdById',
|
||||
'contactId',
|
||||
'accountId',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private FieldUtil $fieldUtil,
|
||||
private MetadataProvider $metadataProvider
|
||||
) {}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder, SearchParams $searchParams): void
|
||||
{
|
||||
$attributeList = $this->getSelectAttributeList($searchParams);
|
||||
|
||||
if ($attributeList) {
|
||||
$queryBuilder->select(
|
||||
$this->prepareAttributeList($attributeList, $searchParams)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $attributeList
|
||||
* @return array<int, array{string, string}|string>
|
||||
*/
|
||||
private function prepareAttributeList(array $attributeList, SearchParams $searchParams): array
|
||||
{
|
||||
$limit = $searchParams->getMaxTextAttributeLength();
|
||||
|
||||
if ($limit === null) {
|
||||
return $attributeList;
|
||||
}
|
||||
|
||||
$resultList = [];
|
||||
|
||||
foreach ($attributeList as $item) {
|
||||
if (
|
||||
$this->metadataProvider->hasAttribute($this->entityType, $item) &&
|
||||
$this->metadataProvider->getAttributeType($this->entityType, $item) === Entity::TEXT &&
|
||||
!$this->metadataProvider->isAttributeNotStorable($this->entityType, $item)
|
||||
) {
|
||||
$resultList[] = [
|
||||
"LEFT:($item, $limit)",
|
||||
$item
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$resultList[] = $item;
|
||||
}
|
||||
|
||||
return $resultList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?string[]
|
||||
*/
|
||||
private function getSelectAttributeList(SearchParams $searchParams): ?array
|
||||
{
|
||||
$passedAttributeList = $searchParams->getSelect();
|
||||
|
||||
if (!$passedAttributeList) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($passedAttributeList === ['*']) {
|
||||
return ['*'];
|
||||
}
|
||||
|
||||
$attributeList = [];
|
||||
|
||||
if (!in_array(Attribute::ID, $passedAttributeList)) {
|
||||
$attributeList[] = Attribute::ID;
|
||||
}
|
||||
|
||||
foreach ($this->getAclAttributeList() as $attribute) {
|
||||
if (in_array($attribute, $passedAttributeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadataProvider->hasAttribute($this->entityType, $attribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributeList[] = $attribute;
|
||||
}
|
||||
|
||||
foreach ($passedAttributeList as $attribute) {
|
||||
if (in_array($attribute, $attributeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadataProvider->hasAttribute($this->entityType, $attribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributeList[] = $attribute;
|
||||
}
|
||||
|
||||
$orderByField = $searchParams->getOrderBy() ?? $this->metadataProvider->getDefaultOrderBy($this->entityType);
|
||||
|
||||
if ($orderByField) {
|
||||
$sortByAttributeList = $this->fieldUtil->getAttributeList($this->entityType, $orderByField);
|
||||
|
||||
foreach ($sortByAttributeList as $attribute) {
|
||||
if (in_array($attribute, $attributeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadataProvider->hasAttribute($this->entityType, $attribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributeList[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
$selectAttributesDependencyMap =
|
||||
$this->metadataProvider->getSelectAttributesDependencyMap($this->entityType) ?? [];
|
||||
|
||||
foreach ($selectAttributesDependencyMap as $attribute => $dependantAttributeList) {
|
||||
if (!in_array($attribute, $attributeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($dependantAttributeList as $dependantAttribute) {
|
||||
if (in_array($dependantAttribute, $attributeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributeList[] = $dependantAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
return $attributeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getAclAttributeList(): array
|
||||
{
|
||||
if ($this->user->isPortal()) {
|
||||
return
|
||||
$this->metadataProvider->getAclPortalAttributeList($this->entityType) ??
|
||||
$this->aclPortalAttributeList;
|
||||
}
|
||||
|
||||
return
|
||||
$this->metadataProvider->getAclAttributeList($this->entityType) ??
|
||||
$this->aclAttributeList;
|
||||
}
|
||||
}
|
||||
105
application/Espo/Core/Select/Select/MetadataProvider.php
Normal file
105
application/Espo/Core/Select/Select/MetadataProvider.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Select;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
class MetadataProvider
|
||||
{
|
||||
public function __construct(private Metadata $metadata, private EntityManager $entityManager)
|
||||
{}
|
||||
|
||||
public function getDefaultOrderBy(string $entityType): ?string
|
||||
{
|
||||
return $this->metadata->get([
|
||||
'entityDefs', $entityType, 'collection', 'orderBy'
|
||||
]) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?array<string, string[]>
|
||||
*/
|
||||
public function getSelectAttributesDependencyMap(string $entityType): ?array
|
||||
{
|
||||
return $this->metadata->get([
|
||||
'selectDefs', $entityType, 'selectAttributesDependencyMap'
|
||||
]) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?string[]
|
||||
*/
|
||||
public function getAclPortalAttributeList(string $entityType): ?array
|
||||
{
|
||||
return $this->metadata->get([
|
||||
'selectDefs', $entityType, 'aclPortalAttributeList'
|
||||
]) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?string[]
|
||||
*/
|
||||
public function getAclAttributeList(string $entityType): ?array
|
||||
{
|
||||
return $this->metadata->get([
|
||||
'selectDefs', $entityType, 'aclAttributeList'
|
||||
]) ?? null;
|
||||
}
|
||||
|
||||
public function hasAttribute(string $entityType, string $attribute): bool
|
||||
{
|
||||
return $this->entityManager
|
||||
->getMetadata()
|
||||
->getDefs()
|
||||
->getEntity($entityType)
|
||||
->hasAttribute($attribute);
|
||||
}
|
||||
|
||||
public function isAttributeNotStorable(string $entityType, string $attribute): bool
|
||||
{
|
||||
return $this->entityManager
|
||||
->getMetadata()
|
||||
->getDefs()
|
||||
->getEntity($entityType)
|
||||
->getAttribute($attribute)
|
||||
->isNotStorable();
|
||||
}
|
||||
|
||||
public function getAttributeType(string $entityType, string $attribute): string
|
||||
{
|
||||
return $this->entityManager
|
||||
->getMetadata()
|
||||
->getDefs()
|
||||
->getEntity($entityType)
|
||||
->getAttribute($attribute)
|
||||
->getType();
|
||||
}
|
||||
}
|
||||
575
application/Espo/Core/Select/SelectBuilder.php
Normal file
575
application/Espo/Core/Select/SelectBuilder.php
Normal file
@@ -0,0 +1,575 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Select\Applier\Factory as ApplierFactory;
|
||||
use Espo\Core\Select\Where\Params as WhereParams;
|
||||
use Espo\Core\Select\Where\Item as WhereItem;
|
||||
use Espo\Core\Select\Order\Params as OrderParams;
|
||||
use Espo\Core\Select\Text\FilterParams as TextFilterParams;
|
||||
use Espo\Core\Select\Where\Applier as WhereApplier;
|
||||
use Espo\Core\Select\Select\Applier as SelectApplier;
|
||||
use Espo\Core\Select\Order\Applier as OrderApplier;
|
||||
use Espo\Core\Select\AccessControl\Applier as AccessControlFilterApplier;
|
||||
use Espo\Core\Select\Primary\Applier as PrimaryFilterApplier;
|
||||
use Espo\Core\Select\Bool\Applier as BoolFilterListApplier;
|
||||
use Espo\Core\Select\Text\Applier as TextFilterApplier;
|
||||
use Espo\Core\Select\Applier\Appliers\Limit as LimitApplier;
|
||||
use Espo\Core\Select\Applier\Appliers\Additional as AdditionalApplier;
|
||||
|
||||
use Espo\ORM\Query\Select as Query;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* Builds select queries for ORM. Applies search parameters(passed from front-end),
|
||||
* ACL restrictions, filters, etc.
|
||||
*/
|
||||
class SelectBuilder
|
||||
{
|
||||
private ?string $entityType = null;
|
||||
private ?OrmSelectBuilder $queryBuilder = null;
|
||||
private ?Query $sourceQuery = null;
|
||||
private ?SearchParams $searchParams = null;
|
||||
private bool $applyAccessControlFilter = false;
|
||||
private bool $applyDefaultOrder = false;
|
||||
private ?string $textFilter = null;
|
||||
private ?string $primaryFilter = null;
|
||||
/** @var string[] */
|
||||
private array $boolFilterList = [];
|
||||
/** @var WhereItem[] */
|
||||
private array $whereItemList = [];
|
||||
private bool $applyWherePermissionCheck = false;
|
||||
private bool $applyComplexExpressionsForbidden = false;
|
||||
/** @var class-string<Applier\AdditionalApplier>[] */
|
||||
private array $additionalApplierClassNameList = [];
|
||||
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private ApplierFactory $applierFactory
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Specify an entity type to select from.
|
||||
*/
|
||||
public function from(string $entityType): self
|
||||
{
|
||||
if ($this->sourceQuery) {
|
||||
throw new LogicException("Can't call 'from' after 'clone'.");
|
||||
}
|
||||
|
||||
$this->entityType = $entityType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start building from an existing select query.
|
||||
*/
|
||||
public function clone(Query $query): self
|
||||
{
|
||||
if ($this->entityType && $this->entityType !== $query->getFrom()) {
|
||||
throw new LogicException("Not matching entity type.");
|
||||
}
|
||||
|
||||
$this->entityType = $query->getFrom();
|
||||
$this->sourceQuery = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a result query.
|
||||
*
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function build(): Query
|
||||
{
|
||||
return $this->buildQueryBuilder()->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an ORM query builder. Used to continue building but by means of ORM.
|
||||
*
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function buildQueryBuilder(): QueryBuilder
|
||||
{
|
||||
$this->queryBuilder = new OrmSelectBuilder();
|
||||
|
||||
if (!$this->entityType) {
|
||||
throw new LogicException("No entity type.");
|
||||
}
|
||||
|
||||
if ($this->sourceQuery) {
|
||||
$this->queryBuilder->clone($this->sourceQuery);
|
||||
} else {
|
||||
$this->queryBuilder->from($this->entityType);
|
||||
}
|
||||
|
||||
$this->applyFromSearchParams();
|
||||
|
||||
if (count($this->whereItemList)) {
|
||||
$this->applyWhereItemList();
|
||||
}
|
||||
|
||||
if ($this->applyDefaultOrder) {
|
||||
$this->applyDefaultOrder();
|
||||
}
|
||||
|
||||
if ($this->primaryFilter) {
|
||||
$this->applyPrimaryFilter();
|
||||
}
|
||||
|
||||
if (count($this->boolFilterList)) {
|
||||
$this->applyBoolFilterList();
|
||||
}
|
||||
|
||||
if ($this->textFilter) {
|
||||
$this->applyTextFilter();
|
||||
}
|
||||
|
||||
if ($this->applyAccessControlFilter) {
|
||||
$this->applyAccessControlFilter();
|
||||
}
|
||||
|
||||
$this->applyAdditional();
|
||||
|
||||
return $this->queryBuilder ?? throw new LogicException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch a user for whom a select query will be built.
|
||||
*/
|
||||
public function forUser(User $user): self
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply search parameters.
|
||||
*
|
||||
* Note: If there's no order set in the search parameters then a default order will be applied.
|
||||
*/
|
||||
public function withSearchParams(SearchParams $searchParams): self
|
||||
{
|
||||
$this->searchParams = $searchParams;
|
||||
|
||||
$this->withBoolFilterList(
|
||||
$searchParams->getBoolFilterList()
|
||||
);
|
||||
|
||||
$primaryFilter = $searchParams->getPrimaryFilter();
|
||||
|
||||
if ($primaryFilter) {
|
||||
$this->withPrimaryFilter($primaryFilter);
|
||||
}
|
||||
|
||||
$textFilter = $searchParams->getTextFilter();
|
||||
|
||||
if ($textFilter !== null) {
|
||||
$this->withTextFilter($textFilter);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply maximum restrictions for a user.
|
||||
*/
|
||||
public function withStrictAccessControl(): self
|
||||
{
|
||||
$this->withAccessControlFilter();
|
||||
$this->withWherePermissionCheck();
|
||||
$this->withComplexExpressionsForbidden();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an access control filter.
|
||||
*/
|
||||
public function withAccessControlFilter(): self
|
||||
{
|
||||
$this->applyAccessControlFilter = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a default order.
|
||||
*/
|
||||
public function withDefaultOrder(): self
|
||||
{
|
||||
$this->applyDefaultOrder = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check permissions to where items.
|
||||
*/
|
||||
public function withWherePermissionCheck(): self
|
||||
{
|
||||
$this->applyWherePermissionCheck = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forbid complex expression usage.
|
||||
*/
|
||||
public function withComplexExpressionsForbidden(): self
|
||||
{
|
||||
$this->applyComplexExpressionsForbidden = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a text filter.
|
||||
*/
|
||||
public function withTextFilter(string $textFilter): self
|
||||
{
|
||||
$this->textFilter = $textFilter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a primary filter.
|
||||
*/
|
||||
public function withPrimaryFilter(string $primaryFilter): self
|
||||
{
|
||||
$this->primaryFilter = $primaryFilter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a bool filter.
|
||||
*/
|
||||
public function withBoolFilter(string $boolFilter): self
|
||||
{
|
||||
$this->boolFilterList[] = $boolFilter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a list of bool filters.
|
||||
*
|
||||
* @param string[] $boolFilterList
|
||||
*/
|
||||
public function withBoolFilterList(array $boolFilterList): self
|
||||
{
|
||||
$this->boolFilterList = array_merge($this->boolFilterList, $boolFilterList);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a Where Item.
|
||||
*/
|
||||
public function withWhere(WhereItem $whereItem): self
|
||||
{
|
||||
$this->whereItemList[] = $whereItem;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a list of additional applier class names.
|
||||
*
|
||||
* @param class-string<Applier\AdditionalApplier>[] $additionalApplierClassNameList
|
||||
*/
|
||||
public function withAdditionalApplierClassNameList(array $additionalApplierClassNameList): self
|
||||
{
|
||||
$this->additionalApplierClassNameList = array_merge(
|
||||
$this->additionalApplierClassNameList,
|
||||
$additionalApplierClassNameList
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function applyPrimaryFilter(): void
|
||||
{
|
||||
assert($this->queryBuilder !== null);
|
||||
assert($this->primaryFilter !== null);
|
||||
|
||||
$this->createPrimaryFilterApplier()
|
||||
->apply(
|
||||
$this->queryBuilder,
|
||||
$this->primaryFilter
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function applyBoolFilterList(): void
|
||||
{
|
||||
assert($this->queryBuilder !== null);
|
||||
|
||||
$this->createBoolFilterListApplier()
|
||||
->apply(
|
||||
$this->queryBuilder,
|
||||
$this->boolFilterList
|
||||
);
|
||||
}
|
||||
|
||||
private function applyTextFilter(): void
|
||||
{
|
||||
assert($this->queryBuilder !== null);
|
||||
assert($this->textFilter !== null);
|
||||
|
||||
$this->createTextFilterApplier()
|
||||
->apply(
|
||||
$this->queryBuilder,
|
||||
$this->textFilter,
|
||||
TextFilterParams::create()
|
||||
);
|
||||
}
|
||||
|
||||
private function applyAccessControlFilter(): void
|
||||
{
|
||||
assert($this->queryBuilder !== null);
|
||||
|
||||
$this->createAccessControlFilterApplier()
|
||||
->apply(
|
||||
$this->queryBuilder
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function applyDefaultOrder(): void
|
||||
{
|
||||
assert($this->queryBuilder !== null);
|
||||
|
||||
$order = $this->searchParams?->getOrder();
|
||||
|
||||
$params = OrderParams::fromAssoc([
|
||||
'forceDefault' => true,
|
||||
'order' => $order,
|
||||
]);
|
||||
|
||||
$this->createOrderApplier()
|
||||
->apply(
|
||||
$this->queryBuilder,
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function applyWhereItemList(): void
|
||||
{
|
||||
foreach ($this->whereItemList as $whereItem) {
|
||||
$this->applyWhereItem($whereItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function applyWhereItem(WhereItem $whereItem): void
|
||||
{
|
||||
assert($this->queryBuilder !== null);
|
||||
|
||||
$params = WhereParams::fromAssoc([
|
||||
'applyPermissionCheck' => $this->applyWherePermissionCheck,
|
||||
'forbidComplexExpressions' => $this->applyComplexExpressionsForbidden,
|
||||
]);
|
||||
|
||||
$this->createWhereApplier()
|
||||
->apply(
|
||||
$this->queryBuilder,
|
||||
$whereItem,
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function applyFromSearchParams(): void
|
||||
{
|
||||
if (!$this->searchParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert($this->queryBuilder !== null);
|
||||
|
||||
if (
|
||||
!$this->applyDefaultOrder &&
|
||||
($this->searchParams->getOrderBy() || $this->searchParams->getOrder())
|
||||
) {
|
||||
$params = OrderParams::fromAssoc([
|
||||
'orderBy' => $this->searchParams->getOrderBy(),
|
||||
'order' => $this->searchParams->getOrder(),
|
||||
'applyPermissionCheck' => $this->applyWherePermissionCheck,
|
||||
]);
|
||||
|
||||
$this->createOrderApplier()
|
||||
->apply(
|
||||
$this->queryBuilder,
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->searchParams->getOrderBy() && !$this->searchParams->getOrder()) {
|
||||
$this->withDefaultOrder();
|
||||
}
|
||||
|
||||
if ($this->searchParams->getMaxSize() !== null || $this->searchParams->getOffset() !== null) {
|
||||
$this->createLimitApplier()
|
||||
->apply(
|
||||
$this->queryBuilder,
|
||||
$this->searchParams->getOffset(),
|
||||
$this->searchParams->getMaxSize()
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->searchParams->getSelect()) {
|
||||
$this->createSelectApplier()
|
||||
->apply(
|
||||
$this->queryBuilder,
|
||||
$this->searchParams
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->searchParams->getWhere()) {
|
||||
$this->whereItemList[] = $this->searchParams->getWhere();
|
||||
}
|
||||
}
|
||||
|
||||
private function applyAdditional(): void
|
||||
{
|
||||
assert($this->queryBuilder !== null);
|
||||
|
||||
$searchParams = SearchParams::create()
|
||||
->withBoolFilterList($this->boolFilterList)
|
||||
->withPrimaryFilter($this->primaryFilter)
|
||||
->withTextFilter($this->textFilter);
|
||||
|
||||
if ($this->searchParams) {
|
||||
$searchParams = SearchParams::merge($searchParams, $this->searchParams);
|
||||
}
|
||||
|
||||
$this->createAdditionalApplier()->apply(
|
||||
$this->additionalApplierClassNameList,
|
||||
$this->queryBuilder,
|
||||
$searchParams
|
||||
);
|
||||
}
|
||||
|
||||
private function createWhereApplier(): WhereApplier
|
||||
{
|
||||
assert($this->entityType !== null);
|
||||
|
||||
return $this->applierFactory->createWhere($this->entityType, $this->user);
|
||||
}
|
||||
|
||||
private function createSelectApplier(): SelectApplier
|
||||
{
|
||||
assert($this->entityType !== null);
|
||||
|
||||
return $this->applierFactory->createSelect($this->entityType, $this->user);
|
||||
}
|
||||
|
||||
private function createOrderApplier(): OrderApplier
|
||||
{
|
||||
assert($this->entityType !== null);
|
||||
|
||||
return $this->applierFactory->createOrder($this->entityType, $this->user);
|
||||
}
|
||||
|
||||
private function createLimitApplier(): LimitApplier
|
||||
{
|
||||
assert($this->entityType !== null);
|
||||
|
||||
return $this->applierFactory->createLimit($this->entityType, $this->user);
|
||||
}
|
||||
|
||||
private function createAccessControlFilterApplier(): AccessControlFilterApplier
|
||||
{
|
||||
assert($this->entityType !== null);
|
||||
|
||||
return $this->applierFactory->createAccessControlFilter($this->entityType, $this->user);
|
||||
}
|
||||
|
||||
private function createTextFilterApplier(): TextFilterApplier
|
||||
{
|
||||
assert($this->entityType !== null);
|
||||
|
||||
return $this->applierFactory->createTextFilter($this->entityType, $this->user);
|
||||
}
|
||||
|
||||
private function createPrimaryFilterApplier(): PrimaryFilterApplier
|
||||
{
|
||||
assert($this->entityType !== null);
|
||||
|
||||
return $this->applierFactory->createPrimaryFilter($this->entityType, $this->user);
|
||||
}
|
||||
|
||||
private function createBoolFilterListApplier(): BoolFilterListApplier
|
||||
{
|
||||
assert($this->entityType !== null);
|
||||
|
||||
return $this->applierFactory->createBoolFilterList($this->entityType, $this->user);
|
||||
}
|
||||
|
||||
private function createAdditionalApplier(): AdditionalApplier
|
||||
{
|
||||
assert($this->entityType !== null);
|
||||
|
||||
return $this->applierFactory->createAdditional($this->entityType, $this->user);
|
||||
}
|
||||
}
|
||||
55
application/Espo/Core/Select/SelectBuilderFactory.php
Normal file
55
application/Espo/Core/Select/SelectBuilderFactory.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Entities\User;
|
||||
|
||||
/**
|
||||
* Creates instances of Select Builder.
|
||||
*/
|
||||
class SelectBuilderFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private User $user,
|
||||
) {}
|
||||
|
||||
public function create(): SelectBuilder
|
||||
{
|
||||
return $this->injectableFactory->createWithBinding(
|
||||
SelectBuilder::class,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(User::class, $this->user)
|
||||
->build()
|
||||
);
|
||||
}
|
||||
}
|
||||
2998
application/Espo/Core/Select/SelectManager.php
Normal file
2998
application/Espo/Core/Select/SelectManager.php
Normal file
File diff suppressed because it is too large
Load Diff
103
application/Espo/Core/Select/SelectManagerFactory.php
Normal file
103
application/Espo/Core/Select/SelectManagerFactory.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select;
|
||||
|
||||
use Espo\Core\Utils\Acl\UserAclManagerProvider;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\ClassFinder;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
/**
|
||||
* @deprecated Use SelectBuilder instead.
|
||||
*
|
||||
* Creates select managers for specific entity types. You can specify a user whose ACL will be applied to queries.
|
||||
* If user is not specified, then the current one will be used.
|
||||
*/
|
||||
class SelectManagerFactory
|
||||
{
|
||||
/**
|
||||
* @var class-string
|
||||
*/
|
||||
protected string $defaultClassName = SelectManager::class;
|
||||
|
||||
private $user;
|
||||
|
||||
private $acl;
|
||||
|
||||
private $aclManagerProvider;
|
||||
|
||||
private $injectableFactory;
|
||||
|
||||
private $classFinder;
|
||||
|
||||
public function __construct(
|
||||
User $user,
|
||||
Acl $acl,
|
||||
UserAclManagerProvider $aclManagerProvider,
|
||||
InjectableFactory $injectableFactory,
|
||||
ClassFinder $classFinder
|
||||
) {
|
||||
$this->user = $user;
|
||||
$this->acl = $acl;
|
||||
$this->aclManagerProvider = $aclManagerProvider;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->classFinder = $classFinder;
|
||||
}
|
||||
|
||||
public function create(string $entityType, ?User $user = null): SelectManager
|
||||
{
|
||||
$className = $this->classFinder->find('SelectManagers', $entityType);
|
||||
|
||||
if (!$className || !class_exists($className)) {
|
||||
$className = $this->defaultClassName;
|
||||
}
|
||||
|
||||
/** @var class-string<SelectManager> $className */
|
||||
|
||||
if ($user) {
|
||||
$acl = $this->aclManagerProvider->get($user)->createUserAcl($user);
|
||||
} else {
|
||||
$acl = $this->acl;
|
||||
$user = $this->user;
|
||||
}
|
||||
|
||||
$selectManager = $this->injectableFactory->createWith($className, [
|
||||
'user' => $user,
|
||||
'acl' => $acl,
|
||||
]);
|
||||
|
||||
$selectManager->setEntityType($entityType);
|
||||
|
||||
return $selectManager;
|
||||
}
|
||||
}
|
||||
212
application/Espo/Core/Select/Text/Applier.php
Normal file
212
application/Espo/Core/Select/Text/Applier.php
Normal file
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text;
|
||||
|
||||
use Espo\Core\Select\Text\FullTextSearch\Data as FullTextSearchData;
|
||||
use Espo\Core\Select\Text\FullTextSearch\DataComposerFactory as FullTextSearchDataComposerFactory;
|
||||
use Espo\Core\Select\Text\FullTextSearch\DataComposer\Params as FullTextSearchDataComposerParams;
|
||||
use Espo\Core\Select\Text\Filter\Data as FilterData;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
use Espo\ORM\Query\Part\Order as OrderExpr;
|
||||
use Espo\ORM\Query\Part\Expression as Expr;
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
use Espo\Entities\User;
|
||||
|
||||
class Applier
|
||||
{
|
||||
/** @todo Move to metadata. */
|
||||
private ?int $fullTextRelevanceThreshold = null; /** @phpstan-ignore-line */
|
||||
/** @todo Move to metadata. */
|
||||
private int $fullTextOrderRelevanceDivider = 5; /** @phpstan-ignore-line */
|
||||
|
||||
private const DEFAULT_FT_ORDER = self::FT_ORDER_COMBINED;
|
||||
private const DEFAULT_ATTRIBUTE_LIST = ['name'];
|
||||
|
||||
private const FT_ORDER_COMBINED = 0;
|
||||
private const FT_ORDER_RELEVANCE = 1;
|
||||
private const FT_ORDER_ORIGINAL = 3;
|
||||
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private MetadataProvider $metadataProvider,
|
||||
private FullTextSearchDataComposerFactory $fullTextSearchDataComposerFactory,
|
||||
private FilterFactory $filterFactory,
|
||||
private ConfigProvider $config
|
||||
) {}
|
||||
|
||||
/** @noinspection PhpUnusedParameterInspection */
|
||||
public function apply(QueryBuilder $queryBuilder, string $filter, FilterParams $params): void
|
||||
{
|
||||
$forceFullText = false;
|
||||
$skipFullText = false;
|
||||
|
||||
if (mb_strpos($filter, 'ft:') === 0) {
|
||||
$filter = mb_substr($filter, 3);
|
||||
$forceFullText = true;
|
||||
}
|
||||
|
||||
$fullTextData = $this->composeFullTextSearchData($filter);
|
||||
|
||||
if ($fullTextData && !$forceFullText && $this->toSkipFullText($filter)) {
|
||||
$skipFullText = true;
|
||||
}
|
||||
|
||||
$fullTextWhere = $fullTextData && !$skipFullText ?
|
||||
$this->processFullTextSearch($queryBuilder, $fullTextData) : null;
|
||||
|
||||
$fieldList = $this->getFieldList($forceFullText, $fullTextData);
|
||||
$filterData = $this->prepareFilterData($filter, $fieldList, $forceFullText, $fullTextWhere);
|
||||
|
||||
$this->applyFilter($queryBuilder, $filterData);
|
||||
}
|
||||
|
||||
private function composeFullTextSearchData(string $filter): ?FullTextSearchData
|
||||
{
|
||||
$composer = $this->fullTextSearchDataComposerFactory->create($this->entityType);
|
||||
|
||||
$params = FullTextSearchDataComposerParams::create();
|
||||
|
||||
return $composer->compose($filter, $params);
|
||||
}
|
||||
|
||||
private function processFullTextSearch(QueryBuilder $queryBuilder, FullTextSearchData $data): WhereItem
|
||||
{
|
||||
$expression = $data->getExpression();
|
||||
|
||||
$orderType = self::DEFAULT_FT_ORDER;
|
||||
|
||||
$orderTypeMap = [
|
||||
'combined' => self::FT_ORDER_COMBINED,
|
||||
'relevance' => self::FT_ORDER_RELEVANCE,
|
||||
'original' => self::FT_ORDER_ORIGINAL,
|
||||
];
|
||||
|
||||
$mOrderType = $this->metadataProvider->getFullTextSearchOrderType($this->entityType);
|
||||
|
||||
if ($mOrderType) {
|
||||
$orderType = $orderTypeMap[$mOrderType];
|
||||
}
|
||||
|
||||
$previousOrderBy = $queryBuilder->build()->getOrder();
|
||||
|
||||
$hasOrderBy = !empty($previousOrderBy);
|
||||
|
||||
if (!$hasOrderBy || $orderType === self::FT_ORDER_RELEVANCE) {
|
||||
$queryBuilder->order([
|
||||
OrderExpr::create($expression)->withDesc()
|
||||
]);
|
||||
} else if ($orderType === self::FT_ORDER_COMBINED) {
|
||||
$orderExpression =
|
||||
Expr::round(
|
||||
Expr::divide($expression, $this->fullTextOrderRelevanceDivider)
|
||||
);
|
||||
|
||||
$newOrderBy = array_merge(
|
||||
[OrderExpr::create($orderExpression)->withDesc()],
|
||||
$previousOrderBy
|
||||
);
|
||||
|
||||
$queryBuilder->order($newOrderBy);
|
||||
}
|
||||
|
||||
if ($this->fullTextRelevanceThreshold) {
|
||||
return Expr::greaterOrEqual(
|
||||
$expression,
|
||||
$this->fullTextRelevanceThreshold
|
||||
);
|
||||
}
|
||||
|
||||
return Expr::notEqual($expression, 0);
|
||||
}
|
||||
|
||||
private function toSkipFullText(string $filter): bool
|
||||
{
|
||||
$min = $this->config->getFullTextSearchMinLength();
|
||||
|
||||
if ($min === null || strlen($filter) >= $min) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return
|
||||
!str_contains($filter, '*') &&
|
||||
!str_contains($filter, '"') &&
|
||||
!str_contains($filter, '+') &&
|
||||
!str_contains($filter, '-');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getFieldList(bool $forceFullTextSearch, ?FullTextSearchData $fullTextData): array
|
||||
{
|
||||
if ($forceFullTextSearch) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fullTextFieldList = $fullTextData ? $fullTextData->getFieldList() : [];
|
||||
|
||||
return array_filter(
|
||||
$this->metadataProvider->getTextFilterAttributeList($this->entityType) ?? self::DEFAULT_ATTRIBUTE_LIST,
|
||||
fn ($field) => !in_array($field, $fullTextFieldList)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $fieldList
|
||||
* @param ?WhereItem $fullTextWhere
|
||||
*/
|
||||
private function prepareFilterData(
|
||||
string $filter,
|
||||
array $fieldList,
|
||||
bool $forceFullTextSearch,
|
||||
?WhereItem $fullTextWhere
|
||||
): FilterData {
|
||||
|
||||
$skipWildcards = false;
|
||||
|
||||
if (mb_strpos($filter, '*') !== false) {
|
||||
$skipWildcards = true;
|
||||
$filter = str_replace('*', '%', $filter);
|
||||
}
|
||||
|
||||
return FilterData::create($filter, $fieldList)
|
||||
->withSkipWildcards($skipWildcards)
|
||||
->withForceFullTextSearch($forceFullTextSearch)
|
||||
->withFullTextSearchWhereItem($fullTextWhere);
|
||||
}
|
||||
|
||||
private function applyFilter(QueryBuilder $queryBuilder, FilterData $filterData): void
|
||||
{
|
||||
$filterObj = $this->filterFactory->create($this->entityType, $this->user);
|
||||
|
||||
$filterObj->apply($queryBuilder, $filterData);
|
||||
}
|
||||
}
|
||||
67
application/Espo/Core/Select/Text/ConfigProvider.php
Normal file
67
application/Espo/Core/Select/Text/ConfigProvider.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
|
||||
class ConfigProvider
|
||||
{
|
||||
private const MIN_LENGTH_FOR_CONTENT_SEARCH = 4;
|
||||
|
||||
public function __construct(private Config $config)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Full-text search min indexed word length.
|
||||
*
|
||||
* @internal Do not use.
|
||||
* @since 8.3.0
|
||||
*/
|
||||
public function getFullTextSearchMinLength(): ?int
|
||||
{
|
||||
return $this->config->get('fullTextSearchMinLength');
|
||||
}
|
||||
|
||||
public function getMinLengthForContentSearch(): int
|
||||
{
|
||||
return $this->config->get('textFilterContainsMinLength') ??
|
||||
self::MIN_LENGTH_FOR_CONTENT_SEARCH;
|
||||
}
|
||||
|
||||
public function useContainsForVarchar(): bool
|
||||
{
|
||||
return $this->config->get('textFilterUseContainsForVarchar') ?? false;
|
||||
}
|
||||
|
||||
public function usePhoneNumberNumericSearch(): bool
|
||||
{
|
||||
return $this->config->get('phoneNumberNumericSearch') ?? false;
|
||||
}
|
||||
}
|
||||
211
application/Espo/Core/Select/Text/DefaultFilter.php
Normal file
211
application/Espo/Core/Select/Text/DefaultFilter.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Select\Text\Filter\Data;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
use Espo\ORM\Query\Part\Where\OrGroup;
|
||||
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
|
||||
use Espo\ORM\Query\Part\Where\Comparison as Cmp;
|
||||
use Espo\ORM\Query\Part\Expression as Expr;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use RuntimeException;
|
||||
|
||||
class DefaultFilter implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private MetadataProvider $metadataProvider,
|
||||
private ConfigProvider $config
|
||||
) {}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder, Data $data): void
|
||||
{
|
||||
$orGroupBuilder = OrGroup::createBuilder();
|
||||
|
||||
foreach ($data->getAttributeList() as $attribute) {
|
||||
$this->applyAttribute($queryBuilder, $orGroupBuilder, $attribute, $data);
|
||||
}
|
||||
|
||||
if ($data->getFullTextSearchWhereItem()) {
|
||||
$orGroupBuilder->add(
|
||||
$data->getFullTextSearchWhereItem()
|
||||
);
|
||||
}
|
||||
|
||||
$orGroup = $orGroupBuilder->build();
|
||||
|
||||
if ($orGroup->getItemCount() === 0) {
|
||||
$queryBuilder->where([Attribute::ID => null]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$queryBuilder->where($orGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo AttributeFilterFactory.
|
||||
*/
|
||||
private function applyAttribute(
|
||||
QueryBuilder $queryBuilder,
|
||||
OrGroupBuilder $orGroupBuilder,
|
||||
string $attribute,
|
||||
Data $data
|
||||
): void {
|
||||
|
||||
$filter = $data->getFilter();
|
||||
$skipWildcards = $data->skipWildcards();
|
||||
|
||||
$attributeType = $this->getAttributeTypeAndApplyJoin($queryBuilder, $attribute);
|
||||
|
||||
if ($attributeType === Entity::INT) {
|
||||
if (is_numeric($filter)) {
|
||||
$orGroupBuilder->add(
|
||||
Cmp::equal(
|
||||
Expr::column($attribute),
|
||||
intval($filter)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!str_contains($attribute, '.') &&
|
||||
$this->metadataProvider->getFieldType($this->entityType, $attribute) === FieldType::EMAIL &&
|
||||
str_contains($filter, ' ')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!str_contains($attribute, '.') &&
|
||||
$this->metadataProvider->getFieldType($this->entityType, $attribute) === FieldType::PHONE
|
||||
) {
|
||||
if (!preg_match("#[0-9()\-+% ]+$#", $filter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->config->usePhoneNumberNumericSearch()) {
|
||||
$attribute = $attribute . 'Numeric';
|
||||
|
||||
$filter = preg_replace('/[^0-9%]/', '', $filter);
|
||||
}
|
||||
|
||||
if (!$filter) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$expression = $filter;
|
||||
|
||||
if (!$skipWildcards) {
|
||||
$expression = $this->checkWhetherToUseContains($attribute, $filter, $attributeType) ?
|
||||
'%' . $filter . '%' :
|
||||
$filter . '%';
|
||||
}
|
||||
|
||||
$expression = addslashes($expression);
|
||||
|
||||
$orGroupBuilder->add(
|
||||
Cmp::like(
|
||||
Expr::column($attribute),
|
||||
$expression
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function getAttributeTypeAndApplyJoin(QueryBuilder $queryBuilder, string $attribute): string
|
||||
{
|
||||
if (str_contains($attribute, '.')) {
|
||||
[$link, $foreignField] = explode('.', $attribute);
|
||||
|
||||
$foreignEntityType = $this->metadataProvider->getRelationEntityType($this->entityType, $link);
|
||||
|
||||
if (!$foreignEntityType) {
|
||||
throw new RuntimeException("Bad relation in text filter field '$attribute'.");
|
||||
}
|
||||
|
||||
if ($this->metadataProvider->getRelationType($this->entityType, $link) === Entity::HAS_MANY) {
|
||||
$queryBuilder->distinct();
|
||||
}
|
||||
|
||||
$queryBuilder->leftJoin($link);
|
||||
|
||||
return $this->metadataProvider->getAttributeType($foreignEntityType, $foreignField);
|
||||
}
|
||||
|
||||
$attributeType = $this->metadataProvider->getAttributeType($this->entityType, $attribute);
|
||||
|
||||
if ($attributeType === Entity::FOREIGN) {
|
||||
$link = $this->metadataProvider->getAttributeRelationParam($this->entityType, $attribute);
|
||||
|
||||
if ($link) {
|
||||
$queryBuilder->leftJoin($link);
|
||||
}
|
||||
}
|
||||
|
||||
return $attributeType;
|
||||
}
|
||||
|
||||
private function checkWhetherToUseContains(string $attribute, string $filter, string $attributeType): bool
|
||||
{
|
||||
if (mb_strlen($filter) < $this->config->getMinLengthForContentSearch()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($attributeType === Entity::TEXT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
in_array(
|
||||
$attribute,
|
||||
$this->metadataProvider->getUseContainsAttributeList($this->entityType)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
$attributeType === Entity::VARCHAR &&
|
||||
$this->config->useContainsForVarchar()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
39
application/Espo/Core/Select/Text/Filter.php
Normal file
39
application/Espo/Core/Select/Text/Filter.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text;
|
||||
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
|
||||
use Espo\Core\Select\Text\Filter\Data;
|
||||
|
||||
interface Filter
|
||||
{
|
||||
public function apply(SelectBuilder $queryBuilder, Data $data): void;
|
||||
}
|
||||
133
application/Espo/Core/Select/Text/Filter/Data.php
Normal file
133
application/Espo/Core/Select/Text/Filter/Data.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text\Filter;
|
||||
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class Data
|
||||
{
|
||||
private string $filter;
|
||||
/** @var string[] */
|
||||
private array $attributeList;
|
||||
private bool $skipWildcards = false;
|
||||
private ?WhereItem $fullTextSearchWhereItem = null;
|
||||
private bool $forceFullTextSearch = false;
|
||||
|
||||
/**
|
||||
* @param string[] $attributeList
|
||||
*/
|
||||
public function __construct(string $filter, array $attributeList)
|
||||
{
|
||||
$this->filter = $filter;
|
||||
$this->attributeList = $attributeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $attributeList
|
||||
*/
|
||||
public static function create(string $filter, array $attributeList): self
|
||||
{
|
||||
return new self($filter, $attributeList);
|
||||
}
|
||||
|
||||
public function withFilter(string $filter): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->filter = $filter;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $attributeList
|
||||
*/
|
||||
public function withAttributeList(array $attributeList): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->attributeList = $attributeList;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withSkipWildcards(bool $skipWildcards = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->skipWildcards = $skipWildcards;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withForceFullTextSearch(bool $forceFullTextSearch = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->forceFullTextSearch = $forceFullTextSearch;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withFullTextSearchWhereItem(?WhereItem $fullTextSearchWhereItem): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->fullTextSearchWhereItem = $fullTextSearchWhereItem;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function getFilter(): string
|
||||
{
|
||||
return $this->filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributeList(): array
|
||||
{
|
||||
return $this->attributeList;
|
||||
}
|
||||
|
||||
public function skipWildcards(): bool
|
||||
{
|
||||
return $this->skipWildcards;
|
||||
}
|
||||
|
||||
public function forceFullTextSearch(): bool
|
||||
{
|
||||
return $this->forceFullTextSearch;
|
||||
}
|
||||
|
||||
public function getFullTextSearchWhereItem(): ?WhereItem
|
||||
{
|
||||
return $this->fullTextSearchWhereItem;
|
||||
}
|
||||
}
|
||||
69
application/Espo/Core/Select/Text/FilterFactory.php
Normal file
69
application/Espo/Core/Select/Text/FilterFactory.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\Binding\ContextualBinder;
|
||||
|
||||
class FilterFactory
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private InjectableFactory $injectableFactory
|
||||
) {}
|
||||
|
||||
public function create(string $entityType, User $user): Filter
|
||||
{
|
||||
/** @var class-string<Filter> $className */
|
||||
$className = $this->metadata->get(['selectDefs', $entityType, 'textFilterClassName']) ??
|
||||
DefaultFilter::class;
|
||||
|
||||
$bindingContainer = BindingContainerBuilder::create()
|
||||
->bindInstance(User::class, $user)
|
||||
->inContext(
|
||||
$className,
|
||||
function (ContextualBinder $binder) use ($entityType) {
|
||||
$binder->bindValue('$entityType', $entityType);
|
||||
}
|
||||
)
|
||||
->inContext(
|
||||
DefaultFilter::class,
|
||||
function (ContextualBinder $binder) use ($entityType) {
|
||||
$binder->bindValue('$entityType', $entityType);
|
||||
}
|
||||
)
|
||||
->build();
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
}
|
||||
58
application/Espo/Core/Select/Text/FilterParams.php
Normal file
58
application/Espo/Core/Select/Text/FilterParams.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class FilterParams
|
||||
{
|
||||
private bool $noFullTextSearch = false;
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function withNoFullTextSearch(bool $noFullTextSearch = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->noFullTextSearch = $noFullTextSearch;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function noFullTextSearch(): bool
|
||||
{
|
||||
return $this->noFullTextSearch;
|
||||
}
|
||||
}
|
||||
92
application/Espo/Core/Select/Text/FullTextSearch/Data.php
Normal file
92
application/Espo/Core/Select/Text/FullTextSearch/Data.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text\FullTextSearch;
|
||||
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class Data
|
||||
{
|
||||
/** @var string[] */
|
||||
private array $fieldList;
|
||||
/** @var string[] */
|
||||
private array $columnList;
|
||||
/** @var Mode::* $mode */
|
||||
private string $mode;
|
||||
|
||||
/**
|
||||
* @param string[] $fieldList
|
||||
* @param string[] $columnList
|
||||
* @param Mode::* $mode
|
||||
*/
|
||||
public function __construct(private Expression $expression, array $fieldList, array $columnList, string $mode)
|
||||
{
|
||||
$this->fieldList = $fieldList;
|
||||
$this->columnList = $columnList;
|
||||
$this->mode = $mode;
|
||||
|
||||
if (!in_array($mode, [Mode::NATURAL_LANGUAGE, Mode::BOOLEAN])) {
|
||||
throw new InvalidArgumentException("Bad mode.");
|
||||
}
|
||||
}
|
||||
|
||||
public function getExpression(): Expression
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getFieldList(): array
|
||||
{
|
||||
return $this->fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getColumnList(): array
|
||||
{
|
||||
return $this->columnList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Mode::*
|
||||
*/
|
||||
public function getMode(): string
|
||||
{
|
||||
return $this->mode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text\FullTextSearch;
|
||||
|
||||
use Espo\Core\Select\Text\FullTextSearch\DataComposer\Params;
|
||||
|
||||
interface DataComposer
|
||||
{
|
||||
public function compose(string $filter, Params $params): ?Data;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text\FullTextSearch\DataComposer;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
private function __construct() {}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text\FullTextSearch;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
class DataComposerFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function create(string $entityType): DataComposer
|
||||
{
|
||||
$className = $this->getClassName($entityType);
|
||||
|
||||
return $this->injectableFactory->createWith($className, [
|
||||
'entityType' => $entityType,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<DataComposer>
|
||||
*/
|
||||
private function getClassName(string $entityType): string
|
||||
{
|
||||
return
|
||||
$this->metadata->get([
|
||||
'selectDefs', $entityType, 'fullTextSearchDataComposerClassName'
|
||||
]) ??
|
||||
DefaultDataComposer::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text\FullTextSearch;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Select\Text\MetadataProvider;
|
||||
use Espo\Core\Select\Text\FullTextSearch\DataComposer\Params;
|
||||
use Espo\ORM\Query\Part\Expression\Util as ExpressionUtil;
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
|
||||
class DefaultDataComposer implements DataComposer
|
||||
{
|
||||
/** @var array<Mode::*, string>*/
|
||||
private array $functionMap = [
|
||||
Mode::BOOLEAN => 'MATCH_BOOLEAN',
|
||||
Mode::NATURAL_LANGUAGE => 'MATCH_NATURAL_LANGUAGE',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private Config $config,
|
||||
private MetadataProvider $metadataProvider
|
||||
) {}
|
||||
|
||||
public function compose(string $filter, Params $params): ?Data
|
||||
{
|
||||
if ($this->config->get('fullTextSearchDisabled')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$columnList = $this->metadataProvider->getFullTextSearchColumnList($this->entityType) ?? [];
|
||||
|
||||
if (!count($columnList)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$fieldList = [];
|
||||
|
||||
foreach ($this->getTextFilterFieldList() as $field) {
|
||||
if (str_contains($field, '.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->metadataProvider->isFieldNotStorable($this->entityType, $field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadataProvider->isFullTextSearchSupportedForField($this->entityType, $field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList[] = $field;
|
||||
}
|
||||
|
||||
if (!count($fieldList)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$preparedFilter = $this->prepareFilter($filter, $params);
|
||||
|
||||
$mode = Mode::BOOLEAN;
|
||||
|
||||
if (
|
||||
mb_strpos($preparedFilter, ' ') === false &&
|
||||
mb_strpos($preparedFilter, '+') === false &&
|
||||
mb_strpos($preparedFilter, ' -') === false &&
|
||||
mb_strpos($preparedFilter, '-') !== 0 &&
|
||||
mb_strpos($preparedFilter, '*') === false
|
||||
) {
|
||||
$mode = Mode::NATURAL_LANGUAGE;
|
||||
}
|
||||
|
||||
if ($mode === Mode::BOOLEAN) {
|
||||
$preparedFilter = str_replace('@', '*', $preparedFilter);
|
||||
}
|
||||
|
||||
$argumentList = array_merge(
|
||||
array_map(fn ($item) => Expression::column($item), $columnList),
|
||||
[$preparedFilter]
|
||||
);
|
||||
|
||||
$function = $this->functionMap[$mode];
|
||||
|
||||
$expression = ExpressionUtil::composeFunction($function, ...$argumentList);
|
||||
|
||||
return new Data(
|
||||
$expression,
|
||||
$fieldList,
|
||||
$columnList,
|
||||
$mode
|
||||
);
|
||||
}
|
||||
|
||||
private function prepareFilter(string $filter, Params $params): string
|
||||
{
|
||||
$filter = str_replace('%', '*', $filter);
|
||||
$filter = str_replace(['(', ')'], '', $filter);
|
||||
$filter = str_replace('"*', '"', $filter);
|
||||
$filter = str_replace('*"', '"', $filter);
|
||||
|
||||
while (str_contains($filter, '**')) {
|
||||
$filter = trim(
|
||||
str_replace('**', '*', $filter)
|
||||
);
|
||||
}
|
||||
|
||||
while (mb_substr($filter, -2) === ' *') {
|
||||
$filter = trim(
|
||||
mb_substr($filter, 0, mb_strlen($filter) - 2)
|
||||
);
|
||||
}
|
||||
|
||||
$filter = str_replace(['+-', '--', '-+', '++', '+*', '-*'], '', $filter);
|
||||
|
||||
while (str_contains($filter, '+ ')) {
|
||||
$filter = str_replace('+ ', '', $filter);
|
||||
}
|
||||
|
||||
while (str_contains($filter, '- ')) {
|
||||
$filter = str_replace('- ', '', $filter);
|
||||
}
|
||||
|
||||
while (in_array(substr($filter, -1), ['-', '+'])) {
|
||||
$filter = substr($filter, 0, -1);
|
||||
}
|
||||
|
||||
if ($filter === '*') {
|
||||
$filter = '';
|
||||
}
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getTextFilterFieldList(): array
|
||||
{
|
||||
return $this->metadataProvider->getTextFilterAttributeList($this->entityType) ?? ['name'];
|
||||
}
|
||||
}
|
||||
36
application/Espo/Core/Select/Text/FullTextSearch/Mode.php
Normal file
36
application/Espo/Core/Select/Text/FullTextSearch/Mode.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text\FullTextSearch;
|
||||
|
||||
class Mode
|
||||
{
|
||||
public const NATURAL_LANGUAGE = 'NATURAL_LANGUAGE';
|
||||
public const BOOLEAN = 'BOOLEAN';
|
||||
}
|
||||
156
application/Espo/Core/Select/Text/MetadataProvider.php
Normal file
156
application/Espo/Core/Select/Text/MetadataProvider.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Text;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\Params\FieldParam;
|
||||
|
||||
class MetadataProvider
|
||||
{
|
||||
private Defs $ormDefs;
|
||||
|
||||
public function __construct(private Metadata $metadata, Defs $ormDefs)
|
||||
{
|
||||
$this->ormDefs = $ormDefs;
|
||||
}
|
||||
|
||||
public function getFullTextSearchOrderType(string $entityType): ?string
|
||||
{
|
||||
return $this->metadata->get([
|
||||
'entityDefs', $entityType, 'collection', 'fullTextSearchOrderType'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]|null
|
||||
*/
|
||||
public function getTextFilterAttributeList(string $entityType): ?array
|
||||
{
|
||||
return $this->metadata->get([
|
||||
'entityDefs', $entityType, 'collection', 'textFilterFields'
|
||||
]);
|
||||
}
|
||||
|
||||
public function isFieldNotStorable(string $entityType, string $field): bool
|
||||
{
|
||||
return (bool) $this->metadata->get([
|
||||
'entityDefs', $entityType, 'fields', $field, Defs\Params\FieldParam::NOT_STORABLE
|
||||
]);
|
||||
}
|
||||
|
||||
public function isFullTextSearchSupportedForField(string $entityType, string $field): bool
|
||||
{
|
||||
$fieldType = $this->metadata->get([
|
||||
'entityDefs', $entityType, 'fields', $field, FieldParam::TYPE
|
||||
]);
|
||||
|
||||
return (bool) $this->metadata->get([
|
||||
'fields', $fieldType, 'fullTextSearch'
|
||||
]);
|
||||
}
|
||||
|
||||
public function hasFullTextSearch(string $entityType): bool
|
||||
{
|
||||
return (bool) $this->metadata->get([
|
||||
'entityDefs', $entityType, 'collection', 'fullTextSearch'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getUseContainsAttributeList(string $entityType): array
|
||||
{
|
||||
return $this->metadata->get([
|
||||
'selectDefs', $entityType, 'textFilterUseContainsAttributeList'
|
||||
]) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]|null
|
||||
*/
|
||||
public function getFullTextSearchColumnList(string $entityType): ?array
|
||||
{
|
||||
return $this->ormDefs
|
||||
->getEntity($entityType)
|
||||
->getParam('fullTextSearchColumnList');
|
||||
}
|
||||
|
||||
public function getRelationType(string $entityType, string $link): string
|
||||
{
|
||||
return $this->ormDefs
|
||||
->getEntity($entityType)
|
||||
->getRelation($link)
|
||||
->getType();
|
||||
}
|
||||
|
||||
public function getAttributeType(string $entityType, string $attribute): string
|
||||
{
|
||||
return $this->ormDefs
|
||||
->getEntity($entityType)
|
||||
->getAttribute($attribute)
|
||||
->getType();
|
||||
}
|
||||
|
||||
public function getFieldType(string $entityType, string $field): ?string
|
||||
{
|
||||
$entityDefs = $this->ormDefs->getEntity($entityType);
|
||||
|
||||
if (!$entityDefs->hasField($field)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $entityDefs->getField($field)->getType();
|
||||
}
|
||||
|
||||
public function getRelationEntityType(string $entityType, string $link): ?string
|
||||
{
|
||||
$relationDefs = $this->ormDefs
|
||||
->getEntity($entityType)
|
||||
->getRelation($link);
|
||||
|
||||
if (!$relationDefs->hasForeignEntityType()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $relationDefs->getForeignEntityType();
|
||||
}
|
||||
|
||||
public function getAttributeRelationParam(string $entityType, string $attribute): ?string
|
||||
{
|
||||
return $this->ormDefs
|
||||
->getEntity($entityType)
|
||||
->getAttribute($attribute)
|
||||
->getParam(AttributeParam::RELATION);
|
||||
}
|
||||
}
|
||||
74
application/Espo/Core/Select/Where/Applier.php
Normal file
74
application/Espo/Core/Select/Where/Applier.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Select\Where\Item as WhereItem;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
use Espo\Entities\User;
|
||||
|
||||
class Applier
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private User $user,
|
||||
private ConverterFactory $converterFactory,
|
||||
private CheckerFactory $checkerFactory
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function apply(QueryBuilder $queryBuilder, WhereItem $whereItem, Params $params): void
|
||||
{
|
||||
$this->check($whereItem, $params);
|
||||
|
||||
$converter = $this->converterFactory->create($this->entityType, $this->user);
|
||||
|
||||
$convertedParams = new Converter\Params(useSubQueryIfMany: true);
|
||||
|
||||
$queryBuilder->where(
|
||||
$converter->convert($queryBuilder, $whereItem, $convertedParams)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function check(Item $whereItem, Params $params): void
|
||||
{
|
||||
$checker = $this->checkerFactory->create($this->entityType, $this->user);
|
||||
|
||||
$checker->check($whereItem, $params);
|
||||
}
|
||||
}
|
||||
307
application/Espo/Core/Select/Where/Checker.php
Normal file
307
application/Espo/Core/Select/Where/Checker.php
Normal file
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Select\Where\Item\Type;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\QueryComposer\Util;
|
||||
use Espo\ORM\QueryComposer\Util as QueryUtil;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\BaseEntity;
|
||||
|
||||
/**
|
||||
* Checks Where parameters. Throws an exception if anything not allowed is met.
|
||||
*
|
||||
* @todo Check read access to foreign entity for belongs-to, belongs-to-parent, has-one.
|
||||
*/
|
||||
class Checker
|
||||
{
|
||||
private ?Entity $seed = null;
|
||||
|
||||
private const TYPE_IN_CATEGORY = 'inCategory';
|
||||
private const TYPE_IS_USER_FROM_TEAMS = 'isUserFromTeams';
|
||||
|
||||
/** @var string[] */
|
||||
private $nestingTypeList = [
|
||||
Type::OR,
|
||||
Type::AND,
|
||||
Type::NOT,
|
||||
Type::SUBQUERY_IN,
|
||||
Type::SUBQUERY_NOT_IN,
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private $subQueryTypeList = [
|
||||
Type::SUBQUERY_IN,
|
||||
Type::SUBQUERY_NOT_IN,
|
||||
Type::NOT,
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private $linkTypeList = [
|
||||
self::TYPE_IN_CATEGORY,
|
||||
self::TYPE_IS_USER_FROM_TEAMS,
|
||||
Type::IS_LINKED_WITH,
|
||||
Type::IS_NOT_LINKED_WITH,
|
||||
Type::IS_LINKED_WITH_ALL,
|
||||
Type::IS_LINKED_WITH_ANY,
|
||||
Type::IS_LINKED_WITH_NONE,
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private $linkWithIdsTypeList = [
|
||||
self::TYPE_IN_CATEGORY,
|
||||
self::TYPE_IS_USER_FROM_TEAMS,
|
||||
Type::IS_LINKED_WITH,
|
||||
Type::IS_NOT_LINKED_WITH,
|
||||
Type::IS_LINKED_WITH_ALL,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private EntityManager $entityManager,
|
||||
private Acl $acl,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Check.
|
||||
*
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function check(Item $item, Params $params): void
|
||||
{
|
||||
$this->checkItem($item, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function checkItem(Item $item, Params $params): void
|
||||
{
|
||||
$type = $item->getType();
|
||||
$attribute = $item->getAttribute();
|
||||
$value = $item->getValue();
|
||||
|
||||
$forbidComplexExpressions = $params->forbidComplexExpressions();
|
||||
$checkWherePermission = $params->applyPermissionCheck();
|
||||
|
||||
if ($forbidComplexExpressions) {
|
||||
if (in_array($type, $this->subQueryTypeList)) {
|
||||
throw new Forbidden("Sub-queries are forbidden in where.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($attribute && $forbidComplexExpressions) {
|
||||
if (QueryUtil::isComplexExpression($attribute)) {
|
||||
throw new Forbidden("Complex expressions are forbidden in where.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($attribute) {
|
||||
$argumentList = Util::getAllAttributesFromComplexExpression($attribute);
|
||||
|
||||
foreach ($argumentList as $argument) {
|
||||
$this->checkAttributeExistence($argument, $type);
|
||||
|
||||
if ($checkWherePermission) {
|
||||
$this->checkAttributePermission($argument, $type, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($type, $this->nestingTypeList) && is_array($value)) {
|
||||
foreach ($value as $subItem) {
|
||||
$this->checkItem(Item::fromRaw($subItem), $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function checkAttributeExistence(string $attribute, string $type): void
|
||||
{
|
||||
if (str_contains($attribute, '.')) {
|
||||
// @todo Check existence of foreign attributes.
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($type, $this->linkTypeList)) {
|
||||
if (!$this->getSeed()->hasRelation($attribute)) {
|
||||
throw new BadRequest("Not existing relation '$attribute' in where.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->getSeed()->hasAttribute($attribute)) {
|
||||
throw new BadRequest("Not existing attribute '$attribute' in where.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function checkAttributePermission(string $attribute, string $type, mixed $value): void
|
||||
{
|
||||
$entityType = $this->entityType;
|
||||
|
||||
if (str_contains($attribute, '.')) {
|
||||
[$link, $attribute] = explode('.', $attribute);
|
||||
|
||||
if (!$this->getSeed()->hasRelation($link)) {
|
||||
// TODO allow alias
|
||||
throw new Forbidden("Bad relation '$link' in where.");
|
||||
}
|
||||
|
||||
$foreignEntityType = $this->getRelationEntityType($this->getSeed(), $link);
|
||||
|
||||
if (!$foreignEntityType) {
|
||||
throw new Forbidden("Bad relation '$link' in where.");
|
||||
}
|
||||
|
||||
if (
|
||||
!$this->acl->checkScope($foreignEntityType) ||
|
||||
in_array($link, $this->acl->getScopeForbiddenLinkList($entityType))
|
||||
) {
|
||||
throw new Forbidden("Forbidden relation '$link' in where.");
|
||||
}
|
||||
|
||||
if (in_array($attribute, $this->acl->getScopeForbiddenAttributeList($foreignEntityType))) {
|
||||
throw new Forbidden("Forbidden attribute '$link.$attribute' in where.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($type, $this->linkTypeList)) {
|
||||
$this->checkLink($type, $entityType, $attribute, $value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($attribute, $this->acl->getScopeForbiddenAttributeList($entityType))) {
|
||||
throw new Forbidden("Forbidden attribute '$attribute' in where.");
|
||||
}
|
||||
}
|
||||
|
||||
private function getSeed(): Entity
|
||||
{
|
||||
$this->seed ??= $this->entityManager->getNewEntity($this->entityType);
|
||||
|
||||
return $this->seed;
|
||||
}
|
||||
|
||||
private function getRelationEntityType(Entity $entity, string $relation): mixed
|
||||
{
|
||||
if ($entity instanceof BaseEntity) {
|
||||
return $entity->getRelationParam($relation, RelationParam::ENTITY);
|
||||
}
|
||||
|
||||
$entityDefs = $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($entity->getEntityType());
|
||||
|
||||
if (!$entityDefs->hasRelation($relation)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $entityDefs->getRelation($relation)->getParam(RelationParam::ENTITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function checkLink(string $type, string $entityType, string $link, mixed $value): void
|
||||
{
|
||||
if (!$this->getSeed()->hasRelation($link)) {
|
||||
throw new Forbidden("Bad relation '$link' in where.");
|
||||
}
|
||||
|
||||
$foreignEntityType = $this->getRelationEntityType($this->getSeed(), $link);
|
||||
|
||||
if (!$foreignEntityType) {
|
||||
throw new Forbidden("Bad relation '$link' in where.");
|
||||
}
|
||||
|
||||
if ($type === self::TYPE_IS_USER_FROM_TEAMS) {
|
||||
$foreignEntityType = Team::ENTITY_TYPE;
|
||||
}
|
||||
|
||||
if (
|
||||
in_array($link, $this->acl->getScopeForbiddenFieldList($entityType)) ||
|
||||
!$this->acl->checkScope($foreignEntityType) ||
|
||||
in_array($link, $this->acl->getScopeForbiddenLinkList($entityType))
|
||||
) {
|
||||
throw new Forbidden("Forbidden relation '$link' in where.");
|
||||
}
|
||||
|
||||
if (!in_array($type, $this->linkWithIdsTypeList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
foreach ($value as $it) {
|
||||
if (!is_string($it)) {
|
||||
throw new BadRequest("Bad where item. Non-string ID.");
|
||||
}
|
||||
}
|
||||
|
||||
// @todo Use the Select Builder instead. Check the result count equal the input IDs count.
|
||||
|
||||
foreach ($value as $id) {
|
||||
$entity = $this->entityManager->getEntityById($foreignEntityType, $id);
|
||||
|
||||
if (!$entity) {
|
||||
throw new Forbidden("Record '$foreignEntityType' `$id` not found.");
|
||||
}
|
||||
|
||||
if (!$this->acl->checkEntityRead($entity)) {
|
||||
throw new Forbidden("No access to '$foreignEntityType' `$id`.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
application/Espo/Core/Select/Where/CheckerFactory.php
Normal file
60
application/Espo/Core/Select/Where/CheckerFactory.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Acl\Exceptions\NotAvailable;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Acl\UserAclManagerProvider;
|
||||
use RuntimeException;
|
||||
|
||||
class CheckerFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private UserAclManagerProvider $userAclManagerProvider
|
||||
) {}
|
||||
|
||||
public function create(string $entityType, User $user): Checker
|
||||
{
|
||||
try {
|
||||
$acl = $this->userAclManagerProvider
|
||||
->get($user)
|
||||
->createUserAcl($user);
|
||||
} catch (NotAvailable $e) {
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
|
||||
return $this->injectableFactory->createWith(Checker::class, [
|
||||
'entityType' => $entityType,
|
||||
'acl' => $acl,
|
||||
]);
|
||||
}
|
||||
}
|
||||
147
application/Espo/Core/Select/Where/Converter.php
Normal file
147
application/Espo/Core/Select/Where/Converter.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Select\Where\Item\Type;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Condition as Cond;
|
||||
use Espo\ORM\Query\Part\Expression as Expr;
|
||||
use Espo\ORM\Query\Part\Where\Comparison;
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Converts a search where (passed from front-end) to a where clause (for ORM).
|
||||
*/
|
||||
class Converter
|
||||
{
|
||||
public function __construct(
|
||||
private ItemConverter $itemConverter,
|
||||
private Scanner $scanner
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function convert(QueryBuilder $queryBuilder, Item $item, ?Converter\Params $params = null): WhereItem
|
||||
{
|
||||
if ($params && $params->useSubQueryIfMany() && $this->hasRelatedMany($queryBuilder, $item)) {
|
||||
return $this->convertSubQuery($queryBuilder, $item);
|
||||
}
|
||||
|
||||
$whereClause = [];
|
||||
|
||||
foreach ($this->itemToList($item) as $subItemRaw) {
|
||||
try {
|
||||
$subItem = Item::fromRaw($subItemRaw);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new BadRequest($e->getMessage());
|
||||
}
|
||||
|
||||
$part = $this->processItem($queryBuilder, $subItem);
|
||||
|
||||
if ($part === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$whereClause[] = $part;
|
||||
}
|
||||
|
||||
$this->scanner->apply($queryBuilder, $item);
|
||||
|
||||
return WhereClause::fromRaw($whereClause);
|
||||
}
|
||||
|
||||
private function hasRelatedMany(QueryBuilder $queryBuilder, Item $item): bool
|
||||
{
|
||||
$entityType = $queryBuilder->build()->getFrom();
|
||||
|
||||
if (!$entityType) {
|
||||
throw new RuntimeException("No 'from' in queryBuilder.");
|
||||
}
|
||||
|
||||
return $this->scanner->hasRelatedMany($entityType, $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int|string, mixed>
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function itemToList(Item $item): array
|
||||
{
|
||||
if ($item->getType() !== Type::AND) {
|
||||
return [
|
||||
$item->getRaw(),
|
||||
];
|
||||
}
|
||||
|
||||
$list = $item->getValue();
|
||||
|
||||
if (!is_array($list)) {
|
||||
throw new BadRequest("Bad where item value.");
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int|string, mixed>
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function processItem(QueryBuilder $queryBuilder, Item $item): array
|
||||
{
|
||||
return $this->itemConverter->convert($queryBuilder, $item)->getRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function convertSubQuery(QueryBuilder $queryBuilder, Item $item): Comparison
|
||||
{
|
||||
$entityType = $queryBuilder->build()->getFrom() ?? throw new RuntimeException();
|
||||
|
||||
$subQueryBuilder = QueryBuilder::create()
|
||||
->from($entityType)
|
||||
->select(Attribute::ID);
|
||||
|
||||
$subQueryBuilder->where(
|
||||
$this->convert($subQueryBuilder, $item)
|
||||
);
|
||||
|
||||
return Cond::in(
|
||||
Expr::column(Attribute::ID),
|
||||
$subQueryBuilder->build()
|
||||
);
|
||||
}
|
||||
}
|
||||
54
application/Espo/Core/Select/Where/Converter/Params.php
Normal file
54
application/Espo/Core/Select/Where/Converter/Params.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where\Converter;
|
||||
|
||||
/**
|
||||
* Where converter parameters.
|
||||
*
|
||||
* Immutable.
|
||||
* @since 9.0.0
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
/**
|
||||
* @param bool $useSubQueryIfMany To use a sub-query if at least one has-many relation appears in a where clause.
|
||||
*/
|
||||
public function __construct(
|
||||
readonly private bool $useSubQueryIfMany = false,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* To use a sub-query if at least one has-many relation appears in a where clause.
|
||||
*/
|
||||
public function useSubQueryIfMany(): bool
|
||||
{
|
||||
return $this->useSubQueryIfMany;
|
||||
}
|
||||
}
|
||||
158
application/Espo/Core/Select/Where/ConverterFactory.php
Normal file
158
application/Espo/Core/Select/Where/ConverterFactory.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Binding\Binder;
|
||||
use Espo\Core\Binding\BindingContainer;
|
||||
use Espo\Core\Binding\BindingData;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\User;
|
||||
|
||||
class ConverterFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function create(string $entityType, User $user): Converter
|
||||
{
|
||||
$dateTimeItemTransformer = $this->createDateTimeItemTransformer($entityType, $user);
|
||||
|
||||
$itemConverter = $this->createItemConverter($entityType, $user, $dateTimeItemTransformer);
|
||||
|
||||
$className = $this->getConverterClassName($entityType);
|
||||
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
$binder
|
||||
->bindInstance(User::class, $user);
|
||||
$binder
|
||||
->for($className)
|
||||
->bindValue('$entityType', $entityType)
|
||||
->bindInstance(ItemConverter::class, $itemConverter);
|
||||
|
||||
$bindingContainer = new BindingContainer($bindingData);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
private function createDateTimeItemTransformer(string $entityType, User $user): DateTimeItemTransformer
|
||||
{
|
||||
$className = $this->getDateTimeItemTransformerClassName($entityType);
|
||||
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
$binder->bindInstance(User::class, $user);
|
||||
$binder
|
||||
->for($className)
|
||||
->bindValue('$entityType', $entityType);
|
||||
$binder
|
||||
->for(DefaultDateTimeItemTransformer::class)
|
||||
->bindValue('$entityType', $entityType);
|
||||
|
||||
$bindingContainer = new BindingContainer($bindingData);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
private function createItemConverter(
|
||||
string $entityType,
|
||||
User $user,
|
||||
DateTimeItemTransformer $dateTimeItemTransformer
|
||||
): ItemConverter {
|
||||
|
||||
$className = $this->getItemConverterClassName($entityType);
|
||||
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
$binder
|
||||
->bindInstance(User::class, $user);
|
||||
$binder
|
||||
->for($className)
|
||||
->bindValue('$entityType', $entityType)
|
||||
->bindInstance(DateTimeItemTransformer::class, $dateTimeItemTransformer);
|
||||
$binder
|
||||
->for(ItemGeneralConverter::class)
|
||||
->bindValue('$entityType', $entityType)
|
||||
->bindInstance(DateTimeItemTransformer::class, $dateTimeItemTransformer);
|
||||
|
||||
$bindingContainer = new BindingContainer($bindingData);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Converter>
|
||||
*/
|
||||
private function getConverterClassName(string $entityType): string
|
||||
{
|
||||
$className = $this->metadata->get(['selectDefs', $entityType, 'whereConverterClassName']);
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
return Converter::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<ItemGeneralConverter>
|
||||
*/
|
||||
private function getItemConverterClassName(string $entityType): string
|
||||
{
|
||||
$className = $this->metadata->get(['selectDefs', $entityType, 'whereItemConverterClassName']);
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
return ItemGeneralConverter::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<DateTimeItemTransformer>
|
||||
*/
|
||||
private function getDateTimeItemTransformerClassName(string $entityType): string
|
||||
{
|
||||
$className = $this->metadata
|
||||
->get(['selectDefs', $entityType, 'whereDateTimeItemTransformerClassName']);
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
return DefaultDateTimeItemTransformer::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
|
||||
/**
|
||||
* Transforms date-time where item. Applies timezone. *
|
||||
* Implementing a custom transformer should not be considered future-proof.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface DateTimeItemTransformer
|
||||
{
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function transform(Item $item): Item;
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Select\Where\Item\Type;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use DateInterval;
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
class DefaultDateTimeItemTransformer implements DateTimeItemTransformer
|
||||
{
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private Config\ApplicationConfig $applicationConfig,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function transform(Item $item): Item
|
||||
{
|
||||
$format = DateTimeUtil::SYSTEM_DATE_TIME_FORMAT;
|
||||
|
||||
$type = $item->getType();
|
||||
$value = $item->getValue();
|
||||
$attribute = $item->getAttribute();
|
||||
$data = $item->getData();
|
||||
|
||||
if (!$data instanceof Item\Data\DateTime) {
|
||||
throw new BadRequest("Bad where item.");
|
||||
}
|
||||
|
||||
$timeZone = $data->getTimeZone() ?? $this->applicationConfig->getTimeZone();
|
||||
|
||||
if (!$attribute) {
|
||||
throw new BadRequest("Bad datetime where item. Empty 'attribute'.");
|
||||
}
|
||||
|
||||
if (!$type) {
|
||||
throw new BadRequest("Bad datetime where item. Empty 'type'.");
|
||||
}
|
||||
|
||||
if (
|
||||
empty($value) &&
|
||||
in_array(
|
||||
$type,
|
||||
[
|
||||
Type::ON,
|
||||
Type::BEFORE,
|
||||
Type::AFTER,
|
||||
]
|
||||
)
|
||||
) {
|
||||
throw new BadRequest("Bad where item. Empty value.");
|
||||
}
|
||||
|
||||
$where = [
|
||||
'attribute' => $attribute,
|
||||
];
|
||||
|
||||
try {
|
||||
$dt = new DateTime('now', new DateTimeZone($timeZone));
|
||||
} catch (Exception) {
|
||||
throw new BadRequest("Bad timezone");
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case Type::TODAY:
|
||||
$where['type'] = Type::BETWEEN;
|
||||
|
||||
$dt->setTime(0, 0);
|
||||
|
||||
$dtTo = clone $dt;
|
||||
$dtTo->modify('+1 day -1 second');
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$from = $dt->format($format);
|
||||
$to = $dtTo->format($format);
|
||||
|
||||
$where['value'] = [$from, $to];
|
||||
|
||||
break;
|
||||
|
||||
case Type::PAST:
|
||||
$where['type'] = Type::LESS_THAN_OR_EQUALS;
|
||||
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$where['value'] = $dt->format($format);
|
||||
|
||||
break;
|
||||
|
||||
case Type::FUTURE:
|
||||
$where['type'] = Type::AFTER;
|
||||
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$where['value'] = $dt->format($format);
|
||||
|
||||
break;
|
||||
|
||||
case Type::LAST_SEVEN_DAYS:
|
||||
$where['type'] = Type::BETWEEN;
|
||||
|
||||
$dtFrom = clone $dt;
|
||||
$dtTo = clone $dt;
|
||||
|
||||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$to = $dtTo->format($format);
|
||||
|
||||
$dtFrom->modify('-7 day');
|
||||
$dtFrom->setTime(0, 0);
|
||||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$from = $dtFrom->format($format);
|
||||
|
||||
$where['value'] = [$from, $to];
|
||||
|
||||
break;
|
||||
|
||||
case Type::LAST_X_DAYS:
|
||||
$where['type'] = Type::BETWEEN;
|
||||
|
||||
$dtFrom = clone $dt;
|
||||
$dtTo = clone $dt;
|
||||
|
||||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$to = $dtTo->format($format);
|
||||
|
||||
$number = strval(intval($value));
|
||||
|
||||
$dtFrom->modify('-'.$number.' day');
|
||||
$dtFrom->setTime(0, 0);
|
||||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$from = $dtFrom->format($format);
|
||||
|
||||
$where['value'] = [$from, $to];
|
||||
|
||||
break;
|
||||
|
||||
case Type::NEXT_X_DAYS:
|
||||
$where['type'] = Type::BETWEEN;
|
||||
|
||||
$dtTo = clone $dt;
|
||||
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$from = $dt->format($format);
|
||||
|
||||
$number = strval(intval($value));
|
||||
|
||||
$dtTo->modify('+'.$number.' day');
|
||||
$dtTo->setTime(24, 59, 59);
|
||||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$to = $dtTo->format($format);
|
||||
|
||||
$where['value'] = [$from, $to];
|
||||
|
||||
break;
|
||||
|
||||
case Type::OLDER_THAN_X_DAYS:
|
||||
$where['type'] = Type::BEFORE;
|
||||
|
||||
$number = strval(intval($value));
|
||||
|
||||
$dt->modify("-$number day");
|
||||
$dt->setTime(0, 0);
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$where['value'] = $dt->format($format);
|
||||
|
||||
break;
|
||||
|
||||
case Type::AFTER_X_DAYS:
|
||||
$where['type'] = Type::GREATER_THAN_OR_EQUALS;
|
||||
|
||||
$number = strval(intval($value));
|
||||
|
||||
$dt->modify("+$number day");
|
||||
$dt->setTime(0, 0);
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$where['value'] = $dt->format($format);
|
||||
|
||||
break;
|
||||
|
||||
case Type::ON:
|
||||
$where['type'] = Type::BETWEEN;
|
||||
|
||||
try {
|
||||
$dt = new DateTime($value, new DateTimeZone($timeZone));
|
||||
} catch (Exception) {
|
||||
throw new BadRequest("Bad date value or timezone.");
|
||||
}
|
||||
|
||||
$dtTo = clone $dt;
|
||||
|
||||
if (strlen($value) <= 10) {
|
||||
$dtTo->modify('+1 day -1 second');
|
||||
}
|
||||
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$from = $dt->format($format);
|
||||
$to = $dtTo->format($format);
|
||||
|
||||
$where['value'] = [$from, $to];
|
||||
|
||||
break;
|
||||
|
||||
case Type::BEFORE:
|
||||
$where['type'] = Type::BEFORE;
|
||||
|
||||
try {
|
||||
$dt = new DateTime($value, new DateTimeZone($timeZone));
|
||||
} catch (Exception) {
|
||||
throw new BadRequest("Bad date value or timezone.");
|
||||
}
|
||||
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$where['value'] = $dt->format($format);
|
||||
|
||||
break;
|
||||
|
||||
case Type::AFTER:
|
||||
$where['type'] = Type::AFTER;
|
||||
|
||||
try {
|
||||
$dt = new DateTime($value, new DateTimeZone($timeZone));
|
||||
} catch (Exception) {
|
||||
throw new BadRequest("Bad date value or timezone.");
|
||||
}
|
||||
|
||||
if (strlen($value) <= 10) {
|
||||
$dt->modify('+1 day -1 second');
|
||||
}
|
||||
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$where['value'] = $dt->format($format);
|
||||
|
||||
break;
|
||||
|
||||
case Type::BETWEEN:
|
||||
$where['type'] = Type::BETWEEN;
|
||||
|
||||
if (!is_array($value) || count($value) < 2) {
|
||||
throw new BadRequest("Bad where item. Bad value.");
|
||||
}
|
||||
|
||||
try {
|
||||
$dt = new DateTime($value[0], new DateTimeZone($timeZone));
|
||||
} catch (Exception) {
|
||||
throw new BadRequest("Bad date value or timezone.");
|
||||
}
|
||||
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$from = $dt->format($format);
|
||||
|
||||
try {
|
||||
$dt = new DateTime($value[1], new DateTimeZone($timeZone));
|
||||
} catch (Exception) {
|
||||
throw new BadRequest("Bad date value or timezone.");
|
||||
}
|
||||
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
if (strlen($value[1]) <= 10) {
|
||||
$dt->modify('+1 day -1 second');
|
||||
}
|
||||
|
||||
$to = $dt->format($format);
|
||||
|
||||
$where['value'] = [$from, $to];
|
||||
|
||||
break;
|
||||
|
||||
case Type::CURRENT_MONTH:
|
||||
case Type::LAST_MONTH:
|
||||
case Type::NEXT_MONTH:
|
||||
$where['type'] = Type::BETWEEN;
|
||||
|
||||
$dtFrom = $dt->modify('first day of this month')->setTime(0, 0);
|
||||
|
||||
if ($type == Type::LAST_MONTH) {
|
||||
$dtFrom->modify('-1 month');
|
||||
} else if ($type == Type::NEXT_MONTH) {
|
||||
$dtFrom->modify('+1 month');
|
||||
}
|
||||
|
||||
$dtTo = clone $dtFrom;
|
||||
$dtTo->modify('+1 month')->modify('-1 second');
|
||||
|
||||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
|
||||
$where['value'] = [$dtFrom->format($format), $dtTo->format($format)];
|
||||
|
||||
break;
|
||||
|
||||
case Type::CURRENT_QUARTER:
|
||||
case Type::LAST_QUARTER:
|
||||
$where['type'] = Type::BETWEEN;
|
||||
|
||||
try {
|
||||
$dt = new DateTime('now', new DateTimeZone($timeZone));
|
||||
} catch (Exception) {
|
||||
throw new BadRequest("Bad timezone.");
|
||||
}
|
||||
|
||||
$quarter = ceil($dt->format('m') / 3);
|
||||
|
||||
$dtFrom = clone $dt;
|
||||
$dtFrom->modify('first day of January this year')->setTime(0, 0);
|
||||
|
||||
if ($type === Type::LAST_QUARTER) {
|
||||
$quarter--;
|
||||
|
||||
if ($quarter == 0) {
|
||||
$quarter = 4;
|
||||
$dtFrom->modify('-1 year');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$dtFrom->add(new DateInterval('P' . (($quarter - 1) * 3) . 'M'));
|
||||
} catch (Exception) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$dtTo = clone $dtFrom;
|
||||
$dtTo->add(new DateInterval('P3M'))->modify('-1 second');
|
||||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$where['value'] = [
|
||||
$dtFrom->format($format),
|
||||
$dtTo->format($format),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case Type::CURRENT_YEAR:
|
||||
case Type::LAST_YEAR:
|
||||
$where['type'] = Type::BETWEEN;
|
||||
|
||||
try {
|
||||
$dtFrom = new DateTime('now', new DateTimeZone($timeZone));
|
||||
} catch (Exception) {
|
||||
throw new BadRequest("Bad timezone.");
|
||||
}
|
||||
|
||||
$dtFrom->modify('first day of January this year')->setTime(0, 0);
|
||||
|
||||
if ($type == Type::LAST_YEAR) {
|
||||
$dtFrom->modify('-1 year');
|
||||
}
|
||||
|
||||
$dtTo = clone $dtFrom;
|
||||
$dtTo = $dtTo->modify('+1 year')->modify('-1 second');
|
||||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$where['value'] = [
|
||||
$dtFrom->format($format),
|
||||
$dtTo->format($format),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case Type::CURRENT_FISCAL_YEAR:
|
||||
case Type::LAST_FISCAL_YEAR:
|
||||
$where['type'] = Type::BETWEEN;
|
||||
|
||||
try {
|
||||
$dtToday = new DateTime('now', new DateTimeZone($timeZone));
|
||||
} catch (Exception) {
|
||||
throw new BadRequest("Bad timezone.");
|
||||
}
|
||||
|
||||
$dt = clone $dtToday;
|
||||
$fiscalYearShift = $this->config->get('fiscalYearShift', 0);
|
||||
|
||||
$dt
|
||||
->modify('first day of January this year')
|
||||
->modify('+' . $fiscalYearShift . ' months')
|
||||
->setTime(0, 0);
|
||||
|
||||
if (intval($dtToday->format('m')) < $fiscalYearShift + 1) {
|
||||
$dt->modify('-1 year');
|
||||
}
|
||||
|
||||
if ($type === Type::LAST_FISCAL_YEAR) {
|
||||
$dt->modify('-1 year');
|
||||
}
|
||||
|
||||
$dtFrom = clone $dt;
|
||||
$dtTo = clone $dt;
|
||||
$dtTo = $dtTo->modify('+1 year')->modify('-1 second');
|
||||
|
||||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$where['value'] = [
|
||||
$dtFrom->format($format),
|
||||
$dtTo->format($format),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case Type::CURRENT_FISCAL_QUARTER:
|
||||
case Type::LAST_FISCAL_QUARTER:
|
||||
$where['type'] = Type::BETWEEN;
|
||||
|
||||
try {
|
||||
$dtToday = new DateTime('now', new DateTimeZone($timeZone));
|
||||
} catch (Exception) {
|
||||
throw new BadRequest("Bad timezone.");
|
||||
}
|
||||
|
||||
$dt = clone $dtToday;
|
||||
|
||||
$fiscalYearShift = $this->config->get('fiscalYearShift', 0);
|
||||
|
||||
$dt
|
||||
->modify('first day of January this year')
|
||||
->modify('+' . $fiscalYearShift . ' months')
|
||||
->setTime(0, 0);
|
||||
|
||||
$month = intval($dtToday->format('m'));
|
||||
|
||||
$quarterShift = floor(($month - $fiscalYearShift - 1) / 3);
|
||||
|
||||
if ($quarterShift) {
|
||||
if ($quarterShift >= 0) {
|
||||
try {
|
||||
$dt->add(new DateInterval('P' . ($quarterShift * 3) . 'M'));
|
||||
} catch (Exception) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
} else {
|
||||
$quarterShift *= -1;
|
||||
|
||||
try {
|
||||
$dt->sub(new DateInterval('P' . ($quarterShift * 3) . 'M'));
|
||||
} catch (Exception) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($type === Type::LAST_FISCAL_QUARTER) {
|
||||
$dt->modify('-3 months');
|
||||
}
|
||||
|
||||
$dtFrom = clone $dt;
|
||||
$dtTo = clone $dt;
|
||||
$dtTo = $dtTo->modify('+3 months')->modify('-1 second');
|
||||
|
||||
$dtFrom->setTimezone(new DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new DateTimeZone('UTC'));
|
||||
|
||||
$where['value'] = [
|
||||
$dtFrom->format($format),
|
||||
$dtTo->format($format),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$where['type'] = $type;
|
||||
$where['value'] = $value;
|
||||
}
|
||||
|
||||
return Item::fromRaw($where);
|
||||
}
|
||||
}
|
||||
253
application/Espo/Core/Select/Where/Item.php
Normal file
253
application/Espo/Core/Select/Where/Item.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Select\Where\Item\Data;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A where item.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class Item
|
||||
{
|
||||
public const TYPE_AND = Item\Type::AND;
|
||||
public const TYPE_OR = Item\Type::OR;
|
||||
|
||||
private ?string $attribute = null;
|
||||
private mixed $value = null;
|
||||
private ?Data $data = null;
|
||||
|
||||
/** @var string[] */
|
||||
private $noAttributeTypeList = [
|
||||
Item\Type::AND,
|
||||
Item\Type::OR,
|
||||
Item\Type::NOT,
|
||||
Item\Type::SUBQUERY_IN,
|
||||
Item\Type::SUBQUERY_NOT_IN,
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private $withNestedItemsTypeList = [
|
||||
Item\Type::AND,
|
||||
Item\Type::OR,
|
||||
];
|
||||
|
||||
private function __construct(private string $type)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
* @internal
|
||||
*/
|
||||
public static function fromRaw(array $params): self
|
||||
{
|
||||
$type = $params['type'] ?? null;
|
||||
|
||||
if (!$type) {
|
||||
throw new InvalidArgumentException("No 'type' in where item.");
|
||||
}
|
||||
|
||||
$obj = new self($type);
|
||||
|
||||
$obj->attribute = $params['attribute'] ?? $params['field'] ?? null;
|
||||
$obj->value = $params['value'] ?? null;
|
||||
|
||||
if ($params['dateTime'] ?? false) {
|
||||
$obj->data = Data\DateTime
|
||||
::create()
|
||||
->withTimeZone($params['timeZone'] ?? null);
|
||||
} else if ($params['date'] ?? null) {
|
||||
$obj->data = Data\Date
|
||||
::create()
|
||||
->withTimeZone($params['timeZone'] ?? null);
|
||||
}
|
||||
|
||||
unset($params['field']);
|
||||
unset($params['dateTime']);
|
||||
unset($params['date']);
|
||||
unset($params['timeZone']);
|
||||
|
||||
foreach (array_keys($params) as $key) {
|
||||
if (!property_exists($obj, $key)) {
|
||||
throw new InvalidArgumentException("Unknown parameter '$key'.");
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!$obj->attribute &&
|
||||
!in_array($obj->type, $obj->noAttributeTypeList)
|
||||
) {
|
||||
throw new InvalidArgumentException("No 'attribute' in where item.");
|
||||
}
|
||||
|
||||
if (in_array($obj->type, $obj->withNestedItemsTypeList)) {
|
||||
$obj->value = $obj->value ?? [];
|
||||
|
||||
if (
|
||||
!is_array($obj->value) ||
|
||||
count($obj->value) && array_keys($obj->value) !== range(0, count($obj->value) - 1)
|
||||
) {
|
||||
throw new InvalidArgumentException("Bad 'value'.");
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array<int|string, mixed>> $paramList
|
||||
* {@internal}
|
||||
*/
|
||||
public static function fromRawAndGroup(array $paramList): self
|
||||
{
|
||||
return self::fromRaw([
|
||||
'type' => Item\Type::AND,
|
||||
'value' => $paramList,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* type: string,
|
||||
* value: mixed,
|
||||
* attribute?: string,
|
||||
* dateTime?: bool,
|
||||
* timeZone?: string,
|
||||
* }
|
||||
* {@internal}
|
||||
*/
|
||||
public function getRaw(): array
|
||||
{
|
||||
$type = $this->type;
|
||||
|
||||
$raw = [
|
||||
'type' => $type,
|
||||
'value' => $this->value,
|
||||
];
|
||||
|
||||
if ($this->attribute) {
|
||||
$raw['attribute'] = $this->attribute;
|
||||
}
|
||||
|
||||
if ($this->data instanceof Data\DateTime || $this->data instanceof Data\Date) {
|
||||
if ($this->data instanceof Data\DateTime) {
|
||||
$raw['dateTime'] = true;
|
||||
}
|
||||
|
||||
$timeZone = $this->data->getTimeZone();
|
||||
|
||||
if ($timeZone) {
|
||||
$raw['timeZone'] = $timeZone;
|
||||
}
|
||||
}
|
||||
|
||||
return $raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a type;
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attribute.
|
||||
*/
|
||||
public function getAttribute(): ?string
|
||||
{
|
||||
return $this->attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nested where items (for 'and', 'or' types).
|
||||
*
|
||||
* @return Item[]
|
||||
* @throws RuntimeException If a type does not support nested items.
|
||||
*/
|
||||
public function getItemList(): array
|
||||
{
|
||||
if (!in_array($this->type, $this->withNestedItemsTypeList)) {
|
||||
throw new RuntimeException("Nested items not supported for '$this->type' type.");
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($this->value as $raw) {
|
||||
$list[] = Item::fromRaw($raw);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a data-object.
|
||||
*/
|
||||
public function getData(): ?Data
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a builder.
|
||||
*/
|
||||
public static function createBuilder(): ItemBuilder
|
||||
{
|
||||
return new ItemBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with data.
|
||||
*
|
||||
* {@internal}
|
||||
*/
|
||||
public function withData(?Data $data): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->data = $data;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
32
application/Espo/Core/Select/Where/Item/Data.php
Normal file
32
application/Espo/Core/Select/Where/Item/Data.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where\Item;
|
||||
|
||||
interface Data {}
|
||||
55
application/Espo/Core/Select/Where/Item/Data/Date.php
Normal file
55
application/Espo/Core/Select/Where/Item/Data/Date.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where\Item\Data;
|
||||
|
||||
use Espo\Core\Select\Where\Item\Data;
|
||||
|
||||
class Date implements Data
|
||||
{
|
||||
private ?string $timeZone = null;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function withTimeZone(?string $timeZone): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->timeZone = $timeZone;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function getTimeZone(): ?string
|
||||
{
|
||||
return $this->timeZone;
|
||||
}
|
||||
}
|
||||
55
application/Espo/Core/Select/Where/Item/Data/DateTime.php
Normal file
55
application/Espo/Core/Select/Where/Item/Data/DateTime.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where\Item\Data;
|
||||
|
||||
use Espo\Core\Select\Where\Item\Data;
|
||||
|
||||
class DateTime implements Data
|
||||
{
|
||||
private ?string $timeZone = null;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function withTimeZone(?string $timeZone): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->timeZone = $timeZone;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function getTimeZone(): ?string
|
||||
{
|
||||
return $this->timeZone;
|
||||
}
|
||||
}
|
||||
95
application/Espo/Core/Select/Where/Item/Type.php
Normal file
95
application/Espo/Core/Select/Where/Item/Type.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where\Item;
|
||||
|
||||
class Type
|
||||
{
|
||||
public const AND = 'and';
|
||||
public const OR = 'or';
|
||||
public const NOT = 'not';
|
||||
public const SUBQUERY_NOT_IN = 'subQueryNotIn';
|
||||
public const SUBQUERY_IN = 'subQueryIn';
|
||||
public const EXPRESSION = 'expression';
|
||||
public const IN = 'in';
|
||||
public const NOT_IN = 'notIn';
|
||||
public const EQUALS = 'equals';
|
||||
public const NOT_EQUALS = 'notEquals';
|
||||
public const ON = 'on';
|
||||
public const NOT_ON = 'notOn';
|
||||
public const LIKE = 'like';
|
||||
public const NOT_LIKE = 'notLike';
|
||||
public const STARTS_WITH = 'startsWith';
|
||||
public const ENDS_WITH = 'endsWith';
|
||||
public const CONTAINS = 'contains';
|
||||
public const NOT_CONTAINS = 'notContains';
|
||||
public const GREATER_THAN = 'greaterThan';
|
||||
public const LESS_THAN = 'lessThan';
|
||||
public const GREATER_THAN_OR_EQUALS = 'greaterThanOrEquals';
|
||||
public const LESS_THAN_OR_EQUALS = 'lessThanOrEquals';
|
||||
public const AFTER = 'after';
|
||||
public const BEFORE = 'before';
|
||||
public const BETWEEN = 'between';
|
||||
public const EVER = 'ever';
|
||||
public const ANY = 'any';
|
||||
public const NONE = 'none';
|
||||
public const IS_NULL = 'isNull';
|
||||
public const IS_NOT_NULL = 'isNotNull';
|
||||
public const IS_TRUE = 'isTrue';
|
||||
public const IS_FALSE = 'isFalse';
|
||||
public const TODAY = 'today';
|
||||
public const PAST = 'past';
|
||||
public const FUTURE = 'future';
|
||||
public const LAST_SEVEN_DAYS = 'lastSevenDays';
|
||||
public const LAST_X_DAYS = 'lastXDays';
|
||||
public const NEXT_X_DAYS = 'nextXDays';
|
||||
public const OLDER_THAN_X_DAYS = 'olderThanXDays';
|
||||
public const AFTER_X_DAYS = 'afterXDays';
|
||||
public const CURRENT_MONTH = 'currentMonth';
|
||||
public const NEXT_MONTH = 'nextMonth';
|
||||
public const LAST_MONTH = 'lastMonth';
|
||||
public const CURRENT_QUARTER = 'currentQuarter';
|
||||
public const LAST_QUARTER = 'lastQuarter';
|
||||
public const CURRENT_YEAR = 'currentYear';
|
||||
public const LAST_YEAR = 'lastYear';
|
||||
public const CURRENT_FISCAL_YEAR = 'currentFiscalYear';
|
||||
public const LAST_FISCAL_YEAR = 'lastFiscalYear';
|
||||
public const CURRENT_FISCAL_QUARTER = 'currentFiscalQuarter';
|
||||
public const LAST_FISCAL_QUARTER = 'lastFiscalQuarter';
|
||||
public const ARRAY_ANY_OF = 'arrayAnyOf';
|
||||
public const ARRAY_NONE_OF = 'arrayNoneOf';
|
||||
public const ARRAY_ALL_OF = 'arrayAllOf';
|
||||
public const ARRAY_IS_EMPTY = 'arrayIsEmpty';
|
||||
public const ARRAY_IS_NOT_EMPTY = 'arrayIsNotEmpty';
|
||||
public const IS_LINKED_WITH = 'linkedWith';
|
||||
public const IS_NOT_LINKED_WITH = 'notLinkedWith';
|
||||
public const IS_LINKED_WITH_ALL = 'linkedWithAll';
|
||||
public const IS_LINKED_WITH_ANY = 'isLinked';
|
||||
public const IS_LINKED_WITH_NONE = 'isNotLinked';
|
||||
}
|
||||
124
application/Espo/Core/Select/Where/ItemBuilder.php
Normal file
124
application/Espo/Core/Select/Where/ItemBuilder.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Select\Where\Item\Data;
|
||||
|
||||
/**
|
||||
* A where-item builder.
|
||||
*/
|
||||
class ItemBuilder
|
||||
{
|
||||
private ?string $type = null;
|
||||
private ?string $attribute = null;
|
||||
/** @var mixed */
|
||||
private $value = null;
|
||||
private ?Data $data = null;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a type.
|
||||
*
|
||||
* @param (Item\Type::*)|string $type
|
||||
* @return $this
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function setType(string $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setValue($value): self
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an attribute.
|
||||
*/
|
||||
public function setAttribute(?string $attribute): self
|
||||
{
|
||||
$this->attribute = $attribute;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data.
|
||||
*/
|
||||
public function setData(?Data $data): self
|
||||
{
|
||||
$this->data = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set nested where item list.
|
||||
*
|
||||
* @param Item[] $itemList
|
||||
* @return self
|
||||
*/
|
||||
public function setItemList(array $itemList): self
|
||||
{
|
||||
$this->value = array_map(
|
||||
function (Item $item): array {
|
||||
return $item->getRaw();
|
||||
},
|
||||
$itemList
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(): Item
|
||||
{
|
||||
return Item
|
||||
::fromRaw([
|
||||
'type' => $this->type,
|
||||
'attribute' => $this->attribute,
|
||||
'value' => $this->value,
|
||||
])
|
||||
->withData($this->data);
|
||||
}
|
||||
}
|
||||
45
application/Espo/Core/Select/Where/ItemConverter.php
Normal file
45
application/Espo/Core/Select/Where/ItemConverter.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* Converts a where item to a where clause item (for ORM).
|
||||
*/
|
||||
interface ItemConverter
|
||||
{
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem;
|
||||
}
|
||||
118
application/Espo/Core/Select/Where/ItemConverterFactory.php
Normal file
118
application/Espo/Core/Select/Where/ItemConverterFactory.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Binding\Binder;
|
||||
use Espo\Core\Binding\BindingContainer;
|
||||
use Espo\Core\Binding\BindingData;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use RuntimeException;
|
||||
|
||||
class ItemConverterFactory
|
||||
{
|
||||
public function __construct(private InjectableFactory $injectableFactory, private Metadata $metadata)
|
||||
{}
|
||||
|
||||
public function hasForType(string $type): bool
|
||||
{
|
||||
return (bool) $this->getClassNameForType($type);
|
||||
}
|
||||
|
||||
public function createForType(string $type, string $entityType, User $user): ItemConverter
|
||||
{
|
||||
$className = $this->getClassNameForType($type);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("Where item converter class name is not defined.");
|
||||
}
|
||||
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder
|
||||
->bindInstance(User::class, $user);
|
||||
|
||||
$binder
|
||||
->for($className)
|
||||
->bindValue('$entityType', $entityType);
|
||||
|
||||
$bindingContainer = new BindingContainer($bindingData);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<ItemConverter>
|
||||
*/
|
||||
protected function getClassNameForType(string $type): ?string
|
||||
{
|
||||
return $this->metadata->get(['app', 'select', 'whereItemConverterClassNameMap', $type]);
|
||||
}
|
||||
|
||||
public function has(string $entityType, string $attribute, string $type): bool
|
||||
{
|
||||
return (bool) $this->getClassName($entityType, $attribute, $type);
|
||||
}
|
||||
|
||||
public function create(string $entityType, string $attribute, string $type, User $user): ItemConverter
|
||||
{
|
||||
$className = $this->getClassName($entityType, $attribute, $type);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("Where item converter class name is not defined.");
|
||||
}
|
||||
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
$binder
|
||||
->bindInstance(User::class, $user);
|
||||
$binder
|
||||
->for($className)
|
||||
->bindValue('$entityType', $entityType);
|
||||
|
||||
$bindingContainer = new BindingContainer($bindingData);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<ItemConverter>
|
||||
*/
|
||||
protected function getClassName(string $entityType, string $attribute, string $type): ?string
|
||||
{
|
||||
return $this->metadata
|
||||
->get(['selectDefs', $entityType, 'whereItemConverterClassNameMap', $attribute . '_' . $type]);
|
||||
}
|
||||
}
|
||||
116
application/Espo/Core/Select/Where/ItemConverters/InCategory.php
Normal file
116
application/Espo/Core/Select/Where/ItemConverters/InCategory.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where\ItemConverters;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Select\Where\Item;
|
||||
use Espo\Core\Select\Where\ItemConverter;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Query\Part\Condition as Cond;
|
||||
use Espo\ORM\Query\Part\Expression as Expr;
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class InCategory implements ItemConverter
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private Defs $ormDefs
|
||||
) {}
|
||||
|
||||
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
|
||||
{
|
||||
$link = $item->getAttribute();
|
||||
$value = $item->getValue();
|
||||
|
||||
if (!$link) {
|
||||
throw new BadRequest("No attribute.");
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
return WhereClause::create();
|
||||
}
|
||||
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
foreach ($value as $it) {
|
||||
if (!is_string($it) && !is_int($it)) {
|
||||
throw new BadRequest("Bad where item. Bad array item.");
|
||||
}
|
||||
}
|
||||
|
||||
$entityDefs = $this->ormDefs->getEntity($this->entityType);
|
||||
|
||||
if (!$entityDefs->hasRelation($link)) {
|
||||
throw new BadRequest("Not existing '$link' in where item.");
|
||||
}
|
||||
|
||||
$defs = $entityDefs->getRelation($link);
|
||||
|
||||
$path = lcfirst($defs->getForeignEntityType()) . 'Path';
|
||||
|
||||
if ($defs->getType() === Entity::MANY_MANY) {
|
||||
$middle = ucfirst($defs->getRelationshipName());
|
||||
|
||||
return Cond::in(
|
||||
Expr::column('id'),
|
||||
QueryBuilder::create()
|
||||
->from($middle, 'm')
|
||||
->select($defs->getMidKey())
|
||||
->join(
|
||||
ucfirst($path),
|
||||
$path,
|
||||
["$path.descendorId:" => "m.{$defs->getForeignMidKey()}"]
|
||||
)
|
||||
->where(["$path.ascendorId" => $value])
|
||||
->build()
|
||||
);
|
||||
}
|
||||
|
||||
if ($defs->getType() === Entity::BELONGS_TO) {
|
||||
$queryBuilder->join(
|
||||
ucfirst($path),
|
||||
$path,
|
||||
["$path.descendorId:" => $defs->getKey()]
|
||||
);
|
||||
|
||||
return WhereClause::fromRaw(["$path.ascendorId" => $value]);
|
||||
}
|
||||
|
||||
throw new BadRequest("Not supported link '$link' in where item.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where\ItemConverters;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Select\Where\Item;
|
||||
use Espo\Core\Select\Where\ItemConverter;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Query\Part\Condition;
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class IsUserFromTeams implements ItemConverter
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private Defs $ormDefs,
|
||||
) {}
|
||||
|
||||
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
|
||||
{
|
||||
$link = $item->getAttribute();
|
||||
$value = $item->getValue();
|
||||
|
||||
if (!$link) {
|
||||
throw new BadRequest("No attribute.");
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
return WhereClause::create();
|
||||
}
|
||||
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
foreach ($value as $it) {
|
||||
if (!is_string($it) && !is_int($it)) {
|
||||
throw new BadRequest("Bad where item. Bad array item.");
|
||||
}
|
||||
}
|
||||
|
||||
$entityDefs = $this->ormDefs->getEntity($this->entityType);
|
||||
|
||||
if (!$entityDefs->hasRelation($link)) {
|
||||
throw new BadRequest("Not existing '$link' in where item.");
|
||||
}
|
||||
|
||||
$defs = $entityDefs->getRelation($link);
|
||||
|
||||
$relationType = $defs->getType();
|
||||
$entityType = $defs->getForeignEntityType();
|
||||
|
||||
if ($entityType !== User::ENTITY_TYPE) {
|
||||
throw new BadRequest("Not supported link '$link' in where item.");
|
||||
}
|
||||
|
||||
if ($relationType === Entity::BELONGS_TO) {
|
||||
return Condition::in(
|
||||
Expression::column($defs->getKey()),
|
||||
SelectBuilder::create()
|
||||
->from(Team::RELATIONSHIP_TEAM_USER, 'sq')
|
||||
->select('userId')
|
||||
->where(['teamId' => $value])
|
||||
->build()
|
||||
);
|
||||
}
|
||||
|
||||
throw new BadRequest("Not supported link '$link' in where item.");
|
||||
}
|
||||
}
|
||||
1713
application/Espo/Core/Select/Where/ItemGeneralConverter.php
Normal file
1713
application/Espo/Core/Select/Where/ItemGeneralConverter.php
Normal file
File diff suppressed because it is too large
Load Diff
84
application/Espo/Core/Select/Where/Params.php
Normal file
84
application/Espo/Core/Select/Where/Params.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Where parameters.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
private bool $applyPermissionCheck = false;
|
||||
private bool $forbidComplexExpressions = false;
|
||||
|
||||
private function __construct()
|
||||
{}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* applyPermissionCheck?: bool,
|
||||
* forbidComplexExpressions?: bool,
|
||||
* } $params
|
||||
*/
|
||||
public static function fromAssoc(array $params): self
|
||||
{
|
||||
$object = new self();
|
||||
|
||||
$object->applyPermissionCheck = $params['applyPermissionCheck'] ?? false;
|
||||
$object->forbidComplexExpressions = $params['forbidComplexExpressions'] ?? false;
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
if (!property_exists($object, $key)) {
|
||||
throw new InvalidArgumentException("Unknown parameter '$key'.");
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply permission check.
|
||||
*/
|
||||
public function applyPermissionCheck(): bool
|
||||
{
|
||||
return $this->applyPermissionCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forbid complex expressions.
|
||||
*/
|
||||
public function forbidComplexExpressions(): bool
|
||||
{
|
||||
return $this->forbidComplexExpressions;
|
||||
}
|
||||
}
|
||||
233
application/Espo/Core/Select/Where/Scanner.php
Normal file
233
application/Espo/Core/Select/Where/Scanner.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Select\Where;
|
||||
|
||||
use Espo\Core\Select\Where\Item\Type;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\BaseEntity;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
use Espo\ORM\QueryComposer\Util as QueryComposerUtil;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Scans where items.
|
||||
*/
|
||||
class Scanner
|
||||
{
|
||||
/** @var array<string, Entity> */
|
||||
private array $seedHash = [];
|
||||
|
||||
/** @var string[] */
|
||||
private $nestingTypeList = [
|
||||
Type::OR,
|
||||
Type::AND,
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private array $subQueryTypeList = [
|
||||
Type::SUBQUERY_IN,
|
||||
Type::SUBQUERY_NOT_IN,
|
||||
Type::NOT,
|
||||
];
|
||||
|
||||
public function __construct(private EntityManager $entityManager)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Check whether at least one has-many link appears in the where-clause.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function hasRelatedMany(string $entityType, Item $item): bool
|
||||
{
|
||||
$type = $item->getType();
|
||||
$attribute = $item->getAttribute();
|
||||
|
||||
if (in_array($type, $this->subQueryTypeList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array($type, $this->nestingTypeList)) {
|
||||
foreach ($item->getItemList() as $subItem) {
|
||||
if ($this->hasRelatedMany($entityType, $subItem)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$attribute) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$seed = $this->getSeed($entityType);
|
||||
|
||||
|
||||
foreach (QueryComposerUtil::getAllAttributesFromComplexExpression($attribute) as $expr) {
|
||||
if (!str_contains($expr, '.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$link,] = explode('.', $expr);
|
||||
|
||||
if (!$seed->hasRelation($link)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isMany = in_array($seed->getRelationType($link), [
|
||||
RelationType::HAS_MANY,
|
||||
RelationType::MANY_MANY,
|
||||
RelationType::HAS_CHILDREN,
|
||||
]);
|
||||
|
||||
if ($isMany) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply needed joins to a query builder.
|
||||
*/
|
||||
public function apply(QueryBuilder $queryBuilder, Item $item): void
|
||||
{
|
||||
$entityType = $queryBuilder->build()->getFrom();
|
||||
|
||||
if (!$entityType) {
|
||||
throw new RuntimeException("No entity type.");
|
||||
}
|
||||
|
||||
$this->applyLeftJoinsFromItem($queryBuilder, $item, $entityType);
|
||||
}
|
||||
|
||||
private function applyLeftJoinsFromItem(QueryBuilder $queryBuilder, Item $item, string $entityType): void
|
||||
{
|
||||
$type = $item->getType();
|
||||
$value = $item->getValue();
|
||||
$attribute = $item->getAttribute();
|
||||
|
||||
if (in_array($type, $this->subQueryTypeList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($type, $this->nestingTypeList)) {
|
||||
if (!is_array($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($value as $subItem) {
|
||||
$this->applyLeftJoinsFromItem($queryBuilder, Item::fromRaw($subItem), $entityType);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$attribute) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->applyLeftJoinsFromAttribute($queryBuilder, $attribute, $entityType);
|
||||
}
|
||||
|
||||
private function applyLeftJoinsFromAttribute(
|
||||
QueryBuilder $queryBuilder,
|
||||
string $attribute,
|
||||
string $entityType
|
||||
): void {
|
||||
|
||||
if (str_contains($attribute, ':')) {
|
||||
$argumentList = QueryComposerUtil::getAllAttributesFromComplexExpression($attribute);
|
||||
|
||||
foreach ($argumentList as $argument) {
|
||||
$this->applyLeftJoinsFromAttribute($queryBuilder, $argument, $entityType);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$seed = $this->getSeed($entityType);
|
||||
|
||||
if (str_contains($attribute, '.')) {
|
||||
[$link,] = explode('.', $attribute);
|
||||
|
||||
if ($seed->hasRelation($link)) {
|
||||
$queryBuilder->leftJoin($link);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$attributeType = $seed->getAttributeType($attribute);
|
||||
|
||||
if ($attributeType === Entity::FOREIGN) {
|
||||
$relation = $this->getAttributeParam($seed, $attribute, AttributeParam::RELATION);
|
||||
|
||||
if ($relation) {
|
||||
$queryBuilder->leftJoin($relation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getSeed(string $entityType): Entity
|
||||
{
|
||||
if (!isset($this->seedHash[$entityType])) {
|
||||
$this->seedHash[$entityType] = $this->entityManager->getNewEntity($entityType);
|
||||
}
|
||||
|
||||
return $this->seedHash[$entityType];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @noinspection PhpSameParameterValueInspection
|
||||
*/
|
||||
private function getAttributeParam(Entity $entity, string $attribute, string $param)
|
||||
{
|
||||
if ($entity instanceof BaseEntity) {
|
||||
return $entity->getAttributeParam($attribute, $param);
|
||||
}
|
||||
|
||||
$entityDefs = $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($entity->getEntityType());
|
||||
|
||||
if (!$entityDefs->hasAttribute($attribute)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $entityDefs->getAttribute($attribute)->getParam($param);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user