Initial commit
This commit is contained in:
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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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,98 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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 ($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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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;
|
||||
}
|
||||
}
|
||||
1040
application/Espo/Core/Utils/Database/Orm/Converter.php
Normal file
1040
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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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,350 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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') ?
|
||||
$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();
|
||||
$baseCurrency = $this->configDataProvider->getBaseCurrency();
|
||||
$rates = $this->configDataProvider->getCurrencyRates()->toAssoc();
|
||||
|
||||
if ($defaultCurrency !== $baseCurrency) {
|
||||
$rates = $this->exchangeRates($baseCurrency, $defaultCurrency, $rates);
|
||||
}
|
||||
|
||||
$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 array<string, float> $currencyRates
|
||||
* @return array<string, float>
|
||||
*/
|
||||
private function exchangeRates(string $baseCurrency, string $defaultCurrency, array $currencyRates): array
|
||||
{
|
||||
$precision = 5;
|
||||
$defaultCurrencyRate = round(1 / $currencyRates[$defaultCurrency], $precision);
|
||||
|
||||
$exchangedRates = [];
|
||||
$exchangedRates[$baseCurrency] = $defaultCurrencyRate;
|
||||
|
||||
unset($currencyRates[$baseCurrency], $currencyRates[$defaultCurrency]);
|
||||
|
||||
foreach ($currencyRates as $currencyName => $rate) {
|
||||
$exchangedRates[$currencyName] = round($rate * $defaultCurrencyRate, $precision);
|
||||
}
|
||||
|
||||
return $exchangedRates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, float> $rates
|
||||
*/
|
||||
private function buildExpression(string $currencyAttribute, array $rates): Expr|float
|
||||
{
|
||||
if ($rates === []) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$currency = array_key_first($rates);
|
||||
$value = $rates[$currency];
|
||||
unset($rates[$currency]);
|
||||
|
||||
return Expr::if(
|
||||
Expr::equal(Expr::column($currencyAttribute), $currency),
|
||||
$value,
|
||||
$this->buildExpression($currencyAttribute, $rates)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{AttributeDefs, AttributeDefs}
|
||||
*/
|
||||
private function applyJoinMode(FieldDefs $fieldDefs, AttributeDefs $amountDefs, string $entityType): array
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$alias = $name . 'CurrencyRate';
|
||||
$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,423 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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,187 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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)";
|
||||
}
|
||||
|
||||
$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}'),
|
||||
],
|
||||
],
|
||||
],
|
||||
'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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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);
|
||||
}
|
||||
}
|
||||
272
application/Espo/Core/Utils/Database/Orm/RelationConverter.php
Normal file
272
application/Espo/Core/Utils/Database/Orm/RelationConverter.php
Normal file
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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;
|
||||
|
||||
/** @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])) {
|
||||
$this->mergeParams($raw[EntityParam::RELATIONS][$name], $params, $foreignParams ?? [], $linkType);
|
||||
$this->correct($raw[EntityParam::RELATIONS][$name]);
|
||||
}
|
||||
|
||||
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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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,247 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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';
|
||||
|
||||
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;
|
||||
|
||||
$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';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema\ColumnPreparators;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
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 PostgresqlColumnPreparator 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';
|
||||
|
||||
/** @var string[] */
|
||||
private array $textTypeList = [
|
||||
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,
|
||||
// DBAL reverse engineers as blob.
|
||||
Types::BINARY => Types::BLOB,
|
||||
];
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
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);
|
||||
|
||||
$columnType = $dbType ?? $type;
|
||||
|
||||
if (in_array($type, $this->textTypeList) && !$dbType) {
|
||||
$columnType = Types::TEXT;
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case Entity::TEXT:
|
||||
$column = $column->withDefault(null);
|
||||
|
||||
break;
|
||||
|
||||
case Entity::JSON_ARRAY:
|
||||
$default = is_array($default) ? json_encode($default) : null;
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
return $column;
|
||||
|
||||
// @todo Revise. Comparator would detect the column as changed if charset is set.
|
||||
/*if (
|
||||
!in_array($columnType, [
|
||||
Types::STRING,
|
||||
Types::TEXT,
|
||||
])
|
||||
) {
|
||||
return $column;
|
||||
}
|
||||
|
||||
return $column->withCharset('UTF8');*/
|
||||
}
|
||||
}
|
||||
375
application/Espo/Core/Utils/Database/Schema/DiffModifier.php
Normal file
375
application/Espo/Core/Utils/Database/Schema/DiffModifier.php
Normal file
@@ -0,0 +1,375 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema;
|
||||
|
||||
use Doctrine\DBAL\Exception as DbalException;
|
||||
use Doctrine\DBAL\Schema\Column as Column;
|
||||
use Doctrine\DBAL\Schema\ColumnDiff;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaDiff;
|
||||
use Doctrine\DBAL\Schema\TableDiff;
|
||||
use Doctrine\DBAL\Types\TextType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Espo\Core\Utils\Database\Dbal\Types\LongtextType;
|
||||
use Espo\Core\Utils\Database\Dbal\Types\MediumtextType;
|
||||
|
||||
class DiffModifier
|
||||
{
|
||||
/**
|
||||
* @param RebuildMode::* $mode
|
||||
* @throws DbalException
|
||||
*/
|
||||
public function modify(
|
||||
SchemaDiff $diff,
|
||||
Schema $schema,
|
||||
bool $secondRun = false,
|
||||
string $mode = RebuildMode::SOFT
|
||||
): bool {
|
||||
|
||||
$reRun = false;
|
||||
$isHard = $mode === RebuildMode::HARD;
|
||||
|
||||
$diff = $this->handleRemovedSequences($diff, $schema);
|
||||
|
||||
$diff->removedTables = [];
|
||||
|
||||
foreach ($diff->changedTables as $tableDiff) {
|
||||
$reRun = $this->amendTableDiff($tableDiff, $secondRun, $isHard) || $reRun;
|
||||
}
|
||||
|
||||
return $reRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DbalException
|
||||
*/
|
||||
private function amendTableDiff(TableDiff $tableDiff, bool $secondRun, bool $isHard): bool
|
||||
{
|
||||
$reRun = false;
|
||||
|
||||
/**
|
||||
* @todo Leave only for MariaDB?
|
||||
* MariaDB supports RENAME INDEX as of v10.5.
|
||||
* Find out how long does it take to rename for different databases.
|
||||
*/
|
||||
|
||||
if (!$isHard) {
|
||||
// Prevent index renaming as an operation may take a lot of time.
|
||||
$tableDiff->renamedIndexes = [];
|
||||
}
|
||||
|
||||
foreach ($tableDiff->removedColumns as $name => $column) {
|
||||
$reRun = $this->moveRemovedAutoincrementColumnToChanged($tableDiff, $column, $name) || $reRun;
|
||||
}
|
||||
|
||||
if (!$isHard) {
|
||||
// Prevent column removal to prevent data loss.
|
||||
$tableDiff->removedColumns = [];
|
||||
}
|
||||
|
||||
// Prevent column renaming as a not desired behavior.
|
||||
foreach ($tableDiff->renamedColumns as $renamedColumn) {
|
||||
$addedName = strtolower($renamedColumn->getName());
|
||||
$tableDiff->addedColumns[$addedName] = $renamedColumn;
|
||||
}
|
||||
|
||||
$tableDiff->renamedColumns = [];
|
||||
|
||||
foreach ($tableDiff->addedColumns as $column) {
|
||||
// Suppress autoincrement as need having a unique index first.
|
||||
$reRun = $this->amendAddedColumnAutoincrement($column) || $reRun;
|
||||
}
|
||||
|
||||
foreach ($tableDiff->changedColumns as $name => $columnDiff) {
|
||||
if (!$isHard) {
|
||||
// Prevent decreasing length for string columns to prevent data loss.
|
||||
$this->amendColumnDiffLength($tableDiff, $columnDiff, $name);
|
||||
// Prevent longtext => mediumtext to prevent data loss.
|
||||
$this->amendColumnDiffTextType($tableDiff, $columnDiff, $name);
|
||||
// Prevent changing collation.
|
||||
$this->amendColumnDiffCollation($tableDiff, $columnDiff, $name);
|
||||
// Prevent changing charset.
|
||||
$this->amendColumnDiffCharset($tableDiff, $columnDiff, $name);
|
||||
}
|
||||
|
||||
// Prevent setting autoincrement in first run.
|
||||
if (!$secondRun) {
|
||||
$reRun = $this->amendColumnDiffAutoincrement($tableDiff, $columnDiff, $name) || $reRun;
|
||||
}
|
||||
}
|
||||
|
||||
return $reRun;
|
||||
}
|
||||
|
||||
private function amendColumnDiffLength(TableDiff $tableDiff, ColumnDiff $columnDiff, string $name): void
|
||||
{
|
||||
$fromColumn = $columnDiff->fromColumn;
|
||||
$column = $columnDiff->column;
|
||||
|
||||
if (!$fromColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array('length', $columnDiff->changedProperties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fromLength = $fromColumn->getLength() ?? 255;
|
||||
$length = $column->getLength() ?? 255;
|
||||
|
||||
if ($fromLength <= $length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$column->setLength($fromLength);
|
||||
|
||||
self::unsetChangedColumnProperty($tableDiff, $columnDiff, $name, 'length');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DbalException
|
||||
*/
|
||||
private function amendColumnDiffTextType(TableDiff $tableDiff, ColumnDiff $columnDiff, string $name): void
|
||||
{
|
||||
$fromColumn = $columnDiff->fromColumn;
|
||||
$column = $columnDiff->column;
|
||||
|
||||
if (!$fromColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array('type', $columnDiff->changedProperties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fromType = $fromColumn->getType();
|
||||
$type = $column->getType();
|
||||
|
||||
if (
|
||||
!$fromType instanceof TextType ||
|
||||
!$type instanceof TextType
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$typePriority = [
|
||||
Types::TEXT,
|
||||
MediumtextType::NAME,
|
||||
LongtextType::NAME,
|
||||
];
|
||||
|
||||
$fromIndex = array_search($fromType->getName(), $typePriority);
|
||||
$index = array_search($type->getName(), $typePriority);
|
||||
|
||||
if ($index >= $fromIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
$column->setType(Type::getType($fromType->getName()));
|
||||
|
||||
self::unsetChangedColumnProperty($tableDiff, $columnDiff, $name, 'type');
|
||||
}
|
||||
|
||||
private function amendColumnDiffCollation(TableDiff $tableDiff, ColumnDiff $columnDiff, string $name): void
|
||||
{
|
||||
$fromColumn = $columnDiff->fromColumn;
|
||||
$column = $columnDiff->column;
|
||||
|
||||
if (!$fromColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array('collation', $columnDiff->changedProperties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fromCollation = $fromColumn->getPlatformOption('collation');
|
||||
|
||||
if (!$fromCollation) {
|
||||
return;
|
||||
}
|
||||
|
||||
$column->setPlatformOption('collation', $fromCollation);
|
||||
|
||||
self::unsetChangedColumnProperty($tableDiff, $columnDiff, $name, 'collation');
|
||||
}
|
||||
|
||||
private function amendColumnDiffCharset(TableDiff $tableDiff, ColumnDiff $columnDiff, string $name): void
|
||||
{
|
||||
$fromColumn = $columnDiff->fromColumn;
|
||||
$column = $columnDiff->column;
|
||||
|
||||
if (!$fromColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array('charset', $columnDiff->changedProperties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fromCharset = $fromColumn->getPlatformOption('charset');
|
||||
|
||||
if (!$fromCharset) {
|
||||
return;
|
||||
}
|
||||
|
||||
$column->setPlatformOption('charset', $fromCharset);
|
||||
|
||||
self::unsetChangedColumnProperty($tableDiff, $columnDiff, $name, 'charset');
|
||||
}
|
||||
|
||||
private function amendColumnDiffAutoincrement(TableDiff $tableDiff, ColumnDiff $columnDiff, string $name): bool
|
||||
{
|
||||
$fromColumn = $columnDiff->fromColumn;
|
||||
$column = $columnDiff->column;
|
||||
|
||||
if (!$fromColumn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!in_array('autoincrement', $columnDiff->changedProperties)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$column
|
||||
->setAutoincrement(false)
|
||||
->setNotnull(false)
|
||||
->setDefault(null);
|
||||
|
||||
if ($name === 'id') {
|
||||
$column->setNotnull(true);
|
||||
}
|
||||
|
||||
self::unsetChangedColumnProperty($tableDiff, $columnDiff, $name, 'autoincrement');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function amendAddedColumnAutoincrement(Column $column): bool
|
||||
{
|
||||
if (!$column->getAutoincrement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$column
|
||||
->setAutoincrement(false)
|
||||
->setNotnull(false)
|
||||
->setDefault(null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function moveRemovedAutoincrementColumnToChanged(TableDiff $tableDiff, Column $column, string $name): bool
|
||||
{
|
||||
if (!$column->getAutoincrement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$newColumn = clone $column;
|
||||
|
||||
$newColumn
|
||||
->setAutoincrement(false)
|
||||
->setNotnull(false)
|
||||
->setDefault(null);
|
||||
|
||||
$changedProperties = [
|
||||
'autoincrement',
|
||||
'notnull',
|
||||
'default',
|
||||
];
|
||||
|
||||
$tableDiff->changedColumns[$name] = new ColumnDiff($name, $newColumn, $changedProperties, $column);
|
||||
|
||||
foreach ($tableDiff->removedIndexes as $indexName => $index) {
|
||||
if ($index->getColumns() === [$name]) {
|
||||
unset($tableDiff->removedIndexes[$indexName]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function unsetChangedColumnProperty(
|
||||
TableDiff $tableDiff,
|
||||
ColumnDiff $columnDiff,
|
||||
string $name,
|
||||
string $property
|
||||
): void {
|
||||
|
||||
if (count($columnDiff->changedProperties) === 1) {
|
||||
unset($tableDiff->changedColumns[$name]);
|
||||
}
|
||||
|
||||
$columnDiff->changedProperties = array_diff($columnDiff->changedProperties, [$property]);
|
||||
}
|
||||
|
||||
/**
|
||||
* DBAL does not handle autoincrement columns that are not primary keys,
|
||||
* making them dropped.
|
||||
*/
|
||||
private function handleRemovedSequences(SchemaDiff $diff, Schema $schema): SchemaDiff
|
||||
{
|
||||
$droppedSequences = $diff->getDroppedSequences();
|
||||
|
||||
if ($droppedSequences === []) {
|
||||
return $diff;
|
||||
}
|
||||
|
||||
foreach ($droppedSequences as $i => $sequence) {
|
||||
foreach ($schema->getTables() as $table) {
|
||||
$namespace = $table->getNamespaceName();
|
||||
$tableName = $table->getShortestName($namespace);
|
||||
|
||||
foreach ($table->getColumns() as $column) {
|
||||
if (!$column->getAutoincrement()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sequenceName = $sequence->getShortestName($namespace);
|
||||
|
||||
$tableSequenceName = sprintf('%s_%s_seq', $tableName, $column->getShortestName($namespace));
|
||||
|
||||
if ($tableSequenceName !== $sequenceName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($droppedSequences[$i]);
|
||||
|
||||
continue 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$diff->removedSequences = array_values($droppedSequences);
|
||||
|
||||
return $diff;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\ORM\Defs\EntityDefs as OrmEntityDefs;
|
||||
|
||||
/**
|
||||
* Modifies definitions before building a schema.
|
||||
*/
|
||||
interface EntityDefsModifier
|
||||
{
|
||||
public function modify(OrmEntityDefs $entityDefs): EntityDefs;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema\EntityDefsModifiers;
|
||||
|
||||
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
|
||||
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
|
||||
use Espo\Core\Utils\Database\Schema\EntityDefsModifier;
|
||||
use Espo\ORM\Defs\EntityDefs as OrmEntityDefs;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
|
||||
/**
|
||||
* A single JSON column instead of multiple field columns.
|
||||
*/
|
||||
class JsonData implements EntityDefsModifier
|
||||
{
|
||||
public function modify(OrmEntityDefs $entityDefs): EntityDefs
|
||||
{
|
||||
$sourceIdAttribute = $entityDefs->getAttribute('id');
|
||||
|
||||
$idAttribute = AttributeDefs::create('id')
|
||||
->withType(AttributeType::ID);
|
||||
|
||||
$length = $sourceIdAttribute->getLength();
|
||||
$dbType = $sourceIdAttribute->getParam(AttributeParam::DB_TYPE);
|
||||
|
||||
if ($length) {
|
||||
$idAttribute = $idAttribute->withLength($length);
|
||||
}
|
||||
|
||||
if ($dbType) {
|
||||
$idAttribute = $idAttribute->withDbType($dbType);
|
||||
}
|
||||
|
||||
return EntityDefs::create()
|
||||
->withAttribute($idAttribute)
|
||||
->withAttribute(
|
||||
AttributeDefs::create('data')
|
||||
->withType(AttributeType::JSON_OBJECT)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Espo\Core\Utils\Database\ConfigDataProvider;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
class MetadataProvider
|
||||
{
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
private function getPlatform(): string
|
||||
{
|
||||
return $this->configDataProvider->getPlatform();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<RebuildAction>[]
|
||||
*/
|
||||
public function getPreRebuildActionClassNameList(): array
|
||||
{
|
||||
/** @var class-string<RebuildAction>[] */
|
||||
return $this->metadata
|
||||
->get(['app', 'databasePlatforms', $this->getPlatform(), 'preRebuildActionClassNameList']) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<RebuildAction>[]
|
||||
*/
|
||||
public function getPostRebuildActionClassNameList(): array
|
||||
{
|
||||
/** @var class-string<RebuildAction>[] */
|
||||
return $this->metadata
|
||||
->get(['app', 'databasePlatforms', $this->getPlatform(), 'postRebuildActionClassNameList']) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, class-string<Type>>
|
||||
*/
|
||||
public function getDbalTypeClassNameMap(): array
|
||||
{
|
||||
/** @var array<string, class-string<Type>> */
|
||||
return $this->metadata
|
||||
->get(['app', 'databasePlatforms', $this->getPlatform(), 'dbalTypeClassNameMap']) ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema as DbalSchema;
|
||||
|
||||
interface RebuildAction
|
||||
{
|
||||
public function process(DbalSchema $oldSchema, DbalSchema $newSchema): void;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema\RebuildActions;
|
||||
|
||||
use Doctrine\DBAL\Exception as DbalException;
|
||||
use Doctrine\DBAL\Schema\Schema as DbalSchema;
|
||||
use Espo\Core\Utils\Database\Helper;
|
||||
use Espo\Core\Utils\Database\Schema\RebuildAction;
|
||||
use Espo\Core\Utils\Log;
|
||||
|
||||
use Exception;
|
||||
|
||||
class PrepareForFulltextIndex implements RebuildAction
|
||||
{
|
||||
public function __construct(
|
||||
private Helper $helper,
|
||||
private Log $log
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws DbalException
|
||||
*/
|
||||
public function process(DbalSchema $oldSchema, DbalSchema $newSchema): void
|
||||
{
|
||||
if ($oldSchema->getTables() === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection = $this->helper->getDbalConnection();
|
||||
$pdo = $this->helper->getPDO();
|
||||
|
||||
foreach ($newSchema->getTables() as $table) {
|
||||
$tableName = $table->getName();
|
||||
$indexes = $table->getIndexes();
|
||||
|
||||
foreach ($indexes as $index) {
|
||||
if (!$index->hasFlag('fulltext')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columns = $index->getColumns();
|
||||
|
||||
foreach ($columns as $columnName) {
|
||||
$sql = "SHOW FULL COLUMNS FROM `" . $tableName . "` WHERE Field = " . $pdo->quote($columnName);
|
||||
|
||||
try {
|
||||
/** @var array{Type: string, Collation: string} $row */
|
||||
$row = $connection->fetchAssociative($sql);
|
||||
} catch (Exception) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (strtoupper($row['Type'])) {
|
||||
case 'LONGTEXT':
|
||||
$alterSql =
|
||||
"ALTER TABLE `{$tableName}` " .
|
||||
"MODIFY `{$columnName}` MEDIUMTEXT COLLATE " . $row['Collation'];
|
||||
|
||||
$this->log->info('SCHEMA, Execute Query: ' . $alterSql);
|
||||
|
||||
$connection->executeQuery($alterSql);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
application/Espo/Core/Utils/Database/Schema/RebuildMode.php
Normal file
36
application/Espo/Core/Utils/Database/Schema/RebuildMode.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema;
|
||||
|
||||
class RebuildMode
|
||||
{
|
||||
public const SOFT = 'soft';
|
||||
public const HARD = 'hard';
|
||||
}
|
||||
255
application/Espo/Core/Utils/Database/Schema/SchemaManager.php
Normal file
255
application/Espo/Core/Utils/Database/Schema/SchemaManager.php
Normal file
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema;
|
||||
|
||||
use Doctrine\DBAL\Connection as DbalConnection;
|
||||
use Doctrine\DBAL\Exception as DbalException;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Schema\AbstractSchemaManager;
|
||||
use Doctrine\DBAL\Schema\Comparator;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaDiff;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Database\Helper;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\Utils\Metadata\OrmMetadataData;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* A database schema manager.
|
||||
*/
|
||||
class SchemaManager
|
||||
{
|
||||
/** @var AbstractSchemaManager<AbstractPlatform> */
|
||||
private AbstractSchemaManager $schemaManager;
|
||||
private Comparator $comparator;
|
||||
private Builder $builder;
|
||||
|
||||
/**
|
||||
* @throws DbalException
|
||||
*/
|
||||
public function __construct(
|
||||
private OrmMetadataData $ormMetadataData,
|
||||
private Log $log,
|
||||
private Helper $helper,
|
||||
private MetadataProvider $metadataProvider,
|
||||
private DiffModifier $diffModifier,
|
||||
private InjectableFactory $injectableFactory
|
||||
) {
|
||||
$this->schemaManager = $this->getDbalConnection()
|
||||
->getDatabasePlatform()
|
||||
->createSchemaManager($this->getDbalConnection());
|
||||
|
||||
// Not using a platform specific comparator as it unsets a collation and charset if
|
||||
// they match a table default.
|
||||
//$this->comparator = $this->schemaManager->createComparator();
|
||||
$this->comparator = new Comparator($this->getPlatform());
|
||||
|
||||
$this->initFieldTypes();
|
||||
|
||||
$this->builder = $this->injectableFactory->createWithBinding(
|
||||
Builder::class,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(Helper::class, $this->helper)
|
||||
->build()
|
||||
);
|
||||
}
|
||||
|
||||
public function getDatabaseHelper(): Helper
|
||||
{
|
||||
return $this->helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DbalException
|
||||
*/
|
||||
private function getPlatform(): AbstractPlatform
|
||||
{
|
||||
return $this->getDbalConnection()->getDatabasePlatform();
|
||||
}
|
||||
|
||||
private function getDbalConnection(): DbalConnection
|
||||
{
|
||||
return $this->helper->getDbalConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DbalException
|
||||
*/
|
||||
private function initFieldTypes(): void
|
||||
{
|
||||
foreach ($this->metadataProvider->getDbalTypeClassNameMap() as $type => $className) {
|
||||
Type::hasType($type) ?
|
||||
Type::overrideType($type, $className) :
|
||||
Type::addType($type, $className);
|
||||
|
||||
$this->getDbalConnection()
|
||||
->getDatabasePlatform()
|
||||
->registerDoctrineTypeMapping($type, $type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild database schema. Creates and alters needed tables and columns.
|
||||
* Does not remove columns, does not decrease column lengths.
|
||||
*
|
||||
* @param ?string[] $entityTypeList Specific entity types.
|
||||
* @param RebuildMode::* $mode A mode.
|
||||
* @throws SchemaException
|
||||
* @throws DbalException
|
||||
* @todo Catch and re-throw exceptions.
|
||||
*/
|
||||
public function rebuild(?array $entityTypeList = null, string $mode = RebuildMode::SOFT): bool
|
||||
{
|
||||
$fromSchema = $this->introspectSchema();
|
||||
$schema = $this->builder->build($this->ormMetadataData->getData(), $entityTypeList);
|
||||
|
||||
try {
|
||||
$this->processPreRebuildActions($fromSchema, $schema);
|
||||
} catch (Throwable $e) {
|
||||
$this->log->alert('Rebuild database pre-rebuild error: '. $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$diff = $this->comparator->compareSchemas($fromSchema, $schema);
|
||||
$needReRun = $this->diffModifier->modify($diff, $schema, false, $mode);
|
||||
$sql = $this->composeDiffSql($diff);
|
||||
|
||||
$result = $this->runSql($sql);
|
||||
|
||||
if (!$result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($needReRun) {
|
||||
// Needed to handle auto-increment column creation/removal/change.
|
||||
// As an auto-increment column requires having a unique index, but
|
||||
// Doctrine DBAL does not handle this.
|
||||
$intermediateSchema = $this->introspectSchema();
|
||||
$schema = $this->builder->build($this->ormMetadataData->getData(), $entityTypeList);
|
||||
|
||||
$diff = $this->comparator->compareSchemas($intermediateSchema, $schema);
|
||||
|
||||
$this->diffModifier->modify($diff, $schema, true);
|
||||
$sql = $this->composeDiffSql($diff);
|
||||
$result = $this->runSql($sql);
|
||||
}
|
||||
|
||||
if (!$result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->processPostRebuildActions($fromSchema, $schema);
|
||||
} catch (Throwable $e) {
|
||||
$this->log->alert('Rebuild database post-rebuild error: ' . $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $queries
|
||||
* @return bool
|
||||
*/
|
||||
private function runSql(array $queries): bool
|
||||
{
|
||||
$result = true;
|
||||
|
||||
$connection = $this->getDbalConnection();
|
||||
|
||||
foreach ($queries as $sql) {
|
||||
$this->log->info('Schema, query: '. $sql);
|
||||
|
||||
try {
|
||||
$connection->executeQuery($sql);
|
||||
} catch (Throwable $e) {
|
||||
$this->log->alert('Rebuild database error: ' . $e->getMessage());
|
||||
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Introspect and return a current database schema.
|
||||
*
|
||||
* @throws DbalException
|
||||
*/
|
||||
private function introspectSchema(): Schema
|
||||
{
|
||||
return $this->schemaManager->introspectSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @throws DbalException
|
||||
*/
|
||||
private function composeDiffSql(SchemaDiff $diff): array
|
||||
{
|
||||
return $this->getPlatform()->getAlterSchemaSQL($diff);
|
||||
}
|
||||
|
||||
private function processPreRebuildActions(Schema $actualSchema, Schema $schema): void
|
||||
{
|
||||
$binding = BindingContainerBuilder::create()
|
||||
->bindInstance(Helper::class, $this->helper)
|
||||
->build();
|
||||
|
||||
foreach ($this->metadataProvider->getPreRebuildActionClassNameList() as $className) {
|
||||
$action = $this->injectableFactory->createWithBinding($className, $binding);
|
||||
|
||||
$action->process($actualSchema, $schema);
|
||||
}
|
||||
}
|
||||
|
||||
private function processPostRebuildActions(Schema $actualSchema, Schema $schema): void
|
||||
{
|
||||
$binding = BindingContainerBuilder::create()
|
||||
->bindInstance(Helper::class, $this->helper)
|
||||
->build();
|
||||
|
||||
foreach ($this->metadataProvider->getPostRebuildActionClassNameList() as $className) {
|
||||
$action = $this->injectableFactory->createWithBinding($className, $binding);
|
||||
|
||||
$action->process($actualSchema, $schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Database\Helper;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
class SchemaManagerProxy
|
||||
{
|
||||
private ?SchemaManager $schemaManager = null;
|
||||
|
||||
public function __construct(private InjectableFactory $injectableFactory) {}
|
||||
|
||||
private function getSchemaManager(): SchemaManager
|
||||
{
|
||||
$this->schemaManager ??= $this->injectableFactory->create(SchemaManager::class);
|
||||
|
||||
return $this->schemaManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string[] $entityTypeList
|
||||
* @param RebuildMode::* $mode
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function rebuild(?array $entityTypeList = null, string $mode = RebuildMode::SOFT): bool
|
||||
{
|
||||
return $this->getSchemaManager()->rebuild($entityTypeList, $mode);
|
||||
}
|
||||
|
||||
public function getDatabaseHelper(): Helper
|
||||
{
|
||||
return $this->getSchemaManager()->getDatabaseHelper();
|
||||
}
|
||||
}
|
||||
237
application/Espo/Core/Utils/Database/Schema/Utils.php
Normal file
237
application/Espo/Core/Utils/Database/Schema/Utils.php
Normal file
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Defs\IndexDefs;
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
use Espo\ORM\Defs\Params\EntityParam;
|
||||
use Espo\ORM\Defs\Params\IndexParam;
|
||||
|
||||
class Utils
|
||||
{
|
||||
/**
|
||||
* Get indexes in specific format.
|
||||
* @deprecated
|
||||
*
|
||||
* @param array<string, mixed> $defs
|
||||
* @param string[] $ignoreFlags @todo Remove parameter?
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
public static function getIndexes(array $defs, array $ignoreFlags = []): array
|
||||
{
|
||||
$indexList = [];
|
||||
|
||||
foreach ($defs as $entityType => $entityParams) {
|
||||
$indexes = $entityParams[EntityParam::INDEXES] ?? [];
|
||||
|
||||
foreach ($indexes as $indexName => $indexParams) {
|
||||
$indexDefs = IndexDefs::fromRaw($indexParams, $indexName);
|
||||
|
||||
$tableIndexName = $indexParams[IndexParam::KEY] ?? null;
|
||||
|
||||
if (!$tableIndexName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columns = $indexDefs->getColumnList();
|
||||
$flags = $indexDefs->getFlagList();
|
||||
|
||||
if ($flags !== []) {
|
||||
$skipIndex = false;
|
||||
|
||||
foreach ($ignoreFlags as $ignoreFlag) {
|
||||
if (($flagKey = array_search($ignoreFlag, $flags)) !== false) {
|
||||
unset($flags[$flagKey]);
|
||||
|
||||
$skipIndex = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($skipIndex && empty($flags)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$indexList[$entityType][$tableIndexName][IndexParam::FLAGS] = $flags;
|
||||
}
|
||||
|
||||
if ($columns !== []) {
|
||||
$indexType = self::getIndexTypeByIndexDefs($indexDefs);
|
||||
|
||||
// @todo Revise, may to be removed.
|
||||
$indexList[$entityType][$tableIndexName][IndexParam::TYPE] = $indexType;
|
||||
|
||||
$indexList[$entityType][$tableIndexName][IndexParam::COLUMNS] = array_map(
|
||||
fn ($item) => Util::toUnderScore($item),
|
||||
$columns
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var array<string, array<string, mixed>> */
|
||||
return $indexList; /** @phpstan-ignore-line */
|
||||
}
|
||||
|
||||
private static function getIndexTypeByIndexDefs(IndexDefs $indexDefs): string
|
||||
{
|
||||
if ($indexDefs->isUnique()) {
|
||||
return 'unique';
|
||||
}
|
||||
|
||||
if (in_array('fulltext', $indexDefs->getFlagList())) {
|
||||
return 'fulltext';
|
||||
}
|
||||
|
||||
return 'index';
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @param array<string, mixed> $ormMeta
|
||||
* @param int $indexMaxLength
|
||||
* @param ?array<string, mixed> $indexList
|
||||
* @param int $characterLength
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function getFieldListExceededIndexMaxLength(
|
||||
array $ormMeta,
|
||||
$indexMaxLength = 1000,
|
||||
?array $indexList = null,
|
||||
$characterLength = 4
|
||||
) {
|
||||
|
||||
$permittedFieldTypeList = [
|
||||
FieldType::VARCHAR,
|
||||
];
|
||||
|
||||
$fields = [];
|
||||
|
||||
if (!isset($indexList)) {
|
||||
$indexList = self::getIndexes($ormMeta, ['fulltext']);
|
||||
}
|
||||
|
||||
foreach ($indexList as $entityName => $indexes) {
|
||||
foreach ($indexes as $indexName => $indexParams) {
|
||||
$columnList = $indexParams['columns'];
|
||||
|
||||
$indexLength = 0;
|
||||
|
||||
foreach ($columnList as $columnName) {
|
||||
$fieldName = Util::toCamelCase($columnName);
|
||||
|
||||
if (!isset($ormMeta[$entityName]['fields'][$fieldName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$indexLength += self::getFieldLength(
|
||||
$ormMeta[$entityName]['fields'][$fieldName],
|
||||
$characterLength
|
||||
);
|
||||
}
|
||||
|
||||
if ($indexLength > $indexMaxLength) {
|
||||
foreach ($columnList as $columnName) {
|
||||
$fieldName = Util::toCamelCase($columnName);
|
||||
|
||||
if (!isset($ormMeta[$entityName]['fields'][$fieldName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldType = self::getFieldType($ormMeta[$entityName]['fields'][$fieldName]);
|
||||
|
||||
if (in_array($fieldType, $permittedFieldTypeList)) {
|
||||
if (!isset($fields[$entityName]) || !in_array($fieldName, $fields[$entityName])) {
|
||||
$fields[$entityName][] = $fieldName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $ormFieldDefs
|
||||
* @param int $characterLength
|
||||
* @return int
|
||||
*/
|
||||
private static function getFieldLength(array $ormFieldDefs, $characterLength = 4)
|
||||
{
|
||||
$length = 0;
|
||||
|
||||
if (isset($ormFieldDefs[AttributeParam::NOT_STORABLE]) && $ormFieldDefs[AttributeParam::NOT_STORABLE]) {
|
||||
return $length;
|
||||
}
|
||||
|
||||
$defaultLength = [
|
||||
'datetime' => 8,
|
||||
'time' => 4,
|
||||
'int' => 4,
|
||||
'bool' => 1,
|
||||
'float' => 4,
|
||||
'varchar' => 255,
|
||||
];
|
||||
|
||||
$type = self::getDbFieldType($ormFieldDefs);
|
||||
|
||||
$length = $defaultLength[$type] ?? $length;
|
||||
|
||||
switch ($type) {
|
||||
case 'varchar':
|
||||
$length = $length * $characterLength;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $ormFieldDefs
|
||||
* @return string
|
||||
*/
|
||||
private static function getDbFieldType(array $ormFieldDefs)
|
||||
{
|
||||
return $ormFieldDefs[AttributeParam::DB_TYPE] ?? $ormFieldDefs['type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $ormFieldDefs
|
||||
*/
|
||||
private static function getFieldType(array $ormFieldDefs): string
|
||||
{
|
||||
return $ormFieldDefs['type'] ?? self::getDbFieldType($ormFieldDefs);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user