snapshot of https://github.com/espocrm/espocrm.git (master, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Test / Test on PHP 8.3 (push) Failing after 5s
Test / Test on PHP 8.4 (push) Failing after 4s
Test / Test on PHP 8.5 (push) Failing after 3s
TypeScript static analysis / TypeScript check (push) Failing after 14s
Test Integration on MySQL / Test on PHP 8.3 (push) Failing after 2s
Test Integration on MySQL / Test on PHP 8.5 (push) Failing after 0s
Test Integration on PostgreSQL / Test on PHP 8.4 fix (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 master (push) Failing after 1s
Some checks failed
Test / Test on PHP 8.3 (push) Failing after 5s
Test / Test on PHP 8.4 (push) Failing after 4s
Test / Test on PHP 8.5 (push) Failing after 3s
TypeScript static analysis / TypeScript check (push) Failing after 14s
Test Integration on MySQL / Test on PHP 8.3 (push) Failing after 2s
Test Integration on MySQL / Test on PHP 8.5 (push) Failing after 0s
Test Integration on PostgreSQL / Test on PHP 8.4 fix (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 master (push) Failing after 1s
This commit is contained in:
92
application/Espo/Core/Utils/Acl/UserAclManagerProvider.php
Normal file
92
application/Espo/Core/Utils/Acl/UserAclManagerProvider.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Acl;
|
||||
|
||||
use Espo\Core\Acl\Exceptions\NotAvailable;
|
||||
use Espo\Entities\Portal;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Core\Portal\AclManagerContainer as PortalAclManagerContainer;
|
||||
use Espo\Core\ApplicationState;
|
||||
|
||||
/**
|
||||
* @todo Use WeakMap (User as a key).
|
||||
*/
|
||||
class UserAclManagerProvider
|
||||
{
|
||||
/** @var array<string, AclManager> */
|
||||
private $map = [];
|
||||
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private AclManager $aclManager,
|
||||
private PortalAclManagerContainer $portalAclManagerContainer,
|
||||
private ApplicationState $applicationState
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws NotAvailable
|
||||
*/
|
||||
public function get(User $user): AclManager
|
||||
{
|
||||
$key = $user->hasId() ? $user->getId() : spl_object_hash($user);
|
||||
|
||||
if (!isset($this->map[$key])) {
|
||||
$this->map[$key] = $this->load($user);
|
||||
}
|
||||
|
||||
return $this->map[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotAvailable
|
||||
*/
|
||||
private function load(User $user): AclManager
|
||||
{
|
||||
$aclManager = $this->aclManager;
|
||||
|
||||
if ($user->isPortal() && !$this->applicationState->isPortal()) {
|
||||
/** @var ?Portal $portal */
|
||||
$portal = $this->entityManager
|
||||
->getRDBRepository(User::ENTITY_TYPE)
|
||||
->getRelation($user, 'portals')
|
||||
->findOne();
|
||||
|
||||
if (!$portal) {
|
||||
throw new NotAvailable("No portal for portal user '" . $user->getId() . "'.");
|
||||
}
|
||||
|
||||
$aclManager = $this->portalAclManagerContainer->get($portal);
|
||||
}
|
||||
|
||||
return $aclManager;
|
||||
}
|
||||
}
|
||||
96
application/Espo/Core/Utils/Address/CountryDataProvider.php
Normal file
96
application/Espo/Core/Utils/Address/CountryDataProvider.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Address;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Utils\Cache\DataCacheAccess;
|
||||
use Espo\Entities\AddressCountry;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
|
||||
class CountryDataProvider
|
||||
{
|
||||
private const string CACHE_KEY = 'addressCountryData';
|
||||
private const int LIMIT = 500;
|
||||
|
||||
/**
|
||||
* @param DataCacheAccess<array{list: string[], preferredList: string[]}> $dataCacheAccess
|
||||
*/
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private DataCacheAccess $dataCacheAccess,
|
||||
) {
|
||||
$dataCacheAccess->init(
|
||||
key: self::CACHE_KEY,
|
||||
loader: function () {
|
||||
return $this->load();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{list: string[], preferredList: string[]}
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
return $this->dataCacheAccess->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{list: string[], preferredList: string[]}
|
||||
*/
|
||||
private function load(): array
|
||||
{
|
||||
$list = [];
|
||||
$preferredList = [];
|
||||
|
||||
/** @var iterable<AddressCountry> $collection */
|
||||
$collection = $this->entityManager
|
||||
->getRDBRepositoryByClass(AddressCountry::class)
|
||||
->sth()
|
||||
->select([Field::NAME, 'isPreferred'])
|
||||
->order(Field::NAME, Order::ASC)
|
||||
->limit(0, self::LIMIT)
|
||||
->find();
|
||||
|
||||
foreach ($collection as $entity) {
|
||||
$list[] = $entity->getName();
|
||||
|
||||
if ($entity->isPreferred()) {
|
||||
$preferredList[] = $entity->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'list' => $list,
|
||||
'preferredList' => $preferredList,
|
||||
];
|
||||
}
|
||||
}
|
||||
103
application/Espo/Core/Utils/ApiKey.php
Normal file
103
application/Espo/Core/Utils/ApiKey.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils;
|
||||
|
||||
use Espo\Core\Utils\Config\ConfigWriter;
|
||||
use SensitiveParameter;
|
||||
|
||||
class ApiKey
|
||||
{
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private ConfigWriter $configWriter)
|
||||
{}
|
||||
|
||||
public static function hash(string $secretKey, string $string = ''): string
|
||||
{
|
||||
return hash_hmac('sha256', $string, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @todo Remove in v11.0.
|
||||
* @internal
|
||||
*/
|
||||
public static function hashLegacy(string $secretKey, string $string = ''): string
|
||||
{
|
||||
return hash_hmac('sha256', $string, $secretKey, true);
|
||||
}
|
||||
|
||||
public function getSecretKeyForUserId(string $id): ?string
|
||||
{
|
||||
$apiSecretKeys = $this->config->get('apiSecretKeys');
|
||||
|
||||
if (!$apiSecretKeys) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_object($apiSecretKeys)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isset($apiSecretKeys->$id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $apiSecretKeys->$id;
|
||||
}
|
||||
|
||||
public function storeSecretKeyForUserId(string $id, #[SensitiveParameter] string $secretKey): void
|
||||
{
|
||||
$apiSecretKeys = $this->config->get('apiSecretKeys');
|
||||
|
||||
if (!is_object($apiSecretKeys)) {
|
||||
$apiSecretKeys = (object) [];
|
||||
}
|
||||
|
||||
$apiSecretKeys->$id = $secretKey;
|
||||
|
||||
$this->configWriter->set('apiSecretKeys', $apiSecretKeys);
|
||||
$this->configWriter->save();
|
||||
}
|
||||
|
||||
public function removeSecretKeyForUserId(string $id): void
|
||||
{
|
||||
$apiSecretKeys = $this->config->get('apiSecretKeys');
|
||||
|
||||
if (!is_object($apiSecretKeys)) {
|
||||
$apiSecretKeys = (object) [];
|
||||
}
|
||||
|
||||
unset($apiSecretKeys->$id);
|
||||
|
||||
$this->configWriter->set('apiSecretKeys', $apiSecretKeys);
|
||||
$this->configWriter->save();
|
||||
}
|
||||
}
|
||||
178
application/Espo/Core/Utils/Autoload.php
Normal file
178
application/Espo/Core/Utils/Autoload.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils;
|
||||
|
||||
use Espo\Core\Binding\Attributes\Qualify;
|
||||
use Espo\Core\Utils\Autoload\Loader;
|
||||
use Espo\Core\Utils\Config\SystemConfig;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
use Espo\Core\Utils\Resource\PathProvider;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Autoload
|
||||
{
|
||||
/** @var ?array<string, mixed> */
|
||||
private $data = null;
|
||||
|
||||
private string $cacheKey = 'autoload';
|
||||
private string $autoloadFileName = 'autoload.json';
|
||||
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
#[Qualify(DataCache::QUALIFIER_SYSTEM)]
|
||||
private DataCache $dataCache,
|
||||
private FileManager $fileManager,
|
||||
private Loader $loader,
|
||||
private PathProvider $pathProvider,
|
||||
private SystemConfig $systemConfig,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getData(): array
|
||||
{
|
||||
if (!isset($this->data)) {
|
||||
$this->init();
|
||||
}
|
||||
|
||||
assert($this->data !== null);
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
private function init(): void
|
||||
{
|
||||
$useCache = $this->systemConfig->useCache();
|
||||
|
||||
if ($useCache && $this->dataCache->has($this->cacheKey)) {
|
||||
/** @var ?array<string, mixed> $data */
|
||||
$data = $this->dataCache->get($this->cacheKey);
|
||||
|
||||
if ($data !== null) {
|
||||
$this->data = $data;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->data = $this->loadData();
|
||||
|
||||
if ($useCache) {
|
||||
$this->dataCache->store($this->cacheKey, $this->data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function loadData(): array
|
||||
{
|
||||
$corePath = $this->pathProvider->getCore() . $this->autoloadFileName;
|
||||
|
||||
$data = $this->loadDataFromFile($corePath);
|
||||
|
||||
foreach ($this->metadata->getModuleList() as $moduleName) {
|
||||
$modulePath = $this->pathProvider->getModule($moduleName) . $this->autoloadFileName;
|
||||
|
||||
$data = array_merge_recursive(
|
||||
$data,
|
||||
$this->loadDataFromFile($modulePath)
|
||||
);
|
||||
}
|
||||
|
||||
$customPath = $this->pathProvider->getCustom() . $this->autoloadFileName;
|
||||
|
||||
return array_merge_recursive(
|
||||
$data,
|
||||
$this->loadDataFromFile($customPath)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
* @throws \JsonException
|
||||
*/
|
||||
private function loadDataFromFile(string $filePath): array
|
||||
{
|
||||
if (!$this->fileManager->isFile($filePath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$content = $this->fileManager->getContents($filePath);
|
||||
|
||||
$arrayContent = Json::decode($content, true);
|
||||
|
||||
return $this->normalizeData($arrayContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function normalizeData(array $data): array
|
||||
{
|
||||
$normalizedData = [];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
switch ($key) {
|
||||
case 'psr-4':
|
||||
case 'psr-0':
|
||||
case 'classmap':
|
||||
case 'files':
|
||||
case 'autoloadFileList':
|
||||
$normalizedData[$key] = $value;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$normalizedData['psr-0'][$key] = $value;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $normalizedData;
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
try {
|
||||
$data = $this->getData();
|
||||
} catch (Exception) {} // bad permissions
|
||||
|
||||
if (empty($data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loader->register($data);
|
||||
}
|
||||
}
|
||||
98
application/Espo/Core/Utils/Autoload/Loader.php
Normal file
98
application/Espo/Core/Utils/Autoload/Loader.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Autoload;
|
||||
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
|
||||
class Loader
|
||||
{
|
||||
public function __construct(
|
||||
private NamespaceLoader $namespaceLoader,
|
||||
private FileManager $fileManager
|
||||
) {}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array{
|
||||
* psr-4?: array<string, mixed>,
|
||||
* psr-0?: array<string, mixed>,
|
||||
* classmap?: array<string, mixed>,
|
||||
* autoloadFileList?: array<string, mixed>,
|
||||
* files?: array<string, mixed>,
|
||||
* } $data
|
||||
*/
|
||||
public function register(array $data): void
|
||||
{
|
||||
/* load "psr-4", "psr-0", "classmap" */
|
||||
$this->namespaceLoader->register($data);
|
||||
|
||||
/* load "autoloadFileList" */
|
||||
$this->registerAutoloadFileList($data);
|
||||
|
||||
/* load "files" */
|
||||
$this->registerFiles($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
private function registerAutoloadFileList(array $data): void
|
||||
{
|
||||
$keyName = 'autoloadFileList';
|
||||
|
||||
if (!isset($data[$keyName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($data[$keyName] as $filePath) {
|
||||
if ($this->fileManager->exists($filePath)) {
|
||||
require_once($filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
private function registerFiles(array $data): void
|
||||
{
|
||||
$keyName = 'files';
|
||||
|
||||
if (!isset($data[$keyName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($data[$keyName] as $filePath) {
|
||||
if ($this->fileManager->exists($filePath)) {
|
||||
require_once($filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
280
application/Espo/Core/Utils/Autoload/NamespaceLoader.php
Normal file
280
application/Espo/Core/Utils/Autoload/NamespaceLoader.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Autoload;
|
||||
|
||||
use Espo\Core\Binding\Attributes\Qualify;
|
||||
use Espo\Core\Utils\Config\SystemConfig;
|
||||
use Espo\Core\Utils\DataCache;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class NamespaceLoader
|
||||
{
|
||||
/**
|
||||
* @var ?array{
|
||||
* psr-4?: array<string, mixed>,
|
||||
* psr-0?: array<string, mixed>,
|
||||
* classmap?: array<string, mixed>,
|
||||
* }
|
||||
*/
|
||||
private $namespaces = null;
|
||||
/** @var ?array<string, mixed> */
|
||||
private $vendorNamespaces = null;
|
||||
private string $autoloadFilePath = 'vendor/autoload.php';
|
||||
/** @var array<'psr-4'|'psr-0'|'classmap', string> */
|
||||
private $namespacesPaths = [
|
||||
'psr-4' => 'vendor/composer/autoload_psr4.php',
|
||||
'psr-0' => 'vendor/composer/autoload_namespaces.php',
|
||||
'classmap' => 'vendor/composer/autoload_classmap.php',
|
||||
];
|
||||
/** @var array<'psr-4'|'psr-0', string> */
|
||||
private $methodNameMap = [
|
||||
'psr-4' => 'addPsr4',
|
||||
'psr-0' => 'add',
|
||||
];
|
||||
private string $cacheKey = 'autoloadVendorNamespaces';
|
||||
|
||||
private ClassLoader $classLoader;
|
||||
|
||||
public function __construct(
|
||||
#[Qualify(DataCache::QUALIFIER_SYSTEM)]
|
||||
private DataCache $dataCache,
|
||||
private FileManager $fileManager,
|
||||
private Log $log,
|
||||
private SystemConfig $systemConfig,
|
||||
) {
|
||||
|
||||
$this->classLoader = new ClassLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* psr-4?: array<string, mixed>,
|
||||
* psr-0?: array<string, mixed>
|
||||
* } $data
|
||||
*/
|
||||
public function register(array $data): void
|
||||
{
|
||||
$this->addListToClassLoader($data);
|
||||
|
||||
$this->classLoader->register(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* psr-4?: array<string, mixed>,
|
||||
* psr-0?: array<string, mixed>,
|
||||
* classmap?: array<string, mixed>,
|
||||
* }
|
||||
*/
|
||||
private function loadNamespaces(string $basePath = ''): array
|
||||
{
|
||||
$namespaces = [];
|
||||
|
||||
foreach ($this->namespacesPaths as $type => $path) {
|
||||
$mapFile = Util::concatPath($basePath, $path);
|
||||
|
||||
if (!$this->fileManager->exists($mapFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$map = require($mapFile);
|
||||
|
||||
if (!empty($map) && is_array($map)) {
|
||||
$namespaces[$type] = $map;
|
||||
}
|
||||
}
|
||||
|
||||
return $namespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return array{
|
||||
* psr-4?: array<string, mixed>,
|
||||
* psr-0?: array<string, mixed>,
|
||||
* classmap?: array<string, mixed>,
|
||||
* }
|
||||
*/
|
||||
private function getNamespaces(): array
|
||||
{
|
||||
if (!$this->namespaces) {
|
||||
$this->namespaces = $this->loadNamespaces();
|
||||
}
|
||||
|
||||
return $this->namespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'psr-4'|'psr-0'|'classmap' $type
|
||||
* @return string[]
|
||||
*/
|
||||
private function getNamespaceList(string $type): array
|
||||
{
|
||||
$namespaces = $this->getNamespaces();
|
||||
|
||||
return array_keys($namespaces[$type] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'psr-4'|'psr-0'|'classmap' $type
|
||||
* @param string|array<string, string> $path
|
||||
*/
|
||||
private function addNamespace(string $type, string $name, $path): void
|
||||
{
|
||||
if (!$this->namespaces) {
|
||||
$this->getNamespaces();
|
||||
}
|
||||
|
||||
$this->namespaces[$type][$name] = (array) $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'psr-4'|'psr-0'|'classmap' $type
|
||||
*/
|
||||
private function hasNamespace(string $type, string $name): bool
|
||||
{
|
||||
if (in_array($name, $this->getNamespaceList($type))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!preg_match('/\\\$/', $name)) {
|
||||
$name = $name . '\\';
|
||||
|
||||
if (in_array($name, $this->getNamespaceList($type))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
private function addListToClassLoader(array $data, bool $skipVendorNamespaces = false): void
|
||||
{
|
||||
foreach ($this->methodNameMap as $type => $methodName) {
|
||||
$itemData = $data[$type] ?? null;
|
||||
|
||||
if ($itemData === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($itemData as $prefix => $path) {
|
||||
if (!$skipVendorNamespaces) {
|
||||
$vendorPaths = is_array($path) ? $path : (array) $path;
|
||||
|
||||
foreach ($vendorPaths as $vendorPath) {
|
||||
$this->addListToClassLoader(
|
||||
$this->getVendorNamespaces($vendorPath),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->hasNamespace($type, $prefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->classLoader->$methodName($prefix, $path);
|
||||
} catch (Throwable $e) {
|
||||
$this->log->error("Could not add '{$prefix}' to autoload: " . $e->getMessage());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addNamespace($type, $prefix, $path);
|
||||
}
|
||||
}
|
||||
|
||||
$classMap = $data['classmap'] ?? null;
|
||||
|
||||
if ($classMap !== null) {
|
||||
$this->classLoader->addClassMap($classMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getVendorNamespaces(string $path): array
|
||||
{
|
||||
$useCache = $this->systemConfig->useCache();
|
||||
|
||||
if (!isset($this->vendorNamespaces)) {
|
||||
$this->vendorNamespaces = [];
|
||||
|
||||
if ($useCache && $this->dataCache->has($this->cacheKey)) {
|
||||
/** @var ?array<string, mixed> $cachedData */
|
||||
$cachedData = $this->dataCache->get($this->cacheKey);
|
||||
|
||||
$this->vendorNamespaces = $cachedData ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists($path, $this->vendorNamespaces)) {
|
||||
$vendorPath = $this->findVendorPath($path);
|
||||
|
||||
if ($vendorPath) {
|
||||
$this->vendorNamespaces[$path] = $this->loadNamespaces($vendorPath);
|
||||
|
||||
if ($useCache) {
|
||||
$this->dataCache->store($this->cacheKey, $this->vendorNamespaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->vendorNamespaces[$path] ?? [];
|
||||
}
|
||||
|
||||
private function findVendorPath(string $path): ?string
|
||||
{
|
||||
$vendor = Util::concatPath($path, $this->autoloadFilePath);
|
||||
|
||||
if ($this->fileManager->exists($vendor)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
$parentDir = dirname($path);
|
||||
|
||||
if (!empty($parentDir) && $parentDir !== '.') {
|
||||
return $this->findVendorPath($parentDir);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
81
application/Espo/Core/Utils/Cache/CacheItem.php
Normal file
81
application/Espo/Core/Utils/Cache/CacheItem.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Cache;
|
||||
|
||||
use DateInterval;
|
||||
use DateTimeInterface;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
|
||||
class CacheItem implements CacheItemInterface
|
||||
{
|
||||
public function __construct(
|
||||
private string $key,
|
||||
private mixed $value,
|
||||
private bool $isHit = false,
|
||||
) {}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function get(): mixed
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function isHit(): bool
|
||||
{
|
||||
return $this->isHit;
|
||||
}
|
||||
|
||||
public function set(mixed $value): static
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported.
|
||||
*/
|
||||
public function expiresAt(?DateTimeInterface $expiration): static
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported.
|
||||
*/
|
||||
public function expiresAfter(DateInterval|int|null $time): static
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
172
application/Espo/Core/Utils/Cache/DataCacheAccess.php
Normal file
172
application/Espo/Core/Utils/Cache/DataCacheAccess.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Cache;
|
||||
|
||||
use Closure;
|
||||
use Espo\Core\Utils\Cache\Exceptions\ReadError;
|
||||
use Espo\Core\Utils\Config\SystemConfig;
|
||||
use Espo\Core\Utils\DataCache;
|
||||
use Espo\Core\Utils\Log;
|
||||
use LogicException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @since 10.1.0
|
||||
* @template T of array<int|string, mixed> | stdClass = array<int|string, mixed> | stdClass
|
||||
* @todo Test.
|
||||
*/
|
||||
class DataCacheAccess
|
||||
{
|
||||
/** @var T|null */
|
||||
private mixed $data = null;
|
||||
|
||||
private ?string $key = null;
|
||||
|
||||
/** @var (Closure(): T)|null */
|
||||
private ?Closure $loader = null;
|
||||
|
||||
/** @var (Closure(T): bool)|null */
|
||||
private ?Closure $validityChecker = null;
|
||||
|
||||
public function __construct(
|
||||
private DataCache $dataCache,
|
||||
private SystemConfig $systemConfig,
|
||||
private Log $log,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param Closure(): T $loader
|
||||
* @param (Closure(T): bool)|null $validityChecker
|
||||
*/
|
||||
public function init(string $key, Closure $loader, ?Closure $validityChecker = null): void
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->loader = $loader;
|
||||
$this->validityChecker = $validityChecker;
|
||||
|
||||
$this->data = null;
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->data = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param T $data
|
||||
*/
|
||||
public function set(mixed $data): void
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function store(): void
|
||||
{
|
||||
if (!$this->key) {
|
||||
throw new LogicException("Not initialized.");
|
||||
}
|
||||
|
||||
if ($this->data === null) {
|
||||
throw new LogicException("Data not set.");
|
||||
}
|
||||
|
||||
if ($this->systemConfig->useCache()) {
|
||||
$this->dataCache->store($this->key, $this->data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
public function get(): mixed
|
||||
{
|
||||
if ($this->data && $this->validityChecker && !($this->validityChecker)($this->data)) {
|
||||
$this->data = null;
|
||||
}
|
||||
|
||||
if ($this->data !== null) {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
$key = $this->key;
|
||||
$loader = $this->loader;
|
||||
|
||||
if (!$key || !$loader) {
|
||||
throw new LogicException("Not initialized.");
|
||||
}
|
||||
|
||||
if ($this->systemConfig->useCache() && $this->dataCache->has($key)) {
|
||||
$this->loadFromCache();
|
||||
}
|
||||
|
||||
if ($this->data === null) {
|
||||
$this->data = $loader();
|
||||
|
||||
if ($this->systemConfig->useCache()) {
|
||||
$this->dataCache->store($key, $this->data);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
private function loadFromCache(): void
|
||||
{
|
||||
$key = $this->key ?? throw new LogicException();
|
||||
|
||||
try {
|
||||
$data = $this->dataCache->get($key);
|
||||
} catch (ReadError $e) {
|
||||
$this->log->warning("Corrupted cache data by key '{key}'.", [
|
||||
'exception' => $e,
|
||||
'key' => $key,
|
||||
]);
|
||||
|
||||
$this->dataCache->clear($key);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($data === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var T $data */
|
||||
|
||||
if ($this->validityChecker && !($this->validityChecker)($data)) {
|
||||
$this->data = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
52
application/Espo/Core/Utils/Cache/DataCacheProvider.php
Normal file
52
application/Espo/Core/Utils/Cache/DataCacheProvider.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Cache;
|
||||
|
||||
use Espo\Core\Container;
|
||||
use Espo\Core\Utils\DataCache;
|
||||
|
||||
/**
|
||||
* May be needed when data cache is corrupted not allowing to load the DataCache service
|
||||
* when clearing cache.
|
||||
*
|
||||
* @internal
|
||||
* @since 10.1.0
|
||||
*/
|
||||
class DataCacheProvider
|
||||
{
|
||||
public function __construct(
|
||||
private Container $container,
|
||||
) {}
|
||||
|
||||
public function get(): DataCache
|
||||
{
|
||||
return $this->container->getByClass(DataCache::class);
|
||||
}
|
||||
}
|
||||
56
application/Espo/Core/Utils/Cache/DataCacheServiceLoader.php
Normal file
56
application/Espo/Core/Utils/Cache/DataCacheServiceLoader.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Cache;
|
||||
|
||||
use Espo\Core\Container;
|
||||
use Espo\Core\Container\Loader;
|
||||
use Espo\Core\Utils\DataCache;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class DataCacheServiceLoader implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private Container $container,
|
||||
) {}
|
||||
|
||||
public function load(): DataCache
|
||||
{
|
||||
$service = $this->container->get(DataCacheServiceName::SYSTEM);
|
||||
|
||||
if (!$service instanceof DataCache) {
|
||||
throw new RuntimeException("Unexpected 'dataCache' service instance.");
|
||||
}
|
||||
|
||||
return $service;
|
||||
}
|
||||
}
|
||||
39
application/Espo/Core/Utils/Cache/DataCacheServiceName.php
Normal file
39
application/Espo/Core/Utils/Cache/DataCacheServiceName.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Cache;
|
||||
|
||||
/**
|
||||
* @since 10.1.0
|
||||
*/
|
||||
class DataCacheServiceName
|
||||
{
|
||||
public const string SYSTEM = 'systemDataCache';
|
||||
public const string APPLICATION = 'dataCache';
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Cache\Exceptions;
|
||||
|
||||
use Psr\Cache\InvalidArgumentException as InvalidArgumentExceptionInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @since 10.1.0
|
||||
*/
|
||||
class InvalidArgument extends RuntimeException implements InvalidArgumentExceptionInterface
|
||||
{}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Cache\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @since 10.1.0
|
||||
*/
|
||||
class PersistenceError extends RuntimeException
|
||||
{}
|
||||
38
application/Espo/Core/Utils/Cache/Exceptions/ReadError.php
Normal file
38
application/Espo/Core/Utils/Cache/Exceptions/ReadError.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Cache\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @since 10.1.0
|
||||
*/
|
||||
class ReadError extends RuntimeException
|
||||
{}
|
||||
176
application/Espo/Core/Utils/Cache/FileCacheItemPool.php
Normal file
176
application/Espo/Core/Utils/Cache/FileCacheItemPool.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Cache;
|
||||
|
||||
use Espo\Core\Utils\Cache\Exceptions\InvalidArgument;
|
||||
use Espo\Core\Utils\File\Exceptions\FileError;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
|
||||
/**
|
||||
* Supports only array and stdClass values.
|
||||
*
|
||||
* @since 10.1.0
|
||||
*/
|
||||
class FileCacheItemPool implements CacheItemPoolInterface
|
||||
{
|
||||
private string $cacheDir = 'data/cache/application/';
|
||||
|
||||
public function __construct(private FileManager $fileManager)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgument
|
||||
*/
|
||||
public function getItem(string $key): CacheItem
|
||||
{
|
||||
$file = $this->getFile($key);
|
||||
|
||||
try {
|
||||
$value = $this->fileManager->getPhpSafeContents($file);
|
||||
} catch (FileError) {
|
||||
return new CacheItem(
|
||||
key: $key,
|
||||
value: null,
|
||||
isHit: false,
|
||||
);
|
||||
}
|
||||
|
||||
return new CacheItem(
|
||||
key: $key,
|
||||
value: $value,
|
||||
isHit: true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<CacheItem>
|
||||
*/
|
||||
public function getItems(array $keys = []): iterable
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$values[] = $this->getItem($key);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgument
|
||||
*/
|
||||
public function hasItem(string $key): bool
|
||||
{
|
||||
$file = $this->getFile($key);
|
||||
|
||||
return $this->fileManager->isFile($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function clear(): bool
|
||||
{
|
||||
return $this->fileManager->removeInDir($this->cacheDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws InvalidArgument
|
||||
*/
|
||||
public function deleteItem(string $key): bool
|
||||
{
|
||||
$file = $this->getFile($key);
|
||||
|
||||
return $this->fileManager->removeFile($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function deleteItems(array $keys): bool
|
||||
{
|
||||
$result = true;
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$result &= $this->deleteItem($key);
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists a cache immediately.
|
||||
*
|
||||
* @todo Throw an error in DataCache if false.
|
||||
*/
|
||||
public function save(CacheItemInterface $item): bool
|
||||
{
|
||||
$file = $this->getFile($item->getKey());
|
||||
|
||||
$result = $this->fileManager->putPhpContents($file, $item->get(), true, true);
|
||||
|
||||
if ($result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a cache item to be persisted later.
|
||||
*/
|
||||
public function saveDeferred(CacheItemInterface $item): bool
|
||||
{
|
||||
return $this->save($item);
|
||||
}
|
||||
|
||||
public function commit(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getFile(string $key): string
|
||||
{
|
||||
if (
|
||||
$key === '' ||
|
||||
preg_match('/[^a-zA-Z0-9_\/\-]/i', $key) ||
|
||||
$key[0] === '/' ||
|
||||
str_ends_with($key, '/')
|
||||
) {
|
||||
throw new InvalidArgumentException("Bad cache key.");
|
||||
}
|
||||
|
||||
return $this->cacheDir . $key . '.php';
|
||||
}
|
||||
}
|
||||
96
application/Espo/Core/Utils/ClassFinder.php
Normal file
96
application/Espo/Core/Utils/ClassFinder.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils;
|
||||
|
||||
use Espo\Core\Utils\File\ClassMap;
|
||||
|
||||
/**
|
||||
* Finds classes of a specific category. Category examples: Services, Controllers.
|
||||
* First it checks in the `custom` folder, then modules, then the internal folder.
|
||||
* Available as 'classFinder' service.
|
||||
*/
|
||||
class ClassFinder
|
||||
{
|
||||
|
||||
/** @var array<string, array<string, class-string>> */
|
||||
private $dataHashMap = [];
|
||||
|
||||
public function __construct(private ClassMap $classMap)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Reset runtime cache.
|
||||
*
|
||||
* @internal
|
||||
* @since 8.4.0
|
||||
*/
|
||||
public function resetRuntimeCache(): void
|
||||
{
|
||||
$this->dataHashMap = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find class name by a category and name.
|
||||
*
|
||||
* @return ?class-string
|
||||
*/
|
||||
public function find(string $category, string $name, bool $subDirs = false): ?string
|
||||
{
|
||||
$map = $this->getMap($category, $subDirs);
|
||||
|
||||
return $map[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a name => class name map.
|
||||
*
|
||||
* @return array<string, class-string>
|
||||
*/
|
||||
public function getMap(string $category, bool $subDirs = false): array
|
||||
{
|
||||
if (!array_key_exists($category, $this->dataHashMap)) {
|
||||
$this->load($category, $subDirs);
|
||||
}
|
||||
|
||||
return $this->dataHashMap[$category] ?? [];
|
||||
}
|
||||
|
||||
private function load(string $category, bool $subDirs = false): void
|
||||
{
|
||||
$cacheFile = $this->buildCacheKey($category);
|
||||
|
||||
$this->dataHashMap[$category] = $this->classMap->getData($category, $cacheFile, null, $subDirs);
|
||||
}
|
||||
|
||||
private function buildCacheKey(string $category): string
|
||||
{
|
||||
return 'classmap' . str_replace('/', '', $category);
|
||||
}
|
||||
}
|
||||
130
application/Espo/Core/Utils/Client/ActionRenderer.php
Normal file
130
application/Espo/Core/Utils/Client/ActionRenderer.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Client;
|
||||
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Utils\Client\ActionRenderer\Params;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Core\Utils\ClientManager;
|
||||
use Espo\Core\Utils\Theme\Direction;
|
||||
|
||||
/**
|
||||
* Renders a front-end page that executes a controller action. Utilized by entry points.
|
||||
*/
|
||||
class ActionRenderer
|
||||
{
|
||||
|
||||
public function __construct(private ClientManager $clientManager)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Writes to a body.
|
||||
*/
|
||||
public function write(Response $response, Params $params): void
|
||||
{
|
||||
$body = $this->render(
|
||||
controller: $params->getController(),
|
||||
action: $params->getAction(),
|
||||
data: $params->getData(),
|
||||
initAuth: $params->initAuth(),
|
||||
scripts: $params->getScripts(),
|
||||
pageTitle: $params->getPageTitle(),
|
||||
theme: $params->getTheme(),
|
||||
direction: $params->getDirection(),
|
||||
login: $params->login(),
|
||||
);
|
||||
|
||||
$securityParams = $params->getSecurityParams();
|
||||
|
||||
if (!$securityParams && $params->getFrameAncestors()) {
|
||||
$securityParams = new SecurityParams(
|
||||
frameAncestors: $params->getFrameAncestors(),
|
||||
);
|
||||
}
|
||||
|
||||
$this->clientManager->writeHeaders($response, $securityParams);
|
||||
|
||||
$response->writeBody($body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?array<string, mixed> $data
|
||||
* @param Script[] $scripts
|
||||
*/
|
||||
private function render(
|
||||
string $controller,
|
||||
string $action,
|
||||
?array $data,
|
||||
bool $initAuth,
|
||||
array $scripts,
|
||||
?string $pageTitle,
|
||||
?string $theme,
|
||||
?Direction $direction,
|
||||
bool $login,
|
||||
): string {
|
||||
|
||||
$encodedData = Json::encode($data);
|
||||
|
||||
$doActionPart = <<<EOF
|
||||
app.doAction({
|
||||
controllerClassName: '$controller',
|
||||
action: '$action',
|
||||
options: $encodedData,
|
||||
});
|
||||
EOF;
|
||||
|
||||
if ($login) {
|
||||
$script = <<<EOF
|
||||
app.start({
|
||||
onAuth: () => {
|
||||
$doActionPart
|
||||
},
|
||||
});
|
||||
EOF;
|
||||
} else {
|
||||
$initAuthPart = $initAuth ? "app.initAuth();\n" : '';
|
||||
|
||||
$script = <<<EOF
|
||||
{$initAuthPart}
|
||||
EOF . "\n" . $doActionPart;
|
||||
}
|
||||
|
||||
$params = new RenderParams(
|
||||
runScript: $script,
|
||||
scripts: $scripts,
|
||||
pageTitle: $pageTitle,
|
||||
theme: $theme,
|
||||
direction: $direction,
|
||||
useRouter: false,
|
||||
);
|
||||
|
||||
return $this->clientManager->render($params);
|
||||
}
|
||||
}
|
||||
249
application/Espo/Core/Utils/Client/ActionRenderer/Params.php
Normal file
249
application/Espo/Core/Utils/Client/ActionRenderer/Params.php
Normal file
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Client\ActionRenderer;
|
||||
|
||||
use Espo\Core\Utils\Client\Script;
|
||||
use Espo\Core\Utils\Client\SecurityParams;
|
||||
use Espo\Core\Utils\Theme\Direction;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
/** @var ?array<string, mixed> */
|
||||
private ?array $data;
|
||||
private bool $initAuth = false;
|
||||
/** @var string[] */
|
||||
private array $frameAncestors = [];
|
||||
/** @var Script[] */
|
||||
private array $scripts = [];
|
||||
private ?string $pageTitle = null;
|
||||
private ?string $theme = null;
|
||||
private ?Direction $direction = null;
|
||||
private bool $login = false;
|
||||
private ?SecurityParams $securityParams = null;
|
||||
|
||||
/**
|
||||
* @param ?array<string, mixed> $data
|
||||
*/
|
||||
public function __construct(
|
||||
private string $controller,
|
||||
private string $action,
|
||||
?array $data = null,
|
||||
) {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?array<string, mixed> $data
|
||||
*/
|
||||
public static function create(string $controller, string $action, ?array $data = null): self
|
||||
{
|
||||
return new self($controller, $action, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function withData(array $data): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->data = $data;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withInitAuth(bool $initAuth = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->initAuth = $initAuth;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v10.1.0. Use `withSecurityParams`.
|
||||
* @param string[] $frameAncestors
|
||||
* @since 9.0.0
|
||||
* @todo Remove in v11.0.
|
||||
*/
|
||||
public function withFrameAncestors(array $frameAncestors): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->frameAncestors = $frameAncestors;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Script[] $scripts
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function withScripts(array $scripts): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->scripts = $scripts;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function withPageTitle(?string $pageTitle): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->pageTitle = $pageTitle;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function withTheme(?string $theme): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->theme = $theme;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withDirection(?Direction $direction): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->direction = $direction;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 10.1.0
|
||||
*/
|
||||
public function withLogin(bool $login = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->login = $login;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 10.1.0
|
||||
*/
|
||||
public function withSecurityParams(?SecurityParams $securityParams): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->securityParams = $securityParams;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function getController(): string
|
||||
{
|
||||
return $this->controller;
|
||||
}
|
||||
|
||||
public function getAction(): string
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?array<string, mixed>
|
||||
*/
|
||||
public function getData(): ?array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function initAuth(): bool
|
||||
{
|
||||
return $this->initAuth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v10.1.0. Use `getSecurityParams`.
|
||||
* @return string[]
|
||||
* @since 9.0.0
|
||||
* @todo Remove in v11.0.
|
||||
*/
|
||||
public function getFrameAncestors(): array
|
||||
{
|
||||
return $this->frameAncestors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Script[]
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public function getScripts(): array
|
||||
{
|
||||
return $this->scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function getPageTitle(): ?string
|
||||
{
|
||||
return $this->pageTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function getTheme(): ?string
|
||||
{
|
||||
return $this->theme;
|
||||
}
|
||||
|
||||
public function getDirection(): ?Direction
|
||||
{
|
||||
return $this->direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 10.1.0
|
||||
*/
|
||||
public function login(): bool
|
||||
{
|
||||
return $this->login;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 10.1.0
|
||||
*/
|
||||
public function getSecurityParams(): ?SecurityParams
|
||||
{
|
||||
return $this->securityParams;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Client;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
use Espo\Core\Utils\Module;
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
/**
|
||||
* Allows bundled extensions to work when the system is in the developer mode.
|
||||
*/
|
||||
class DevModeExtensionInitJsFileListProvider
|
||||
{
|
||||
public function __construct(
|
||||
private Module $module,
|
||||
private FileManager $fileManager,
|
||||
private Config $config,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
$developedModule = $this->config->get('developedModule');
|
||||
|
||||
if (!$developedModule) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($this->getBundledModuleList() as $module) {
|
||||
if ($module === $developedModule) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file = "client/custom/modules/$module/lib/init.js";
|
||||
|
||||
if ($this->fileManager->exists($file)) {
|
||||
$output[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getBundledModuleList(): array
|
||||
{
|
||||
$modules = array_values(array_filter(
|
||||
$this->module->getList(),
|
||||
fn ($item) => $this->module->get([$item, 'bundled'])
|
||||
));
|
||||
|
||||
return array_map(
|
||||
fn ($item) => Util::fromCamelCase($item, '-'),
|
||||
$modules
|
||||
);
|
||||
}
|
||||
}
|
||||
109
application/Espo/Core/Utils/Client/DevModeJsFileListProvider.php
Normal file
109
application/Espo/Core/Utils/Client/DevModeJsFileListProvider.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Client;
|
||||
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @internal Also used by the installer w/o DI.
|
||||
*/
|
||||
class DevModeJsFileListProvider
|
||||
{
|
||||
private const LIBS_FILE = 'frontend/libs.json';
|
||||
|
||||
public function __construct(private FileManager $fileManager)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
$items = json_decode($this->fileManager->getContents(self::LIBS_FILE));
|
||||
|
||||
foreach ($items as $item) {
|
||||
if (!($item->bundle ?? false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$files = $item->files ?? null;
|
||||
|
||||
if ($files !== null) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
array_map(
|
||||
fn ($item) => self::prepareBundleLibFilePath($item),
|
||||
$files
|
||||
)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($item->src)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[] = self::prepareBundleLibFilePath($item);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
private function prepareBundleLibFilePath(object $item): string
|
||||
{
|
||||
$amdId = $item->amdId ?? null;
|
||||
|
||||
if ($amdId) {
|
||||
$file = $amdId;
|
||||
|
||||
if (str_starts_with($amdId, '@')) {
|
||||
$file = substr($amdId, 1);
|
||||
$file = str_replace('/', '-', $file);
|
||||
}
|
||||
|
||||
return 'client/lib/original/' . $file . '.js';
|
||||
}
|
||||
|
||||
$src = $item->src ?? null;
|
||||
|
||||
if (!$src) {
|
||||
throw new RuntimeException("Missing 'src' in bundled lib definition.");
|
||||
}
|
||||
|
||||
$arr = explode('/', $src);
|
||||
|
||||
return 'client/lib/original/' . array_slice($arr, -1)[0];
|
||||
}
|
||||
}
|
||||
67
application/Espo/Core/Utils/Client/LoaderParamsProvider.php
Normal file
67
application/Espo/Core/Utils/Client/LoaderParamsProvider.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Client;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
class LoaderParamsProvider
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function getLibsConfig(): object
|
||||
{
|
||||
return (object) $this->metadata->get(['app', 'jsLibs'], []);
|
||||
}
|
||||
|
||||
public function getAliasMap(): object
|
||||
{
|
||||
$map = (object) [];
|
||||
|
||||
/** @var array<string, array<string, mixed>> $libs */
|
||||
$libs = $this->metadata->get(['app', 'jsLibs'], []);
|
||||
|
||||
foreach ($libs as $name => $item) {
|
||||
/** @var ?string[] $aliases */
|
||||
$aliases = $item['aliases'] ?? null;
|
||||
|
||||
$map->$name = 'lib!' . $name;
|
||||
|
||||
if ($aliases) {
|
||||
foreach ($aliases as $alias) {
|
||||
$map->$alias = 'lib!' . $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
52
application/Espo/Core/Utils/Client/RenderParams.php
Normal file
52
application/Espo/Core/Utils/Client/RenderParams.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Client;
|
||||
|
||||
use Espo\Core\Utils\Theme\Direction;
|
||||
|
||||
readonly class RenderParams
|
||||
{
|
||||
/**
|
||||
* @param ?string $runScript A JS run-script.
|
||||
* @param Script[] $scripts Scripts to include on the page.
|
||||
* @param ?string $pageTitle A page title. Since 9.1.0.
|
||||
* @param ?string $theme A page theme name.
|
||||
* @param ?Direction $direction A document direction.
|
||||
* @param bool $useRouter Use the frontend router. As of v10.1.0.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $runScript = null,
|
||||
public array $scripts = [],
|
||||
public ?string $pageTitle = null,
|
||||
public ?string $theme = null,
|
||||
public ?Direction $direction = null,
|
||||
public bool $useRouter = true,
|
||||
) {}
|
||||
}
|
||||
40
application/Espo/Core/Utils/Client/Script.php
Normal file
40
application/Espo/Core/Utils/Client/Script.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Client;
|
||||
|
||||
class Script
|
||||
{
|
||||
public function __construct(
|
||||
readonly public string $source,
|
||||
readonly public bool $cacheBusting = false,
|
||||
readonly public bool $async = false,
|
||||
readonly public bool $defer = false,
|
||||
) {}
|
||||
}
|
||||
42
application/Espo/Core/Utils/Client/SecurityParams.php
Normal file
42
application/Espo/Core/Utils/Client/SecurityParams.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Client;
|
||||
|
||||
readonly class SecurityParams
|
||||
{
|
||||
/**
|
||||
* @param string[] $frameAncestors
|
||||
* @param ?string[] $formAction
|
||||
*/
|
||||
public function __construct(
|
||||
public array $frameAncestors = [],
|
||||
public ?array $formAction = null,
|
||||
) {}
|
||||
}
|
||||
559
application/Espo/Core/Utils/ClientManager.php
Normal file
559
application/Espo/Core/Utils/ClientManager.php
Normal file
@@ -0,0 +1,559 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils;
|
||||
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Api\ResponseWrapper;
|
||||
use Espo\Core\Utils\Client\DevModeExtensionInitJsFileListProvider;
|
||||
use Espo\Core\Utils\Client\DevModeJsFileListProvider;
|
||||
use Espo\Core\Utils\Client\LoaderParamsProvider;
|
||||
use Espo\Core\Utils\Client\RenderParams;
|
||||
use Espo\Core\Utils\Client\Script;
|
||||
use Espo\Core\Utils\Client\SecurityParams;
|
||||
use Espo\Core\Utils\Config\ApplicationConfig;
|
||||
use Espo\Core\Utils\Config\SystemConfig;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
|
||||
use Espo\Core\Utils\Theme\Direction;
|
||||
use Espo\Core\Utils\Theme\MetadataProvider as ThemeMetadataProvider;
|
||||
use Slim\Psr7\Response as Psr7Response;
|
||||
use Slim\ResponseEmitter;
|
||||
|
||||
/**
|
||||
* Renders the main HTML page.
|
||||
*/
|
||||
class ClientManager
|
||||
{
|
||||
private string $mainHtmlFilePath = 'html/main.html';
|
||||
private string $runScript = 'app.start();';
|
||||
private string $faviconAlternate = 'client/img/favicon.ico';
|
||||
private string $favicon = 'client/img/favicon.svg';
|
||||
private string $basePath = '';
|
||||
private string $apiUrl = 'api/v1';
|
||||
private string $applicationId = 'espocrm';
|
||||
|
||||
private string $nonce;
|
||||
|
||||
private const string APP_DESCRIPTION = "EspoCRM – Open Source CRM application.";
|
||||
private const string APP_NAME = 'EspoCRM';
|
||||
|
||||
private const int AJAX_TIMEOUT = 60000;
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private ThemeManager $themeManager,
|
||||
private Metadata $metadata,
|
||||
private FileManager $fileManager,
|
||||
private DevModeJsFileListProvider $devModeJsFileListProvider,
|
||||
private DevModeExtensionInitJsFileListProvider $devModeExtensionInitJsFileListProvider,
|
||||
private Module $module,
|
||||
private LoaderParamsProvider $loaderParamsProvider,
|
||||
private SystemConfig $systemConfig,
|
||||
private ApplicationConfig $applicationConfig,
|
||||
private ThemeMetadataProvider $themeMetadataProvider,
|
||||
) {
|
||||
$this->nonce = Util::generateCryptId();
|
||||
}
|
||||
|
||||
public function setBasePath(string $basePath): void
|
||||
{
|
||||
$this->basePath = $basePath;
|
||||
}
|
||||
|
||||
public function getBasePath(): string
|
||||
{
|
||||
return $this->basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Move to a separate class.
|
||||
*/
|
||||
public function writeHeaders(Response $response, ?SecurityParams $params = null): void
|
||||
{
|
||||
if ($this->config->get('clientSecurityHeadersDisabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$params ??= new SecurityParams();
|
||||
|
||||
$response->setHeader('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
$this->writeContentSecurityPolicyHeader($response, $params);
|
||||
$this->writeStrictTransportSecurityHeader($response);
|
||||
}
|
||||
|
||||
private function writeContentSecurityPolicyHeader(Response $response, SecurityParams $params): void
|
||||
{
|
||||
if ($this->config->get('clientCspDisabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$string = "script-src 'self' 'nonce-$this->nonce' 'unsafe-eval'";
|
||||
|
||||
/** @var string[] $scriptSourceList */
|
||||
$scriptSourceList = $this->config->get('clientCspScriptSourceList') ?? [];
|
||||
|
||||
foreach ($scriptSourceList as $src) {
|
||||
$string .= ' ' . $src;
|
||||
}
|
||||
|
||||
if ($params->formAction !== null) {
|
||||
if ($params->formAction !== []) {
|
||||
$string .= '; form-action';
|
||||
|
||||
foreach ($params->formAction as $item) {
|
||||
$string .= ' ' . $item;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!$this->config->get('clientCspFormActionDisabled')) {
|
||||
$string .= "; form-action 'self'";
|
||||
}
|
||||
|
||||
// Checking the parameter for bc.
|
||||
if (!$this->config->get('clientXFrameOptionsHeaderDisabled')) {
|
||||
$string .= '; frame-ancestors';
|
||||
|
||||
foreach (["'self'", ...$params->frameAncestors] as $item) {
|
||||
$string .= ' ' . $item;
|
||||
}
|
||||
}
|
||||
|
||||
$response->setHeader('Content-Security-Policy', $string);
|
||||
}
|
||||
|
||||
private function writeStrictTransportSecurityHeader(Response $response): void
|
||||
{
|
||||
if ($this->config->get('clientStrictTransportSecurityHeaderDisabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$siteUrl = $this->applicationConfig->getSiteUrl();
|
||||
|
||||
if (str_starts_with($siteUrl, 'https://')) {
|
||||
$response->setHeader('Strict-Transport-Security', 'max-age=10368000');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $vars
|
||||
*/
|
||||
public function display(?string $runScript = null, ?string $htmlFilePath = null, array $vars = []): void
|
||||
{
|
||||
$body = $this->renderInternal($runScript, $htmlFilePath, $vars);
|
||||
|
||||
$response = new ResponseWrapper(new Psr7Response());
|
||||
|
||||
$this->writeHeaders($response);
|
||||
$response->writeBody($body);
|
||||
|
||||
(new ResponseEmitter())->emit($response->toPsr7());
|
||||
}
|
||||
|
||||
/**
|
||||
* Render.
|
||||
*
|
||||
* @param RenderParams $params Parameters.
|
||||
* @return string A result HTML.
|
||||
*/
|
||||
public function render(RenderParams $params): string
|
||||
{
|
||||
return $this->renderInternal(
|
||||
runScript: $params->runScript,
|
||||
additionalScripts: $params->scripts,
|
||||
pageTitle: $params->pageTitle,
|
||||
theme: $params->theme,
|
||||
direction: $params->direction,
|
||||
useRouter: $params->useRouter,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $vars
|
||||
* @param Script[] $additionalScripts
|
||||
*/
|
||||
private function renderInternal(
|
||||
?string $runScript = null,
|
||||
?string $htmlFilePath = null,
|
||||
array $vars = [],
|
||||
array $additionalScripts = [],
|
||||
?string $pageTitle = null,
|
||||
?string $theme = null,
|
||||
?Direction $direction = null,
|
||||
bool $useRouter = true,
|
||||
): string {
|
||||
|
||||
$runScript ??= $this->runScript;
|
||||
$htmlFilePath ??= $this->mainHtmlFilePath;
|
||||
|
||||
$cacheTimestamp = $this->getCacheTimestamp();
|
||||
$jsFileList = $this->getJsFileList();
|
||||
$appTimestamp = $this->getAppTimestamp();
|
||||
|
||||
if ($this->isDeveloperMode()) {
|
||||
$useCache = $this->useCacheInDeveloperMode();
|
||||
$loaderCacheTimestamp = null;
|
||||
} else {
|
||||
$useCache = $this->useCache();
|
||||
$loaderCacheTimestamp = $appTimestamp;
|
||||
}
|
||||
|
||||
$cssFileList = $this->metadata->get(['app', 'client', 'cssList'], []);
|
||||
$linkList = $this->metadata->get(['app', 'client', 'linkList'], []);
|
||||
|
||||
$faviconAlternate = $this->metadata->get('app.client.faviconAlternate') ?? $this->faviconAlternate;
|
||||
[$favicon, $faviconType] = $this->getFaviconData();
|
||||
|
||||
$scriptsHtml = implode('',
|
||||
array_map(fn ($file) => $this->getScriptItemHtml($file, $appTimestamp), $jsFileList)
|
||||
);
|
||||
|
||||
foreach ($additionalScripts as $it) {
|
||||
$scriptsHtml .= $this->getScriptItemHtml(
|
||||
file: null,
|
||||
appTimestamp: $appTimestamp,
|
||||
withNonce: true,
|
||||
cache: $it->cacheBusting,
|
||||
source: $it->source,
|
||||
async: $it->async,
|
||||
defer: $it->defer,
|
||||
);
|
||||
}
|
||||
|
||||
$additionalStyleSheetsHtml = implode('',
|
||||
array_map(fn ($file) => $this->getCssItemHtml($file, $appTimestamp), $cssFileList)
|
||||
);
|
||||
|
||||
$linksHtml = implode('',
|
||||
array_map(fn ($item) => $this->getLinkItemHtml($item, $appTimestamp), $linkList)
|
||||
);
|
||||
|
||||
$internalModuleList = array_map(
|
||||
fn ($moduleName) => Util::fromCamelCase($moduleName, '-'),
|
||||
$this->module->getInternalList()
|
||||
);
|
||||
|
||||
$stylesheet = $theme ?
|
||||
$this->themeMetadataProvider->getStylesheet($theme) :
|
||||
$this->themeManager->getStylesheet();
|
||||
|
||||
$direction ??= $this->themeManager->getDirection();
|
||||
|
||||
$data = [
|
||||
'applicationId' => $this->applicationId,
|
||||
'apiUrl' => $this->apiUrl,
|
||||
'applicationName' => $this->getApplicationName($pageTitle),
|
||||
'cacheTimestamp' => $cacheTimestamp,
|
||||
'appTimestamp' => $appTimestamp,
|
||||
'loaderCacheTimestamp' => Json::encode($loaderCacheTimestamp),
|
||||
'stylesheet' => $stylesheet,
|
||||
'direction' => $direction->value,
|
||||
'theme' => Json::encode($theme),
|
||||
'runScript' => $runScript,
|
||||
'basePath' => $this->basePath,
|
||||
'useCache' => self::boolToString($useCache),
|
||||
'appClientClassName' => 'app',
|
||||
'scriptsHtml' => $scriptsHtml,
|
||||
'additionalStyleSheetsHtml' => $additionalStyleSheetsHtml,
|
||||
'linksHtml' => $linksHtml,
|
||||
'faviconAlternate' => $faviconAlternate,
|
||||
'favicon' => $favicon,
|
||||
'faviconType' => $faviconType,
|
||||
'ajaxTimeout' => $this->getAjaxTimeout(),
|
||||
'internalModuleList' => Json::encode($internalModuleList),
|
||||
'bundledModuleList' => Json::encode($this->getBundledModuleList()),
|
||||
'applicationDescription' => $this->getApplicationDescription(),
|
||||
'nonce' => $this->nonce,
|
||||
'useRouter' => self::boolToString($useRouter),
|
||||
'loaderParams' => Json::encode([
|
||||
'basePath' => $this->basePath,
|
||||
'cacheTimestamp' => $loaderCacheTimestamp,
|
||||
'internalModuleList' => $internalModuleList,
|
||||
'transpiledModuleList' => $this->getTranspiledModuleList(),
|
||||
'libsConfig' => $this->loaderParamsProvider->getLibsConfig(),
|
||||
'aliasMap' => $this->loaderParamsProvider->getAliasMap(),
|
||||
]),
|
||||
];
|
||||
|
||||
$html = $this->fileManager->getContents($htmlFilePath);
|
||||
|
||||
$replaceData = $this->prepareRelaceData($vars, $data);
|
||||
|
||||
return strtr($html, $replaceData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getJsFileList(): array
|
||||
{
|
||||
if ($this->isDeveloperMode()) {
|
||||
return array_merge(
|
||||
$this->metadata->get(['app', 'client', 'developerModeScriptList']) ?? [],
|
||||
$this->getDeveloperModeBundleLibFileList(),
|
||||
$this->devModeExtensionInitJsFileListProvider->get(),
|
||||
);
|
||||
}
|
||||
|
||||
return $this->metadata->get(['app', 'client', 'scriptList']) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getDeveloperModeBundleLibFileList(): array
|
||||
{
|
||||
return $this->devModeJsFileListProvider->get();
|
||||
}
|
||||
|
||||
private function isDeveloperMode(): bool
|
||||
{
|
||||
return (bool) $this->config->get('isDeveloperMode');
|
||||
}
|
||||
|
||||
private function useCache(): bool
|
||||
{
|
||||
return $this->systemConfig->useCache();
|
||||
}
|
||||
|
||||
private function useCacheInDeveloperMode(): bool
|
||||
{
|
||||
return (bool) $this->config->get('useCacheInDeveloperMode');
|
||||
}
|
||||
|
||||
private function getCacheTimestamp(): int
|
||||
{
|
||||
if (!$this->useCache()) {
|
||||
return time();
|
||||
}
|
||||
|
||||
return $this->config->get('cacheTimestamp', 0);
|
||||
}
|
||||
|
||||
private function getAppTimestamp(): int
|
||||
{
|
||||
if (!$this->useCache()) {
|
||||
return time();
|
||||
}
|
||||
|
||||
return $this->config->get('appTimestamp', 0);
|
||||
}
|
||||
|
||||
private function getScriptItemHtml(
|
||||
?string $file,
|
||||
int $appTimestamp,
|
||||
bool $withNonce = false,
|
||||
bool $cache = true,
|
||||
?string $source = null,
|
||||
bool $async = false,
|
||||
bool $defer = false,
|
||||
): string {
|
||||
|
||||
$src = $source ?? $this->basePath . $file;
|
||||
|
||||
if ($cache) {
|
||||
$src .= '?r=' . $appTimestamp;
|
||||
}
|
||||
|
||||
$noncePart = '';
|
||||
|
||||
if ($withNonce) {
|
||||
$noncePart = " nonce=\"$this->nonce\"";
|
||||
}
|
||||
|
||||
$paramsPart = '';
|
||||
|
||||
if ($async) {
|
||||
$paramsPart .= ' async';
|
||||
}
|
||||
|
||||
if ($defer) {
|
||||
$paramsPart .= ' defer';
|
||||
}
|
||||
|
||||
return $this->getTabHtml() .
|
||||
"<script src=\"$src\" data-base-path=\"$this->basePath\"$noncePart$paramsPart></script>";
|
||||
}
|
||||
|
||||
private function getCssItemHtml(string $file, int $appTimestamp): string
|
||||
{
|
||||
$src = $this->basePath . $file . '?r=' . $appTimestamp;
|
||||
|
||||
return $this->getTabHtml() . "<link rel=\"stylesheet\" href=\"$src\">";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* href: string,
|
||||
* noTimestamp?: bool,
|
||||
* as?: string,
|
||||
* rel?: string,
|
||||
* type?: string,
|
||||
* crossorigin?: bool,
|
||||
* } $item
|
||||
*/
|
||||
private function getLinkItemHtml(array $item, int $appTimestamp): string
|
||||
{
|
||||
$href = $this->basePath . $item['href'];
|
||||
|
||||
if (empty($item['noTimestamp'])) {
|
||||
$href .= '?r=' . $appTimestamp;
|
||||
}
|
||||
|
||||
$as = $item['as'] ?? '';
|
||||
$rel = $item['rel'] ?? '';
|
||||
$type = $item['type'] ?? '';
|
||||
$part = '';
|
||||
|
||||
if ($item['crossorigin'] ?? false) {
|
||||
$part .= ' crossorigin';
|
||||
}
|
||||
|
||||
return $this->getTabHtml() .
|
||||
"<link rel=\"$rel\" href=\"$href\" as=\"$as\" as=\"$type\"$part>";
|
||||
}
|
||||
|
||||
private function getTabHtml(): string
|
||||
{
|
||||
return "\n ";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getTranspiledModuleList(): array
|
||||
{
|
||||
$modules = array_values(array_filter(
|
||||
$this->module->getList(),
|
||||
fn ($item) => $this->module->get([$item, 'jsTranspiled'])
|
||||
));
|
||||
|
||||
return array_map(
|
||||
fn ($item) => Util::fromCamelCase($item, '-'),
|
||||
$modules
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getBundledModuleList(): array
|
||||
{
|
||||
$modules = array_values(array_filter(
|
||||
$this->module->getList(),
|
||||
fn ($item) => $this->module->get([$item, 'bundled'])
|
||||
));
|
||||
|
||||
return array_map(
|
||||
fn ($item) => Util::fromCamelCase($item, '-'),
|
||||
$modules
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public function setApiUrl(string $apiUrl): void
|
||||
{
|
||||
$this->apiUrl = $apiUrl;
|
||||
}
|
||||
|
||||
public function setApplicationId(string $applicationId): void
|
||||
{
|
||||
$this->applicationId = $applicationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{string, string}
|
||||
*/
|
||||
private function getFaviconData(): array
|
||||
{
|
||||
$faviconSvgPath = $this->metadata->get('app.client.favicon') ?? $this->favicon;
|
||||
$faviconType = str_ends_with($faviconSvgPath, '.svg') ? 'image/svg+xml' : 'image/png';
|
||||
|
||||
return [$faviconSvgPath, $faviconType];
|
||||
}
|
||||
|
||||
private function escapeValue(string $value): string
|
||||
{
|
||||
$value = htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
|
||||
return str_replace(['{', '}'], ['{', '}'], $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $vars
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function prepareRelaceData(array $vars, array $data): array
|
||||
{
|
||||
$replaceData = [];
|
||||
|
||||
foreach ($vars as $key => $value) {
|
||||
$replaceData['{{' . $key . '}}'] = (string) $value;
|
||||
}
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (array_key_exists($key, $vars)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$replaceData['{{' . $key . '}}'] = (string) $value;
|
||||
}
|
||||
|
||||
return $replaceData;
|
||||
}
|
||||
|
||||
private function getApplicationName(?string $pageTitle): string
|
||||
{
|
||||
$value = $pageTitle ?? $this->config->get('applicationName', self::APP_NAME);
|
||||
|
||||
return $this->escapeValue($value);
|
||||
}
|
||||
|
||||
private function getApplicationDescription(): string
|
||||
{
|
||||
$value = $this->config->get('applicationDescription') ?? self::APP_DESCRIPTION;
|
||||
|
||||
return $this->escapeValue($value);
|
||||
}
|
||||
|
||||
|
||||
private static function boolToString(bool $useCache): string
|
||||
{
|
||||
return $useCache ? 'true' : 'false';
|
||||
}
|
||||
|
||||
private function getAjaxTimeout(): int
|
||||
{
|
||||
return (int) ($this->config->get('ajaxTimeout') ?? self::AJAX_TIMEOUT);
|
||||
}
|
||||
}
|
||||
449
application/Espo/Core/Utils/Config.php
Normal file
449
application/Espo/Core/Utils/Config.php
Normal file
@@ -0,0 +1,449 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils;
|
||||
|
||||
use Espo\Core\Utils\Config\ConfigFileManager;
|
||||
|
||||
use stdClass;
|
||||
use RuntimeException;
|
||||
|
||||
use const E_USER_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Access to the application config parameters.
|
||||
*/
|
||||
class Config
|
||||
{
|
||||
private string $systemConfigPath = 'application/Espo/Resources/defaults/systemConfig.php';
|
||||
private string $configPath = 'data/config.php';
|
||||
private string $internalConfigPath = 'data/config-internal.php';
|
||||
private string $overrideConfigPath = 'data/config-override.php';
|
||||
private string $internalOverrideConfigPath = 'data/config-internal-override.php';
|
||||
private string $stateConfigPath = 'data/state.php';
|
||||
private string $cacheTimestamp = 'cacheTimestamp';
|
||||
/** @var string[] */
|
||||
protected $associativeArrayAttributeList = [
|
||||
'currencyRates',
|
||||
'database',
|
||||
'logger',
|
||||
'defaultPermissions',
|
||||
];
|
||||
|
||||
/** @var ?array<string, mixed> */
|
||||
private $data = null;
|
||||
/** @var array<string, mixed> */
|
||||
private $changedData = [];
|
||||
/** @var string[] */
|
||||
private $removeData = [];
|
||||
/** @var string[] */
|
||||
private $internalParamList = [];
|
||||
|
||||
public function __construct(private ConfigFileManager $fileManager)
|
||||
{}
|
||||
|
||||
/**
|
||||
* A path to the config file.
|
||||
*
|
||||
* @todo Move to ConfigData.
|
||||
*/
|
||||
public function getConfigPath(): string
|
||||
{
|
||||
return $this->configPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* A path to the internal config file.
|
||||
*
|
||||
* @todo Move to ConfigData.
|
||||
* @internal
|
||||
*/
|
||||
public function getInternalConfigPath(): string
|
||||
{
|
||||
return $this->internalConfigPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Move to ConfigData.
|
||||
* @internal
|
||||
*/
|
||||
public function getStateConfigPath(): string
|
||||
{
|
||||
return $this->stateConfigPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter value.
|
||||
*
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $name, $default = null)
|
||||
{
|
||||
$keys = explode('.', $name);
|
||||
|
||||
$lastBranch = $this->getData();
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (!is_array($lastBranch) && !is_object($lastBranch)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if (is_array($lastBranch) && !array_key_exists($key, $lastBranch)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if (is_object($lastBranch) && !property_exists($lastBranch, $key)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if (is_array($lastBranch)) {
|
||||
$lastBranch = $lastBranch[$key];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$lastBranch = $lastBranch->$key;
|
||||
}
|
||||
|
||||
return $lastBranch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a parameter is set.
|
||||
*/
|
||||
public function has(string $name): bool
|
||||
{
|
||||
$keys = explode('.', $name);
|
||||
|
||||
$lastBranch = $this->getData();
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (!is_array($lastBranch) && !is_object($lastBranch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_array($lastBranch) && !array_key_exists($key, $lastBranch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_object($lastBranch) && !property_exists($lastBranch, $key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_array($lastBranch)) {
|
||||
$lastBranch = $lastBranch[$key];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$lastBranch = $lastBranch->$key;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-load data.
|
||||
*
|
||||
* @todo Get rid of this method. Use ConfigData as a dependency.
|
||||
* `$configData->update();`
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
$this->load();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v7.0. Use ConfigWriter instead.
|
||||
*
|
||||
* @param string|array<string, mixed>|stdClass $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function set($name, $value = null, bool $dontMarkDirty = false): void
|
||||
{
|
||||
if (is_object($name)) {
|
||||
$name = get_object_vars($name);
|
||||
}
|
||||
|
||||
if (!is_array($name)) {
|
||||
$name = [$name => $value];
|
||||
}
|
||||
|
||||
foreach ($name as $key => $value) {
|
||||
if (in_array($key, $this->associativeArrayAttributeList) && is_object($value)) {
|
||||
$value = (array) $value;
|
||||
}
|
||||
|
||||
$this->data[$key] = $value;
|
||||
|
||||
if (!$dontMarkDirty) {
|
||||
$this->changedData[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v7.0. Use ConfigWriter instead.
|
||||
*/
|
||||
public function remove(string $name): bool
|
||||
{
|
||||
assert($this->data !== null);
|
||||
|
||||
if (array_key_exists($name, $this->data)) {
|
||||
unset($this->data[$name]);
|
||||
|
||||
$this->removeData[] = $name;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v7.0. Use ConfigWriter instead.
|
||||
* @return bool
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
trigger_error(
|
||||
"Config::save is deprecated. Use `Espo\Core\Utils\Config\ConfigWriter` to save the config.",
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
$values = $this->changedData;
|
||||
|
||||
if (!isset($values[$this->cacheTimestamp])) {
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
$values = array_merge($this->updateCacheTimestamp(true) ?? [], $values);
|
||||
}
|
||||
|
||||
$removeData = empty($this->removeData) ? null : $this->removeData;
|
||||
|
||||
$configPath = $this->getConfigPath();
|
||||
|
||||
if (!$this->fileManager->isFile($configPath)) {
|
||||
throw new RuntimeException("Config file '$configPath' is not found.");
|
||||
}
|
||||
|
||||
$data = include($configPath);
|
||||
|
||||
if (!is_array($data)) {
|
||||
$data = include($configPath);
|
||||
}
|
||||
|
||||
if (is_array($values)) {
|
||||
foreach ($values as $key => $value) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($removeData)) {
|
||||
foreach ($removeData as $key) {
|
||||
unset($data[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_array($data)) {
|
||||
throw new RuntimeException('Invalid config data while saving.');
|
||||
}
|
||||
|
||||
$data['microtime'] = microtime(true);
|
||||
|
||||
$this->fileManager->putPhpContents($configPath, $data);
|
||||
|
||||
$this->changedData = [];
|
||||
$this->removeData = [];
|
||||
|
||||
$this->load();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isLoaded(): bool
|
||||
{
|
||||
return !empty($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getData(): array
|
||||
{
|
||||
if (!$this->isLoaded()) {
|
||||
$this->load();
|
||||
}
|
||||
|
||||
assert($this->data !== null);
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
private function load(): void
|
||||
{
|
||||
$systemData = $this->fileManager->getPhpContents($this->systemConfigPath);
|
||||
$data = $this->readFile($this->configPath);
|
||||
$internalData = $this->readFile($this->internalConfigPath);
|
||||
$overrideData = $this->readFile($this->overrideConfigPath);
|
||||
$internalOverrideData = $this->readFile($this->internalOverrideConfigPath);
|
||||
$stateConfigData = $this->readFile($this->stateConfigPath);
|
||||
|
||||
$this->data = $this->mergeData(
|
||||
systemData: $systemData,
|
||||
data: $data,
|
||||
internalData: $internalData,
|
||||
overrideData: $overrideData,
|
||||
internalOverrideData: $internalOverrideData,
|
||||
stateData: $stateConfigData,
|
||||
);
|
||||
|
||||
$this->internalParamList = array_values(array_merge(
|
||||
array_keys($internalData),
|
||||
array_keys($internalOverrideData)
|
||||
));
|
||||
|
||||
$this->fileManager->setConfig($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $systemData
|
||||
* @param array<string, mixed> $data
|
||||
* @param array<string, mixed> $internalData
|
||||
* @param array<string, mixed> $overrideData
|
||||
* @param array<string, mixed> $internalOverrideData
|
||||
* @param array<string, mixed> $stateData
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function mergeData(
|
||||
array $systemData,
|
||||
array $data,
|
||||
array $internalData,
|
||||
array $overrideData,
|
||||
array $internalOverrideData,
|
||||
array $stateData,
|
||||
): array {
|
||||
|
||||
/** @var array<string, mixed> $mergedData */
|
||||
$mergedData = Util::merge($systemData, $data);
|
||||
|
||||
/** @var array<string, mixed> $mergedData */
|
||||
$mergedData = Util::merge($mergedData, $internalData);
|
||||
|
||||
/** @var array<string, mixed> $mergedData */
|
||||
$mergedData = Util::merge($mergedData, $overrideData);
|
||||
|
||||
/** @var array<string, mixed> $mergedData */
|
||||
$mergedData = Util::merge($mergedData, $internalOverrideData);
|
||||
|
||||
/** @var array<string, mixed> $mergedData */
|
||||
$mergedData = Util::merge($mergedData, $stateData);
|
||||
|
||||
return $mergedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function readFile(string $path): array
|
||||
{
|
||||
return $this->fileManager->isFile($path) ?
|
||||
$this->fileManager->getPhpContents($path) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all parameters excluding those that are set in the internal config.
|
||||
*/
|
||||
public function getAllNonInternalData(): stdClass
|
||||
{
|
||||
$data = (object) $this->getData();
|
||||
|
||||
foreach ($this->internalParamList as $param) {
|
||||
unset($data->$param);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a parameter is set in the internal config.
|
||||
*/
|
||||
public function isInternal(string $name): bool
|
||||
{
|
||||
if (!$this->isLoaded()) {
|
||||
$this->load();
|
||||
}
|
||||
|
||||
return in_array($name, $this->internalParamList);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of 7.0. Use ConfigWriter instead.
|
||||
* @param array<string, mixed> $data
|
||||
* @return void
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
if (is_object($data)) {
|
||||
/** @noinspection PhpParamsInspection */
|
||||
$data = get_object_vars($data);
|
||||
}
|
||||
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
$this->set($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of 7.0. Use ConfigWriter instead.
|
||||
* @return ?array<string, int>
|
||||
*/
|
||||
public function updateCacheTimestamp(bool $returnOnlyValue = false)
|
||||
{
|
||||
$timestamp = [
|
||||
$this->cacheTimestamp => time()
|
||||
];
|
||||
|
||||
if ($returnOnlyValue) {
|
||||
return $timestamp;
|
||||
}
|
||||
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
$this->set($timestamp);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use Espo\Core\Config\ApplicationConfig
|
||||
*/
|
||||
public function getSiteUrl(): string
|
||||
{
|
||||
return rtrim($this->get('siteUrl'), '/');
|
||||
}
|
||||
}
|
||||
215
application/Espo/Core/Utils/Config/Access.php
Normal file
215
application/Espo/Core/Utils/Config/Access.php
Normal file
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Utils\FieldUtil;
|
||||
use Espo\Entities\Settings;
|
||||
use Espo\ORM\Defs\Params\FieldParam;
|
||||
|
||||
class Access
|
||||
{
|
||||
/** Logged-in users can read. Admin can write. */
|
||||
public const LEVEL_DEFAULT = 'default';
|
||||
/** No one can read/write. */
|
||||
public const LEVEL_SYSTEM = 'system';
|
||||
/** No one can read, admin can write. */
|
||||
public const LEVEL_INTERNAL = 'internal';
|
||||
/** Only super-admin can read/write. */
|
||||
public const LEVEL_SUPER_ADMIN = 'superAdmin';
|
||||
/** Only admin can read/write. */
|
||||
public const LEVEL_ADMIN = 'admin';
|
||||
/** Even not logged-in can read. Admin can write. */
|
||||
public const LEVEL_GLOBAL = 'global';
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private Metadata $metadata,
|
||||
private FieldUtil $fieldUtil
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get read-only parameters.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getReadOnlyParamList(): array
|
||||
{
|
||||
$itemList = [];
|
||||
|
||||
$fieldDefs = $this->metadata->get(['entityDefs', Settings::ENTITY_TYPE, 'fields']);
|
||||
|
||||
foreach ($fieldDefs as $field => $fieldParams) {
|
||||
if (empty($fieldParams[FieldParam::READ_ONLY])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->fieldUtil->getAttributeList(Settings::ENTITY_TYPE, $field) as $attribute) {
|
||||
$itemList[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
$params = $this->metadata->get(['app', 'config', 'params']) ?? [];
|
||||
|
||||
foreach ($params as $name => $item) {
|
||||
if ($item['readOnly'] ?? false) {
|
||||
$itemList[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($itemList));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAdminParamList(): array
|
||||
{
|
||||
$itemList = $this->config->get('adminItems') ?? [];
|
||||
|
||||
$fieldDefs = $this->metadata->get(['entityDefs', Settings::ENTITY_TYPE, 'fields']);
|
||||
|
||||
foreach ($fieldDefs as $field => $fieldParams) {
|
||||
if (empty($fieldParams['onlyAdmin'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->fieldUtil->getAttributeList(Settings::ENTITY_TYPE, $field) as $attribute) {
|
||||
$itemList[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(
|
||||
array_merge(
|
||||
$itemList,
|
||||
$this->getParamListByLevel(self::LEVEL_ADMIN)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getInternalParamList(): array
|
||||
{
|
||||
return $this->getParamListByLevel(self::LEVEL_INTERNAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSystemParamList(): array
|
||||
{
|
||||
$itemList = $this->config->get('systemItems') ?? [];
|
||||
|
||||
$fieldDefs = $this->metadata->get(['entityDefs', Settings::ENTITY_TYPE, 'fields']);
|
||||
|
||||
foreach ($fieldDefs as $field => $fieldParams) {
|
||||
if (empty($fieldParams['onlySystem'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->fieldUtil->getAttributeList(Settings::ENTITY_TYPE, $field) as $attribute) {
|
||||
$itemList[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(
|
||||
array_merge(
|
||||
$itemList,
|
||||
$this->getParamListByLevel(self::LEVEL_SYSTEM)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getGlobalParamList(): array
|
||||
{
|
||||
$itemList = $this->config->get('globalItems', []);
|
||||
|
||||
$fieldDefs = $this->metadata->get(['entityDefs', Settings::ENTITY_TYPE, 'fields']);
|
||||
|
||||
foreach ($fieldDefs as $field => $fieldParams) {
|
||||
if (empty($fieldParams['global'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->fieldUtil->getAttributeList(Settings::ENTITY_TYPE, $field) as $attribute) {
|
||||
$itemList[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(
|
||||
array_merge(
|
||||
$itemList,
|
||||
$this->getParamListByLevel(self::LEVEL_GLOBAL)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSuperAdminParamList(): array
|
||||
{
|
||||
return array_values(
|
||||
array_merge(
|
||||
$this->config->get('superAdminItems') ?? [],
|
||||
$this->getParamListByLevel(self::LEVEL_SUPER_ADMIN)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param self::LEVEL_* $level
|
||||
* @return string[]
|
||||
*/
|
||||
private function getParamListByLevel(string $level): array
|
||||
{
|
||||
$itemList = [];
|
||||
|
||||
$params = $this->metadata->get(['app', 'config', 'params']) ?? [];
|
||||
|
||||
foreach ($params as $name => $item) {
|
||||
$levelItem = $item['level'] ?? null;
|
||||
|
||||
if ($levelItem !== $level) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemList[] = $name;
|
||||
}
|
||||
|
||||
return $itemList;
|
||||
}
|
||||
}
|
||||
83
application/Espo/Core/Utils/Config/ApplicationConfig.php
Normal file
83
application/Espo/Core/Utils/Config/ApplicationConfig.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
|
||||
/**
|
||||
* @since 9.0.0
|
||||
*/
|
||||
class ApplicationConfig
|
||||
{
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
) {}
|
||||
|
||||
public function getSiteUrl(): string
|
||||
{
|
||||
return rtrim($this->config->get('siteUrl') ?? '', '/');
|
||||
}
|
||||
|
||||
public function getDateFormat(): string
|
||||
{
|
||||
return $this->config->get('dateFormat') ?? 'DD.MM.YYYY';
|
||||
}
|
||||
|
||||
public function getTimeFormat(): string
|
||||
{
|
||||
return $this->config->get('timeFormat') ?? 'HH:mm';
|
||||
}
|
||||
|
||||
public function getTimeZone(): string
|
||||
{
|
||||
return $this->config->get('timeZone') ?? 'UTC';
|
||||
}
|
||||
|
||||
public function getLanguage(): string
|
||||
{
|
||||
return $this->config->get('language') ?? 'en_US';
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.2.0
|
||||
*/
|
||||
public function getRecordsPerPage(): int
|
||||
{
|
||||
return (int) $this->config->get('recordsPerPage');
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 10.1.0
|
||||
*/
|
||||
public function getApplicationName(): string
|
||||
{
|
||||
return $this->config->get('applicationName', 'EspoCRM');
|
||||
}
|
||||
}
|
||||
101
application/Espo/Core/Utils/Config/ConfigFileManager.php
Normal file
101
application/Espo/Core/Utils/Config/ConfigFileManager.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ConfigFileManager
|
||||
{
|
||||
protected FileManager $fileManager;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->fileManager = new FileManager();
|
||||
}
|
||||
|
||||
public function setConfig(Config $config): void
|
||||
{
|
||||
$this->fileManager = new FileManager(
|
||||
$config->get('defaultPermissions')
|
||||
);
|
||||
}
|
||||
|
||||
public function isFile(string $filePath): bool
|
||||
{
|
||||
return $this->fileManager->isFile($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function putPhpContentsInternal(string $path, array $data, bool $useRenaming = false): void
|
||||
{
|
||||
$result = $this->fileManager->putPhpContents($path, $data, true, $useRenaming);
|
||||
|
||||
if ($result === false) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function putPhpContents(string $path, array $data): void
|
||||
{
|
||||
$this->putPhpContentsInternal($path, $data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function putPhpContentsNoRenaming(string $path, array $data): void
|
||||
{
|
||||
$this->putPhpContentsInternal($path, $data, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getPhpContents(string $path): array
|
||||
{
|
||||
$data = $this->fileManager->getPhpContents($path);
|
||||
|
||||
if (!is_array($data)) {
|
||||
throw new RuntimeException("Bad data stored in '{$path}.");
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
260
application/Espo/Core/Utils/Config/ConfigWriter.php
Normal file
260
application/Espo/Core/Utils/Config/ConfigWriter.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Writes into the config.
|
||||
*/
|
||||
class ConfigWriter
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private $changedData = [];
|
||||
/** @var string[] */
|
||||
private $removeParamList = [];
|
||||
/** @var string[] */
|
||||
protected $associativeArrayAttributeList = [
|
||||
'currencyRates',
|
||||
'database',
|
||||
'logger',
|
||||
'defaultPermissions',
|
||||
];
|
||||
|
||||
private string $cacheTimestampParam = 'cacheTimestamp';
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private ConfigWriterFileManager $fileManager,
|
||||
private ConfigWriterHelper $helper,
|
||||
private InternalConfigHelper $internalConfigHelper
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Set a parameter.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function set(string $name, $value): void
|
||||
{
|
||||
if (in_array($name, $this->associativeArrayAttributeList) && is_object($value)) {
|
||||
$value = (array) $value;
|
||||
}
|
||||
|
||||
$this->changedData[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple parameters.
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public function setMultiple(array $params): void
|
||||
{
|
||||
foreach ($params as $name => $value) {
|
||||
$this->set($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a parameter.
|
||||
*/
|
||||
public function remove(string $name): void
|
||||
{
|
||||
$this->removeParamList[] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save config changes to the file.
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
$changedData = $this->changedData;
|
||||
|
||||
if (!isset($changedData[$this->cacheTimestampParam])) {
|
||||
$changedData[$this->cacheTimestampParam] = $this->generateCacheTimestamp();
|
||||
}
|
||||
|
||||
$configPath = $this->config->getConfigPath();
|
||||
$internalConfigPath = $this->config->getInternalConfigPath();
|
||||
$stateConfigPath = $this->config->getStateConfigPath();
|
||||
|
||||
if (!$this->fileManager->isFile($configPath)) {
|
||||
throw new RuntimeException("Config file '$configPath' not found.");
|
||||
}
|
||||
|
||||
$data = $this->fileManager->getPhpContents($configPath);
|
||||
|
||||
$dataInternal = $this->fileManager->isFile($internalConfigPath) ?
|
||||
$this->fileManager->getPhpContents($internalConfigPath) : [];
|
||||
|
||||
$dataState = $this->fileManager->isFile($stateConfigPath) ?
|
||||
$this->fileManager->getPhpContents($stateConfigPath) : [];
|
||||
|
||||
if (!is_array($data)) {
|
||||
throw new RuntimeException("Could not read config.");
|
||||
}
|
||||
|
||||
if (!is_array($dataInternal)) {
|
||||
throw new RuntimeException("Could not read config-internal.");
|
||||
}
|
||||
|
||||
if (!is_array($dataState)) {
|
||||
throw new RuntimeException("Could not read state.");
|
||||
}
|
||||
|
||||
$toSaveInternal = false;
|
||||
$toSaveState = false;
|
||||
$toSaveMain = false;
|
||||
|
||||
foreach (array_merge(array_keys($changedData), $this->removeParamList) as $key) {
|
||||
if (array_key_exists($key, $data)) {
|
||||
$toSaveMain = true;
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $dataInternal)) {
|
||||
$toSaveInternal = true;
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $dataState)) {
|
||||
$toSaveState = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($changedData as $key => $value) {
|
||||
if ($this->internalConfigHelper->isParamForStateConfig($key)) {
|
||||
$dataState[$key] = $value;
|
||||
|
||||
unset($data[$key]);
|
||||
unset($dataInternal[$key]);
|
||||
|
||||
$toSaveState = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->internalConfigHelper->isParamForInternalConfig($key)) {
|
||||
$dataInternal[$key] = $value;
|
||||
|
||||
unset($data[$key]);
|
||||
unset($dataState[$key]);
|
||||
|
||||
$toSaveInternal = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[$key] = $value;
|
||||
|
||||
unset($dataState[$key]);
|
||||
unset($dataInternal[$key]);
|
||||
|
||||
$toSaveMain = true;
|
||||
}
|
||||
|
||||
foreach ($this->removeParamList as $key) {
|
||||
if (array_key_exists($key, $data)) {
|
||||
unset($data[$key]);
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $dataInternal)) {
|
||||
unset($dataInternal[$key]);
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $dataState)) {
|
||||
unset($dataState[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($toSaveInternal) {
|
||||
$this->saveData($internalConfigPath, $dataInternal, 'microtimeInternal');
|
||||
}
|
||||
|
||||
if ($toSaveMain) {
|
||||
$this->saveData($configPath, $data, 'microtime');
|
||||
}
|
||||
|
||||
if ($toSaveState) {
|
||||
$this->saveData($stateConfigPath, $dataState, 'microtimeState');
|
||||
}
|
||||
|
||||
$this->changedData = [];
|
||||
$this->removeParamList = [];
|
||||
|
||||
$this->config->update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
private function saveData(string $path, array &$data, string $timeParam): void
|
||||
{
|
||||
$data[$timeParam] = $microtime = $this->helper->generateMicrotime();
|
||||
|
||||
try {
|
||||
$this->fileManager->putPhpContents($path, $data);
|
||||
} catch (Exception) {
|
||||
throw new RuntimeException("Could not save config.");
|
||||
}
|
||||
|
||||
$reloadedData = $this->fileManager->getPhpContents($path);
|
||||
|
||||
if (
|
||||
is_array($reloadedData) &&
|
||||
$microtime === ($reloadedData[$timeParam] ?? null)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->fileManager->putPhpContentsNoRenaming($path, $data);
|
||||
} catch (Exception) {
|
||||
throw new RuntimeException("Could not save config.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cache timestamp.
|
||||
*
|
||||
* @todo Remove? Saving re-writes the cache timestamp anyway.
|
||||
*/
|
||||
public function updateCacheTimestamp(): void
|
||||
{
|
||||
$this->set($this->cacheTimestampParam, $this->generateCacheTimestamp());
|
||||
}
|
||||
|
||||
protected function generateCacheTimestamp(): int
|
||||
{
|
||||
return $this->helper->generateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
122
application/Espo/Core/Utils/Config/ConfigWriterFileManager.php
Normal file
122
application/Espo/Core/Utils/Config/ConfigWriterFileManager.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ConfigWriterFileManager
|
||||
{
|
||||
private FileManager $fileManager;
|
||||
|
||||
/**
|
||||
* @param ?array{
|
||||
* dir: string|int|null,
|
||||
* file: string|int|null,
|
||||
* user: string|int|null,
|
||||
* group: string|int|null,
|
||||
* } $defaultPermissions
|
||||
*/
|
||||
public function __construct(?Config $config = null, ?array $defaultPermissions = null)
|
||||
{
|
||||
$defaultPermissionsToSet = null;
|
||||
|
||||
if ($defaultPermissions) {
|
||||
$defaultPermissionsToSet = $defaultPermissions;
|
||||
} else if ($config) {
|
||||
$defaultPermissionsToSet = $config->get('defaultPermissions');
|
||||
}
|
||||
|
||||
$this->fileManager = new FileManager($defaultPermissionsToSet);
|
||||
}
|
||||
|
||||
public function setConfig(Config $config): void
|
||||
{
|
||||
$this->fileManager = new FileManager(
|
||||
$config->get('defaultPermissions')
|
||||
);
|
||||
}
|
||||
|
||||
public function isFile(string $filePath): bool
|
||||
{
|
||||
return $this->fileManager->isFile($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
protected function putPhpContentsInternal(string $path, array $data, bool $useRenaming = false): void
|
||||
{
|
||||
$result = $this->fileManager->putPhpContents($path, $data, true, $useRenaming);
|
||||
|
||||
if ($result === false) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data $data
|
||||
*/
|
||||
public function putPhpContents(string $path, array $data): void
|
||||
{
|
||||
$this->putPhpContentsInternal($path, $data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function putPhpContentsNoRenaming(string $path, array $data): void
|
||||
{
|
||||
$this->putPhpContentsInternal($path, $data, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Supposed to return array. False means the file is being written or corrupted.
|
||||
* @return array<string, mixed>|false
|
||||
*/
|
||||
public function getPhpContents(string $path)
|
||||
{
|
||||
try {
|
||||
$data = $this->fileManager->getPhpContents($path);
|
||||
} catch (RuntimeException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_array($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
43
application/Espo/Core/Utils/Config/ConfigWriterHelper.php
Normal file
43
application/Espo/Core/Utils/Config/ConfigWriterHelper.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
class ConfigWriterHelper
|
||||
{
|
||||
public function generateCacheTimestamp(): int
|
||||
{
|
||||
return time();
|
||||
}
|
||||
|
||||
public function generateMicrotime(): float
|
||||
{
|
||||
return microtime(true);
|
||||
}
|
||||
}
|
||||
73
application/Espo/Core/Utils/Config/InternalConfigHelper.php
Normal file
73
application/Espo/Core/Utils/Config/InternalConfigHelper.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
class InternalConfigHelper
|
||||
{
|
||||
/** @var string[] */
|
||||
private array $stateParamList = [
|
||||
'appTimestamp',
|
||||
'cacheTimestamp',
|
||||
'version',
|
||||
'latestVersion',
|
||||
'latestExtensionVersions',
|
||||
'currencyRates',
|
||||
];
|
||||
|
||||
public function __construct(private Config $config, private Metadata $metadata)
|
||||
{}
|
||||
|
||||
public function isParamForStateConfig(string $name): bool
|
||||
{
|
||||
return in_array($name, $this->stateParamList);
|
||||
}
|
||||
|
||||
public function isParamForInternalConfig(string $name): bool
|
||||
{
|
||||
if ($this->config->isInternal($name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($name, $this->config->get('systemItems') ?? [])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$level = $this->metadata->get(['app', 'config', 'params', $name, 'level']);
|
||||
|
||||
if ($level === Access::LEVEL_SYSTEM || $level === Access::LEVEL_INTERNAL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class MissingDefaultParamsSaver
|
||||
{
|
||||
private string $defaultConfigPath = 'application/Espo/Resources/defaults/config.php';
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private ConfigWriter $configWriter,
|
||||
private FileManager $fileManager
|
||||
) {}
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
$data = $this->fileManager->getPhpSafeContents($this->defaultConfigPath);
|
||||
|
||||
if (!is_array($data)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $data */
|
||||
|
||||
$newData = [];
|
||||
|
||||
foreach ($data as $param => $value) {
|
||||
if ($this->config->has($param)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newData[$param] = $value;
|
||||
}
|
||||
|
||||
if (!count($newData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->configWriter->setMultiple($newData);
|
||||
$this->configWriter->save();
|
||||
}
|
||||
}
|
||||
56
application/Espo/Core/Utils/Config/Populator.php
Normal file
56
application/Espo/Core/Utils/Config/Populator.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
|
||||
use Espo\Core\Utils\File\Manager;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @since 10.0.0
|
||||
*/
|
||||
class Populator
|
||||
{
|
||||
private string $configFile = 'data/config.php';
|
||||
|
||||
public function __construct(
|
||||
private Manager $fileManager,
|
||||
private MissingDefaultParamsSaver $missingDefaultParamsSaver,
|
||||
) {}
|
||||
|
||||
public function populate(): void
|
||||
{
|
||||
if (!$this->fileManager->exists($this->configFile)) {
|
||||
$this->fileManager->putPhpContents($this->configFile, []);
|
||||
}
|
||||
|
||||
$this->missingDefaultParamsSaver->process();
|
||||
}
|
||||
}
|
||||
47
application/Espo/Core/Utils/Config/StateConfig.php
Normal file
47
application/Espo/Core/Utils/Config/StateConfig.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
|
||||
/**
|
||||
* @since 10.1.0
|
||||
*/
|
||||
class StateConfig
|
||||
{
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
) {}
|
||||
|
||||
public function getCacheTimestamp(): int
|
||||
{
|
||||
return $this->config->get('cacheTimestamp') ?? 0;
|
||||
}
|
||||
}
|
||||
52
application/Espo/Core/Utils/Config/StateConfigDirect.php
Normal file
52
application/Espo/Core/Utils/Config/StateConfigDirect.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @since 10.1.0
|
||||
*/
|
||||
class StateConfigDirect
|
||||
{
|
||||
private string $stateConfigPath = 'data/state.php';
|
||||
|
||||
private const string CACHE_TIMESTAMP_KEY = 'cacheTimestamp';
|
||||
|
||||
public function __construct(
|
||||
private ConfigFileManager $configFileManager,
|
||||
) {}
|
||||
|
||||
public function getCacheTimestamp(): int
|
||||
{
|
||||
$data = $this->configFileManager->getPhpContents($this->stateConfigPath);
|
||||
|
||||
return $data[self::CACHE_TIMESTAMP_KEY] ?? 0;
|
||||
}
|
||||
}
|
||||
81
application/Espo/Core/Utils/Config/SystemConfig.php
Normal file
81
application/Espo/Core/Utils/Config/SystemConfig.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Config;
|
||||
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
|
||||
/**
|
||||
* @since 9.1.0
|
||||
*/
|
||||
class SystemConfig
|
||||
{
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
) {}
|
||||
|
||||
public function useCache(): bool
|
||||
{
|
||||
return (bool) $this->config->get('useCache');
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
return (string) $this->config->get('version');
|
||||
}
|
||||
|
||||
/**
|
||||
* Is restricted mode.
|
||||
*
|
||||
* @since 9.1.8
|
||||
*/
|
||||
public function isRestrictedMode(): bool
|
||||
{
|
||||
return (bool) $this->config->get('restrictedMode');
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 10.0.0
|
||||
*/
|
||||
public function isCronEnabled(): bool
|
||||
{
|
||||
return !$this->config->get('cronDisabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* A maintenance mode. Only admin can log in.
|
||||
*
|
||||
* @since 10.1.0
|
||||
*/
|
||||
public function isMaintenanceMode(): bool
|
||||
{
|
||||
return (bool) $this->config->get('maintenanceMode');
|
||||
}
|
||||
}
|
||||
104
application/Espo/Core/Utils/Crypt.php
Normal file
104
application/Espo/Core/Utils/Crypt.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class Crypt
|
||||
{
|
||||
private string $cryptKey;
|
||||
private ?string $key = null;
|
||||
private ?string $iv = null;
|
||||
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
$this->cryptKey = $config->get('cryptKey', '');
|
||||
}
|
||||
|
||||
private function getKey(): string
|
||||
{
|
||||
if ($this->key === null) {
|
||||
$this->key = hash('sha256', $this->cryptKey, true);
|
||||
}
|
||||
|
||||
if (!$this->key) {
|
||||
throw new RuntimeException("Could not hash the key.");
|
||||
}
|
||||
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
private function getIv(): string
|
||||
{
|
||||
if ($this->iv === null) {
|
||||
if (!extension_loaded('openssl')) {
|
||||
throw new RuntimeException("openssl extension is not loaded.");
|
||||
}
|
||||
|
||||
$iv = openssl_random_pseudo_bytes(16);
|
||||
|
||||
$this->iv = $iv;
|
||||
}
|
||||
|
||||
return $this->iv;
|
||||
}
|
||||
|
||||
public function encrypt(string $string): string
|
||||
{
|
||||
$iv = $this->getIv();
|
||||
|
||||
if (!extension_loaded('openssl')) {
|
||||
throw new RuntimeException("openssl extension is not loaded.");
|
||||
}
|
||||
|
||||
return base64_encode(
|
||||
openssl_encrypt($string, 'aes-256-cbc', $this->getKey(), OPENSSL_RAW_DATA, $iv) . $iv
|
||||
);
|
||||
}
|
||||
|
||||
public function decrypt(string $encryptedString): string
|
||||
{
|
||||
$encryptedStringDecoded = base64_decode($encryptedString);
|
||||
$string = substr($encryptedStringDecoded, 0, strlen($encryptedStringDecoded) - 16);
|
||||
$iv = substr($encryptedStringDecoded, -16);
|
||||
|
||||
if (!extension_loaded('openssl')) {
|
||||
throw new RuntimeException("openssl extension is not loaded.");
|
||||
}
|
||||
|
||||
$value = openssl_decrypt($string, 'aes-256-cbc', $this->getKey(), OPENSSL_RAW_DATA, $iv);
|
||||
|
||||
if ($value === false) {
|
||||
throw new RuntimeException("OpenSSL decrypt failure.");
|
||||
}
|
||||
|
||||
return trim($value);
|
||||
}
|
||||
}
|
||||
121
application/Espo/Core/Utils/Currency/DatabasePopulator.php
Normal file
121
application/Espo/Core/Utils/Currency/DatabasePopulator.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Currency;
|
||||
|
||||
use Espo\Core\Currency\ConfigDataProvider;
|
||||
use Espo\Entities\Currency;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\Tools\Currency\RateEntryProvider;
|
||||
use Espo\Tools\Currency\Exceptions\NotEnabled;
|
||||
|
||||
/**
|
||||
* Populates currency rates into database.
|
||||
*/
|
||||
class DatabasePopulator
|
||||
{
|
||||
private const int PRECISION = 6;
|
||||
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private RateEntryProvider $rateEntryProvider,
|
||||
) {}
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
$defaultCurrency = $this->configDataProvider->getDefaultCurrency();
|
||||
$baseCurrency = $this->configDataProvider->getBaseCurrency();
|
||||
|
||||
$currencyRates = $this->prepareRates($defaultCurrency, $baseCurrency);
|
||||
|
||||
$this->entityManager->getTransactionManager()->start();
|
||||
|
||||
$delete = $this->entityManager->getQueryBuilder()
|
||||
->delete()
|
||||
->from(Currency::ENTITY_TYPE)
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($delete);
|
||||
|
||||
foreach ($currencyRates as $currencyName => $rate) {
|
||||
$this->entityManager->createEntity(Currency::ENTITY_TYPE, [
|
||||
Attribute::ID => $currencyName,
|
||||
Currency::FIELD_RATE => $rate,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->entityManager->getTransactionManager()->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, float> $currencyRates
|
||||
* @return array<string, float>
|
||||
*/
|
||||
private function exchangeRates(string $baseCurrency, string $defaultCurrency, array $currencyRates): array
|
||||
{
|
||||
$defaultCurrencyRate = round(1 / $currencyRates[$defaultCurrency], self::PRECISION);
|
||||
|
||||
$exchangedRates = [];
|
||||
$exchangedRates[$baseCurrency] = $defaultCurrencyRate;
|
||||
|
||||
unset($currencyRates[$baseCurrency], $currencyRates[$defaultCurrency]);
|
||||
|
||||
foreach ($currencyRates as $currencyName => $rate) {
|
||||
$exchangedRates[$currencyName] = round($rate * $defaultCurrencyRate, self::PRECISION);
|
||||
}
|
||||
|
||||
return $exchangedRates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, float>
|
||||
*/
|
||||
private function prepareRates(string $defaultCurrency, string $baseCurrency): array
|
||||
{
|
||||
$currencyRates = [];
|
||||
|
||||
foreach ($this->configDataProvider->getCurrencyList() as $itCode) {
|
||||
try {
|
||||
$currencyRates[$itCode] = (float) ($this->rateEntryProvider->getRate($itCode) ?? 1);
|
||||
} catch (NotEnabled) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($defaultCurrency !== $baseCurrency) {
|
||||
$currencyRates = $this->exchangeRates($baseCurrency, $defaultCurrency, $currencyRates);
|
||||
}
|
||||
|
||||
$currencyRates[$defaultCurrency] = 1.00;
|
||||
|
||||
return $currencyRates;
|
||||
}
|
||||
}
|
||||
169
application/Espo/Core/Utils/DataCache.php
Normal file
169
application/Espo/Core/Utils/DataCache.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils;
|
||||
|
||||
use Espo\Core\Utils\Cache\CacheItem;
|
||||
use Espo\Core\Utils\Cache\Exceptions\InvalidArgument;
|
||||
use Espo\Core\Utils\Cache\Exceptions\PersistenceError;
|
||||
use Espo\Core\Utils\Cache\Exceptions\ReadError;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\Cache\InvalidArgumentException;
|
||||
use stdClass;
|
||||
|
||||
class DataCache
|
||||
{
|
||||
/**
|
||||
* DI qualifier. Can be applied to the DataCache and DataCacheAccess dependencies.
|
||||
*
|
||||
* @since 10.1.0
|
||||
*/
|
||||
public const string QUALIFIER_SYSTEM = 'system';
|
||||
|
||||
public function __construct(
|
||||
private CacheItemPoolInterface $pool,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Whether is cached.
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
{
|
||||
try {
|
||||
return $this->pool->hasItem($key);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new InvalidArgument(previous: $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a stored value. Returns null if not hit.
|
||||
*
|
||||
* @return array<int|string, mixed>|stdClass|null
|
||||
* @throws ReadError If data is corrupted.
|
||||
*/
|
||||
public function get(string $key): array|stdClass|null
|
||||
{
|
||||
try {
|
||||
$item = $this->pool->getItem($key);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new InvalidArgument(previous: $e);
|
||||
}
|
||||
|
||||
if (!$item->isHit()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = $item->get();
|
||||
|
||||
if (!is_array($value) && !$value instanceof stdClass) {
|
||||
throw new ReadError("Bad cache data by key '$key'.");
|
||||
}
|
||||
|
||||
/** @var array<int|string, mixed>|stdClass */
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get a stored value. Does not throw ReadError.
|
||||
*
|
||||
* @return array<int|string, mixed>|stdClass|null
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public function tryGet(string $key): array|stdClass|null
|
||||
{
|
||||
if (!$this->has($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->get($key);
|
||||
} catch (ReadError) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store in cache.
|
||||
*
|
||||
* @param array<int|string, mixed>|stdClass $data
|
||||
* @throws PersistenceError
|
||||
*/
|
||||
public function store(string $key, $data): void
|
||||
{
|
||||
/** @phpstan-var mixed $data */
|
||||
|
||||
if (!$this->checkDataIsValid($data)) {
|
||||
throw new InvalidArgument("Bad cache data type.");
|
||||
}
|
||||
|
||||
$item = new CacheItem(
|
||||
key: $key,
|
||||
value: $data,
|
||||
);
|
||||
|
||||
$result = $this->pool->save($item);
|
||||
|
||||
if ($result === false) {
|
||||
throw new PersistenceError("Could not store '$key'.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes in cache.
|
||||
*
|
||||
* @throws InvalidArgument
|
||||
*/
|
||||
public function clear(string $key): void
|
||||
{
|
||||
$this->pool->deleteItem($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all cache.
|
||||
*
|
||||
* @since 10.1.0
|
||||
* @throws PersistenceError
|
||||
*/
|
||||
public function clearAll(): void
|
||||
{
|
||||
$result = $this->pool->clear();
|
||||
|
||||
if ($result === false) {
|
||||
throw new PersistenceError("Could not clear cache.");
|
||||
}
|
||||
}
|
||||
|
||||
private function checkDataIsValid(mixed $data): bool
|
||||
{
|
||||
$isInvalid = !is_array($data) && !$data instanceof stdClass;
|
||||
|
||||
return !$isInvalid;
|
||||
}
|
||||
}
|
||||
253
application/Espo/Core/Utils/DataUtil.php
Normal file
253
application/Espo/Core/Utils/DataUtil.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
|
||||
class DataUtil
|
||||
{
|
||||
/**
|
||||
* @param array<string|int, mixed>|stdClass $data
|
||||
* @param array<int, string|string[]>|string $unsetList
|
||||
* @return array<string|int, mixed>|stdClass
|
||||
*/
|
||||
public static function unsetByKey(&$data, $unsetList, bool $removeEmptyItems = false)
|
||||
{
|
||||
if (empty($unsetList)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if (is_string($unsetList)) {
|
||||
$unsetList = [$unsetList];
|
||||
} else if (!is_array($unsetList)) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
foreach ($unsetList as $unsetItem) {
|
||||
if (is_array($unsetItem)) {
|
||||
$path = $unsetItem;
|
||||
} else if (is_string($unsetItem)) {
|
||||
$path = explode('.', $unsetItem);
|
||||
} else {
|
||||
throw new LogicException('Bad unset parameter');
|
||||
}
|
||||
|
||||
$pointer = &$data;
|
||||
|
||||
$elementArr = [];
|
||||
$elementArr[] = &$pointer;
|
||||
|
||||
foreach ($path as $i => $key) {
|
||||
if ($i === count($path) - 1) {
|
||||
if (is_array($pointer)) {
|
||||
if (array_key_exists($key, $pointer)) {
|
||||
unset($pointer[$key]);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_object($pointer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($pointer->$key);
|
||||
|
||||
if (!$removeEmptyItems) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($j = count($elementArr); $j > 0; $j--) {
|
||||
$pointerBack =& $elementArr[$j];
|
||||
|
||||
if (is_object($pointerBack) && count(get_object_vars($pointerBack)) === 0) {
|
||||
$previous =& $elementArr[$j - 1];
|
||||
|
||||
if (is_object($previous)) {
|
||||
$key = $path[$j - 1];
|
||||
unset($previous->$key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($pointer)) {
|
||||
$pointer = &$pointer[$key];
|
||||
} else if (is_object($pointer)) {
|
||||
if (!property_exists($pointer, $key)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$pointer = &$pointer->$key;
|
||||
}
|
||||
|
||||
$elementArr[] = &$pointer;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, mixed>|stdClass $data
|
||||
* @param mixed $needle
|
||||
* @return array<string|int, mixed>|stdClass
|
||||
*/
|
||||
public static function unsetByValue(&$data, $needle)
|
||||
{
|
||||
if (is_object($data)) {
|
||||
foreach (get_object_vars($data) as $key => $value) {
|
||||
self::unsetByValue($data->$key, $needle);
|
||||
|
||||
if ($data->$key === $needle) {
|
||||
unset($data->$key);
|
||||
}
|
||||
}
|
||||
} else if (is_array($data)) {
|
||||
$doReindex = false;
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
self::unsetByValue($data[$key], $needle);
|
||||
|
||||
if ($data[$key] === $needle) {
|
||||
unset($data[$key]);
|
||||
$doReindex = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($doReindex) {
|
||||
$data = array_values($data);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|stdClass $data
|
||||
* @param array<string, mixed>|stdClass $overrideData
|
||||
* @return array<string|int, mixed>|stdClass
|
||||
*/
|
||||
public static function merge($data, $overrideData)
|
||||
{
|
||||
$appendIdentifier = '__APPEND__';
|
||||
|
||||
/** @var mixed $data */
|
||||
/** @var mixed $overrideData */
|
||||
|
||||
if (empty($data) && empty($overrideData)) {
|
||||
if (is_array($data) || is_array($overrideData)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array<string|int, mixed>|stdClass */
|
||||
return $overrideData; /** @phpstan-ignore-line */
|
||||
}
|
||||
|
||||
if (is_object($overrideData)) {
|
||||
if (empty($data)) {
|
||||
$data = (object) [];
|
||||
}
|
||||
|
||||
foreach (get_object_vars($overrideData) as $key => $value) {
|
||||
if (isset($data->$key)) {
|
||||
$data->$key = self::merge($data->$key, $overrideData->$key);
|
||||
} else {
|
||||
$data->$key = $overrideData->$key;
|
||||
self::unsetByValue($data->$key, $appendIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
if (is_array($overrideData)) {
|
||||
if (empty($data)) {
|
||||
$data = [];
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $data */
|
||||
|
||||
if (in_array($appendIdentifier, $overrideData)) {
|
||||
foreach ($overrideData as $item) {
|
||||
if ($item === $appendIdentifier) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[] = $item;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
return $overrideData;
|
||||
}
|
||||
|
||||
/** @var array<string|int, mixed>|stdClass */
|
||||
return $overrideData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $path
|
||||
* @since 9.2.0
|
||||
* @internal
|
||||
*/
|
||||
public static function setByPath(stdClass $data, array $path, mixed $value): void
|
||||
{
|
||||
if (count($path) === 1) {
|
||||
$property = $path[0];
|
||||
|
||||
$data->$property = $value;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($path) < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$property = array_shift($path);
|
||||
|
||||
if (!isset($data->$property)) {
|
||||
$data->$property = (object) [];
|
||||
}
|
||||
|
||||
if (!($data->$property instanceof stdClass)) {
|
||||
throw new RuntimeException("Cannot set by path. Not an object.");
|
||||
}
|
||||
|
||||
self::setByPath($data->$property, $path, $value);
|
||||
}
|
||||
}
|
||||
35
application/Espo/Core/Utils/Database/ConfigDataProvider.php
Normal file
35
application/Espo/Core/Utils/Database/ConfigDataProvider.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database;
|
||||
|
||||
interface ConfigDataProvider
|
||||
{
|
||||
public function getPlatform(): string;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Dbal;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
|
||||
interface ConnectionFactory
|
||||
{
|
||||
public function create(DatabaseParams $databaseParams): Connection;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Dbal;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class ConnectionFactoryFactory
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private InjectableFactory $injectableFactory
|
||||
) {}
|
||||
|
||||
public function create(string $platform, PDO $pdo): ConnectionFactory
|
||||
{
|
||||
/** @var ?class-string<ConnectionFactory> $className */
|
||||
$className = $this->metadata
|
||||
->get(['app', 'databasePlatforms', $platform, 'dbalConnectionFactoryClassName']);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("No DBAL ConnectionFactory for {$platform}.");
|
||||
}
|
||||
|
||||
$bindingContainer = BindingContainerBuilder::create()
|
||||
->bindInstance(PDO::class, $pdo)
|
||||
->build();
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Dbal\Factories;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\PDO\MySQL\Driver as PDOMySQLDriver;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
|
||||
use Espo\Core\Utils\Database\Dbal\ConnectionFactory;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
use Espo\ORM\PDO\Options as PdoOptions;
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class MysqlConnectionFactory implements ConnectionFactory
|
||||
{
|
||||
private const DEFAULT_CHARSET = 'utf8mb4';
|
||||
|
||||
public function __construct(
|
||||
private PDO $pdo
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function create(DatabaseParams $databaseParams): Connection
|
||||
{
|
||||
$driver = new PDOMySQLDriver();
|
||||
|
||||
if (!$databaseParams->getHost()) {
|
||||
throw new RuntimeException("No database host in config.");
|
||||
}
|
||||
|
||||
$params = [
|
||||
'pdo' => $this->pdo,
|
||||
'host' => $databaseParams->getHost(),
|
||||
'driverOptions' => PdoOptions::getOptionsFromDatabaseParams($databaseParams),
|
||||
];
|
||||
|
||||
if ($databaseParams->getName() !== null) {
|
||||
$params['dbname'] = $databaseParams->getName();
|
||||
}
|
||||
|
||||
if ($databaseParams->getPort() !== null) {
|
||||
$params['port'] = $databaseParams->getPort();
|
||||
}
|
||||
|
||||
if ($databaseParams->getUsername() !== null) {
|
||||
$params['user'] = $databaseParams->getUsername();
|
||||
}
|
||||
|
||||
if ($databaseParams->getPassword() !== null) {
|
||||
$params['password'] = $databaseParams->getPassword();
|
||||
}
|
||||
|
||||
$params['charset'] = $databaseParams->getCharset() ?? self::DEFAULT_CHARSET;
|
||||
|
||||
return new Connection($params, $driver);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Dbal\Factories;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\PDO\PgSQL\Driver as PostgreSQLDriver;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Espo\Core\Utils\Database\Dbal\ConnectionFactory;
|
||||
use Espo\Core\Utils\Database\Dbal\Platforms\PostgresqlPlatform;
|
||||
use Espo\Core\Utils\Database\Helper;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
use Espo\ORM\PDO\Options as PdoOptions;
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class PostgresqlConnectionFactory implements ConnectionFactory
|
||||
{
|
||||
private const DEFAULT_CHARSET = 'utf8';
|
||||
|
||||
public function __construct(
|
||||
private PDO $pdo,
|
||||
private Helper $helper
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function create(DatabaseParams $databaseParams): Connection
|
||||
{
|
||||
$driver = new PostgreSQLDriver();
|
||||
|
||||
if (!$databaseParams->getHost()) {
|
||||
throw new RuntimeException("No database host in config.");
|
||||
}
|
||||
|
||||
$platform = new PostgresqlPlatform();
|
||||
|
||||
if ($databaseParams->getName()) {
|
||||
$platform->setTextSearchConfig($this->helper->getParam('default_text_search_config'));
|
||||
}
|
||||
|
||||
$params = [
|
||||
'platform' => $platform,
|
||||
'pdo' => $this->pdo,
|
||||
'host' => $databaseParams->getHost(),
|
||||
'driverOptions' => PdoOptions::getOptionsFromDatabaseParams($databaseParams),
|
||||
];
|
||||
|
||||
if ($databaseParams->getName() !== null) {
|
||||
$params['dbname'] = $databaseParams->getName();
|
||||
}
|
||||
|
||||
if ($databaseParams->getPort() !== null) {
|
||||
$params['port'] = $databaseParams->getPort();
|
||||
}
|
||||
|
||||
if ($databaseParams->getUsername() !== null) {
|
||||
$params['user'] = $databaseParams->getUsername();
|
||||
}
|
||||
|
||||
if ($databaseParams->getPassword() !== null) {
|
||||
$params['password'] = $databaseParams->getPassword();
|
||||
}
|
||||
|
||||
$params['charset'] = $databaseParams->getCharset() ?? self::DEFAULT_CHARSET;
|
||||
|
||||
return new Connection($params, $driver);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Dbal\Platforms\Keywords;
|
||||
|
||||
use Doctrine\DBAL\Platforms\Keywords\MariaDBKeywords;
|
||||
|
||||
/**
|
||||
* 'LEAD' happened to be a reserved words on some environments.
|
||||
*/
|
||||
class MariaDb102Keywords extends MariaDBKeywords
|
||||
{
|
||||
/** @deprecated */
|
||||
public function getName(): string
|
||||
{
|
||||
return 'MariaDb102';
|
||||
}
|
||||
|
||||
protected function getKeywords(): array
|
||||
{
|
||||
return [
|
||||
...parent::getKeywords(),
|
||||
'LEAD',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Dbal\Platforms;
|
||||
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\PostgreSQLSchemaManager as BasePostgreSQLSchemaManager;
|
||||
|
||||
class PostgreSQLSchemaManager extends BasePostgreSQLSchemaManager
|
||||
{
|
||||
/**
|
||||
* DBAL does not add the 'fulltext' flag on reverse engineering.
|
||||
*/
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
|
||||
{
|
||||
$indexes = parent::_getPortableTableIndexesList($tableIndexes, $tableName);
|
||||
|
||||
foreach ($tableIndexes as $row) {
|
||||
$key = $row['relname'];
|
||||
|
||||
if (is_string($tableName) && str_starts_with($tableName, '"') && str_ends_with($tableName, '"')) {
|
||||
$tableName = substr($tableName, 1, -1);
|
||||
}
|
||||
|
||||
if ($key === "idx_{$tableName}_system_full_text_search") {
|
||||
$sql = "SELECT indexdef FROM pg_indexes WHERE indexname = '{$key}'";
|
||||
|
||||
$rows = $this->_conn->fetchAllAssociative($sql);
|
||||
|
||||
if (!$rows) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columns = self::parseColumnsIndexFromDeclaration($rows[0]['indexdef']);
|
||||
|
||||
$indexes[$key] = new Index(
|
||||
$key,
|
||||
$columns,
|
||||
false,
|
||||
false,
|
||||
['fulltext']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function parseColumnsIndexFromDeclaration(string $string): array
|
||||
{
|
||||
preg_match('/to_tsvector\((.*),(.*)\)/i', $string, $matches);
|
||||
|
||||
if (!$matches || count($matches) < 3) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$part = $matches[2];
|
||||
|
||||
$part = str_replace("|| ' '::text", '', $part);
|
||||
$part = str_replace("::text", '', $part);
|
||||
$part = str_replace(" ", '', $part);
|
||||
$part = str_replace("||", ' ', $part);
|
||||
$part = str_replace("(", '', $part);
|
||||
$part = str_replace(")", '', $part);
|
||||
|
||||
$list = array_map(
|
||||
fn ($item) => trim($item),
|
||||
explode(' ', $part)
|
||||
);
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Dbal\Platforms;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQL100Platform;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
|
||||
class PostgresqlPlatform extends PostgreSQL100Platform
|
||||
{
|
||||
private const TEXT_SEARCH_CONFIG = 'pg_catalog.simple';
|
||||
|
||||
private ?string $textSearchConfig;
|
||||
|
||||
public function setTextSearchConfig(?string $textSearchConfig): void
|
||||
{
|
||||
$this->textSearchConfig = $textSearchConfig;
|
||||
}
|
||||
|
||||
public function createSchemaManager(Connection $connection): PostgreSQLSchemaManager
|
||||
{
|
||||
return new PostgreSQLSchemaManager($connection, $this);
|
||||
}
|
||||
|
||||
public function getCreateIndexSQL(Index $index, $table)
|
||||
{
|
||||
if (!$index->hasFlag('fulltext')) {
|
||||
return parent::getCreateIndexSQL($index, $table);
|
||||
}
|
||||
|
||||
if ($table instanceof Table) {
|
||||
$table = $table->getQuotedName($this);
|
||||
}
|
||||
|
||||
$name = $index->getQuotedName($this);
|
||||
$columns = $index->getColumns();
|
||||
|
||||
if (count($columns) === 0) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Incomplete or invalid index definition %s on table %s',
|
||||
$name,
|
||||
$table,
|
||||
));
|
||||
}
|
||||
|
||||
$columnsPart = implode(" || ' ' || ", $index->getQuotedColumns($this));
|
||||
$partialPart = $this->getPartialIndexSQL($index);
|
||||
|
||||
$textSearchConfig = $this->textSearchConfig ?? self::TEXT_SEARCH_CONFIG;
|
||||
$textSearchConfig = preg_replace('/[^A-Za-z0-9_.\-]+/', '', $textSearchConfig) ?? '';
|
||||
$configPart = $this->quoteStringLiteral($textSearchConfig);
|
||||
|
||||
return "CREATE INDEX {$name} ON {$table} USING GIN (TO_TSVECTOR({$configPart}, {$columnsPart})) {$partialPart}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Dbal\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\TextType;
|
||||
|
||||
/**
|
||||
* MySQL only.
|
||||
*/
|
||||
class LongtextType extends TextType
|
||||
{
|
||||
public const NAME = 'longtext';
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function getSQLDeclaration(array $column, AbstractPlatform $platform)
|
||||
{
|
||||
return 'LONGTEXT';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Dbal\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\TextType;
|
||||
|
||||
/**
|
||||
* MySQL only.
|
||||
*/
|
||||
class MediumtextType extends TextType
|
||||
{
|
||||
public const NAME = 'mediumtext';
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function getSQLDeclaration(array $column, AbstractPlatform $platform)
|
||||
{
|
||||
return 'MEDIUMTEXT';
|
||||
}
|
||||
}
|
||||
51
application/Espo/Core/Utils/Database/Dbal/Types/UuidType.php
Normal file
51
application/Espo/Core/Utils/Database/Dbal/Types/UuidType.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Dbal\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
/**
|
||||
* Supported in MariaDB from v10.7.
|
||||
*/
|
||||
class UuidType extends Type
|
||||
{
|
||||
public const NAME = 'uuid';
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function getSQLDeclaration(array $column, AbstractPlatform $platform)
|
||||
{
|
||||
return 'UUID';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
|
||||
class DefaultConfigDataProvider implements ConfigDataProvider
|
||||
{
|
||||
private const DEFAULT_PLATFORM = 'Mysql';
|
||||
|
||||
public function __construct(private Config $config) {}
|
||||
|
||||
public function getPlatform(): string
|
||||
{
|
||||
return $this->config->get('database.platform') ?? self::DEFAULT_PLATFORM;
|
||||
}
|
||||
}
|
||||
41
application/Espo/Core/Utils/Database/DetailsProvider.php
Normal file
41
application/Espo/Core/Utils/Database/DetailsProvider.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database;
|
||||
|
||||
interface DetailsProvider
|
||||
{
|
||||
public function getType(): string;
|
||||
|
||||
public function getVersion(): string;
|
||||
|
||||
public function getServerVersion(): string;
|
||||
|
||||
public function getParam(string $name): ?string;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class DetailsProviderFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function create(string $platform, PDO $pdo): DetailsProvider
|
||||
{
|
||||
/** @var ?class-string<DetailsProvider> $className */
|
||||
$className = $this->metadata
|
||||
->get(['app', 'databasePlatforms', $platform, 'detailsProviderClassName']);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("No Details-Provider for {$platform}.");
|
||||
}
|
||||
|
||||
$binding = BindingContainerBuilder::create()
|
||||
->bindInstance(PDO::class, $pdo)
|
||||
->build();
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $binding);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\DetailsProviders;
|
||||
|
||||
use Espo\Core\Utils\Database\DetailsProvider;
|
||||
use PDO;
|
||||
|
||||
class MysqlDetailsProvider implements DetailsProvider
|
||||
{
|
||||
public const TYPE_MYSQL = 'MySQL';
|
||||
public const TYPE_MARIADB = 'MariaDB';
|
||||
|
||||
public function __construct(
|
||||
private PDO $pdo
|
||||
) {}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
$version = $this->getFullDatabaseVersion() ?? '';
|
||||
|
||||
if (preg_match('/mariadb/i', $version)) {
|
||||
return self::TYPE_MARIADB;
|
||||
}
|
||||
|
||||
return self::TYPE_MYSQL;
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
$fullVersion = $this->getFullDatabaseVersion() ?? '';
|
||||
|
||||
if (preg_match('/[0-9]+\.[0-9]+\.[0-9]+/', $fullVersion, $match)) {
|
||||
return $match[0];
|
||||
}
|
||||
|
||||
return '0.0.0';
|
||||
}
|
||||
|
||||
public function getServerVersion(): string
|
||||
{
|
||||
return (string) $this->getParam('version');
|
||||
}
|
||||
|
||||
public function getParam(string $name): ?string
|
||||
{
|
||||
$sql = "SHOW VARIABLES LIKE :param";
|
||||
|
||||
$sth = $this->pdo->prepare($sql);
|
||||
$sth->execute([':param' => $name]);
|
||||
|
||||
$row = $sth->fetch(PDO::FETCH_NUM);
|
||||
|
||||
$index = 1;
|
||||
|
||||
$value = $row[$index] ?: null;
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
private function getFullDatabaseVersion(): ?string
|
||||
{
|
||||
$sql = "select version()";
|
||||
|
||||
$sth = $this->pdo->prepare($sql);
|
||||
$sth->execute();
|
||||
|
||||
/** @var string|null|false $result */
|
||||
$result = $sth->fetchColumn();
|
||||
|
||||
if ($result === false || $result === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\DetailsProviders;
|
||||
|
||||
use Espo\Core\Utils\Database\DetailsProvider;
|
||||
use PDO;
|
||||
|
||||
class PostgresqlDetailsProvider implements DetailsProvider
|
||||
{
|
||||
private const TYPE_POSTGRESQL = 'PostgreSQL';
|
||||
|
||||
public function __construct(private PDO $pdo)
|
||||
{}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_POSTGRESQL;
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
$fullVersion = $this->getFullDatabaseVersion() ?? '';
|
||||
|
||||
if (preg_match('/[0-9]+\.[0-9]+/', $fullVersion, $match)) {
|
||||
return $match[0];
|
||||
}
|
||||
|
||||
return '0.0';
|
||||
}
|
||||
|
||||
public function getServerVersion(): string
|
||||
{
|
||||
return (string) $this->getFullDatabaseVersion();
|
||||
}
|
||||
|
||||
public function getParam(string $name): ?string
|
||||
{
|
||||
$name = preg_replace('/[^A-Za-z0-9_]+/', '', $name);
|
||||
|
||||
$sql = "SHOW {$name}";
|
||||
|
||||
$sth = $this->pdo->query($sql);
|
||||
|
||||
if ($sth === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = $sth->fetch(PDO::FETCH_NUM);
|
||||
|
||||
if ($row === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = $row[0] ?: null;
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
private function getFullDatabaseVersion(): ?string
|
||||
{
|
||||
$sql = "select version()";
|
||||
|
||||
$sth = $this->pdo->prepare($sql);
|
||||
$sth->execute();
|
||||
|
||||
/** @var string|null|false $result */
|
||||
$result = $sth->fetchColumn();
|
||||
|
||||
if ($result === false || $result === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
150
application/Espo/Core/Utils/Database/Helper.php
Normal file
150
application/Espo/Core/Utils/Database/Helper.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database;
|
||||
|
||||
use Doctrine\DBAL\Connection as DbalConnection;
|
||||
|
||||
use Espo\Core\ORM\DatabaseParamsFactory;
|
||||
use Espo\Core\ORM\PDO\PDOFactoryFactory;
|
||||
use Espo\Core\Utils\Database\Dbal\ConnectionFactoryFactory as DBALConnectionFactoryFactory;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class Helper
|
||||
{
|
||||
private ?DbalConnection $dbalConnection = null;
|
||||
private ?PDO $pdo = null;
|
||||
|
||||
public function __construct(
|
||||
private PDOFactoryFactory $pdoFactoryFactory,
|
||||
private DBALConnectionFactoryFactory $dbalConnectionFactoryFactory,
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private DetailsProviderFactory $detailsProviderFactory,
|
||||
private DatabaseParamsFactory $databaseParamsFactory
|
||||
) {}
|
||||
|
||||
public function getDbalConnection(): DbalConnection
|
||||
{
|
||||
if (!isset($this->dbalConnection)) {
|
||||
$this->dbalConnection = $this->createDbalConnection();
|
||||
}
|
||||
|
||||
return $this->dbalConnection;
|
||||
}
|
||||
|
||||
public function getPDO(): PDO
|
||||
{
|
||||
if (!isset($this->pdo)) {
|
||||
$this->pdo = $this->createPDO();
|
||||
}
|
||||
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with another PDO connection.
|
||||
*/
|
||||
public function withPDO(PDO $pdo): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->pdo = $pdo;
|
||||
$obj->dbalConnection = null;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a PDO connection.
|
||||
*/
|
||||
public function createPDO(?DatabaseParams $params = null): PDO
|
||||
{
|
||||
$params = $params ?? $this->databaseParamsFactory->create();
|
||||
|
||||
return $this->pdoFactoryFactory
|
||||
->create($params->getPlatform() ?? '')
|
||||
->create($params);
|
||||
}
|
||||
|
||||
private function createDbalConnection(): DbalConnection
|
||||
{
|
||||
$params = $this->databaseParamsFactory->create();
|
||||
|
||||
$platform = $params->getPlatform();
|
||||
|
||||
if (!$platform) {
|
||||
throw new RuntimeException("No database platform.");
|
||||
}
|
||||
|
||||
return $this->dbalConnectionFactoryFactory
|
||||
->create($platform, $this->getPDO())
|
||||
->create($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a database type (MySQL, MariaDB, PostgreSQL).
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->createDetailsProvider()->getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a database version.
|
||||
*/
|
||||
public function getVersion(): string
|
||||
{
|
||||
return $this->createDetailsProvider()->getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a database parameter.
|
||||
*/
|
||||
public function getParam(string $name): ?string
|
||||
{
|
||||
return $this->createDetailsProvider()->getParam($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a database server version string.
|
||||
*/
|
||||
public function getServerVersion(): string
|
||||
{
|
||||
return $this->createDetailsProvider()->getServerVersion();
|
||||
}
|
||||
|
||||
private function createDetailsProvider(): DetailsProvider
|
||||
{
|
||||
$platform = $this->configDataProvider->getPlatform();
|
||||
|
||||
return $this->detailsProviderFactory->create($platform, $this->getPDO());
|
||||
}
|
||||
}
|
||||
54
application/Espo/Core/Utils/Database/MetadataProvider.php
Normal file
54
application/Espo/Core/Utils/Database/MetadataProvider.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
class MetadataProvider
|
||||
{
|
||||
private const DEFAULT_ID_LENGTH = 24;
|
||||
private const DEFAULT_ID_DB_TYPE = Types::STRING;
|
||||
|
||||
public function __construct(private Metadata $metadata)
|
||||
{}
|
||||
|
||||
public function getIdLength(): int
|
||||
{
|
||||
return $this->metadata->get(['app', 'recordId', 'length']) ??
|
||||
self::DEFAULT_ID_LENGTH;
|
||||
}
|
||||
|
||||
public function getIdDbType(): string
|
||||
{
|
||||
return $this->metadata->get(['app', 'recordId', 'dbType']) ??
|
||||
self::DEFAULT_ID_DB_TYPE;
|
||||
}
|
||||
}
|
||||
1043
application/Espo/Core/Utils/Database/Orm/Converter.php
Normal file
1043
application/Espo/Core/Utils/Database/Orm/Converter.php
Normal file
File diff suppressed because it is too large
Load Diff
178
application/Espo/Core/Utils/Database/Orm/Defs/AttributeDefs.php
Normal file
178
application/Espo/Core/Utils/Database/Orm/Defs/AttributeDefs.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\Defs;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class AttributeDefs
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $params = [];
|
||||
|
||||
private function __construct(private string $name) {}
|
||||
|
||||
public static function create(string $name): self
|
||||
{
|
||||
return new self($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attribute name.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a type.
|
||||
*
|
||||
* @return AttributeType::*
|
||||
*/
|
||||
public function getType(): ?string
|
||||
{
|
||||
/** @var ?AttributeType::* $value */
|
||||
$value = $this->getParam(AttributeParam::TYPE);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a type.
|
||||
*
|
||||
* @param AttributeType::* $type
|
||||
*/
|
||||
public function withType(string $type): self
|
||||
{
|
||||
return $this->withParam(AttributeParam::TYPE, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a DB type.
|
||||
*/
|
||||
public function withDbType(string $dbType): self
|
||||
{
|
||||
return $this->withParam(AttributeParam::DB_TYPE, $dbType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with not-storable.
|
||||
*/
|
||||
public function withNotStorable(bool $value = true): self
|
||||
{
|
||||
return $this->withParam(AttributeParam::NOT_STORABLE, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a length.
|
||||
*/
|
||||
public function withLength(int $length): self
|
||||
{
|
||||
return $this->withParam(AttributeParam::LEN, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a default value.
|
||||
*/
|
||||
public function withDefault(mixed $value): self
|
||||
{
|
||||
return $this->withParam(AttributeParam::DEFAULT, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a parameter is set.
|
||||
*/
|
||||
public function hasParam(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter value.
|
||||
*/
|
||||
public function getParam(string $name): mixed
|
||||
{
|
||||
return $this->params[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a parameter.
|
||||
*/
|
||||
public function withParam(string $name, mixed $value): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->params[$name] = $value;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone without a parameter.
|
||||
*/
|
||||
public function withoutParam(string $name): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
unset($obj->params[$name]);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with parameters merged.
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public function withParamsMerged(array $params): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
/** @var array<string, mixed> $params */
|
||||
$params = Util::merge($this->params, $params);
|
||||
|
||||
$obj->params = $params;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* To an associative array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toAssoc(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
}
|
||||
155
application/Espo/Core/Utils/Database/Orm/Defs/EntityDefs.php
Normal file
155
application/Espo/Core/Utils/Database/Orm/Defs/EntityDefs.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\Defs;
|
||||
|
||||
use Espo\ORM\Defs\Params\EntityParam;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class EntityDefs
|
||||
{
|
||||
/** @var array<string, AttributeDefs> */
|
||||
private array $attributes = [];
|
||||
/** @var array<string, RelationDefs> */
|
||||
private array $relations = [];
|
||||
/** @var array<string, IndexDefs> */
|
||||
private array $indexes = [];
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function withAttribute(AttributeDefs $attributeDefs): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->attributes[$attributeDefs->getName()] = $attributeDefs;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withRelation(RelationDefs $relationDefs): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->relations[$relationDefs->getName()] = $relationDefs;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withIndex(IndexDefs $index): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->indexes[$index->getName()] = $index;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withoutAttribute(string $name): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
unset($obj->attributes[$name]);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withoutRelation(string $name): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
unset($obj->relations[$name]);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withoutIndex(string $name): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
unset($obj->indexes[$name]);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function getAttribute(string $name): ?AttributeDefs
|
||||
{
|
||||
return $this->attributes[$name] ?? null;
|
||||
}
|
||||
|
||||
public function getRelation(string $name): ?RelationDefs
|
||||
{
|
||||
return $this->relations[$name] ?? null;
|
||||
}
|
||||
|
||||
public function getIndex(string $name): ?IndexDefs
|
||||
{
|
||||
return $this->indexes[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
public function toAssoc(): array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
if (count($this->attributes)) {
|
||||
$attributesData = [];
|
||||
|
||||
foreach ($this->attributes as $name => $attributeDefs) {
|
||||
$attributesData[$name] = $attributeDefs->toAssoc();
|
||||
}
|
||||
|
||||
$data[EntityParam::ATTRIBUTES] = $attributesData;
|
||||
}
|
||||
|
||||
if (count($this->relations)) {
|
||||
$relationsData = [];
|
||||
|
||||
foreach ($this->relations as $name => $relationDefs) {
|
||||
$relationsData[$name] = $relationDefs->toAssoc();
|
||||
}
|
||||
|
||||
$data[EntityParam::RELATIONS] = $relationsData;
|
||||
}
|
||||
|
||||
if (count($this->indexes)) {
|
||||
$indexesData = [];
|
||||
|
||||
foreach ($this->indexes as $name => $indexDefs) {
|
||||
$indexesData[$name] = $indexDefs->toAssoc();
|
||||
}
|
||||
|
||||
$data[EntityParam::INDEXES] = $indexesData;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
175
application/Espo/Core/Utils/Database/Orm/Defs/IndexDefs.php
Normal file
175
application/Espo/Core/Utils/Database/Orm/Defs/IndexDefs.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\Defs;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Defs\Params\IndexParam;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class IndexDefs
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $params = [];
|
||||
|
||||
private function __construct(private string $name) {}
|
||||
|
||||
public static function create(string $name): self
|
||||
{
|
||||
return new self($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a relation name.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a parameter is set.
|
||||
*/
|
||||
public function hasParam(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter value.
|
||||
*/
|
||||
public function getParam(string $name): mixed
|
||||
{
|
||||
return $this->params[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a parameter.
|
||||
*/
|
||||
public function withParam(string $name, mixed $value): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->params[$name] = $value;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone without a parameter.
|
||||
*/
|
||||
public function withoutParam(string $name): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
unset($obj->params[$name]);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withUnique(): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->params[IndexParam::TYPE] = 'unique';
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withoutUnique(): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
unset($obj->params[IndexParam::TYPE]);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withFlag(string $flag): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$flags = $obj->params[IndexParam::FLAGS] ?? [];
|
||||
|
||||
if (!in_array($flag, $flags)) {
|
||||
$flags[] = $flag;
|
||||
}
|
||||
|
||||
$obj->params[IndexParam::FLAGS] = $flags;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withoutFlag(string $flag): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$flags = $obj->params[IndexParam::FLAGS] ?? [];
|
||||
|
||||
$index = array_search($flag, $flags, true);
|
||||
|
||||
if ($index !== -1) {
|
||||
unset($flags[$index]);
|
||||
$flags = array_values($flags);
|
||||
}
|
||||
|
||||
$obj->params[IndexParam::FLAGS] = $flags;
|
||||
|
||||
if ($flags === []) {
|
||||
unset($obj->params[IndexParam::FLAGS]);
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with parameters merged.
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public function withParamsMerged(array $params): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
/** @var array<string, mixed> $params */
|
||||
$params = Util::merge($this->params, $params);
|
||||
|
||||
$obj->params = $params;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* To an associative array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toAssoc(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
}
|
||||
256
application/Espo/Core/Utils/Database/Orm/Defs/RelationDefs.php
Normal file
256
application/Espo/Core/Utils/Database/Orm/Defs/RelationDefs.php
Normal file
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\Defs;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class RelationDefs
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $params = [];
|
||||
|
||||
private function __construct(private string $name) {}
|
||||
|
||||
public static function create(string $name): self
|
||||
{
|
||||
return new self($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a relation name.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a type.
|
||||
*
|
||||
* @return RelationType::*
|
||||
*/
|
||||
public function getType(): ?string
|
||||
{
|
||||
/** @var ?RelationType::* */
|
||||
return $this->getParam(RelationParam::TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a type.
|
||||
*
|
||||
* @param RelationType::* $type
|
||||
*/
|
||||
public function withType(string $type): self
|
||||
{
|
||||
return $this->withParam(RelationParam::TYPE, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a foreign entity type.
|
||||
*/
|
||||
public function withForeignEntityType(string $entityType): self
|
||||
{
|
||||
return $this->withParam(RelationParam::ENTITY, $entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a foreign entity type.
|
||||
*/
|
||||
public function getForeignEntityType(): ?string
|
||||
{
|
||||
return $this->getParam(RelationParam::ENTITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a foreign relation name.
|
||||
*/
|
||||
public function withForeignRelationName(?string $name): self
|
||||
{
|
||||
return $this->withParam(RelationParam::FOREIGN, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a foreign relation name.
|
||||
*/
|
||||
public function getForeignRelationName(): ?string
|
||||
{
|
||||
return $this->getParam(RelationParam::FOREIGN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a relationship name.
|
||||
*/
|
||||
public function withRelationshipName(string $name): self
|
||||
{
|
||||
return $this->withParam(RelationParam::RELATION_NAME, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a foreign relation name.
|
||||
*/
|
||||
public function getRelationshipName(): ?string
|
||||
{
|
||||
return $this->getParam(RelationParam::RELATION_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a key.
|
||||
*/
|
||||
public function withKey(string $key): self
|
||||
{
|
||||
return $this->withParam(RelationParam::KEY, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a key.
|
||||
*/
|
||||
public function getKey(): ?string
|
||||
{
|
||||
return $this->getParam(RelationParam::KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a key.
|
||||
*/
|
||||
public function withForeignKey(string $foreignKey): self
|
||||
{
|
||||
return $this->withParam(RelationParam::FOREIGN_KEY, $foreignKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a key.
|
||||
*/
|
||||
public function getForeignKey(): ?string
|
||||
{
|
||||
return $this->getParam(RelationParam::FOREIGN_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with middle keys.
|
||||
*/
|
||||
public function withMidKeys(string $midKey, string $foreignMidKey): self
|
||||
{
|
||||
return $this->withParam(RelationParam::MID_KEYS, [$midKey, $foreignMidKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a parameter is set.
|
||||
*/
|
||||
public function hasParam(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter value.
|
||||
*/
|
||||
public function getParam(string $name): mixed
|
||||
{
|
||||
return $this->params[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a parameter.
|
||||
*/
|
||||
public function withParam(string $name, mixed $value): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->params[$name] = $value;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone without a parameter.
|
||||
*/
|
||||
public function withoutParam(string $name): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
unset($obj->params[$name]);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with conditions. Conditions are used for relationships that share a same middle table.
|
||||
*
|
||||
* @param array<string, scalar|(array<int, mixed>)|null> $conditions
|
||||
*/
|
||||
public function withConditions(array $conditions): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
return $obj->withParam(RelationParam::CONDITIONS, $conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with an additional middle table column.
|
||||
*/
|
||||
public function withAdditionalColumn(AttributeDefs $attributeDefs): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
/** @var array<string, array<string, mixed>> $list */
|
||||
$list = $obj->getParam(RelationParam::ADDITIONAL_COLUMNS) ?? [];
|
||||
|
||||
$list[$attributeDefs->getName()] = $attributeDefs->toAssoc();
|
||||
|
||||
return $obj->withParam(RelationParam::ADDITIONAL_COLUMNS, $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with parameters merged.
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public function withParamsMerged(array $params): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
/** @var array<string, mixed> $params */
|
||||
$params = Util::merge($this->params, $params);
|
||||
|
||||
$obj->params = $params;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* To an associative array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toAssoc(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
}
|
||||
41
application/Espo/Core/Utils/Database/Orm/FieldConverter.php
Normal file
41
application/Espo/Core/Utils/Database/Orm/FieldConverter.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm;
|
||||
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
|
||||
/**
|
||||
* Converts field definitions to ORM definitions.
|
||||
*/
|
||||
interface FieldConverter
|
||||
{
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\ORM\Defs\AttributeParam;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
class AttachmentMultiple implements FieldConverter
|
||||
{
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Ids')
|
||||
->withType(AttributeType::JSON_ARRAY)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
'orderBy' => [
|
||||
[Field::CREATED_AT, Order::ASC],
|
||||
[Field::NAME, Order::ASC],
|
||||
],
|
||||
AttributeParam::IS_LINK_MULTIPLE_ID_LIST => true,
|
||||
'relation' => $name,
|
||||
])
|
||||
)
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Names')
|
||||
->withType(AttributeType::JSON_OBJECT)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
AttributeParam::IS_LINK_MULTIPLE_NAME_MAP => true,
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Espo\Core\Currency\ConfigDataProvider;
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\Params\FieldParam;
|
||||
use Espo\ORM\Query\Part\Expression as Expr;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
class Currency implements FieldConverter
|
||||
{
|
||||
private const DEFAULT_PRECISION = 13;
|
||||
private const DEFAULT_SCALE = 4;
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private ConfigDataProvider $configDataProvider
|
||||
) {}
|
||||
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$amountDefs = AttributeDefs::create($name)
|
||||
->withType(AttributeType::FLOAT)
|
||||
->withParamsMerged([
|
||||
'attributeRole' => 'value',
|
||||
'fieldType' => FieldType::CURRENCY,
|
||||
]);
|
||||
|
||||
$currencyDefs = AttributeDefs::create($name . 'Currency')
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withParamsMerged([
|
||||
'attributeRole' => 'currency',
|
||||
'fieldType' => FieldType::CURRENCY,
|
||||
]);
|
||||
|
||||
$convertedDefs = null;
|
||||
|
||||
if ($fieldDefs->getParam(FieldParam::DECIMAL)) {
|
||||
$dbType = $fieldDefs->getParam(FieldParam::DB_TYPE) ?? Types::DECIMAL;
|
||||
$precision = $fieldDefs->getParam(FieldParam::PRECISION) ?? self::DEFAULT_PRECISION;
|
||||
$scale = $fieldDefs->getParam(FieldParam::SCALE) ?? self::DEFAULT_SCALE;
|
||||
|
||||
$amountDefs = $amountDefs
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withDbType($dbType)
|
||||
->withParam(AttributeParam::PRECISION, $precision)
|
||||
->withParam(AttributeParam::SCALE, $scale);
|
||||
|
||||
$defaultValue = $fieldDefs->getParam(AttributeParam::DEFAULT);
|
||||
|
||||
if (is_int($defaultValue) || is_float($defaultValue)) {
|
||||
$defaultValue = number_format($defaultValue, $scale, '.', '');
|
||||
|
||||
$amountDefs = $amountDefs->withParam(AttributeParam::DEFAULT, $defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
if ($fieldDefs->isNotStorable()) {
|
||||
$amountDefs = $amountDefs->withNotStorable();
|
||||
$currencyDefs = $currencyDefs->withNotStorable();
|
||||
}
|
||||
|
||||
if (!$fieldDefs->isNotStorable()) {
|
||||
[$amountDefs, $convertedDefs] = ($this->config->get('currencyNoJoinMode') !== false) ?
|
||||
$this->applyNoJoinMode($fieldDefs, $amountDefs) :
|
||||
$this->applyJoinMode($fieldDefs, $amountDefs, $entityType);
|
||||
}
|
||||
|
||||
$entityDefs = EntityDefs::create()
|
||||
->withAttribute($amountDefs)
|
||||
->withAttribute($currencyDefs);
|
||||
|
||||
if ($convertedDefs) {
|
||||
$entityDefs = $entityDefs->withAttribute($convertedDefs);
|
||||
}
|
||||
|
||||
return $entityDefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{AttributeDefs, AttributeDefs}
|
||||
*/
|
||||
private function applyNoJoinMode(FieldDefs $fieldDefs, AttributeDefs $amountDefs): array
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$currencyAttribute = $name . 'Currency';
|
||||
|
||||
$defaultCurrency = $this->configDataProvider->getDefaultCurrency();
|
||||
|
||||
$rates = $this->configDataProvider->getCurrencyList();
|
||||
|
||||
$expr = Expr::multiply(
|
||||
Expr::column($name),
|
||||
Expr::if(
|
||||
Expr::equal(Expr::column($currencyAttribute), $defaultCurrency),
|
||||
1.0,
|
||||
$this->buildExpression($currencyAttribute, $rates)
|
||||
)
|
||||
)->getValue();
|
||||
|
||||
$exprForeign = Expr::multiply(
|
||||
Expr::column("ALIAS.{$name}"),
|
||||
Expr::if(
|
||||
Expr::equal(Expr::column("ALIAS.{$name}Currency"), $defaultCurrency),
|
||||
1.0,
|
||||
$this->buildExpression("ALIAS.{$name}Currency", $rates)
|
||||
)
|
||||
)->getValue();
|
||||
|
||||
$exprForeign = str_replace('ALIAS', '{alias}', $exprForeign);
|
||||
|
||||
$convertedDefs = AttributeDefs::create($name . 'Converted')
|
||||
->withType(AttributeType::FLOAT)
|
||||
->withParamsMerged([
|
||||
'select' => [
|
||||
'select' => $expr,
|
||||
],
|
||||
'selectForeign' => [
|
||||
'select' => $exprForeign,
|
||||
],
|
||||
'where' => [
|
||||
"=" => [
|
||||
'whereClause' => [
|
||||
$expr . '=' => '{value}',
|
||||
],
|
||||
],
|
||||
">" => [
|
||||
'whereClause' => [
|
||||
$expr . '>' => '{value}',
|
||||
],
|
||||
],
|
||||
"<" => [
|
||||
'whereClause' => [
|
||||
$expr . '<' => '{value}',
|
||||
],
|
||||
],
|
||||
">=" => [
|
||||
'whereClause' => [
|
||||
$expr . '>=' => '{value}',
|
||||
],
|
||||
],
|
||||
"<=" => [
|
||||
'whereClause' => [
|
||||
$expr . '<=' => '{value}',
|
||||
],
|
||||
],
|
||||
"<>" => [
|
||||
'whereClause' => [
|
||||
$expr . '!=' => '{value}',
|
||||
],
|
||||
],
|
||||
"IS NULL" => [
|
||||
'whereClause' => [
|
||||
$expr . '=' => null,
|
||||
],
|
||||
],
|
||||
"IS NOT NULL" => [
|
||||
'whereClause' => [
|
||||
$expr . '!=' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
AttributeParam::NOT_STORABLE => true,
|
||||
'order' => [
|
||||
'order' => [
|
||||
[$expr, '{direction}'],
|
||||
],
|
||||
],
|
||||
'attributeRole' => 'valueConverted',
|
||||
'fieldType' => FieldType::CURRENCY,
|
||||
]);
|
||||
|
||||
return [$amountDefs, $convertedDefs];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $rates
|
||||
*/
|
||||
private function buildExpression(string $currencyAttribute, array $rates): Expr|float
|
||||
{
|
||||
if ($rates === []) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$currency = array_shift($rates);
|
||||
|
||||
return Expr::if(
|
||||
Expr::equal(Expr::column($currencyAttribute), $currency),
|
||||
Expr::create("CURRENCY_RATE:('$currency')"),
|
||||
$this->buildExpression($currencyAttribute, $rates)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{AttributeDefs, AttributeDefs}
|
||||
*/
|
||||
private function applyJoinMode(FieldDefs $fieldDefs, AttributeDefs $amountDefs, string $entityType): array
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$alias = $name . 'CurrencyRecordRate';
|
||||
$leftJoins = [
|
||||
[
|
||||
'Currency',
|
||||
$alias,
|
||||
[$alias . '.id:' => $name . 'Currency'],
|
||||
]
|
||||
];
|
||||
$foreignCurrencyAlias = "{$alias}{$entityType}{alias}Foreign";
|
||||
$mulExpression = "MUL:({$name}, {$alias}.rate)";
|
||||
|
||||
$amountDefs = $amountDefs->withParamsMerged([
|
||||
'order' => [
|
||||
'order' => [
|
||||
[$mulExpression, '{direction}'],
|
||||
],
|
||||
'leftJoins' => $leftJoins,
|
||||
'additionalSelect' => ["{$alias}.rate"],
|
||||
]
|
||||
]);
|
||||
|
||||
$convertedDefs = AttributeDefs::create($name . 'Converted')
|
||||
->withType(AttributeType::FLOAT)
|
||||
->withParamsMerged([
|
||||
'select' => [
|
||||
'select' => $mulExpression,
|
||||
'leftJoins' => $leftJoins,
|
||||
],
|
||||
'selectForeign' => [
|
||||
'select' => "MUL:({alias}.{$name}, {$foreignCurrencyAlias}.rate)",
|
||||
'leftJoins' => [
|
||||
[
|
||||
'Currency',
|
||||
$foreignCurrencyAlias,
|
||||
[$foreignCurrencyAlias . '.id:' => "{alias}.{$name}Currency"]
|
||||
]
|
||||
],
|
||||
],
|
||||
'where' => [
|
||||
"=" => [
|
||||
'whereClause' => [$mulExpression . '=' => '{value}'],
|
||||
'leftJoins' => $leftJoins,
|
||||
],
|
||||
">" => [
|
||||
'whereClause' => [$mulExpression . '>' => '{value}'],
|
||||
'leftJoins' => $leftJoins,
|
||||
],
|
||||
"<" => [
|
||||
'whereClause' => [$mulExpression . '<' => '{value}'],
|
||||
'leftJoins' => $leftJoins,
|
||||
],
|
||||
">=" => [
|
||||
'whereClause' => [$mulExpression . '>=' => '{value}'],
|
||||
'leftJoins' => $leftJoins,
|
||||
],
|
||||
"<=" => [
|
||||
'whereClause' => [$mulExpression . '<=' => '{value}'],
|
||||
'leftJoins' => $leftJoins,
|
||||
],
|
||||
"<>" => [
|
||||
'whereClause' => [$mulExpression . '!=' => '{value}'],
|
||||
'leftJoins' => $leftJoins,
|
||||
],
|
||||
"IS NULL" => [
|
||||
'whereClause' => [$name . '=' => null],
|
||||
],
|
||||
"IS NOT NULL" => [
|
||||
'whereClause' => [$name . '!=' => null],
|
||||
],
|
||||
],
|
||||
AttributeParam::NOT_STORABLE => true,
|
||||
'order' => [
|
||||
'order' => [
|
||||
[$mulExpression, '{direction}'],
|
||||
],
|
||||
'leftJoins' => $leftJoins,
|
||||
'additionalSelect' => ["{$alias}.rate"],
|
||||
],
|
||||
'attributeRole' => 'valueConverted',
|
||||
'fieldType' => FieldType::CURRENCY,
|
||||
]);
|
||||
|
||||
return [$amountDefs, $convertedDefs];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\Params\FieldParam;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
class Decimal implements FieldConverter
|
||||
{
|
||||
private const DEFAULT_PRECISION = 13;
|
||||
private const DEFAULT_SCALE = 4;
|
||||
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$dbType = $fieldDefs->getParam(FieldParam::DB_TYPE) ?? Types::DECIMAL;
|
||||
$precision = $fieldDefs->getParam(FieldParam::PRECISION) ?? self::DEFAULT_PRECISION;
|
||||
$scale = $fieldDefs->getParam(FieldParam::SCALE) ?? self::DEFAULT_SCALE;
|
||||
$notStorable = $fieldDefs->getParam(FieldParam::NOT_STORABLE);
|
||||
|
||||
$defs = AttributeDefs::create($name)
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withDbType($dbType)
|
||||
->withParam(AttributeParam::PRECISION, $precision)
|
||||
->withParam(AttributeParam::SCALE, $scale);
|
||||
|
||||
if ($notStorable) {
|
||||
$defs = $defs->withParam(AttributeParam::NOT_STORABLE, $notStorable);
|
||||
}
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute($defs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Espo\Core\ORM\Defs\AttributeParam;
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\Entities\EmailAddress;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class Email implements FieldConverter
|
||||
{
|
||||
private const COLUMN_ENTITY_TYPE_LENGTH = 100;
|
||||
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$foreignJoinAlias = "$name$entityType{alias}Foreign";
|
||||
$foreignJoinMiddleAlias = "$name$entityType{alias}ForeignMiddle";
|
||||
|
||||
$emailAddressDefs = AttributeDefs
|
||||
::create($name)
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withParamsMerged(
|
||||
$this->getEmailAddressParams($entityType, $foreignJoinAlias, $foreignJoinMiddleAlias)
|
||||
);
|
||||
|
||||
$dataDefs = AttributeDefs
|
||||
::create($name . 'Data')
|
||||
->withType(AttributeType::JSON_ARRAY)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
AttributeParam::NOT_EXPORTABLE => true,
|
||||
'isEmailAddressData' => true,
|
||||
'field' => $name,
|
||||
]);
|
||||
|
||||
$isOptedOutDefs = AttributeDefs
|
||||
::create($name . 'IsOptedOut')
|
||||
->withType(AttributeType::BOOL)
|
||||
->withNotStorable()
|
||||
->withParamsMerged(
|
||||
$this->getIsOptedOutParams($foreignJoinAlias, $foreignJoinMiddleAlias)
|
||||
);
|
||||
|
||||
$isInvalidDefs = AttributeDefs
|
||||
::create($name . 'IsInvalid')
|
||||
->withType(AttributeType::BOOL)
|
||||
->withNotStorable()
|
||||
->withParamsMerged(
|
||||
$this->getIsInvalidParams($foreignJoinAlias, $foreignJoinMiddleAlias)
|
||||
);
|
||||
|
||||
$relationDefs = RelationDefs
|
||||
::create('emailAddresses')
|
||||
->withType(RelationType::MANY_MANY)
|
||||
->withForeignEntityType(EmailAddress::ENTITY_TYPE)
|
||||
->withRelationshipName('entityEmailAddress')
|
||||
->withMidKeys('entityId', 'emailAddressId')
|
||||
->withConditions(['entityType' => $entityType])
|
||||
->withAdditionalColumn(
|
||||
AttributeDefs
|
||||
::create('entityType')
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withLength(self::COLUMN_ENTITY_TYPE_LENGTH)
|
||||
)
|
||||
->withAdditionalColumn(
|
||||
AttributeDefs
|
||||
::create('primary')
|
||||
->withType(AttributeType::BOOL)
|
||||
->withDefault(false)
|
||||
);
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute($emailAddressDefs)
|
||||
->withAttribute($dataDefs)
|
||||
->withAttribute($isOptedOutDefs)
|
||||
->withAttribute($isInvalidDefs)
|
||||
->withRelation($relationDefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getEmailAddressParams(
|
||||
string $entityType,
|
||||
string $foreignJoinAlias,
|
||||
string $foreignJoinMiddleAlias,
|
||||
): array {
|
||||
|
||||
return [
|
||||
'select' => [
|
||||
"select" => "emailAddresses.name",
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => true]]],
|
||||
],
|
||||
'selectForeign' => [
|
||||
"select" => "$foreignJoinAlias.name",
|
||||
'leftJoins' => [
|
||||
[
|
||||
'EntityEmailAddress',
|
||||
$foreignJoinMiddleAlias,
|
||||
[
|
||||
"$foreignJoinMiddleAlias.entityId:" => "{alias}.id",
|
||||
"$foreignJoinMiddleAlias.primary" => true,
|
||||
"$foreignJoinMiddleAlias.deleted" => false,
|
||||
]
|
||||
],
|
||||
[
|
||||
EmailAddress::ENTITY_TYPE,
|
||||
$foreignJoinAlias,
|
||||
[
|
||||
"$foreignJoinAlias.id:" => "$foreignJoinMiddleAlias.emailAddressId",
|
||||
"$foreignJoinAlias.deleted" => false,
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
'fieldType' => FieldType::EMAIL,
|
||||
'where' => [
|
||||
'LIKE' => [
|
||||
'whereClause' => [
|
||||
'id=s' => [
|
||||
'from' => 'EntityEmailAddress',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'emailAddress',
|
||||
'emailAddress',
|
||||
[
|
||||
'emailAddress.id:' => 'emailAddressId',
|
||||
'emailAddress.deleted' => false,
|
||||
],
|
||||
]
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
"LIKE:(emailAddress.lower, LOWER:({value})):" => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'NOT LIKE' => [
|
||||
'whereClause' => [
|
||||
'id!=s' => [
|
||||
'from' => 'EntityEmailAddress',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'emailAddress',
|
||||
'emailAddress',
|
||||
[
|
||||
'emailAddress.id:' => 'emailAddressId',
|
||||
'emailAddress.deleted' => false,
|
||||
],
|
||||
]
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
"LIKE:(emailAddress.lower, LOWER:({value})):" => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'=' => [
|
||||
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
|
||||
'whereClause' => [
|
||||
"EQUAL:(emailAddressesMultiple.lower, LOWER:({value})):" => null,
|
||||
]
|
||||
],
|
||||
'<>' => [
|
||||
'whereClause' => [
|
||||
'id!=s' => [
|
||||
'from' => 'EntityEmailAddress',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'emailAddress',
|
||||
'emailAddress',
|
||||
[
|
||||
'emailAddress.id:' => 'emailAddressId',
|
||||
'emailAddress.deleted' => false,
|
||||
],
|
||||
]
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
"EQUAL:(emailAddress.lower, LOWER:({value})):" => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'IN' => [
|
||||
'whereClause' => [
|
||||
'id=s' => [
|
||||
'from' => 'EntityEmailAddress',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'emailAddress',
|
||||
'emailAddress',
|
||||
[
|
||||
'emailAddress.id:' => 'emailAddressId',
|
||||
'emailAddress.deleted' => false,
|
||||
],
|
||||
]
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
"emailAddress.lower" => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'NOT IN' => [
|
||||
'whereClause' => [
|
||||
'id!=s' => [
|
||||
'from' => 'EntityEmailAddress',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'emailAddress',
|
||||
'emailAddress',
|
||||
[
|
||||
'emailAddress.id:' => 'emailAddressId',
|
||||
'emailAddress.deleted' => false,
|
||||
],
|
||||
]
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
"emailAddress.lower" => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'IS NULL' => [
|
||||
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
|
||||
'whereClause' => [
|
||||
'emailAddressesMultiple.lower=' => null,
|
||||
]
|
||||
],
|
||||
'IS NOT NULL' => [
|
||||
'whereClause' => [
|
||||
'id=s' => [
|
||||
'from' => 'EntityEmailAddress',
|
||||
'select' => ['entityId'],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'order' => [
|
||||
'order' => [
|
||||
['emailAddresses.lower', '{direction}'],
|
||||
],
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => true]]],
|
||||
'additionalSelect' => ['emailAddresses.lower'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getIsOptedOutParams(string $foreignJoinAlias, string $foreignJoinMiddleAlias): array
|
||||
{
|
||||
return [
|
||||
'select' => [
|
||||
'select' => "emailAddresses.optOut",
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => true]]],
|
||||
],
|
||||
'selectForeign' => [
|
||||
'select' => "$foreignJoinAlias.optOut",
|
||||
'leftJoins' => [
|
||||
[
|
||||
'EntityEmailAddress',
|
||||
$foreignJoinMiddleAlias,
|
||||
[
|
||||
"$foreignJoinMiddleAlias.entityId:" => "{alias}.id",
|
||||
"$foreignJoinMiddleAlias.primary" => true,
|
||||
"$foreignJoinMiddleAlias.deleted" => false,
|
||||
]
|
||||
],
|
||||
[
|
||||
EmailAddress::ENTITY_TYPE,
|
||||
$foreignJoinAlias,
|
||||
[
|
||||
"$foreignJoinAlias.id:" => "$foreignJoinMiddleAlias.emailAddressId",
|
||||
"$foreignJoinAlias.deleted" => false,
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
'where' => [
|
||||
'= TRUE' => [
|
||||
'whereClause' => [
|
||||
['emailAddresses.optOut=' => true],
|
||||
['emailAddresses.optOut!=' => null],
|
||||
],
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => true]]],
|
||||
],
|
||||
'= FALSE' => [
|
||||
'whereClause' => [
|
||||
'OR' => [
|
||||
['emailAddresses.optOut=' => false],
|
||||
['emailAddresses.optOut=' => null],
|
||||
]
|
||||
],
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => true]]],
|
||||
]
|
||||
],
|
||||
'order' => [
|
||||
'order' => [
|
||||
['emailAddresses.optOut', '{direction}'],
|
||||
],
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => true]]],
|
||||
'additionalSelect' => ['emailAddresses.optOut'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getIsInvalidParams(string $foreignJoinAlias, string $foreignJoinMiddleAlias): array
|
||||
{
|
||||
return [
|
||||
'select' => [
|
||||
'select' => "emailAddresses.invalid",
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => true]]],
|
||||
],
|
||||
'selectForeign' => [
|
||||
'select' => "$foreignJoinAlias.invalid",
|
||||
'leftJoins' => [
|
||||
[
|
||||
'EntityEmailAddress',
|
||||
$foreignJoinMiddleAlias,
|
||||
[
|
||||
"$foreignJoinMiddleAlias.entityId:" => "{alias}.id",
|
||||
"$foreignJoinMiddleAlias.primary" => true,
|
||||
"$foreignJoinMiddleAlias.deleted" => false,
|
||||
]
|
||||
],
|
||||
[
|
||||
EmailAddress::ENTITY_TYPE,
|
||||
$foreignJoinAlias,
|
||||
[
|
||||
"$foreignJoinAlias.id:" => "$foreignJoinMiddleAlias.emailAddressId",
|
||||
"$foreignJoinAlias.deleted" => false,
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
'where' => [
|
||||
'= TRUE' => [
|
||||
'whereClause' => [
|
||||
['emailAddresses.invalid=' => true],
|
||||
['emailAddresses.invalid!=' => null],
|
||||
],
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => true]]],
|
||||
],
|
||||
'= FALSE' => [
|
||||
'whereClause' => [
|
||||
'OR' => [
|
||||
['emailAddresses.invalid=' => false],
|
||||
['emailAddresses.invalid=' => null],
|
||||
]
|
||||
],
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => true]]],
|
||||
]
|
||||
],
|
||||
'order' => [
|
||||
'order' => [
|
||||
['emailAddresses.invalid', '{direction}'],
|
||||
],
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => true]]],
|
||||
'additionalSelect' => ['emailAddresses.invalid'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class File implements FieldConverter
|
||||
{
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$idName = $name . 'Id';
|
||||
$nameName = $name . 'Name';
|
||||
|
||||
$idDefs = AttributeDefs::create($idName)
|
||||
->withType(AttributeType::FOREIGN_ID)
|
||||
->withParam('index', false);
|
||||
|
||||
$nameDefs = AttributeDefs::create($nameName)
|
||||
->withType(AttributeType::FOREIGN);
|
||||
|
||||
if ($fieldDefs->isNotStorable()) {
|
||||
$idDefs = $idDefs->withNotStorable();
|
||||
|
||||
$nameDefs = $nameDefs->withType(AttributeType::VARCHAR);
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $defaults */
|
||||
$defaults = $fieldDefs->getParam('defaultAttributes') ?? [];
|
||||
|
||||
if (array_key_exists($idName, $defaults)) {
|
||||
$idDefs = $idDefs->withDefault($defaults[$idName]);
|
||||
}
|
||||
|
||||
$relationDefs = null;
|
||||
|
||||
if (!$fieldDefs->isNotStorable()) {
|
||||
$nameDefs = $nameDefs->withParamsMerged([
|
||||
AttributeParam::RELATION => $name,
|
||||
AttributeParam::FOREIGN => Field::NAME,
|
||||
]);
|
||||
|
||||
$relationDefs = RelationDefs::create($name)
|
||||
->withType(RelationType::BELONGS_TO)
|
||||
->withForeignEntityType(Attachment::ENTITY_TYPE)
|
||||
->withKey($idName)
|
||||
->withForeignKey(Attribute::ID)
|
||||
->withParam(RelationParam::FOREIGN, null);
|
||||
}
|
||||
|
||||
$entityDefs = EntityDefs::create()
|
||||
->withAttribute($idDefs)
|
||||
->withAttribute($nameDefs);
|
||||
|
||||
if ($relationDefs) {
|
||||
$entityDefs = $entityDefs->withRelation($relationDefs);
|
||||
}
|
||||
|
||||
return $entityDefs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
class Link implements FieldConverter
|
||||
{
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$idName = $name . 'Id';
|
||||
$nameName = $name . 'Name';
|
||||
|
||||
$idDefs = AttributeDefs::create($idName)
|
||||
->withType(AttributeType::FOREIGN_ID)
|
||||
->withParamsMerged([
|
||||
'index' => $name,
|
||||
'attributeRole' => 'id',
|
||||
'fieldType' => FieldType::LINK,
|
||||
]);
|
||||
|
||||
$nameDefs = AttributeDefs::create($nameName)
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
'attributeRole' => 'name',
|
||||
'fieldType' => FieldType::LINK,
|
||||
]);
|
||||
|
||||
if ($fieldDefs->isNotStorable()) {
|
||||
$idDefs = $idDefs->withNotStorable();
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $defaults */
|
||||
$defaults = $fieldDefs->getParam('defaultAttributes') ?? [];
|
||||
|
||||
if (array_key_exists($idName, $defaults)) {
|
||||
$idDefs = $idDefs->withDefault($defaults[$idName]);
|
||||
}
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute($idDefs)
|
||||
->withAttribute($nameDefs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Espo\Core\ORM\Defs\AttributeParam;
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
class LinkMultiple implements FieldConverter
|
||||
{
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$idsName = $name . 'Ids';
|
||||
$namesName = $name . 'Names';
|
||||
$columnsName = $name . 'Columns';
|
||||
|
||||
$idsDefs = AttributeDefs::create($idsName)
|
||||
->withType(AttributeType::JSON_ARRAY)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
AttributeParam::IS_LINK_MULTIPLE_ID_LIST => true,
|
||||
'relation' => $name,
|
||||
'isUnordered' => true,
|
||||
'attributeRole' => 'idList',
|
||||
'fieldType' => FieldType::LINK_MULTIPLE,
|
||||
]);
|
||||
|
||||
/** @var array<string, mixed> $defaults */
|
||||
$defaults = $fieldDefs->getParam('defaultAttributes') ?? [];
|
||||
|
||||
if (array_key_exists($idsName, $defaults)) {
|
||||
$idsDefs = $idsDefs->withDefault($defaults[$idsName]);
|
||||
}
|
||||
|
||||
$namesDefs = AttributeDefs::create($namesName)
|
||||
->withType(AttributeType::JSON_OBJECT)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
AttributeParam::IS_LINK_MULTIPLE_NAME_MAP => true,
|
||||
'attributeRole' => 'nameMap',
|
||||
'fieldType' => FieldType::LINK_MULTIPLE,
|
||||
]);
|
||||
|
||||
$orderBy = $fieldDefs->getParam('orderBy');
|
||||
$orderDirection = $fieldDefs->getParam('orderDirection');
|
||||
|
||||
if ($orderBy) {
|
||||
$idsDefs = $idsDefs->withParam('orderBy', $orderBy);
|
||||
|
||||
if ($orderDirection !== null) {
|
||||
$idsDefs = $idsDefs->withParam('orderDirection', $orderDirection);
|
||||
}
|
||||
}
|
||||
|
||||
$columns = $fieldDefs->getParam('columns');
|
||||
|
||||
$columnsDefs = $columns ?
|
||||
AttributeDefs::create($columnsName)
|
||||
->withType(AttributeType::JSON_OBJECT)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
'columns' => $columns,
|
||||
'attributeRole' => 'columnsMap',
|
||||
])
|
||||
: null;
|
||||
|
||||
$entityDefs = EntityDefs::create()
|
||||
->withAttribute($idsDefs)
|
||||
->withAttribute($namesDefs);
|
||||
|
||||
if ($columnsDefs) {
|
||||
$entityDefs = $entityDefs->withAttribute($columnsDefs);
|
||||
}
|
||||
|
||||
return $entityDefs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
class LinkOne implements FieldConverter
|
||||
{
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Id')
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
'attributeRole' => 'id',
|
||||
'fieldType' => FieldType::LINK_ONE,
|
||||
])
|
||||
)
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Name')
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
'attributeRole' => 'name',
|
||||
'fieldType' => FieldType::LINK_ONE,
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
class LinkParent implements FieldConverter
|
||||
{
|
||||
private const TYPE_LENGTH = 100;
|
||||
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$idName = $name . 'Id';
|
||||
$typeName = $name . 'Type';
|
||||
$nameName = $name . 'Name';
|
||||
|
||||
$idDefs = AttributeDefs::create($idName)
|
||||
->withType(AttributeType::FOREIGN_ID)
|
||||
->withParamsMerged([
|
||||
'index' => $name,
|
||||
'attributeRole' => 'id',
|
||||
'fieldType' => FieldType::LINK_PARENT,
|
||||
]);
|
||||
|
||||
$typeDefs = AttributeDefs::create($typeName)
|
||||
->withType(AttributeType::FOREIGN_TYPE)
|
||||
->withParam(AttributeParam::NOT_NULL, false)
|
||||
->withParam('index', $name)
|
||||
->withLength(self::TYPE_LENGTH)
|
||||
->withParamsMerged([
|
||||
'attributeRole' => 'type',
|
||||
'fieldType' => FieldType::LINK_PARENT,
|
||||
]);
|
||||
|
||||
$nameDefs = AttributeDefs::create($nameName)
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
AttributeParam::RELATION => $name,
|
||||
'isParentName' => true,
|
||||
'attributeRole' => 'name',
|
||||
'fieldType' => FieldType::LINK_PARENT,
|
||||
]);
|
||||
|
||||
if ($fieldDefs->isNotStorable()) {
|
||||
$idDefs = $idDefs->withNotStorable();
|
||||
$typeDefs = $typeDefs->withNotStorable();
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $defaults */
|
||||
$defaults = $fieldDefs->getParam('defaultAttributes') ?? [];
|
||||
|
||||
if (array_key_exists($idName, $defaults)) {
|
||||
$idDefs = $idDefs->withDefault($defaults[$idName]);
|
||||
}
|
||||
|
||||
if (array_key_exists($typeName, $defaults)) {
|
||||
$typeDefs = $idDefs->withDefault($defaults[$typeName]);
|
||||
}
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute($idDefs)
|
||||
->withAttribute($typeDefs)
|
||||
->withAttribute($nameDefs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\Params\FieldParam;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class PersonName implements FieldConverter
|
||||
{
|
||||
private const FORMAT_LAST_FIRST = 'lastFirst';
|
||||
private const FORMAT_LAST_FIRST_MIDDLE = 'lastFirstMiddle';
|
||||
private const FORMAT_FIRST_MIDDLE_LAST = 'firstMiddleLast';
|
||||
|
||||
public function __construct(private Config $config) {}
|
||||
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$format = $this->config->get('personNameFormat');
|
||||
|
||||
$name = $fieldDefs->getName();
|
||||
$firstName = 'first' . ucfirst($name);
|
||||
$lastName = 'last' . ucfirst($name);
|
||||
$middleName = 'middle' . ucfirst($name);
|
||||
|
||||
$subList = match ($format) {
|
||||
self::FORMAT_LAST_FIRST => [$lastName, ' ', $firstName],
|
||||
self::FORMAT_LAST_FIRST_MIDDLE => [$lastName, ' ', $firstName, ' ', $middleName],
|
||||
self::FORMAT_FIRST_MIDDLE_LAST => [$firstName, ' ', $middleName, ' ', $lastName],
|
||||
default => [$firstName, ' ', $lastName],
|
||||
};
|
||||
|
||||
if (
|
||||
$format === self::FORMAT_LAST_FIRST_MIDDLE ||
|
||||
$format === self::FORMAT_LAST_FIRST
|
||||
) {
|
||||
$orderBy1Field = $lastName;
|
||||
$orderBy2Field = $firstName;
|
||||
} else {
|
||||
$orderBy1Field = $firstName;
|
||||
$orderBy2Field = $lastName;
|
||||
}
|
||||
|
||||
$fullList = [];
|
||||
$whereItems = [];
|
||||
|
||||
foreach ($subList as $subFieldName) {
|
||||
$fieldNameTrimmed = trim($subFieldName);
|
||||
|
||||
if (empty($fieldNameTrimmed)) {
|
||||
$fullList[] = "'" . $subFieldName . "'";
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$fullList[] = $fieldNameTrimmed;
|
||||
$whereItems[] = $fieldNameTrimmed;
|
||||
}
|
||||
|
||||
$whereItems[] = "CONCAT:($firstName, ' ', $lastName)";
|
||||
$whereItems[] = "CONCAT:($lastName, ' ', $firstName)";
|
||||
|
||||
if ($format === self::FORMAT_FIRST_MIDDLE_LAST) {
|
||||
$whereItems[] = "CONCAT:($firstName, ' ', $middleName, ' ', $lastName)";
|
||||
} else if ($format === self::FORMAT_LAST_FIRST_MIDDLE) {
|
||||
$whereItems[] = "CONCAT:($lastName, ' ', $firstName, ' ', $middleName)";
|
||||
}
|
||||
|
||||
$nameColumns = [
|
||||
$firstName,
|
||||
$middleName,
|
||||
$lastName,
|
||||
];
|
||||
|
||||
$selectExpression = $this->getSelect($fullList);
|
||||
$selectForeignExpression = $this->getSelect($fullList, '{alias}');
|
||||
|
||||
if (
|
||||
$format === self::FORMAT_FIRST_MIDDLE_LAST ||
|
||||
$format === self::FORMAT_LAST_FIRST_MIDDLE
|
||||
) {
|
||||
$selectExpression = "REPLACE:($selectExpression, ' ', ' ')";
|
||||
$selectForeignExpression = "REPLACE:($selectForeignExpression, ' ', ' ')";
|
||||
}
|
||||
|
||||
$attributeDefs = AttributeDefs::create($name)
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
'select' => [
|
||||
'select' => $selectExpression,
|
||||
],
|
||||
'selectForeign' => [
|
||||
'select' => $selectForeignExpression,
|
||||
],
|
||||
'where' => [
|
||||
'LIKE' => [
|
||||
'whereClause' => [
|
||||
'OR' => array_fill_keys(
|
||||
array_map(fn ($item) => $item . '*', $whereItems),
|
||||
'{value}'
|
||||
),
|
||||
],
|
||||
],
|
||||
'NOT LIKE' => [
|
||||
'whereClause' => [
|
||||
'AND' => array_fill_keys(
|
||||
array_map(fn ($item) => $item . '!*', $whereItems),
|
||||
'{value}'
|
||||
),
|
||||
],
|
||||
],
|
||||
'=' => [
|
||||
'whereClause' => [
|
||||
'OR' => array_fill_keys($whereItems, '{value}'),
|
||||
],
|
||||
],
|
||||
'<>' => [
|
||||
'whereClause' => [
|
||||
'AND' => array_fill_keys(
|
||||
array_map(fn ($item) => $item . '!=', $whereItems),
|
||||
'{value}'
|
||||
),
|
||||
],
|
||||
],
|
||||
'IN' => [
|
||||
// Not supported.
|
||||
'whereClause' => [
|
||||
'false:' => null,
|
||||
]
|
||||
],
|
||||
'NOT IN' => [
|
||||
// Not supported.
|
||||
'whereClause' => [
|
||||
'false:' => null,
|
||||
]
|
||||
],
|
||||
'IS NULL' => [
|
||||
'whereClause' => [
|
||||
'AND' => array_fill_keys(
|
||||
array_map(fn ($item) => $item . '=', $nameColumns),
|
||||
null
|
||||
),
|
||||
]
|
||||
|
||||
],
|
||||
'IS NOT NULL' => [
|
||||
'whereClause' => [
|
||||
'OR' => array_fill_keys(
|
||||
array_map(fn ($item) => $item . '!=', $nameColumns),
|
||||
null
|
||||
),
|
||||
]
|
||||
],
|
||||
],
|
||||
'order' => [
|
||||
'order' => [
|
||||
[$orderBy1Field, '{direction}'],
|
||||
[$orderBy2Field, '{direction}'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$dependeeAttributeList = $fieldDefs->getParam(FieldParam::DEPENDEE_ATTRIBUTE_LIST);
|
||||
|
||||
if ($dependeeAttributeList) {
|
||||
$attributeDefs = $attributeDefs->withParam(AttributeParam::DEPENDEE_ATTRIBUTE_LIST, $dependeeAttributeList);
|
||||
}
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute($attributeDefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $fullList
|
||||
*/
|
||||
private function getSelect(array $fullList, ?string $alias = null): string
|
||||
{
|
||||
foreach ($fullList as &$item) {
|
||||
$rowItem = trim($item, " '");
|
||||
|
||||
if (empty($rowItem)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($alias) {
|
||||
$item = $alias . '.' . $item;
|
||||
}
|
||||
|
||||
$item = "IFNULL:($item, '')";
|
||||
}
|
||||
|
||||
return "NULLIF:(TRIM:(CONCAT:(" . implode(", ", $fullList) . ")), '')";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\FieldConverters;
|
||||
|
||||
use Espo\Core\ORM\Defs\AttributeParam;
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\FieldConverter;
|
||||
use Espo\Entities\PhoneNumber;
|
||||
use Espo\ORM\Defs\FieldDefs;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class Phone implements FieldConverter
|
||||
{
|
||||
private const COLUMN_ENTITY_TYPE_LENGTH = 100;
|
||||
|
||||
public function convert(FieldDefs $fieldDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$foreignJoinAlias = "$name$entityType{alias}Foreign";
|
||||
$foreignJoinMiddleAlias = "$name$entityType{alias}ForeignMiddle";
|
||||
|
||||
$emailAddressDefs = AttributeDefs
|
||||
::create($name)
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withParamsMerged(
|
||||
$this->getPhoneNumberParams($entityType, $foreignJoinAlias, $foreignJoinMiddleAlias)
|
||||
);
|
||||
|
||||
$dataDefs = AttributeDefs
|
||||
::create($name . 'Data')
|
||||
->withType(AttributeType::JSON_ARRAY)
|
||||
->withNotStorable()
|
||||
->withParamsMerged([
|
||||
AttributeParam::NOT_EXPORTABLE => true,
|
||||
'isPhoneNumberData' => true,
|
||||
'field' => $name,
|
||||
]);
|
||||
|
||||
$isOptedOutDefs = AttributeDefs
|
||||
::create($name . 'IsOptedOut')
|
||||
->withType(AttributeType::BOOL)
|
||||
->withNotStorable()
|
||||
->withParamsMerged(
|
||||
$this->getIsOptedOutParams($foreignJoinAlias, $foreignJoinMiddleAlias)
|
||||
);
|
||||
|
||||
$isInvalidDefs = AttributeDefs
|
||||
::create($name . 'IsInvalid')
|
||||
->withType(AttributeType::BOOL)
|
||||
->withNotStorable()
|
||||
->withParamsMerged(
|
||||
$this->getIsInvalidParams($foreignJoinAlias, $foreignJoinMiddleAlias)
|
||||
);
|
||||
|
||||
$numericAttribute = AttributeDefs
|
||||
::create($name . 'Numeric')
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withNotStorable()
|
||||
->withParamsMerged(
|
||||
$this->getNumericParams($entityType)
|
||||
);
|
||||
|
||||
$relationDefs = RelationDefs
|
||||
::create('phoneNumbers')
|
||||
->withType(RelationType::MANY_MANY)
|
||||
->withForeignEntityType(PhoneNumber::ENTITY_TYPE)
|
||||
->withRelationshipName('entityPhoneNumber')
|
||||
->withMidKeys('entityId', 'phoneNumberId')
|
||||
->withConditions(['entityType' => $entityType])
|
||||
->withAdditionalColumn(
|
||||
AttributeDefs
|
||||
::create('entityType')
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withLength(self::COLUMN_ENTITY_TYPE_LENGTH)
|
||||
)
|
||||
->withAdditionalColumn(
|
||||
AttributeDefs
|
||||
::create('primary')
|
||||
->withType(AttributeType::BOOL)
|
||||
->withDefault(false)
|
||||
);
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute($emailAddressDefs)
|
||||
->withAttribute($dataDefs)
|
||||
->withAttribute($isOptedOutDefs)
|
||||
->withAttribute($isInvalidDefs)
|
||||
->withAttribute($numericAttribute)
|
||||
->withRelation($relationDefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getPhoneNumberParams(
|
||||
string $entityType,
|
||||
string $foreignJoinAlias,
|
||||
string $foreignJoinMiddleAlias,
|
||||
): array {
|
||||
|
||||
return [
|
||||
'select' => [
|
||||
"select" => "phoneNumbers.name",
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => true]]],
|
||||
],
|
||||
'selectForeign' => [
|
||||
"select" => "$foreignJoinAlias.name",
|
||||
'leftJoins' => [
|
||||
[
|
||||
'EntityPhoneNumber',
|
||||
$foreignJoinMiddleAlias,
|
||||
[
|
||||
"$foreignJoinMiddleAlias.entityId:" => "{alias}.id",
|
||||
"$foreignJoinMiddleAlias.primary" => true,
|
||||
"$foreignJoinMiddleAlias.deleted" => false,
|
||||
]
|
||||
],
|
||||
[
|
||||
PhoneNumber::ENTITY_TYPE,
|
||||
$foreignJoinAlias,
|
||||
[
|
||||
"$foreignJoinAlias.id:" => "$foreignJoinMiddleAlias.phoneNumberId",
|
||||
"$foreignJoinAlias.deleted" => false,
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
'fieldType' => FieldType::PHONE,
|
||||
'where' => [
|
||||
'LIKE' => [
|
||||
'whereClause' => [
|
||||
'id=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'phoneNumber',
|
||||
'phoneNumber',
|
||||
[
|
||||
'phoneNumber.id:' => 'phoneNumberId',
|
||||
'phoneNumber.deleted' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
'phoneNumber.name*' => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'NOT LIKE' => [
|
||||
'whereClause' => [
|
||||
'id!=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'phoneNumber',
|
||||
'phoneNumber',
|
||||
[
|
||||
'phoneNumber.id:' => 'phoneNumberId',
|
||||
'phoneNumber.deleted' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
'phoneNumber.name*' => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'=' => [
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
|
||||
'whereClause' => [
|
||||
'phoneNumbersMultiple.name=' => '{value}',
|
||||
]
|
||||
],
|
||||
'<>' => [
|
||||
'whereClause' => [
|
||||
'id!=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'phoneNumber',
|
||||
'phoneNumber',
|
||||
[
|
||||
'phoneNumber.id:' => 'phoneNumberId',
|
||||
'phoneNumber.deleted' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
'phoneNumber.name' => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'IN' => [
|
||||
'whereClause' => [
|
||||
'id=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'phoneNumber',
|
||||
'phoneNumber',
|
||||
[
|
||||
'phoneNumber.id:' => 'phoneNumberId',
|
||||
'phoneNumber.deleted' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
'phoneNumber.name' => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'NOT IN' => [
|
||||
'whereClause' => [
|
||||
'id=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'phoneNumber',
|
||||
'phoneNumber',
|
||||
[
|
||||
'phoneNumber.id:' => 'phoneNumberId',
|
||||
'phoneNumber.deleted' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
'phoneNumber.name!=' => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'IS NULL' => [
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
|
||||
'whereClause' => [
|
||||
'phoneNumbersMultiple.name=' => null,
|
||||
]
|
||||
],
|
||||
'IS NOT NULL' => [
|
||||
'whereClause' => [
|
||||
'id=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'order' => [
|
||||
'order' => [
|
||||
['phoneNumbers.name', '{direction}'],
|
||||
],
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => true]]],
|
||||
'additionalSelect' => ['phoneNumbers.name'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getIsOptedOutParams(string $foreignJoinAlias, string $foreignJoinMiddleAlias): array
|
||||
{
|
||||
return [
|
||||
'select' => [
|
||||
'select' => 'phoneNumbers.optOut',
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => true]]],
|
||||
],
|
||||
'selectForeign' => [
|
||||
'select' => "$foreignJoinAlias.optOut",
|
||||
'leftJoins' => [
|
||||
[
|
||||
'EntityPhoneNumber',
|
||||
$foreignJoinMiddleAlias,
|
||||
[
|
||||
"$foreignJoinMiddleAlias.entityId:" => "{alias}.id",
|
||||
"$foreignJoinMiddleAlias.primary" => true,
|
||||
"$foreignJoinMiddleAlias.deleted" => false,
|
||||
]
|
||||
],
|
||||
[
|
||||
PhoneNumber::ENTITY_TYPE,
|
||||
$foreignJoinAlias,
|
||||
[
|
||||
"$foreignJoinAlias.id:" => "$foreignJoinMiddleAlias.phoneNumberId",
|
||||
"$foreignJoinAlias.deleted" => false,
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
'where' => [
|
||||
'= TRUE' => [
|
||||
'whereClause' => [
|
||||
['phoneNumbers.optOut=' => true],
|
||||
['phoneNumbers.optOut!=' => null],
|
||||
],
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => true]]],
|
||||
],
|
||||
'= FALSE' => [
|
||||
'whereClause' => [
|
||||
'OR' => [
|
||||
['phoneNumbers.optOut=' => false],
|
||||
['phoneNumbers.optOut=' => null],
|
||||
]
|
||||
],
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => true]]],
|
||||
]
|
||||
],
|
||||
'order' => [
|
||||
'order' => [
|
||||
['phoneNumbers.optOut', '{direction}'],
|
||||
],
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => true]]],
|
||||
'additionalSelect' => ['phoneNumbers.optOut'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getIsInvalidParams(string $foreignJoinAlias, string $foreignJoinMiddleAlias): array
|
||||
{
|
||||
return [
|
||||
'select' => [
|
||||
'select' => 'phoneNumbers.invalid',
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => true]]],
|
||||
],
|
||||
'selectForeign' => [
|
||||
'select' => "$foreignJoinAlias.invalid",
|
||||
'leftJoins' => [
|
||||
[
|
||||
'EntityPhoneNumber',
|
||||
$foreignJoinMiddleAlias,
|
||||
[
|
||||
"$foreignJoinMiddleAlias.entityId:" => "{alias}.id",
|
||||
"$foreignJoinMiddleAlias.primary" => true,
|
||||
"$foreignJoinMiddleAlias.deleted" => false,
|
||||
]
|
||||
],
|
||||
[
|
||||
PhoneNumber::ENTITY_TYPE,
|
||||
$foreignJoinAlias,
|
||||
[
|
||||
"$foreignJoinAlias.id:" => "$foreignJoinMiddleAlias.phoneNumberId",
|
||||
"$foreignJoinAlias.deleted" => false,
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
'where' => [
|
||||
'= TRUE' => [
|
||||
'whereClause' => [
|
||||
['phoneNumbers.invalid=' => true],
|
||||
['phoneNumbers.invalid!=' => null],
|
||||
],
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => true]]],
|
||||
],
|
||||
'= FALSE' => [
|
||||
'whereClause' => [
|
||||
'OR' => [
|
||||
['phoneNumbers.invalid=' => false],
|
||||
['phoneNumbers.invalid=' => null],
|
||||
]
|
||||
],
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => true]]],
|
||||
]
|
||||
],
|
||||
'order' => [
|
||||
'order' => [
|
||||
['phoneNumbers.invalid', '{direction}'],
|
||||
],
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => true]]],
|
||||
'additionalSelect' => ['phoneNumbers.invalid'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getNumericParams(string $entityType): array
|
||||
{
|
||||
return [
|
||||
AttributeParam::NOT_EXPORTABLE => true,
|
||||
'where' => [
|
||||
'LIKE' => [
|
||||
'whereClause' => [
|
||||
'id=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'phoneNumber',
|
||||
'phoneNumber',
|
||||
[
|
||||
'phoneNumber.id:' => 'phoneNumberId',
|
||||
'phoneNumber.deleted' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
'phoneNumber.numeric*' => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'NOT LIKE' => [
|
||||
'whereClause' => [
|
||||
'id!=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'phoneNumber',
|
||||
'phoneNumber',
|
||||
[
|
||||
'phoneNumber.id:' => 'phoneNumberId',
|
||||
'phoneNumber.deleted' => false,
|
||||
],
|
||||
]
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
'phoneNumber.numeric*' => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'=' => [
|
||||
'whereClause' => [
|
||||
'id=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'phoneNumber',
|
||||
'phoneNumber',
|
||||
[
|
||||
'phoneNumber.id:' => 'phoneNumberId',
|
||||
'phoneNumber.deleted' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
'phoneNumber.numeric' => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'<>' => [
|
||||
'whereClause' => [
|
||||
'id!=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'phoneNumber',
|
||||
'phoneNumber',
|
||||
[
|
||||
'phoneNumber.id:' => 'phoneNumberId',
|
||||
'phoneNumber.deleted' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
'phoneNumber.numeric' => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'IN' => [
|
||||
'whereClause' => [
|
||||
'id=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'phoneNumber',
|
||||
'phoneNumber',
|
||||
[
|
||||
'phoneNumber.id:' => 'phoneNumberId',
|
||||
'phoneNumber.deleted' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
'phoneNumber.numeric' => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'NOT IN' => [
|
||||
'whereClause' => [
|
||||
'id!=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'joins' => [
|
||||
[
|
||||
'phoneNumber',
|
||||
'phoneNumber',
|
||||
[
|
||||
'phoneNumber.id:' => 'phoneNumberId',
|
||||
'phoneNumber.deleted' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
'phoneNumber.numeric' => '{value}',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'IS NULL' => [
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbersMultiple']],
|
||||
'whereClause' => [
|
||||
'phoneNumbersMultiple.numeric=' => null,
|
||||
]
|
||||
],
|
||||
'IS NOT NULL' => [
|
||||
'whereClause' => [
|
||||
'id=s' => [
|
||||
'from' => 'EntityPhoneNumber',
|
||||
'select' => ['entityId'],
|
||||
'whereClause' => [
|
||||
Attribute::DELETED => false,
|
||||
'entityType' => $entityType,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
40
application/Espo/Core/Utils/Database/Orm/IndexHelper.php
Normal file
40
application/Espo/Core/Utils/Database/Orm/IndexHelper.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm;
|
||||
|
||||
use Espo\ORM\Defs\IndexDefs;
|
||||
|
||||
interface IndexHelper
|
||||
{
|
||||
/**
|
||||
* Compose an index DB name. Depending on database, the name can be unique, limited by a max length.
|
||||
*/
|
||||
public function composeKey(IndexDefs $defs, string $entityType): string;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use RuntimeException;
|
||||
|
||||
class IndexHelperFactory
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private InjectableFactory $injectableFactory
|
||||
) {}
|
||||
|
||||
public function create(string $platform): IndexHelper
|
||||
{
|
||||
/** @var ?class-string<IndexHelper> $className */
|
||||
$className = $this->metadata
|
||||
->get(['app', 'databasePlatforms', $platform, 'indexHelperClassName']);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("No Index Helper for {$platform}");
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\IndexHelpers;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\IndexHelper;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Defs\IndexDefs;
|
||||
|
||||
class MysqlIndexHelper implements IndexHelper
|
||||
{
|
||||
private const MAX_LENGTH = 60;
|
||||
|
||||
public function composeKey(IndexDefs $defs, string $entityType): string
|
||||
{
|
||||
$name = $defs->getName();
|
||||
$prefix = $defs->isUnique() ? 'UNIQ' : 'IDX';
|
||||
|
||||
$parts = [$prefix, strtoupper(Util::toUnderScore($name))];
|
||||
|
||||
$key = implode('_', $parts);
|
||||
|
||||
return substr($key, 0, self::MAX_LENGTH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\IndexHelpers;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\IndexHelper;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Defs\IndexDefs;
|
||||
|
||||
class PostgresqlIndexHelper implements IndexHelper
|
||||
{
|
||||
private const MAX_LENGTH = 59;
|
||||
|
||||
public function composeKey(IndexDefs $defs, string $entityType): string
|
||||
{
|
||||
$name = $defs->getName();
|
||||
$prefix = $defs->isUnique() ? 'UNIQ' : 'IDX';
|
||||
|
||||
$parts = [
|
||||
$prefix,
|
||||
strtoupper(Util::toUnderScore($entityType)),
|
||||
strtoupper(Util::toUnderScore($name)),
|
||||
];
|
||||
|
||||
$key = implode('_', $parts);
|
||||
|
||||
return self::decreaseLength($key);
|
||||
}
|
||||
|
||||
private static function decreaseLength(string $key): string
|
||||
{
|
||||
if (strlen($key) <= self::MAX_LENGTH) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$list = explode('_', $key);
|
||||
|
||||
$maxItemLength = 0;
|
||||
foreach ($list as $item) {
|
||||
if (strlen($item) > $maxItemLength) {
|
||||
$maxItemLength = strlen($item);
|
||||
}
|
||||
}
|
||||
$maxItemLength--;
|
||||
|
||||
$list = array_map(
|
||||
fn ($item) => substr($item, 0, min($maxItemLength, strlen($item))),
|
||||
$list
|
||||
);
|
||||
|
||||
$key = implode('_', $list);
|
||||
|
||||
return self::decreaseLength($key);
|
||||
}
|
||||
}
|
||||
41
application/Espo/Core/Utils/Database/Orm/LinkConverter.php
Normal file
41
application/Espo/Core/Utils/Database/Orm/LinkConverter.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm;
|
||||
|
||||
use Espo\ORM\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
|
||||
/**
|
||||
* Converts link definitions to ORM definitions.
|
||||
*/
|
||||
interface LinkConverter
|
||||
{
|
||||
public function convert(RelationDefs $linkDefs, string $entityType): EntityDefs;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use LogicException;
|
||||
|
||||
class Attachments implements LinkConverter
|
||||
{
|
||||
public function __construct(private HasChildren $hasChildren) {}
|
||||
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
|
||||
$entityDefs = $this->hasChildren->convert($linkDefs, $entityType);
|
||||
|
||||
$entityDefs = $entityDefs->withAttribute(
|
||||
AttributeDefs::create($name . 'Types')
|
||||
->withType(AttributeType::JSON_OBJECT)
|
||||
->withNotStorable()
|
||||
);
|
||||
|
||||
$relationDefs = $entityDefs->getRelation($name);
|
||||
|
||||
if (!$relationDefs) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
$relationDefs = $relationDefs->withConditions([
|
||||
'OR' => [
|
||||
['field' => null],
|
||||
['field' => $name],
|
||||
]
|
||||
]);
|
||||
|
||||
return $entityDefs->withRelation($relationDefs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class BelongsTo implements LinkConverter
|
||||
{
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
$foreignEntityType = $linkDefs->getForeignEntityType();
|
||||
$foreignRelationName = $linkDefs->hasForeignRelationName() ? $linkDefs->getForeignRelationName() : null;
|
||||
$noIndex = $linkDefs->getParam('noIndex');
|
||||
$noForeignName = $linkDefs->getParam('noForeignName');
|
||||
$foreignName = $linkDefs->getParam('foreignName') ?? 'name';
|
||||
$noJoin = $linkDefs->getParam(RelationParam::NO_JOIN);
|
||||
|
||||
$idName = $name . 'Id';
|
||||
$nameName = $name . 'Name';
|
||||
|
||||
$idAttributeDefs = AttributeDefs::create($idName)
|
||||
->withType(AttributeType::FOREIGN_ID)
|
||||
->withParam('index', !$noIndex);
|
||||
|
||||
$relationDefs = RelationDefs::create($name)
|
||||
->withType(RelationType::BELONGS_TO)
|
||||
->withForeignEntityType($foreignEntityType)
|
||||
->withKey($idName)
|
||||
->withForeignKey('id')
|
||||
->withForeignRelationName($foreignRelationName);
|
||||
|
||||
if ($linkDefs->getParam(RelationParam::DEFERRED_LOAD)) {
|
||||
$relationDefs = $relationDefs->withParam(RelationParam::DEFERRED_LOAD, true);
|
||||
}
|
||||
|
||||
$nameAttributeDefs = !$noForeignName ?
|
||||
(
|
||||
$noJoin ?
|
||||
AttributeDefs::create($nameName)
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withNotStorable()
|
||||
->withParam(AttributeParam::RELATION, $name)
|
||||
->withParam(AttributeParam::FOREIGN, $foreignName) :
|
||||
AttributeDefs::create($nameName)
|
||||
->withType(AttributeType::FOREIGN)
|
||||
->withNotStorable() // Used to be false before v7.4.
|
||||
->withParam(AttributeParam::RELATION, $name)
|
||||
->withParam(AttributeParam::FOREIGN, $foreignName)
|
||||
) : null;
|
||||
|
||||
$entityDefs = EntityDefs::create()
|
||||
->withAttribute($idAttributeDefs)
|
||||
->withRelation($relationDefs);
|
||||
|
||||
if ($nameAttributeDefs) {
|
||||
$entityDefs = $entityDefs->withAttribute($nameAttributeDefs);
|
||||
}
|
||||
|
||||
return $entityDefs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class BelongsToParent implements LinkConverter
|
||||
{
|
||||
private const TYPE_LENGTH = 100;
|
||||
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
|
||||
$foreignRelationName = $linkDefs->hasForeignRelationName() ?
|
||||
$linkDefs->getForeignRelationName() : null;
|
||||
|
||||
$idName = $name . 'Id';
|
||||
$nameName = $name . 'Name';
|
||||
$typeName = $name . 'Type';
|
||||
|
||||
$relationDefs = RelationDefs::create($name)
|
||||
->withType(RelationType::BELONGS_TO_PARENT)
|
||||
->withKey($idName)
|
||||
->withForeignRelationName($foreignRelationName);
|
||||
|
||||
if ($linkDefs->getParam(RelationParam::DEFERRED_LOAD)) {
|
||||
$relationDefs = $relationDefs->withParam(RelationParam::DEFERRED_LOAD, true);
|
||||
}
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute(
|
||||
AttributeDefs::create($idName)
|
||||
->withType(AttributeType::FOREIGN_ID)
|
||||
->withParam('index', $name)
|
||||
)
|
||||
->withAttribute(
|
||||
AttributeDefs::create($typeName)
|
||||
->withType(AttributeType::FOREIGN_TYPE)
|
||||
->withParam(AttributeParam::NOT_NULL, false) // Revise whether needed.
|
||||
->withParam('index', $name)
|
||||
->withLength(self::TYPE_LENGTH)
|
||||
)
|
||||
->withAttribute(
|
||||
AttributeDefs::create($nameName)
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withNotStorable()
|
||||
)
|
||||
->withRelation($relationDefs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\Entities\EmailAddress;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class EmailEmailAddress implements LinkConverter
|
||||
{
|
||||
public function __construct() {}
|
||||
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
$hasField = $linkDefs->getParam('hasField');
|
||||
|
||||
$foreignEntityType = EmailAddress::ENTITY_TYPE;
|
||||
|
||||
$key1 = lcfirst($entityType) . 'Id';
|
||||
$key2 = lcfirst($foreignEntityType) . 'Id';
|
||||
|
||||
$relationDefs = RelationDefs::create($name)
|
||||
->withType(RelationType::MANY_MANY)
|
||||
->withForeignEntityType($foreignEntityType)
|
||||
->withKey(Attribute::ID)
|
||||
->withForeignKey(Attribute::ID)
|
||||
->withMidKeys($key1, $key2);
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Ids')
|
||||
->withType(AttributeType::JSON_ARRAY)
|
||||
->withNotStorable()
|
||||
->withParam('isLinkStub', !$hasField)
|
||||
)
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Names')
|
||||
->withType(AttributeType::JSON_OBJECT)
|
||||
->withNotStorable()
|
||||
->withParam('isLinkStub', !$hasField)
|
||||
)
|
||||
->withRelation($relationDefs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class EntityCollaborator implements LinkConverter
|
||||
{
|
||||
private const ENTITY_TYPE_LENGTH = 100;
|
||||
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
$relationshipName = $linkDefs->getRelationshipName();
|
||||
|
||||
return EntityDefs::create()
|
||||
->withRelation(
|
||||
RelationDefs::create($name)
|
||||
->withType(RelationType::MANY_MANY)
|
||||
->withForeignEntityType(User::ENTITY_TYPE)
|
||||
->withRelationshipName($relationshipName)
|
||||
->withMidKeys('entityId', 'userId')
|
||||
->withConditions(['entityType' => $entityType])
|
||||
->withAdditionalColumn(
|
||||
AttributeDefs::create('entityType')
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withLength(self::ENTITY_TYPE_LENGTH)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class EntityTeam implements LinkConverter
|
||||
{
|
||||
private const ENTITY_TYPE_LENGTH = 100;
|
||||
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
$relationshipName = $linkDefs->getRelationshipName();
|
||||
|
||||
return EntityDefs::create()
|
||||
->withRelation(
|
||||
RelationDefs::create($name)
|
||||
->withType(RelationType::MANY_MANY)
|
||||
->withForeignEntityType(Team::ENTITY_TYPE)
|
||||
->withRelationshipName($relationshipName)
|
||||
->withMidKeys('entityId', 'teamId')
|
||||
->withConditions(['entityType' => $entityType])
|
||||
->withAdditionalColumn(
|
||||
AttributeDefs::create('entityType')
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withLength(self::ENTITY_TYPE_LENGTH)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class EntityUser implements LinkConverter
|
||||
{
|
||||
private const ENTITY_TYPE_LENGTH = 100;
|
||||
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
$relationshipName = $linkDefs->getRelationshipName();
|
||||
|
||||
return EntityDefs::create()
|
||||
->withRelation(
|
||||
RelationDefs::create($name)
|
||||
->withType(RelationType::MANY_MANY)
|
||||
->withForeignEntityType(User::ENTITY_TYPE)
|
||||
->withRelationshipName($relationshipName)
|
||||
->withMidKeys('entityId', 'userId')
|
||||
->withConditions(['entityType' => $entityType])
|
||||
->withAdditionalColumn(
|
||||
AttributeDefs::create('entityType')
|
||||
->withType(AttributeType::VARCHAR)
|
||||
->withLength(self::ENTITY_TYPE_LENGTH)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class HasChildren implements LinkConverter
|
||||
{
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
$foreignEntityType = $linkDefs->getForeignEntityType();
|
||||
$foreignRelationName = $linkDefs->hasForeignRelationName() ? $linkDefs->getForeignRelationName() : null;
|
||||
$hasField = $linkDefs->getParam('hasField');
|
||||
|
||||
$relationDefs = RelationDefs::create($name)
|
||||
->withType(RelationType::HAS_CHILDREN)
|
||||
->withForeignEntityType($foreignEntityType)
|
||||
->withForeignKey($foreignRelationName . 'Id')
|
||||
->withParam('foreignType', $foreignRelationName . 'Type')
|
||||
->withForeignRelationName($foreignRelationName);
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Ids')
|
||||
->withType(AttributeType::JSON_ARRAY)
|
||||
->withNotStorable()
|
||||
->withParam('isLinkStub', !$hasField) // Revise. Change to notExportable?
|
||||
)
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Names')
|
||||
->withType(AttributeType::JSON_OBJECT)
|
||||
->withNotStorable()
|
||||
->withParam('isLinkStub', !$hasField)
|
||||
)
|
||||
->withRelation($relationDefs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class HasMany implements LinkConverter
|
||||
{
|
||||
public function __construct(private Log $log) {}
|
||||
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
$foreignEntityType = $linkDefs->getForeignEntityType();
|
||||
$foreignRelationName = $linkDefs->getForeignRelationName();
|
||||
$hasField = $linkDefs->getParam('hasField');
|
||||
|
||||
$type = RelationType::HAS_MANY;
|
||||
|
||||
/*$type = $linkDefs->hasRelationshipName() ?
|
||||
RelationType::MANY_MANY : // Revise.
|
||||
RelationType::HAS_MANY;*/
|
||||
|
||||
if ($linkDefs->hasRelationshipName()) {
|
||||
$this->log->warning(
|
||||
"Issue with the link '{$name}' in '{$entityType}' entity type. Might be the foreign link " .
|
||||
"'{$foreignRelationName}' in '{$foreignEntityType}' entity type is missing. " .
|
||||
"Remove the problem link manually.");
|
||||
|
||||
return EntityDefs::create();
|
||||
}
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Ids')
|
||||
->withType(AttributeType::JSON_ARRAY)
|
||||
->withNotStorable()
|
||||
->withParam('isLinkStub', !$hasField) // Revise. Change to notExportable?
|
||||
)
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Names')
|
||||
->withType(AttributeType::JSON_OBJECT)
|
||||
->withNotStorable()
|
||||
->withParam('isLinkStub', !$hasField)
|
||||
)
|
||||
->withRelation(
|
||||
RelationDefs::create($name)
|
||||
->withType($type)
|
||||
->withForeignEntityType($foreignEntityType)
|
||||
->withForeignKey($foreignRelationName . 'Id')
|
||||
->withForeignRelationName($foreignRelationName)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class HasOne implements LinkConverter
|
||||
{
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
$foreignEntityType = $linkDefs->getForeignEntityType();
|
||||
$foreignRelationName = $linkDefs->hasForeignRelationName() ? $linkDefs->getForeignRelationName() : null;
|
||||
$noForeignName = $linkDefs->getParam('noForeignName');
|
||||
$foreignName = $linkDefs->getParam('foreignName') ?? 'name';
|
||||
$noJoin = $linkDefs->getParam('noJoin');
|
||||
|
||||
$idName = $name . 'Id';
|
||||
$nameName = $name . 'Name';
|
||||
|
||||
$idAttributeDefs = AttributeDefs::create($idName)
|
||||
->withType($noJoin ? AttributeType::VARCHAR : AttributeType::FOREIGN)
|
||||
->withNotStorable()
|
||||
->withParam(AttributeParam::RELATION, $name)
|
||||
->withParam(AttributeParam::FOREIGN, Attribute::ID);
|
||||
|
||||
$nameAttributeDefs = !$noForeignName ?
|
||||
(
|
||||
AttributeDefs::create($nameName)
|
||||
->withType($noJoin ? AttributeType::VARCHAR : AttributeType::FOREIGN)
|
||||
->withNotStorable()
|
||||
->withParam(AttributeParam::RELATION, $name)
|
||||
->withParam(AttributeParam::FOREIGN, $foreignName)
|
||||
) : null;
|
||||
|
||||
$relationDefs = RelationDefs::create($name)
|
||||
->withType(RelationType::HAS_ONE)
|
||||
->withForeignEntityType($foreignEntityType);
|
||||
|
||||
if ($foreignRelationName) {
|
||||
$relationDefs = $relationDefs
|
||||
->withForeignKey($foreignRelationName . 'Id')
|
||||
->withForeignRelationName($foreignRelationName);
|
||||
}
|
||||
|
||||
$entityDefs = EntityDefs::create()
|
||||
->withAttribute($idAttributeDefs)
|
||||
->withRelation($relationDefs);
|
||||
|
||||
if ($nameAttributeDefs) {
|
||||
$entityDefs = $entityDefs->withAttribute($nameAttributeDefs);
|
||||
}
|
||||
|
||||
return $entityDefs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class ManyMany implements LinkConverter
|
||||
{
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
$foreignEntityType = $linkDefs->getForeignEntityType();
|
||||
$foreignRelationName = $linkDefs->getForeignRelationName();
|
||||
$hasField = $linkDefs->getParam('hasField');
|
||||
$columnAttributeMap = $linkDefs->getParam('columnAttributeMap');
|
||||
|
||||
$relationshipName = $linkDefs->hasRelationshipName() ?
|
||||
$linkDefs->getRelationshipName() :
|
||||
self::composeRelationshipName($entityType, $foreignEntityType);
|
||||
|
||||
if ($linkDefs->hasMidKey() && $linkDefs->hasForeignMidKey()) {
|
||||
$key1 = $linkDefs->getMidKey();
|
||||
$key2 = $linkDefs->getForeignMidKey();
|
||||
} else {
|
||||
$key1 = lcfirst($entityType) . 'Id';
|
||||
$key2 = lcfirst($foreignEntityType) . 'Id';
|
||||
|
||||
if ($key1 === $key2) {
|
||||
[$key1, $key2] = strcmp($name, $foreignRelationName) > 0 ?
|
||||
['leftId', 'rightId'] :
|
||||
['rightId', 'leftId'];
|
||||
}
|
||||
}
|
||||
|
||||
$relationDefs = RelationDefs::create($name)
|
||||
->withType(RelationType::MANY_MANY)
|
||||
->withForeignEntityType($foreignEntityType)
|
||||
->withRelationshipName($relationshipName)
|
||||
->withKey(Attribute::ID)
|
||||
->withForeignKey(Attribute::ID)
|
||||
->withMidKeys($key1, $key2)
|
||||
->withForeignRelationName($foreignRelationName);
|
||||
|
||||
if ($columnAttributeMap) {
|
||||
$relationDefs = $relationDefs->withParam('columnAttributeMap', $columnAttributeMap);
|
||||
}
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Ids')
|
||||
->withType(AttributeType::JSON_ARRAY)
|
||||
->withNotStorable()
|
||||
->withParam('isLinkStub', !$hasField) // Revise.
|
||||
)
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Names')
|
||||
->withType(AttributeType::JSON_OBJECT)
|
||||
->withNotStorable()
|
||||
->withParam('isLinkStub', !$hasField) // Revise.
|
||||
)
|
||||
->withRelation($relationDefs);
|
||||
}
|
||||
|
||||
private static function composeRelationshipName(string $left, string $right): string
|
||||
{
|
||||
$parts = [
|
||||
Util::toCamelCase(lcfirst($left)),
|
||||
Util::toCamelCase(lcfirst($right)),
|
||||
];
|
||||
|
||||
sort($parts);
|
||||
|
||||
return Util::toCamelCase(implode('_', $parts));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm\LinkConverters;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverter;
|
||||
use Espo\Entities\PhoneNumber;
|
||||
use Espo\ORM\Defs\RelationDefs as LinkDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
class SmsPhoneNumber implements LinkConverter
|
||||
{
|
||||
public function __construct() {}
|
||||
|
||||
public function convert(LinkDefs $linkDefs, string $entityType): EntityDefs
|
||||
{
|
||||
$name = $linkDefs->getName();
|
||||
$foreignEntityType = PhoneNumber::ENTITY_TYPE;
|
||||
|
||||
$key1 = lcfirst($entityType) . 'Id';
|
||||
$key2 = lcfirst($foreignEntityType) . 'Id';
|
||||
|
||||
$relationDefs = RelationDefs::create($name)
|
||||
->withType(RelationType::MANY_MANY)
|
||||
->withForeignEntityType($foreignEntityType)
|
||||
->withKey('id')
|
||||
->withForeignKey('id')
|
||||
->withMidKeys($key1, $key2);
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Ids')
|
||||
->withType(AttributeType::JSON_ARRAY)
|
||||
->withNotStorable()
|
||||
)
|
||||
->withAttribute(
|
||||
AttributeDefs::create($name . 'Names')
|
||||
->withType(AttributeType::JSON_OBJECT)
|
||||
->withNotStorable()
|
||||
)
|
||||
->withRelation($relationDefs);
|
||||
}
|
||||
}
|
||||
282
application/Espo/Core/Utils/Database/Orm/RelationConverter.php
Normal file
282
application/Espo/Core/Utils/Database/Orm/RelationConverter.php
Normal file
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Orm;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverters\BelongsTo;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverters\BelongsToParent;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverters\HasChildren;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverters\HasMany;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverters\HasOne;
|
||||
use Espo\Core\Utils\Database\Orm\LinkConverters\ManyMany;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\Params\EntityParam;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Defs\RelationDefs;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class RelationConverter
|
||||
{
|
||||
private const DEFAULT_VARCHAR_LENGTH = 255;
|
||||
|
||||
/**
|
||||
* Parameters to merge from both sides.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $mergeParams = [
|
||||
RelationParam::RELATION_NAME,
|
||||
RelationParam::CONDITIONS,
|
||||
RelationParam::ADDITIONAL_COLUMNS,
|
||||
'noJoin',
|
||||
RelationParam::INDEXES,
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private $manyMergeParams = [
|
||||
RelationParam::ORDER_BY,
|
||||
RelationParam::ORDER,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Log $log
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array<string, mixed> $params
|
||||
* @param string $entityType
|
||||
* @return ?array<string, mixed>
|
||||
*/
|
||||
public function process(string $name, array $params, string $entityType): ?array
|
||||
{
|
||||
$foreignEntityType = $params[RelationParam::ENTITY] ?? null;
|
||||
$foreignLinkName = $params[RelationParam::FOREIGN] ?? null;
|
||||
|
||||
/** @var ?array<string, mixed> $foreignParams */
|
||||
$foreignParams = $foreignEntityType && $foreignLinkName ?
|
||||
$this->metadata->get(['entityDefs', $foreignEntityType, 'links', $foreignLinkName]) :
|
||||
null;
|
||||
|
||||
/** @var ?string $relationshipName */
|
||||
$relationshipName = $params[RelationParam::RELATION_NAME] ?? null;
|
||||
|
||||
if ($relationshipName) {
|
||||
$relationshipName = lcfirst($relationshipName);
|
||||
$params[RelationParam::RELATION_NAME] = $relationshipName;
|
||||
}
|
||||
|
||||
$linkType = $params[RelationParam::TYPE] ?? null;
|
||||
$foreignLinkType = $foreignParams ? $foreignParams[RelationParam::TYPE] : null;
|
||||
|
||||
if (!$linkType) {
|
||||
$this->log->warning("Link $entityType.$name has no type.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$params['hasField'] = (bool) $this->metadata
|
||||
->get(['entityDefs', $entityType, 'fields', $name]);
|
||||
|
||||
$relationDefs = RelationDefs::fromRaw($params, $name);
|
||||
|
||||
$converter = $this->createLinkConverter($relationshipName, $linkType, $foreignLinkType);
|
||||
|
||||
$convertedEntityDefs = $converter->convert($relationDefs, $entityType);
|
||||
|
||||
$raw = $convertedEntityDefs->toAssoc();
|
||||
|
||||
if (isset($raw[EntityParam::RELATIONS][$name])) {
|
||||
$defs = &$raw[EntityParam::RELATIONS][$name];
|
||||
|
||||
$this->mergeParams($defs, $params, $foreignParams ?? [], $linkType);
|
||||
$this->correct($defs);
|
||||
|
||||
if ($params[RelationParam::CASCADE_REMOVAL] ?? false) {
|
||||
$defs[RelationParam::CASCADE_REMOVAL] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return [$entityType => $raw];
|
||||
}
|
||||
|
||||
private function createLinkConverter(?string $relationship, string $type, ?string $foreignType): LinkConverter
|
||||
{
|
||||
$className = $this->getLinkConverterClassName($relationship, $type, $foreignType);
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<LinkConverter>
|
||||
*/
|
||||
private function getLinkConverterClassName(?string $relationship, string $type, ?string $foreignType): string
|
||||
{
|
||||
if ($relationship) {
|
||||
/** @var class-string<LinkConverter> $className */
|
||||
$className = $this->metadata->get(['app', 'relationships', $relationship, 'converterClassName']);
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
}
|
||||
|
||||
if ($type === RelationType::HAS_MANY && $foreignType === RelationType::HAS_MANY) {
|
||||
return ManyMany::class;
|
||||
}
|
||||
|
||||
if ($type === RelationType::HAS_MANY) {
|
||||
return HasMany::class;
|
||||
}
|
||||
|
||||
if ($type === RelationType::HAS_CHILDREN) {
|
||||
return HasChildren::class;
|
||||
}
|
||||
|
||||
if ($type === RelationType::HAS_ONE) {
|
||||
return HasOne::class;
|
||||
}
|
||||
|
||||
if ($type === RelationType::BELONGS_TO) {
|
||||
return BelongsTo::class;
|
||||
}
|
||||
|
||||
if ($type === RelationType::BELONGS_TO_PARENT) {
|
||||
return BelongsToParent::class;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unsupported link type '$type'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $relationDefs
|
||||
* @param array<string, mixed> $params
|
||||
* @param array<string, mixed> $foreignParams
|
||||
*/
|
||||
private function mergeParams(array &$relationDefs, array $params, array $foreignParams, string $linkType): void
|
||||
{
|
||||
$mergeParams = $this->mergeParams;
|
||||
|
||||
if (
|
||||
$linkType === RelationType::HAS_MANY ||
|
||||
$linkType === RelationType::HAS_CHILDREN
|
||||
) {
|
||||
$mergeParams = array_merge($mergeParams, $this->manyMergeParams);
|
||||
}
|
||||
|
||||
foreach ($mergeParams as $name) {
|
||||
$additionalParam = $this->getMergedParam($name, $params, $foreignParams);
|
||||
|
||||
if ($additionalParam === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relationDefs[$name] = $additionalParam;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
* @param array<string, mixed> $foreignParams
|
||||
* @return array<string, mixed>|scalar|null
|
||||
*/
|
||||
private function getMergedParam(string $name, array $params, array $foreignParams): mixed
|
||||
{
|
||||
$value = $params[$name] ?? null;
|
||||
$foreignValue = $foreignParams[$name] ?? null;
|
||||
|
||||
if ($value !== null && $foreignValue !== null) {
|
||||
if (!empty($value) && !is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (!empty($foreignValue) && !is_array($foreignValue)) {
|
||||
return $foreignValue;
|
||||
}
|
||||
|
||||
/** @var array<int|string, mixed> $value */
|
||||
/** @var array<int|string, mixed> $foreignValue */
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
return Util::merge($value, $foreignValue);
|
||||
}
|
||||
|
||||
if (isset($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (isset($foreignValue)) {
|
||||
return $foreignValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $relationDefs
|
||||
*/
|
||||
private function correct(array &$relationDefs): void
|
||||
{
|
||||
if (
|
||||
isset($relationDefs[RelationParam::ORDER]) &&
|
||||
is_string($relationDefs[RelationParam::ORDER])
|
||||
) {
|
||||
$relationDefs[RelationParam::ORDER] = strtoupper($relationDefs[RelationParam::ORDER]);
|
||||
}
|
||||
|
||||
if (!isset($relationDefs[RelationParam::ADDITIONAL_COLUMNS])) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var array<string, array<string, mixed>> $additionalColumns */
|
||||
$additionalColumns = &$relationDefs[RelationParam::ADDITIONAL_COLUMNS];
|
||||
|
||||
foreach ($additionalColumns as &$columnDefs) {
|
||||
$columnDefs[AttributeParam::TYPE] ??= AttributeType::VARCHAR;
|
||||
|
||||
if (
|
||||
$columnDefs[AttributeParam::TYPE] === AttributeType::VARCHAR &&
|
||||
!isset($columnDefs[AttributeParam::LEN])
|
||||
) {
|
||||
$columnDefs[AttributeParam::LEN] = self::DEFAULT_VARCHAR_LENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
$relationDefs[RelationParam::ADDITIONAL_COLUMNS] = $additionalColumns;
|
||||
}
|
||||
}
|
||||
488
application/Espo/Core/Utils/Database/Schema/Builder.php
Normal file
488
application/Espo/Core/Utils/Database/Schema/Builder.php
Normal file
@@ -0,0 +1,488 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Schema;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Database\ConfigDataProvider;
|
||||
use Espo\Core\Utils\Database\MetadataProvider as MetadataProvider;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use Espo\ORM\Defs\AttributeDefs;
|
||||
use Espo\ORM\Defs\EntityDefs;
|
||||
use Espo\ORM\Defs\IndexDefs;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\Params\EntityParam;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Defs\RelationDefs;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Schema\Schema as DbalSchema;
|
||||
use Doctrine\DBAL\Types\Type as DbalType;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
/**
|
||||
* Schema representation builder.
|
||||
*/
|
||||
class Builder
|
||||
{
|
||||
private const ATTR_ID = 'id';
|
||||
private const ATTR_DELETED = 'deleted';
|
||||
|
||||
private int $idLength;
|
||||
private string $idDbType;
|
||||
|
||||
/** @var string[] */
|
||||
private $typeList;
|
||||
private ColumnPreparator $columnPreparator;
|
||||
|
||||
public function __construct(
|
||||
private Log $log,
|
||||
private InjectableFactory $injectableFactory,
|
||||
ConfigDataProvider $configDataProvider,
|
||||
ColumnPreparatorFactory $columnPreparatorFactory,
|
||||
MetadataProvider $metadataProvider
|
||||
) {
|
||||
$this->typeList = array_keys(DbalType::getTypesMap());
|
||||
|
||||
$platform = $configDataProvider->getPlatform();
|
||||
|
||||
$this->columnPreparator = $columnPreparatorFactory->create($platform);
|
||||
|
||||
$this->idLength = $metadataProvider->getIdLength();
|
||||
$this->idDbType = $metadataProvider->getIdDbType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a schema representation for an ORM metadata.
|
||||
*
|
||||
* @param array<string, mixed> $ormMeta Raw ORM metadata.
|
||||
* @param ?string[] $entityTypeList Specific entity types.
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function build(array $ormMeta, ?array $entityTypeList = null): DbalSchema
|
||||
{
|
||||
$this->log->debug('Schema\Builder - Start');
|
||||
|
||||
$ormMeta = $this->amendMetadata($ormMeta, $entityTypeList);
|
||||
$tables = [];
|
||||
|
||||
$schema = new DbalSchema();
|
||||
|
||||
foreach ($ormMeta as $entityType => $entityParams) {
|
||||
$entityDefs = EntityDefs::fromRaw($entityParams, $entityType);
|
||||
|
||||
$this->buildEntity($entityDefs, $schema, $tables);
|
||||
}
|
||||
|
||||
foreach ($ormMeta as $entityType => $entityParams) {
|
||||
foreach (($entityParams[EntityParam::RELATIONS] ?? []) as $relationName => $relationParams) {
|
||||
$relationDefs = RelationDefs::fromRaw($relationParams, $relationName);
|
||||
|
||||
if ($relationDefs->getType() !== Entity::MANY_MANY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->buildManyMany($entityType, $relationDefs, $schema, $tables);
|
||||
}
|
||||
}
|
||||
|
||||
$this->log->debug('Schema\Builder - End');
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, Table> $tables
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function buildEntity(EntityDefs $entityDefs, DbalSchema $schema, array &$tables): void
|
||||
{
|
||||
if ($entityDefs->getParam('skipRebuild')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entityType = $entityDefs->getName();
|
||||
|
||||
$modifier = $this->getEntityDefsModifier($entityDefs);
|
||||
|
||||
if ($modifier) {
|
||||
$modifiedEntityDefs = $modifier->modify($entityDefs);
|
||||
|
||||
$entityDefs = EntityDefs::fromRaw($modifiedEntityDefs->toAssoc(), $entityType);
|
||||
}
|
||||
|
||||
$this->log->debug("Schema\Builder: Entity $entityType");
|
||||
|
||||
$tableName = Util::toUnderScore($entityType);
|
||||
|
||||
if ($schema->hasTable($tableName)) {
|
||||
$tables[$entityType] ??= $schema->getTable($tableName);
|
||||
|
||||
$this->log->debug('Schema\Builder: Table [' . $tableName . '] exists.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $schema->createTable($tableName);
|
||||
|
||||
$tables[$entityType] = $table;
|
||||
|
||||
/** @var array<string, mixed> $tableParams */
|
||||
$tableParams = $entityDefs->getParam('params') ?? [];
|
||||
|
||||
foreach ($tableParams as $paramName => $paramValue) {
|
||||
$table->addOption($paramName, $paramValue);
|
||||
}
|
||||
|
||||
$primaryColumns = [];
|
||||
|
||||
foreach ($entityDefs->getAttributeList() as $attributeDefs) {
|
||||
if (
|
||||
$attributeDefs->isNotStorable() ||
|
||||
$attributeDefs->getType() === Entity::FOREIGN
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$column = $this->columnPreparator->prepare($attributeDefs);
|
||||
|
||||
if ($attributeDefs->getType() === Entity::ID) {
|
||||
$primaryColumns[] = $column->getName();
|
||||
}
|
||||
|
||||
if (!in_array($column->getType(), $this->typeList)) {
|
||||
$this->log->warning(
|
||||
'Schema\Builder: Column type [' . $column->getType() . '] not supported, ' .
|
||||
$entityType . ':' . $attributeDefs->getName()
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($table->hasColumn($column->getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addColumn($table, $column);
|
||||
}
|
||||
|
||||
$table->setPrimaryKey($primaryColumns);
|
||||
|
||||
$this->addIndexes($table, $entityDefs->getIndexList());
|
||||
}
|
||||
|
||||
private function getEntityDefsModifier(EntityDefs $entityDefs): ?EntityDefsModifier
|
||||
{
|
||||
/** @var ?class-string<EntityDefsModifier> $modifierClassName */
|
||||
$modifierClassName = $entityDefs->getParam('modifierClassName');
|
||||
|
||||
if (!$modifierClassName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($modifierClassName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $ormMeta
|
||||
* @param ?string[] $entityTypeList
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function amendMetadata(array $ormMeta, ?array $entityTypeList): array
|
||||
{
|
||||
if (isset($ormMeta['unsetIgnore'])) {
|
||||
$protectedOrmMeta = [];
|
||||
|
||||
foreach ($ormMeta['unsetIgnore'] as $protectedKey) {
|
||||
$protectedOrmMeta = Util::merge(
|
||||
$protectedOrmMeta,
|
||||
Util::fillArrayKeys($protectedKey, Util::getValueByKey($ormMeta, $protectedKey))
|
||||
);
|
||||
}
|
||||
|
||||
unset($ormMeta['unsetIgnore']);
|
||||
}
|
||||
|
||||
// Unset some keys.
|
||||
if (isset($ormMeta['unset'])) {
|
||||
/** @var array<string, mixed> $ormMeta */
|
||||
$ormMeta = Util::unsetInArray($ormMeta, $ormMeta['unset']);
|
||||
|
||||
unset($ormMeta['unset']);
|
||||
}
|
||||
|
||||
if (isset($protectedOrmMeta)) {
|
||||
/** @var array<string, mixed> $ormMeta */
|
||||
$ormMeta = Util::merge($ormMeta, $protectedOrmMeta);
|
||||
}
|
||||
|
||||
if (isset($entityTypeList)) {
|
||||
$dependentEntityTypeList = $this->getDependentEntityTypeList($entityTypeList, $ormMeta);
|
||||
|
||||
$this->log->debug(
|
||||
'Schema\Builder: Rebuild for entity types: [' .
|
||||
implode(', ', $entityTypeList) . '] with dependent entity types: [' .
|
||||
implode(', ', $dependentEntityTypeList) . ']'
|
||||
);
|
||||
|
||||
$ormMeta = array_intersect_key($ormMeta, array_flip($dependentEntityTypeList));
|
||||
}
|
||||
|
||||
return $ormMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function addColumn(Table $table, Column $column): void
|
||||
{
|
||||
$table->addColumn(
|
||||
$column->getName(),
|
||||
$column->getType(),
|
||||
self::convertColumn($column)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a relation table for the manyMany relation.
|
||||
*
|
||||
* @param string $entityType
|
||||
* @param array<string, Table> $tables
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function buildManyMany(
|
||||
string $entityType,
|
||||
RelationDefs $relationDefs,
|
||||
DbalSchema $schema,
|
||||
array &$tables
|
||||
): void {
|
||||
|
||||
$relationshipName = $relationDefs->getRelationshipName();
|
||||
|
||||
if (isset($tables[$relationshipName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tableName = Util::toUnderScore($relationshipName);
|
||||
|
||||
$this->log->debug("Schema\Builder: ManyMany for $entityType.{$relationDefs->getName()}");
|
||||
|
||||
if ($schema->hasTable($tableName)) {
|
||||
$this->log->debug('Schema\Builder: Table [' . $tableName . '] exists.');
|
||||
|
||||
$tables[$relationshipName] ??= $schema->getTable($tableName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $schema->createTable($tableName);
|
||||
|
||||
$idColumn = $this->columnPreparator->prepare(
|
||||
AttributeDefs::fromRaw([
|
||||
AttributeParam::DB_TYPE => Types::BIGINT,
|
||||
'type' => Entity::ID,
|
||||
AttributeParam::LEN => 20,
|
||||
'autoincrement' => true,
|
||||
], self::ATTR_ID)
|
||||
);
|
||||
|
||||
$this->addColumn($table, $idColumn);
|
||||
|
||||
if (!$relationDefs->hasMidKey() || !$relationDefs->getForeignMidKey()) {
|
||||
$this->log->error('Schema\Builder: Relationship midKeys are empty.', [
|
||||
'entityType' => $entityType,
|
||||
'relationName' => $relationDefs->getName(),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$midKeys = [
|
||||
$relationDefs->getMidKey(),
|
||||
$relationDefs->getForeignMidKey(),
|
||||
];
|
||||
|
||||
foreach ($midKeys as $midKey) {
|
||||
$column = $this->columnPreparator->prepare(
|
||||
AttributeDefs::fromRaw([
|
||||
'type' => Entity::FOREIGN_ID,
|
||||
AttributeParam::DB_TYPE => $this->idDbType,
|
||||
AttributeParam::LEN => $this->idLength,
|
||||
], $midKey)
|
||||
);
|
||||
|
||||
$this->addColumn($table, $column);
|
||||
}
|
||||
|
||||
/** @var array<string, array<string, mixed>> $additionalColumns */
|
||||
$additionalColumns = $relationDefs->getParam(RelationParam::ADDITIONAL_COLUMNS) ?? [];
|
||||
|
||||
foreach ($additionalColumns as $fieldName => $fieldParams) {
|
||||
if ($fieldParams['type'] === AttributeType::FOREIGN_ID) {
|
||||
$fieldParams = array_merge([
|
||||
AttributeParam::DB_TYPE => $this->idDbType,
|
||||
AttributeParam::LEN => $this->idLength,
|
||||
], $fieldParams);
|
||||
}
|
||||
|
||||
$column = $this->columnPreparator->prepare(AttributeDefs::fromRaw($fieldParams, $fieldName));
|
||||
|
||||
$this->addColumn($table, $column);
|
||||
}
|
||||
|
||||
$deletedColumn = $this->columnPreparator->prepare(
|
||||
AttributeDefs::fromRaw([
|
||||
'type' => Entity::BOOL,
|
||||
'default' => false,
|
||||
], self::ATTR_DELETED)
|
||||
);
|
||||
|
||||
$this->addColumn($table, $deletedColumn);
|
||||
|
||||
$table->setPrimaryKey([self::ATTR_ID]);
|
||||
|
||||
$this->addIndexes($table, $relationDefs->getIndexList());
|
||||
|
||||
$tables[$relationshipName] = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IndexDefs[] $indexDefsList
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function addIndexes(Table $table, array $indexDefsList): void
|
||||
{
|
||||
foreach ($indexDefsList as $indexDefs) {
|
||||
$columns = array_map(
|
||||
fn($item) => Util::toUnderScore($item),
|
||||
$indexDefs->getColumnList()
|
||||
);
|
||||
|
||||
if ($indexDefs->isUnique()) {
|
||||
$table->addUniqueIndex($columns, $indexDefs->getKey());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$table->addIndex($columns, $indexDefs->getKey(), $indexDefs->getFlagList());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Move to a class. Add unit test.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function convertColumn(Column $column): array
|
||||
{
|
||||
$result = [
|
||||
'notnull' => $column->isNotNull(),
|
||||
];
|
||||
|
||||
if ($column->getLength() !== null) {
|
||||
$result['length'] = $column->getLength();
|
||||
}
|
||||
|
||||
if ($column->getDefault() !== null) {
|
||||
$result['default'] = $column->getDefault();
|
||||
}
|
||||
|
||||
if ($column->getAutoincrement() !== null) {
|
||||
$result['autoincrement'] = $column->getAutoincrement();
|
||||
}
|
||||
|
||||
if ($column->getPrecision() !== null) {
|
||||
$result['precision'] = $column->getPrecision();
|
||||
}
|
||||
|
||||
if ($column->getScale() !== null) {
|
||||
$result['scale'] = $column->getScale();
|
||||
}
|
||||
|
||||
if ($column->getUnsigned() !== null) {
|
||||
$result['unsigned'] = $column->getUnsigned();
|
||||
}
|
||||
|
||||
if ($column->getFixed() !== null) {
|
||||
$result['fixed'] = $column->getFixed();
|
||||
}
|
||||
|
||||
// Can't use customSchemaOptions as it causes unwanted ALTER TABLE.
|
||||
$result['platformOptions'] = [];
|
||||
|
||||
if ($column->getCollation()) {
|
||||
$result['platformOptions']['collation'] = $column->getCollation();
|
||||
}
|
||||
|
||||
if ($column->getCharset()) {
|
||||
$result['platformOptions']['charset'] = $column->getCharset();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $entityTypeList
|
||||
* @param array<string, mixed> $ormMeta
|
||||
* @param string[] $depList
|
||||
* @return string[]
|
||||
*/
|
||||
private function getDependentEntityTypeList(array $entityTypeList, array $ormMeta, array $depList = []): array
|
||||
{
|
||||
foreach ($entityTypeList as $entityType) {
|
||||
if (in_array($entityType, $depList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$depList[] = $entityType;
|
||||
|
||||
$entityDefs = EntityDefs::fromRaw($ormMeta[$entityType] ?? [], $entityType);
|
||||
|
||||
foreach ($entityDefs->getRelationList() as $relationDefs) {
|
||||
if (!$relationDefs->hasForeignEntityType()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemEntityType = $relationDefs->getForeignEntityType();
|
||||
|
||||
if (in_array($itemEntityType, $depList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$depList = $this->getDependentEntityTypeList([$itemEntityType], $ormMeta, $depList);
|
||||
}
|
||||
}
|
||||
|
||||
return $depList;
|
||||
}
|
||||
}
|
||||
203
application/Espo/Core/Utils/Database/Schema/Column.php
Normal file
203
application/Espo/Core/Utils/Database/Schema/Column.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Schema;
|
||||
|
||||
/**
|
||||
* A DB column parameters.
|
||||
*/
|
||||
class Column
|
||||
{
|
||||
private bool $notNull = false;
|
||||
private ?int $length = null;
|
||||
private mixed $default = null;
|
||||
private ?bool $autoincrement = null;
|
||||
private ?int $precision = null;
|
||||
private ?int $scale = null;
|
||||
private ?bool $unsigned = null;
|
||||
private ?bool $fixed = null;
|
||||
private ?string $collation = null;
|
||||
private ?string $charset = null;
|
||||
|
||||
private function __construct(
|
||||
private string $name,
|
||||
private string $type
|
||||
) {}
|
||||
|
||||
public static function create(string $name, string $type): self
|
||||
{
|
||||
return new self($name, $type);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function isNotNull(): bool
|
||||
{
|
||||
return $this->notNull;
|
||||
}
|
||||
|
||||
public function getLength(): ?int
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
public function getDefault(): mixed
|
||||
{
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
public function getAutoincrement(): ?bool
|
||||
{
|
||||
return $this->autoincrement;
|
||||
}
|
||||
|
||||
public function getUnsigned(): ?bool
|
||||
{
|
||||
return $this->unsigned;
|
||||
}
|
||||
|
||||
public function getPrecision(): ?int
|
||||
{
|
||||
return $this->precision;
|
||||
}
|
||||
|
||||
public function getScale(): ?int
|
||||
{
|
||||
return $this->scale;
|
||||
}
|
||||
|
||||
public function getFixed(): ?bool
|
||||
{
|
||||
return $this->fixed;
|
||||
}
|
||||
|
||||
public function getCollation(): ?string
|
||||
{
|
||||
return $this->collation;
|
||||
}
|
||||
|
||||
public function getCharset(): ?string
|
||||
{
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
public function withNotNull(bool $notNull = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->notNull = $notNull;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withLength(?int $length): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->length = $length;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withDefault(mixed $default): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->default = $default;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withAutoincrement(?bool $autoincrement = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->autoincrement = $autoincrement;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsigned. Supported only by MySQL.
|
||||
*/
|
||||
public function withUnsigned(?bool $unsigned = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->unsigned = $unsigned;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withPrecision(?int $precision): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->precision = $precision;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withScale(?int $scale): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->scale = $scale;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed length. For string and binary types.
|
||||
*/
|
||||
public function withFixed(?bool $fixed = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->fixed = $fixed;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withCollation(?string $collation): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->collation = $collation;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withCharset(?string $charset): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->charset = $charset;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Schema;
|
||||
|
||||
use Espo\ORM\Defs\AttributeDefs;
|
||||
|
||||
interface ColumnPreparator
|
||||
{
|
||||
public function prepare(AttributeDefs $defs): Column;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Schema;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Database\Helper;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use RuntimeException;
|
||||
|
||||
class ColumnPreparatorFactory
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Helper $helper
|
||||
) {}
|
||||
|
||||
public function create(string $platform): ColumnPreparator
|
||||
{
|
||||
/** @var ?class-string<ColumnPreparator> $className */
|
||||
$className = $this->metadata
|
||||
->get(['app', 'databasePlatforms', $platform, 'columnPreparatorClassName']);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("No Column-Preparator for {$platform}.");
|
||||
}
|
||||
|
||||
$binding = BindingContainerBuilder::create()
|
||||
->bindInstance(Helper::class, $this->helper)
|
||||
->build();
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $binding);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 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\Utils\Database\Schema\ColumnPreparators;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Espo\Core\Utils\Database\Dbal\Types\LongtextType;
|
||||
use Espo\Core\Utils\Database\Dbal\Types\MediumtextType;
|
||||
use Espo\Core\Utils\Database\Helper;
|
||||
use Espo\Core\Utils\Database\Schema\Column;
|
||||
use Espo\Core\Utils\Database\Schema\ColumnPreparator;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Defs\AttributeDefs;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class MysqlColumnPreparator implements ColumnPreparator
|
||||
{
|
||||
private const PARAM_DB_TYPE = AttributeParam::DB_TYPE;
|
||||
private const PARAM_DEFAULT = AttributeParam::DEFAULT;
|
||||
private const PARAM_NOT_NULL = AttributeParam::NOT_NULL;
|
||||
private const PARAM_AUTOINCREMENT = 'autoincrement';
|
||||
private const PARAM_PRECISION = 'precision';
|
||||
private const PARAM_SCALE = 'scale';
|
||||
private const PARAM_BINARY = 'binary';
|
||||
private const string PARAM_STORE_ARRAY_VALUES = 'storeArrayValues';
|
||||
|
||||
public const TYPE_MYSQL = 'MySQL';
|
||||
public const TYPE_MARIADB = 'MariaDB';
|
||||
|
||||
private const MB4_INDEX_LENGTH_LIMIT = 3072;
|
||||
private const DEFAULT_INDEX_LIMIT = 1000;
|
||||
|
||||
/** @var string[] */
|
||||
private array $mediumTextTypeList = [
|
||||
Entity::TEXT,
|
||||
Entity::JSON_OBJECT,
|
||||
Entity::JSON_ARRAY,
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $columnTypeMap = [
|
||||
Entity::BOOL => Types::BOOLEAN,
|
||||
Entity::INT => Types::INTEGER,
|
||||
Entity::VARCHAR => Types::STRING,
|
||||
];
|
||||
|
||||
private ?int $maxIndexLength = null;
|
||||
|
||||
public function __construct(
|
||||
private Helper $helper
|
||||
) {}
|
||||
|
||||
public function prepare(AttributeDefs $defs): Column
|
||||
{
|
||||
$dbType = $defs->getParam(self::PARAM_DB_TYPE);
|
||||
$type = $defs->getType();
|
||||
$length = $defs->getLength();
|
||||
$default = $defs->getParam(self::PARAM_DEFAULT);
|
||||
$notNull = $defs->getParam(self::PARAM_NOT_NULL);
|
||||
$autoincrement = $defs->getParam(self::PARAM_AUTOINCREMENT);
|
||||
$precision = $defs->getParam(self::PARAM_PRECISION);
|
||||
$scale = $defs->getParam(self::PARAM_SCALE);
|
||||
$binary = $defs->getParam(self::PARAM_BINARY);
|
||||
|
||||
$columnType = $dbType ?? $type;
|
||||
|
||||
if (in_array($type, $this->mediumTextTypeList) && !$dbType) {
|
||||
$columnType = MediumtextType::NAME;
|
||||
}
|
||||
|
||||
$columnType = $this->columnTypeMap[$columnType] ?? $columnType;
|
||||
|
||||
$columnName = Util::toUnderScore($defs->getName());
|
||||
|
||||
$column = Column::create($columnName, strtolower($columnType));
|
||||
|
||||
if ($length !== null) {
|
||||
$column = $column->withLength($length);
|
||||
}
|
||||
|
||||
if ($default !== null) {
|
||||
$column = $column->withDefault($default);
|
||||
}
|
||||
|
||||
if ($notNull !== null) {
|
||||
$column = $column->withNotNull($notNull);
|
||||
}
|
||||
|
||||
if ($autoincrement !== null) {
|
||||
$column = $column->withAutoincrement($autoincrement);
|
||||
}
|
||||
|
||||
if ($precision !== null) {
|
||||
$column = $column->withPrecision($precision);
|
||||
}
|
||||
|
||||
if ($scale !== null) {
|
||||
$column = $column->withScale($scale);
|
||||
}
|
||||
|
||||
$mb3 = false;
|
||||
|
||||
switch ($type) {
|
||||
case Entity::ID:
|
||||
case Entity::FOREIGN_ID:
|
||||
case Entity::FOREIGN_TYPE:
|
||||
$mb3 = $this->getMaxIndexLength() < self::MB4_INDEX_LENGTH_LIMIT;
|
||||
|
||||
break;
|
||||
|
||||
case Entity::TEXT:
|
||||
$column = $column->withDefault(null);
|
||||
|
||||
break;
|
||||
|
||||
case Entity::JSON_ARRAY:
|
||||
$default = is_array($default) ? json_encode($default) : null;
|
||||
|
||||
if ($defs->getParam(self::PARAM_STORE_ARRAY_VALUES)) {
|
||||
$default = json_encode([]);
|
||||
}
|
||||
|
||||
$column = $column->withDefault($default);
|
||||
|
||||
break;
|
||||
|
||||
case Entity::BOOL:
|
||||
$default = intval($default ?? false);
|
||||
|
||||
$column = $column->withDefault($default);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($type !== Entity::ID && $autoincrement) {
|
||||
$column = $column
|
||||
->withNotNull()
|
||||
->withUnsigned();
|
||||
}
|
||||
|
||||
if (
|
||||
!in_array($columnType, [
|
||||
Types::STRING,
|
||||
Types::TEXT,
|
||||
MediumtextType::NAME,
|
||||
LongtextType::NAME,
|
||||
])
|
||||
) {
|
||||
return $column;
|
||||
}
|
||||
|
||||
$collation = $binary ?
|
||||
'utf8mb4_bin' :
|
||||
'utf8mb4_unicode_ci';
|
||||
|
||||
$charset = 'utf8mb4';
|
||||
|
||||
if ($mb3) {
|
||||
$collation = $binary ?
|
||||
'utf8mb3_bin' :
|
||||
'utf8mb3_unicode_ci';
|
||||
|
||||
$charset = 'utf8mb3';
|
||||
}
|
||||
|
||||
return $column
|
||||
->withCollation($collation)
|
||||
->withCharset($charset);
|
||||
}
|
||||
|
||||
private function getMaxIndexLength(): int
|
||||
{
|
||||
if (!isset($this->maxIndexLength)) {
|
||||
$this->maxIndexLength = $this->detectMaxIndexLength();
|
||||
}
|
||||
|
||||
return $this->maxIndexLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum index length.
|
||||
*/
|
||||
private function detectMaxIndexLength(): int
|
||||
{
|
||||
$tableEngine = $this->getTableEngine();
|
||||
|
||||
if (!$tableEngine) {
|
||||
return self::DEFAULT_INDEX_LIMIT;
|
||||
}
|
||||
|
||||
return match ($tableEngine) {
|
||||
'InnoDB' => 3072,
|
||||
default => 1000,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a table or default engine.
|
||||
*/
|
||||
private function getTableEngine(): ?string
|
||||
{
|
||||
$databaseType = $this->helper->getType();
|
||||
|
||||
if (!in_array($databaseType, [self::TYPE_MYSQL, self::TYPE_MARIADB])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$query = "SHOW TABLE STATUS WHERE Engine = 'MyISAM'";
|
||||
|
||||
$vars = [];
|
||||
|
||||
$pdo = $this->helper->getPDO();
|
||||
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute($vars);
|
||||
|
||||
$result = $sth->fetchColumn();
|
||||
|
||||
if (!empty($result)) {
|
||||
return 'MyISAM';
|
||||
}
|
||||
|
||||
return 'InnoDB';
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user