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,193 @@
<?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\Upgrades\Migrations\V7_2;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Core\Utils\Metadata;
use Espo\Entities\Portal;
use Espo\Entities\Preferences;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\KnowledgeBaseArticle;
use Espo\ORM\EntityManager;
use Espo\Core\Utils\Config;
class AfterUpgrade implements Script
{
public function __construct(
private Metadata $metadata,
private EntityManager $entityManager,
private Config $config,
private Config\ConfigWriter $configWriter
) {}
public function run(): void
{
$this->updateEventMetadata();
$this->updateTheme();
$this->updateKbArticles();
}
private function updateEventMetadata(): void
{
$metadata = $this->metadata;
$defs = $metadata->get(['scopes']);
$toSave = false;
foreach ($defs as $entityType => $item) {
$isCustom = $item['isCustom'] ?? false;
$type = $item['type'] ?? false;
if (!$isCustom || $type !== 'Event') {
continue;
}
$toSave = true;
$metadata->set('recordDefs', $entityType, [
'beforeUpdateHookClassNameList' => [
"__APPEND__",
"Espo\\Classes\\RecordHooks\\Event\\BeforeUpdatePreserveDuration"
]
]);
$metadata->set('clientDefs', $entityType, [
'forcePatchAttributeDependencyMap' => [
"dateEnd" => ["dateStart"],
"dateEndDate" => ["dateStartDate"]
]
]);
if ($metadata->get(['entityDefs', $entityType, 'fields', 'isAllDay'])) {
$metadata->set('entityDefs', $entityType, [
'fields' => [
'isAllDay' => [
'readOnly' => false,
],
]
]);
}
}
if ($toSave) {
$metadata->save();
}
}
private function updateTheme(): void
{
$themeList = [
'EspoVertical',
'HazyblueVertical',
'VioletVertical',
'SakuraVertical',
'DarkVertical',
];
$theme = $this->config->get('theme');
$navbar = 'top';
if (in_array($theme, $themeList)) {
$theme = substr($theme, 0, -8);
$navbar = 'side';
}
$this->configWriter->set('theme', $theme);
$this->configWriter->set('themeParams', (object) ['navbar' => $navbar]);
$this->configWriter->save();
$userList = $this->entityManager->getRDBRepository(User::ENTITY_TYPE)
->where([
'type' => ['regular', 'admin']
])
->find();
foreach ($userList as $user) {
$preferences = $this->entityManager->getEntityById(Preferences::ENTITY_TYPE, $user->getId());
if (!$preferences) {
continue;
}
$theme = $preferences->get('theme');
$navbar = 'top';
if (!$theme) {
continue;
}
if (in_array($theme, $themeList)) {
$theme = substr($theme, 0, -8);
$navbar = 'side';
}
$preferences->set('theme', $theme);
$preferences->set('themeParams', (object) ['navbar' => $navbar]);
$this->entityManager->saveEntity($preferences);
}
$portalList = $this->entityManager
->getRDBRepository(Portal::ENTITY_TYPE)
->where([
'theme!=' => null,
])
->find();
foreach ($portalList as $portal) {
$theme = $portal->get('theme');
$navbar = 'top';
if (in_array($theme, $themeList)) {
$theme = substr($theme, 0, -8);
$navbar = 'side';
}
$portal->set('theme', $theme);
$portal->set('themeParams', (object) ['navbar' => $navbar]);
$this->entityManager->saveEntity($portal);
}
}
private function updateKbArticles(): void
{
$query = $this->entityManager
->getQueryBuilder()
->update()
->in(KnowledgeBaseArticle::ENTITY_TYPE)
->where(['type' => null])
->set(['type' => 'Article'])
->build();
$this->entityManager->getQueryExecutor()->execute($query);
}
}

View File

@@ -0,0 +1,118 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Upgrades\Migrations\V7_4;
use Espo\Core\ORM\Type\FieldType;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Core\Utils\Metadata;
use Espo\Core\Templates\Entities\Event;
class AfterUpgrade implements Script
{
public function __construct(
private Metadata $metadata,
) {}
public function run(): void
{
$this->updateMetadata();
}
private function updateMetadata(): void
{
$this->metadata->set('app', 'recordId', [
'length' => 24,
]);
$this->fixParent();
$this->updateEventMetadata();
$this->metadata->save();
}
private function fixParent(): void
{
$metadata = $this->metadata;
foreach ($metadata->get(['entityDefs']) as $scope => $defs) {
foreach ($metadata->get(['entityDefs', $scope, 'fields']) as $field => $fieldDefs) {
$custom = $metadata->getCustom('entityDefs', $scope);
if (!$custom) {
continue;
}
if (
($fieldDefs['type'] ?? null) === FieldType::LINK_PARENT &&
($fieldDefs['notStorable'] ?? false)
) {
if ($custom->fields?->$field?->notStorable) {
$metadata->delete('entityDefs', $scope, "fields.$field.notStorable");
}
}
}
}
}
private function updateEventMetadata(): void
{
$metadata = $this->metadata;
$defs = $metadata->get(['scopes']);
foreach ($defs as $entityType => $item) {
$isCustom = $item['isCustom'] ?? false;
$type = $item['type'] ?? false;
if (!$isCustom || $type !== Event::TEMPLATE_TYPE) {
continue;
}
if (!is_string($metadata->get(['entityDefs', $entityType, 'fields', 'duration', 'select']))) {
continue;
}
$metadata->delete('entityDefs', $entityType, 'fields.duration.orderBy');
$metadata->set('entityDefs', $entityType, [
'fields' => [
'duration' => [
'select' => [
'select' => "TIMESTAMPDIFF_SECOND:(dateStart, dateEnd)"
],
'order' => [
'order' => [["TIMESTAMPDIFF_SECOND:(dateStart, dateEnd)", "{direction}"]]
],
]
]
]);
}
}
}

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\Upgrades\Migrations\V7_5;
use Espo\Core\Templates\Entities\Event;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Core\Utils\File\Manager;
use Espo\Core\Utils\Json;
use Espo\Core\Utils\Metadata;
class AfterUpgrade implements Script
{
public function __construct(
private Metadata $metadata,
private Manager $fileManger
) {}
public function run(): void
{
$this->updateEventMetadata();
}
private function updateEventMetadata(): void
{
$metadata = $this->metadata;
$fileManager = $this->fileManger;
$defs = $metadata->get(['scopes']);
$path1 = "application/Espo/Core/Templates/Metadata/Event/selectDefs.json";
$contents1 = $fileManager->getContents($path1);
$data1 = Json::decode($contents1, true);
$primaryFilterClassNameMap = (object) $data1['primaryFilterClassNameMap'];
foreach ($defs as $entityType => $item) {
$isCustom = $item['isCustom'] ?? false;
$type = $item['type'] ?? false;
if (!$isCustom || $type !== Event::TEMPLATE_TYPE) {
continue;
}
$data1 = $metadata->getCustom('selectDefs', $entityType) ?? (object) [];
$data1->primaryFilterClassNameMap = $primaryFilterClassNameMap;
$metadata->saveCustom('selectDefs', $entityType, $data1);
$data2 = $metadata->getCustom('scopes', $entityType) ?? (object) [];
$data2->completedStatusList = ['Held'];
$data2->canceledStatusList = ['Not Held'];
$metadata->saveCustom('scopes', $entityType, $data2);
}
}
}

View File

@@ -0,0 +1,53 @@
<?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\Upgrades\Migrations\V7_5;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Entities\User;
use Espo\ORM\EntityManager;
use Espo\ORM\Name\Attribute;
use Espo\ORM\Query\DeleteBuilder;
class Prepare implements Script
{
public function __construct(
private EntityManager $entityManager
) {}
public function run(): void
{
$query = DeleteBuilder::create()
->from(User::ENTITY_TYPE)
->where([Attribute::DELETED => true])
->build();
$this->entityManager->getQueryExecutor()->execute($query);
}
}

View File

@@ -0,0 +1,120 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Upgrades\Migrations\V8_0;
use Espo\Core\Container;
use Espo\Core\Name\Field;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Core\Utils\Metadata;
use Espo\Entities\Role;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\Part\Expression;
use Espo\ORM\Query\UpdateBuilder;
class AfterUpgrade implements Script
{
public function __construct(
private Container $container
) {}
public function run(): void
{
$this->updateRoles(
$this->container->getByClass(EntityManager::class)
);
$this->updateMetadata(
$this->container->getByClass(Metadata::class)
);
}
private function updateRoles(EntityManager $entityManager): void
{
$query = UpdateBuilder::create()
->in(Role::ENTITY_TYPE)
->set(['messagePermission' => Expression::column('assignmentPermission')])
->build();
$entityManager->getQueryExecutor()->execute($query);
}
private function updateMetadata(Metadata $metadata): void
{
$defs = $metadata->get(['scopes']);
foreach ($defs as $entityType => $item) {
$isCustom = $item['isCustom'] ?? false;
$type = $item['type'] ?? false;
if (!$isCustom) {
continue;
}
if ($type === 'Event') {
$metadata->set('entityDefs', $entityType, [
'fields' => [
'dateEnd' => [
'suppressValidationList' => ['required'],
],
]
]);
$metadata->save();
continue;
}
if (
!in_array($type, [
'BasePlus',
'Base',
'Company',
'Person',
])
) {
continue;
}
$recordDefs = $metadata->getCustom('recordDefs', $entityType) ?? (object) [];
$scopes = $metadata->getCustom('scopes', $entityType) ?? (object) [];
$recordDefs->duplicateWhereBuilderClassName = "Espo\\Classes\\DuplicateWhereBuilders\\General";
$scopes->duplicateCheckFieldList = [];
if ($type === 'Company' || $type === 'Person') {
$scopes->duplicateCheckFieldList = [Field::NAME, 'emailAddress'];
}
$metadata->saveCustom('recordDefs', $entityType, $recordDefs);
$metadata->saveCustom('scopes', $entityType, $scopes);
}
}
}

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\Upgrades\Migrations\V8_1;
use Espo\Core\Container;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\Config;
class AfterUpgrade implements Script
{
public function __construct(
private Container $container
) {}
public function run(): void
{
$config = $this->container->getByClass(Config::class);
$configWriter = $this->container->getByClass(InjectableFactory::class)
->create(Config\ConfigWriter::class);
$configWriter->setMultiple([
'phoneNumberNumericSearch' => false,
'phoneNumberInternational' => false,
]);
if ($config->get('pdfEngine') === 'Tcpdf') {
$configWriter->set('pdfEngine', 'Dompdf');
}
$configWriter->save();
}
}

View File

@@ -0,0 +1,125 @@
<?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\Upgrades\Migrations\V8_2;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Entities\Template;
use Espo\ORM\EntityManager;
use Espo\Tools\Pdf\Template as PdfTemplate;
class AfterUpgrade implements Script
{
public function __construct(
private InjectableFactory $injectableFactory,
private Config $config,
private EntityManager $entityManager,
private Metadata $metadata
) {}
public function run(): void
{
$configWriter = $this->injectableFactory->create(Config\ConfigWriter::class);
$configWriter->setMultiple([
'jobForceUtc' => true,
]);
$configWriter->save();
$em = $this->entityManager;
$config = $this->config;
$this->updateTemplates($em, $config);
$this->updateTargetList($this->metadata);
}
private function updateTemplates(EntityManager $entityManager, Config $config): void
{
if ($config->get('pdfEngine') !== 'Dompdf') {
return;
}
/** @var iterable<Template> $templates */
$templates = $entityManager->getRDBRepositoryByClass(Template::class)
->sth()
->where(['pageFormat' => PdfTemplate::PAGE_FORMAT_CUSTOM])
->find();
foreach ($templates as $template) {
$width = $template->get('pageWidth') ?? 0.0;
$height = $template->get('pageHeight') ?? 0.0;
$template->setMultiple([
'pageWidth' => $width / 2.83465,
'pageHeight' => $height / 2.83465,
]);
$entityManager->saveEntity($template);
}
}
private function updateTargetList(Metadata $metadata): void
{
$links = $metadata->get('entityDefs.TargetList.links') ?? [];
$toSave = false;
foreach ($links as $link => $defs) {
if (empty($defs['isCustom'])) {
continue;
}
if (!$metadata->get("clientDefs.TargetList.relationshipPanels.$link.massSelect")) {
continue;
}
$metadata->set('recordDefs', 'TargetList', [
'relationships' => [
$link => [
'massLink' => true,
'linkRequiredForeignAccess' => 'read',
'mandatoryAttributeList' => ['targetListIsOptedOut'],
]
]
]);
$toSave = true;
}
if (!$toSave) {
return;
}
$metadata->save();
}
}

View File

@@ -0,0 +1,147 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Upgrades\Migrations\V8_3;
use Doctrine\DBAL\Exception as DbalException;
use Espo\Core\Templates\Entities\Event;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Database\Helper;
use Espo\Core\Utils\Metadata;
use Espo\Entities\AuthenticationProvider;
use Espo\Entities\Role;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\Part\Expression;
use Espo\ORM\Query\UpdateBuilder;
class AfterUpgrade implements Script
{
public function __construct(
private EntityManager $entityManager,
private Metadata $metadata,
private Config $config,
private Helper $helper
) {}
/**
* @throws DbalException
*/
public function run(): void
{
$this->updateRoles();
$this->updateMetadata();
$this->updateAuthenticationProviders();
$this->renameSubscription();
}
private function updateRoles(): void
{
$query = UpdateBuilder::create()
->in(Role::ENTITY_TYPE)
->set(['mentionPermission' => Expression::column('assignmentPermission')])
->build();
$this->entityManager->getQueryExecutor()->execute($query);
}
private function updateMetadata(): void
{
$defs = $this->metadata->get(['scopes']);
foreach ($defs as $entityType => $item) {
$isCustom = $item['isCustom'] ?? false;
$type = $item['type'] ?? false;
if (!$isCustom) {
continue;
}
if ($type !== Event::TEMPLATE_TYPE) {
continue;
}
$clientDefs = $this->metadata->getCustom('clientDefs', $entityType) ?? (object) [];
$clientDefs->viewSetupHandlers ??= (object) [];
$clientDefs->viewSetupHandlers->{'record/detail'} = [
"__APPEND__",
"crm:handlers/event/reminders-handler"
];
$clientDefs->viewSetupHandlers->{'record/edit'} = [
"__APPEND__",
"crm:handlers/event/reminders-handler"
];
if (isset($clientDefs->dynamicLogic->fields->reminders)) {
unset($clientDefs->dynamicLogic->fields->reminders);
}
$this->metadata->saveCustom('clientDefs', $entityType, $clientDefs);
}
}
private function updateAuthenticationProviders(): void
{
$collection = $this->entityManager->getRDBRepositoryByClass(AuthenticationProvider::class)
->where(['method' => 'Oidc'])
->find();
foreach ($collection as $entity) {
$entity->set('oidcAuthorizationPrompt', $this->config->get('oidcAuthorizationPrompt'));
$this->entityManager->saveEntity($entity);
}
}
/**
* @throws DbalException
*/
private function renameSubscription(): void
{
$connection = $this->helper->getDbalConnection();
$schemaManager = $connection->createSchemaManager();
if (!$schemaManager->tablesExist('subscription')) {
return;
}
if ($schemaManager->tablesExist('stream_subscription')) {
try {
$schemaManager->dropTable('stream_subscription');
} catch (DbalException) {
$schemaManager->renameTable('stream_subscription', 'stream_subscription_waste');
}
}
$schemaManager->renameTable('subscription', 'stream_subscription');
}
}

View File

@@ -0,0 +1,102 @@
<?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\Upgrades\Migrations\V8_4;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Defs\Params\RelationParam;
use Espo\ORM\Type\RelationType;
class AfterUpgrade implements Script
{
public function __construct(
private Metadata $metadata,
) {}
public function run(): void
{
$this->updateMetadata();
}
private function updateMetadata(): void
{
$defs = $this->metadata->get(['entityDefs']);
$toSave = false;
foreach ($defs as $entityType => $item) {
if (!isset($item['links'])) {
continue;
}
foreach ($item['links'] as $link => $linkDefs) {
$type = $linkDefs['type'] ?? null;
$foreignEntityType = $linkDefs['entity'] ?? null;
$midKeys = $linkDefs[RelationParam::MID_KEYS] ?? null;
$isCustom = $linkDefs['isCustom'] ?? false;
if ($type !== RelationType::HAS_MANY) {
continue;
}
if ($foreignEntityType !== $entityType) {
continue;
}
if (!$midKeys) {
continue;
}
if (!$isCustom) {
continue;
}
if ($linkDefs['_keysSwappedAfterUpgrade'] ?? false) {
continue;
}
$this->metadata->set('entityDefs', $entityType, [
'links' => [
$link => [
RelationParam::MID_KEYS => array_reverse($midKeys),
'_keysSwappedAfterUpgrade' => true,
]
]
]);
$toSave = true;
}
if ($toSave) {
$this->metadata->save();
}
}
}
}

View File

@@ -0,0 +1,205 @@
<?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\Upgrades\Migrations\V9_0;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Entities\Preferences;
use Espo\Entities\Role;
use Espo\Entities\ScheduledJob;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\Part\Expression;
use Espo\ORM\Query\UpdateBuilder;
class AfterUpgrade implements Script
{
public function __construct(
private EntityManager $entityManager,
private Metadata $metadata,
private Config $config,
private Config\ConfigWriter $configWriter,
) {}
public function run(): void
{
$this->updateRoles();
$this->setReactionNotifications();
$this->createScheduledJob();
$this->setAclLinks();
$this->fixTimezone();
}
private function updateRoles(): void
{
$query = UpdateBuilder::create()
->in(Role::ENTITY_TYPE)
->set(['userCalendarPermission' => Expression::column('userPermission')])
->build();
$this->entityManager->getQueryExecutor()->execute($query);
}
private function createScheduledJob(): void
{
$found = $this->entityManager
->getRDBRepositoryByClass(ScheduledJob::class)
->where(['job' => 'SendScheduledEmails'])
->findOne();
if ($found) {
return;
}
$this->entityManager->createEntity(ScheduledJob::ENTITY_TYPE, [
'name' => 'Send Scheduled Emails',
'job' => 'SendScheduledEmails',
'status' => ScheduledJob::STATUS_ACTIVE,
'scheduling' => '*/10 * * * *',
], [SaveOption::SKIP_ALL => true]);
}
private function setReactionNotifications(): void
{
$users = $this->entityManager
->getRDBRepositoryByClass(User::class)
->sth()
->where([
'isActive' => true,
'type' => [
User::TYPE_ADMIN,
User::TYPE_REGULAR,
User::TYPE_PORTAL,
]
])
->find();
foreach ($users as $user) {
$preferences = $this->entityManager->getRepositoryByClass(Preferences::class)->getById($user->getId());
if (!$preferences) {
continue;
}
$preferences->set('reactionNotifications', true);
$this->entityManager->saveEntity($preferences);
}
}
private function setAclLinks(): void
{
/** @var array<string, array<string, mixed>> $scopes */
$scopes = $this->metadata->get('scopes', []);
foreach ($scopes as $scope => $defs) {
if (($defs['entity'] ?? false) && ($defs['isCustom'] ?? false)) {
$this->setAclLinksForEntityType($scope);
}
}
}
private function setAclLinksForEntityType(string $entityType): void
{
$relations = $this->entityManager
->getDefs()
->getEntity($entityType)
->getRelationList();
$contactLink = null;
$accountLink = null;
foreach ($relations as $relation) {
if (
$relation->getName() === 'contact' &&
$relation->tryGetForeignEntityType() === Contact::ENTITY_TYPE
) {
$contactLink = $relation->getName();
}
}
if (!$contactLink) {
foreach ($relations as $relation) {
if (
$relation->getName() === 'contacts' &&
$relation->tryGetForeignEntityType() === Contact::ENTITY_TYPE
) {
$contactLink = $relation->getName();
}
}
}
foreach ($relations as $relation) {
if (
$relation->getName() === 'account' &&
$relation->tryGetForeignEntityType() === Account::ENTITY_TYPE
) {
$accountLink = $relation->getName();
}
}
if (!$accountLink) {
foreach ($relations as $relation) {
if (
$relation->getName() === 'accounts' &&
$relation->tryGetForeignEntityType() === Account::ENTITY_TYPE
) {
$accountLink = $relation->getName();
}
}
}
$this->metadata->set('aclDefs', $entityType, ['contactLink' => $contactLink]);
$this->metadata->set('aclDefs', $entityType, ['accountLink' => $accountLink]);
$this->metadata->save();
}
private function fixTimezone(): void
{
$map = [
'Europe/Kiev' => 'Europe/Kyiv',
'Europe/Uzhgorod' => 'Europe/Uzhhorod',
'Europe/Zaporozhye' => 'Europe/Zaporozhye',
];
$timeZone = $this->config->get('timeZone');
if (in_array($timeZone, array_keys($map))) {
$timeZone = $map[$timeZone];
$this->configWriter->set('timeZone', $timeZone);
$this->configWriter->save();
}
}
}

View File

@@ -0,0 +1,180 @@
<?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\Upgrades\Migrations\V9_1;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Crypt;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\ObjectUtil;
use Espo\Core\Utils\SystemUser;
use Espo\Entities\InboundEmail;
use Espo\Modules\Crm\Entities\KnowledgeBaseArticle;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\Part\Condition;
use Espo\ORM\Query\Part\Expression;
use Espo\Tools\Email\Util;
use stdClass;
class AfterUpgrade implements Script
{
public function __construct(
private EntityManager $entityManager,
private Metadata $metadata,
private Config $config,
private Config\ConfigWriter $configWriter,
private Crypt $crypt,
private SystemUser $systemUser,
) {}
public function run(): void
{
$this->processKbArticles();
$this->processDynamicLogicMetadata();
$this->processGroupEmailAccount();
}
private function processKbArticles(): void
{
if (!str_starts_with(php_sapi_name(), 'cli')) {
return;
}
$articles = $this->entityManager
->getRDBRepositoryByClass(KnowledgeBaseArticle::class)
->sth()
->select([
'id',
'body',
'bodyPlain',
])
->limit(0, 3000)
->find();
foreach ($articles as $article) {
$plain = Util::stripHtml($article->getBody() ?? '') ?: null;
$article->set('bodyPlain', $plain);
$this->entityManager->saveEntity($article, [SaveOption::SKIP_HOOKS => true]);
}
}
private function processDynamicLogicMetadata(): void
{
/** @var string[] $scopes */
$scopes = array_keys($this->metadata->get('clientDefs', []));
foreach ($scopes as $scope) {
$customClientDefs = $this->metadata->getCustom('clientDefs', $scope);
if (!$customClientDefs instanceof stdClass) {
continue;
}
if (
!property_exists($customClientDefs, 'dynamicLogic') ||
!$customClientDefs->dynamicLogic instanceof stdClass
) {
continue;
}
$this->metadata->saveCustom('logicDefs', $scope, $customClientDefs->dynamicLogic);
$customClientDefs = ObjectUtil::clone($customClientDefs);
unset($customClientDefs->dynamicLogic);
$this->metadata->saveCustom('clientDefs', $scope, $customClientDefs);
}
}
private function processGroupEmailAccount(): void
{
if (!$this->config->get('smtpServer')) {
return;
}
$outboundEmailFromAddress = $this->config->get('outboundEmailFromAddress');
if (!$outboundEmailFromAddress) {
return;
}
$groupAccount = $this->entityManager
->getRDBRepositoryByClass(InboundEmail::class)
->where([
'status' => InboundEmail::STATUS_ACTIVE,
'useSmtp' => true,
])
->where(
Condition::equal(
Expression::lowerCase(
Expression::column('emailAddress')
),
strtolower($outboundEmailFromAddress)
)
)
->findOne();
$this->configWriter->set('smtpServer', null);
if ($groupAccount) {
$this->configWriter->save();
return;
}
$password = $this->config->get('smtpPassword');
$groupAccount = $this->entityManager->getRDBRepositoryByClass(InboundEmail::class)->getNew();
$groupAccount->setMultiple([
'emailAddress' => $outboundEmailFromAddress,
'name' => $outboundEmailFromAddress . ' (system)',
'useImap' => false,
'useSmtp' => true,
'smtpHost' => $this->config->get('smtpServer'),
'smtpPort' => $this->config->get('smtpPort'),
'smtpAuth' => $this->config->get('smtpAuth'),
'smtpAuthMechanism' => $this->config->get('smtpAuthMechanism') ?? 'login',
'fromName' => $this->config->get('outboundEmailFromName'),
'smtpUsername' => $this->config->get('smtpUsername'),
'smtpPassword' => $password !== null ? $this->crypt->encrypt($password) : null,
]);
$this->entityManager->saveEntity($groupAccount, [
SaveOption::SKIP_HOOKS => true,
SaveOption::CREATED_BY_ID => $this->systemUser->getId(),
]);
$this->configWriter->save();
}
}

View File

@@ -0,0 +1,60 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Upgrades\Migrations\V9_2;
use Espo\Core\Upgrades\Migration\Script;
use Espo\Entities\Note;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\UpdateBuilder;
class AfterUpgrade implements Script
{
public function __construct(
private EntityManager $entityManager,
) {}
public function run(): void
{
$this->updateNotes();
}
private function updateNotes(): void
{
$update = UpdateBuilder::create()
->in(Note::ENTITY_TYPE)
->set([
'type' => Note::TYPE_UPDATE,
])
->where(['type' => 'Status'])
->build();
$this->entityManager->getQueryExecutor()->execute($update);
}
}