Initial commit

This commit is contained in:
root
2026-01-19 17:44:46 +01:00
commit 823af8b11d
8721 changed files with 1130846 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Utils\Database\Dbal;
use Doctrine\DBAL\Connection;
use Espo\ORM\DatabaseParams;
interface ConnectionFactory
{
public function create(DatabaseParams $databaseParams): Connection;
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,88 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\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);
}
}

View File

@@ -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);
}
}

View File

@@ -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',
];
}
}

View File

@@ -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;
}
}

View File

@@ -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}";
}
}

View 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\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';
}
}

View 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\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';
}
}

View 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';
}
}