Initial commit
This commit is contained in:
141
application/Espo/Core/Upgrades/ActionManager.php
Normal file
141
application/Espo/Core/Upgrades/ActionManager.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?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;
|
||||
|
||||
use Espo\Core\Container;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Upgrades\Actions\Base as ActionBase;
|
||||
|
||||
class ActionManager
|
||||
{
|
||||
private string $managerName;
|
||||
private Container $container;
|
||||
/** @var array<string, array<string, ActionBase>> */
|
||||
private $objects;
|
||||
protected ?string $currentAction;
|
||||
/** @var array<string, mixed> */
|
||||
protected array $params;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public function __construct(string $managerName, Container $container, array $params)
|
||||
{
|
||||
$this->managerName = $managerName;
|
||||
$this->container = $container;
|
||||
|
||||
$params['name'] = $managerName;
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
protected function getManagerName(): string
|
||||
{
|
||||
return $this->managerName;
|
||||
}
|
||||
|
||||
protected function getContainer(): Container
|
||||
{
|
||||
return $this->container;
|
||||
}
|
||||
|
||||
public function setAction(string $action): void
|
||||
{
|
||||
$this->currentAction = $action;
|
||||
}
|
||||
|
||||
public function getAction(): string
|
||||
{
|
||||
assert($this->currentAction !== null);
|
||||
|
||||
return $this->currentAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getParams(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function run(mixed $data): mixed
|
||||
{
|
||||
$object = $this->getObject();
|
||||
|
||||
return $object->run($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function getActionClass(string $actionName): ActionBase
|
||||
{
|
||||
return $this->getObject($actionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
* @throws Error
|
||||
*/
|
||||
public function getManifest(): array
|
||||
{
|
||||
return $this->getObject()->getManifest();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string $actionName
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getObject(?string $actionName = null): ActionBase
|
||||
{
|
||||
$managerName = $this->getManagerName();
|
||||
|
||||
if (!$actionName) {
|
||||
$actionName = $this->getAction();
|
||||
}
|
||||
|
||||
if (!isset($this->objects[$managerName][$actionName])) {
|
||||
$class = "Espo\\Core\\Upgrades\\Actions\\" . ucfirst($managerName) . '\\' . ucfirst($actionName);
|
||||
|
||||
if (!class_exists($class)) {
|
||||
throw new Error('Could not find an action ['.ucfirst($actionName).'], class ['.$class.'].');
|
||||
}
|
||||
|
||||
/** @var class-string<ActionBase> $class */
|
||||
|
||||
$this->objects[$managerName][$actionName] = new $class($this->container, $this);
|
||||
}
|
||||
|
||||
return $this->objects[$managerName][$actionName];
|
||||
}
|
||||
}
|
||||
1106
application/Espo/Core/Upgrades/Actions/Base.php
Normal file
1106
application/Espo/Core/Upgrades/Actions/Base.php
Normal file
File diff suppressed because it is too large
Load Diff
78
application/Espo/Core/Upgrades/Actions/Base/Delete.php
Normal file
78
application/Espo/Core/Upgrades/Actions/Base/Delete.php
Normal file
@@ -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\Upgrades\Actions\Base;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Upgrades\Actions\Base;
|
||||
|
||||
class Delete extends Base
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function run(mixed $data): mixed
|
||||
{
|
||||
$processId = $data['id'];
|
||||
|
||||
$this->getLog()->debug('Delete package process ['.$processId.']: start run.');
|
||||
|
||||
if (empty($processId)) {
|
||||
throw new Error('Delete package package ID was not specified.');
|
||||
}
|
||||
|
||||
$this->initialize();
|
||||
$this->setProcessId($processId);
|
||||
|
||||
if (isset($data['parentProcessId'])) {
|
||||
$this->setParentProcessId($data['parentProcessId']);
|
||||
}
|
||||
|
||||
$this->beforeRunAction();
|
||||
/* delete a package */
|
||||
$this->deletePackage();
|
||||
$this->afterRunAction();
|
||||
$this->finalize();
|
||||
|
||||
$this->getLog()->debug('Delete package process ['.$processId.']: end run.');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function deletePackage(): bool
|
||||
{
|
||||
$packageArchivePath = $this->getPackagePath(true);
|
||||
|
||||
return $this->getFileManager()->removeFile($packageArchivePath);
|
||||
}
|
||||
}
|
||||
341
application/Espo/Core/Upgrades/Actions/Base/Install.php
Normal file
341
application/Espo/Core/Upgrades/Actions/Base/Install.php
Normal file
@@ -0,0 +1,341 @@
|
||||
<?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\Actions\Base;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Upgrades\Actions\Base;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Throwable;
|
||||
|
||||
class Install extends Base
|
||||
{
|
||||
/**
|
||||
* Main installation process.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function run(mixed $data): mixed
|
||||
{
|
||||
$processId = $data['id'];
|
||||
|
||||
$this->getLog()->debug("Installation process [$processId]: start run.");
|
||||
|
||||
$this->stepInit($data);
|
||||
$this->stepCopyBefore($data);
|
||||
|
||||
if ($this->getCopyFilesPath('before')) {
|
||||
$this->stepRebuild($data);
|
||||
}
|
||||
|
||||
$this->stepBeforeInstallScript($data);
|
||||
|
||||
if ($this->getScriptPath('before')) {
|
||||
$this->stepRebuild($data);
|
||||
}
|
||||
|
||||
$this->stepCopy($data);
|
||||
$this->stepRebuild($data);
|
||||
|
||||
$this->stepCopyAfter($data);
|
||||
|
||||
if ($this->getCopyFilesPath('after')) {
|
||||
$this->stepRebuild($data);
|
||||
}
|
||||
|
||||
$this->stepAfterInstallScript($data);
|
||||
if ($this->getScriptPath('after')) {
|
||||
$this->stepRebuild($data);
|
||||
}
|
||||
|
||||
$this->stepFinalize($data);
|
||||
|
||||
$this->getLog()->debug("Installation process [$processId]: end run.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
protected function initPackage(array $data): void
|
||||
{
|
||||
$processId = $data['id'];
|
||||
|
||||
if (empty($processId)) {
|
||||
throw new Error('Installation package ID was not specified.');
|
||||
}
|
||||
|
||||
$this->setProcessId($processId);
|
||||
|
||||
if (isset($data['parentProcessId'])) {
|
||||
$this->setParentProcessId($data['parentProcessId']);
|
||||
}
|
||||
|
||||
/** check if an archive is unzipped, if no then unzip */
|
||||
$packagePath = $this->getPackagePath();
|
||||
|
||||
if (!file_exists($packagePath)) {
|
||||
$this->unzipArchive();
|
||||
$this->isAcceptable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function stepInit(array $data): void
|
||||
{
|
||||
$this->initPackage($data);
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: Start \"init\" step.");
|
||||
|
||||
if (!$this->systemRebuild()) {
|
||||
$this->throwErrorAndRemovePackage('Rebuild is failed. Fix all errors before upgrade.');
|
||||
}
|
||||
|
||||
$this->initialize();
|
||||
$this->checkIsWritable();
|
||||
$this->enableMaintenanceMode();
|
||||
$this->beforeRunAction();
|
||||
$this->backupExistingFiles();
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: End \"init\" step.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function stepCopyBefore(array $data): void
|
||||
{
|
||||
$this->initPackage($data);
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: Start \"copyBefore\" step.");
|
||||
|
||||
if (!$this->copyFiles('before')) {
|
||||
$this->throwErrorAndRemovePackage('Cannot copy beforeInstall files.');
|
||||
}
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: End \"copyBefore\" step.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function stepBeforeInstallScript(array $data): void
|
||||
{
|
||||
$this->initPackage($data);
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: Start \"beforeInstallScript\" step.");
|
||||
|
||||
if (!isset($data['skipBeforeScript']) || !$data['skipBeforeScript']) {
|
||||
$this->runScript('before');
|
||||
}
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: End \"beforeInstallScript\" step.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function stepCopy(array $data): void
|
||||
{
|
||||
$this->initPackage($data);
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: Start \"copy\" step.");
|
||||
|
||||
/* remove files defined in a manifest */
|
||||
if (!$this->deleteFiles('delete', true)) {
|
||||
$this->throwErrorAndRemovePackage('Cannot delete files.');
|
||||
}
|
||||
|
||||
/* copy files from directory "Files" to EspoCRM files */
|
||||
if (!$this->copyFiles()) {
|
||||
$this->throwErrorAndRemovePackage('Cannot copy files.');
|
||||
}
|
||||
|
||||
if (!$this->deleteFiles('vendor')) {
|
||||
$this->throwErrorAndRemovePackage('Cannot delete vendor files.');
|
||||
}
|
||||
|
||||
if (!$this->copyFiles('vendor')) {
|
||||
$this->throwErrorAndRemovePackage('Cannot copy vendor files.');
|
||||
}
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: End \"copy\" step.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function stepRebuild(array $data): void
|
||||
{
|
||||
$this->initPackage($data);
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: Start \"rebuild\" step.");
|
||||
|
||||
if (!isset($data['skipSystemRebuild']) || !$data['skipSystemRebuild']) {
|
||||
if (!$this->systemRebuild()) {
|
||||
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild. More detail in the log.');
|
||||
}
|
||||
}
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: End \"rebuild\" step.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function stepCopyAfter(array $data): void
|
||||
{
|
||||
$this->initPackage($data);
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: Start \"copyAfter\" step.");
|
||||
|
||||
//afterInstallFiles
|
||||
if (!$this->copyFiles('after')) {
|
||||
$this->throwErrorAndRemovePackage('Cannot copy afterInstall files.');
|
||||
}
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: End \"copyAfter\" step.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function stepAfterInstallScript(array $data): void
|
||||
{
|
||||
$this->initPackage($data);
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: Start \"afterInstallScript\" step.");
|
||||
|
||||
/* run after install script */
|
||||
if (!isset($data['skipAfterScript']) || !$data['skipAfterScript']) {
|
||||
$this->runScript('after');
|
||||
}
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: End \"afterInstallScript\" step.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function stepFinalize(array $data): void
|
||||
{
|
||||
$this->initPackage($data);
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: Start \"finalize\" step.");
|
||||
|
||||
$this->disableMaintenanceMode();
|
||||
$this->afterRunAction();
|
||||
$this->finalize();
|
||||
|
||||
/* delete unzipped files */
|
||||
$this->deletePackageFiles();
|
||||
|
||||
if ($this->getManifestParam('skipBackup')) {
|
||||
$path = Util::concatPath($this->getPath('backupPath'), self::FILES);
|
||||
|
||||
$this->getFileManager()->removeInDir($path);
|
||||
}
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: End \"finalize\" step.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function stepRevert(array $data): void
|
||||
{
|
||||
$this->initPackage($data);
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: Start \"revert\" step.");
|
||||
|
||||
$this->restoreFiles();
|
||||
|
||||
$this->getLog()->info("Installation process [{$this->getProcessId()}]: End \"revert\" step.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function restoreFiles(): bool
|
||||
{
|
||||
$this->getLog()->info('Installer: Restore previous files.');
|
||||
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
$backupFilePath = Util::concatPath($backupPath, self::FILES);
|
||||
|
||||
if (!file_exists($backupFilePath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$backupFileList = $this->getRestoreFileList();
|
||||
$copyFileList = $this->getCopyFileList();
|
||||
$deleteFileList = array_diff($copyFileList, $backupFileList);
|
||||
|
||||
$res = $this->copy($backupFilePath, '', true);
|
||||
|
||||
if (!empty($deleteFileList)) {
|
||||
$res &= $this->getFileManager()->remove($deleteFileList, null, true);
|
||||
}
|
||||
|
||||
if ($res) {
|
||||
$this->getFileManager()->removeInDir($backupPath, true);
|
||||
}
|
||||
|
||||
return (bool) $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function throwErrorAndRemovePackage(
|
||||
string $errorMessage = '',
|
||||
bool $deletePackage = true,
|
||||
bool $systemRebuild = true,
|
||||
?Throwable $exception = null
|
||||
): void {
|
||||
|
||||
$this->restoreFiles();
|
||||
|
||||
parent::throwErrorAndRemovePackage($errorMessage, $deletePackage, $systemRebuild, $exception);
|
||||
}
|
||||
}
|
||||
250
application/Espo/Core/Upgrades/Actions/Base/Uninstall.php
Normal file
250
application/Espo/Core/Upgrades/Actions/Base/Uninstall.php
Normal file
@@ -0,0 +1,250 @@
|
||||
<?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\Actions\Base;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Upgrades\Actions\Base;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Throwable;
|
||||
|
||||
class Uninstall extends Base
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function run(mixed $data): mixed
|
||||
{
|
||||
$processId = $data['id'];
|
||||
|
||||
$this->getLog()->debug('Uninstallation process ['.$processId.']: start run.');
|
||||
|
||||
if (empty($processId)) {
|
||||
throw new Error('Uninstallation package ID was not specified.');
|
||||
}
|
||||
|
||||
$this->setProcessId($processId);
|
||||
|
||||
if (isset($data['parentProcessId'])) {
|
||||
$this->setParentProcessId($data['parentProcessId']);
|
||||
}
|
||||
|
||||
$this->initialize();
|
||||
$this->checkIsWritable();
|
||||
$this->enableMaintenanceMode();
|
||||
$this->beforeRunAction();
|
||||
|
||||
/* run before uninstall script */
|
||||
if (!isset($data['skipBeforeScript']) || !$data['skipBeforeScript']) {
|
||||
$this->runScript('beforeUninstall');
|
||||
}
|
||||
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
if (file_exists($backupPath)) {
|
||||
/* copy core files */
|
||||
if (!$this->copyFiles()) {
|
||||
$this->throwErrorAndRemovePackage('Cannot copy files.');
|
||||
}
|
||||
}
|
||||
|
||||
/* remove extension files, saved in fileList */
|
||||
if (!$this->deleteFiles('delete', true)) {
|
||||
$this->throwErrorAndRemovePackage('Permission denied to delete files.');
|
||||
}
|
||||
|
||||
$this->disableMaintenanceMode();
|
||||
|
||||
if (!isset($data['skipSystemRebuild']) || !$data['skipSystemRebuild']) {
|
||||
if (!$this->systemRebuild()) {
|
||||
$this->throwErrorAndRemovePackage(
|
||||
'Error occurred while EspoCRM rebuild. More detail in the log.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* run after uninstall script */
|
||||
if (!isset($data['skipAfterScript']) || !$data['skipAfterScript']) {
|
||||
$this->runScript('afterUninstall');
|
||||
}
|
||||
|
||||
$this->afterRunAction();
|
||||
/* delete backup files */
|
||||
$this->deletePackageFiles();
|
||||
$this->finalize();
|
||||
|
||||
$this->getLog()->debug('Uninstallation process ['.$processId.']: end run.');
|
||||
|
||||
$this->clearCache();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function restoreFiles(): bool
|
||||
{
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
$packagePath = $this->getPath('packagePath');
|
||||
|
||||
$manifestPath = Util::concatPath($packagePath, $this->manifestName);
|
||||
|
||||
if (!file_exists($manifestPath)) {
|
||||
$this->unzipArchive($packagePath);
|
||||
}
|
||||
|
||||
$fileDirs = $this->getFileDirs($packagePath);
|
||||
|
||||
$res = true;
|
||||
|
||||
foreach ($fileDirs as $filesPath) {
|
||||
if (file_exists($filesPath)) {
|
||||
$res = $this->copy($filesPath, '', true);
|
||||
}
|
||||
}
|
||||
|
||||
$manifestJson = $this->getFileManager()->getContents($manifestPath);
|
||||
$manifest = Json::decode($manifestJson, true);
|
||||
|
||||
if (!empty($manifest['delete'])) {
|
||||
$res &= $this->getFileManager()->remove($manifest['delete'], null, true);
|
||||
}
|
||||
|
||||
$res &= $this->getFileManager()->removeInDir($packagePath, true);
|
||||
|
||||
return (bool) $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string $type
|
||||
* @param string $dest
|
||||
* @throws Error
|
||||
*/
|
||||
protected function copyFiles($type = null, $dest = ''): bool
|
||||
{
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
|
||||
$source = Util::concatPath($backupPath, self::FILES);
|
||||
|
||||
return $this->copy($source, $dest, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get backup path.
|
||||
*
|
||||
* @param bool $isPackage
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getPackagePath($isPackage = false): string
|
||||
{
|
||||
if ($isPackage) {
|
||||
return $this->getPath('packagePath', $isPackage);
|
||||
}
|
||||
|
||||
return $this->getPath('backupPath');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function deletePackageFiles(): bool
|
||||
{
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
|
||||
return $this->getFileManager()->removeInDir($backupPath, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function throwErrorAndRemovePackage(
|
||||
string $errorMessage = '',
|
||||
bool $deletePackage = true,
|
||||
bool $systemRebuild = true,
|
||||
?Throwable $exception = null
|
||||
): void {
|
||||
|
||||
$this->restoreFiles();
|
||||
|
||||
parent::throwErrorAndRemovePackage($errorMessage, false, $systemRebuild, $exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getCopyFileList(): array
|
||||
{
|
||||
if (!isset($this->data['fileList'])) {
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
$filesPath = Util::concatPath($backupPath, self::FILES);
|
||||
|
||||
$this->data['fileList'] = $this->getFileManager()->getFileList($filesPath, true, '', true, true);
|
||||
}
|
||||
|
||||
return $this->data['fileList'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getRestoreFileList(): array
|
||||
{
|
||||
if (!isset($this->data['restoreFileList'])) {
|
||||
$packagePath = $this->getPackagePath();
|
||||
$filesPath = Util::concatPath($packagePath, self::FILES);
|
||||
|
||||
if (!file_exists($filesPath)) {
|
||||
$this->unzipArchive($packagePath);
|
||||
}
|
||||
|
||||
$this->data['restoreFileList'] = $this->getFileManager()->getFileList($filesPath, true, '', true, true);
|
||||
}
|
||||
|
||||
return $this->data['restoreFileList'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getDeleteList($type = 'delete'): array
|
||||
{
|
||||
if ($type == 'delete') {
|
||||
$packageFileList = $this->getRestoreFileList();
|
||||
$backupFileList = $this->getCopyFileList();
|
||||
|
||||
return array_diff($packageFileList, $backupFileList);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
78
application/Espo/Core/Upgrades/Actions/Base/Upload.php
Normal file
78
application/Espo/Core/Upgrades/Actions/Base/Upload.php
Normal file
@@ -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\Upgrades\Actions\Base;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Upgrades\Actions\Base;
|
||||
|
||||
class Upload extends Base
|
||||
{
|
||||
/**
|
||||
* Upload an upgrade/extension package.
|
||||
*
|
||||
* @param string $data
|
||||
* @return string ID of upgrade/extension process.
|
||||
* @throws Error
|
||||
*/
|
||||
public function run(mixed $data): string
|
||||
{
|
||||
$processId = $this->createProcessId();
|
||||
|
||||
$this->getLog()->debug("Installation process [$processId]: start upload the package.");
|
||||
|
||||
$this->initialize();
|
||||
$this->beforeRunAction();
|
||||
|
||||
$packageArchivePath = $this->getPackagePath(true);
|
||||
|
||||
$contents = null;
|
||||
|
||||
if (!empty($data)) {
|
||||
[, $contents] = explode(',', $data);
|
||||
|
||||
$contents = base64_decode($contents);
|
||||
}
|
||||
|
||||
$res = $this->getFileManager()->putContents($packageArchivePath, $contents);
|
||||
|
||||
if ($res === false) {
|
||||
throw new Error('Could not upload the package.');
|
||||
}
|
||||
|
||||
$this->unzipArchive();
|
||||
$this->isAcceptable();
|
||||
$this->afterRunAction();
|
||||
$this->finalize();
|
||||
|
||||
$this->getLog()->debug("Installation process [$processId]: end upload the package.");
|
||||
|
||||
return $processId;
|
||||
}
|
||||
}
|
||||
68
application/Espo/Core/Upgrades/Actions/Extension/Delete.php
Normal file
68
application/Espo/Core/Upgrades/Actions/Extension/Delete.php
Normal file
@@ -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\Upgrades\Actions\Extension;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Entities\Extension;
|
||||
|
||||
class Delete extends \Espo\Core\Upgrades\Actions\Base\Delete
|
||||
{
|
||||
protected ?Extension $extensionEntity = null;
|
||||
|
||||
/**
|
||||
* Get an entity of this extension.
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getExtensionEntity(): Extension
|
||||
{
|
||||
if (!$this->extensionEntity) {
|
||||
$processId = $this->getProcessId();
|
||||
|
||||
$this->extensionEntity = $this->getEntityManager()->getEntityById(Extension::ENTITY_TYPE, $processId);
|
||||
|
||||
if (!$this->extensionEntity) {
|
||||
throw new Error('Extension entity not found.');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->extensionEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function afterRunAction(): void
|
||||
{
|
||||
/** Delete extension entity */
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
|
||||
$this->getEntityManager()->removeEntity($extensionEntity);
|
||||
}
|
||||
}
|
||||
245
application/Espo/Core/Upgrades/Actions/Extension/Install.php
Normal file
245
application/Espo/Core/Upgrades/Actions/Extension/Install.php
Normal file
@@ -0,0 +1,245 @@
|
||||
<?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\Actions\Extension;
|
||||
|
||||
use Espo\Core\Upgrades\Base;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Entities\Extension;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Throwable;
|
||||
|
||||
class Install extends \Espo\Core\Upgrades\Actions\Base\Install
|
||||
{
|
||||
protected ?Extension $extensionEntity = null;
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function beforeRunAction(): void
|
||||
{
|
||||
$this->loadExtension();
|
||||
|
||||
if (!$this->isNew()) {
|
||||
$this->scriptParams['isUpgrade'] = true;
|
||||
|
||||
$this->compareVersion();
|
||||
$this->uninstallExtension();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function afterRunAction(): void
|
||||
{
|
||||
if (!$this->isNew()) {
|
||||
$this->deleteExtension();
|
||||
}
|
||||
|
||||
$this->storeExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy Existing files to a backup directory.
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
protected function backupExistingFiles(): bool
|
||||
{
|
||||
parent::backupExistingFiles();
|
||||
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
|
||||
/** copy scripts files */
|
||||
$packagePath = $this->getPackagePath();
|
||||
|
||||
$source = Util::concatPath($packagePath, self::SCRIPTS);
|
||||
$destination = Util::concatPath($backupPath, self::SCRIPTS);
|
||||
|
||||
return $this->copy($source, $destination, true);
|
||||
}
|
||||
|
||||
private function isNew(): bool
|
||||
{
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
|
||||
if ($extensionEntity) {
|
||||
$id = $extensionEntity->get(Attribute::ID);
|
||||
}
|
||||
|
||||
return !isset($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity of the extension.
|
||||
*/
|
||||
private function getExtensionEntity(): ?Extension
|
||||
{
|
||||
return $this->extensionEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the extension entity.
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
private function loadExtension(): void
|
||||
{
|
||||
$manifest = $this->getManifest();
|
||||
|
||||
$this->extensionEntity = $this->getEntityManager()
|
||||
->getRDBRepositoryByClass(Extension::class)
|
||||
->where([
|
||||
'name' => $manifest['name'],
|
||||
'isInstalled' => true,
|
||||
])
|
||||
->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a record of an extension entity.
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
private function storeExtension(): void
|
||||
{
|
||||
$entityManager = $this->getEntityManager();
|
||||
|
||||
$extensionEntity = null;
|
||||
|
||||
if ($this->getProcessId()) {
|
||||
$extensionEntity = $entityManager->getEntityById(Extension::ENTITY_TYPE, $this->getProcessId());
|
||||
}
|
||||
|
||||
if (!$extensionEntity) {
|
||||
$extensionEntity = $entityManager->getNewEntity(Extension::ENTITY_TYPE);
|
||||
}
|
||||
|
||||
$manifest = $this->getManifest();
|
||||
$fileList = $this->getCopyFileList();
|
||||
|
||||
$data = [
|
||||
'id' => $this->getProcessId(),
|
||||
'name' => trim($manifest['name']),
|
||||
'isInstalled' => true,
|
||||
'version' => $manifest['version'],
|
||||
'fileList' => $fileList,
|
||||
'description' => $manifest['description'],
|
||||
];
|
||||
|
||||
if (!empty($manifest['checkVersionUrl'])) {
|
||||
$data['checkVersionUrl'] = $manifest['checkVersionUrl'];
|
||||
}
|
||||
|
||||
$extensionEntity->set($data);
|
||||
|
||||
try {
|
||||
$entityManager->saveEntity($extensionEntity);
|
||||
} catch (Throwable $e) {
|
||||
$msg = "Error while saving the extension entity. The error occurred in an existing hook. " .
|
||||
"{$e->getMessage()} at {$e->getFile()}:{$e->getLine()}";
|
||||
|
||||
$this->getLog()->error($msg);
|
||||
$this->throwErrorAndRemovePackage('Error saving extension entity. Check logs for details.', false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare version between installed and a new extensions.
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
private function compareVersion(): void
|
||||
{
|
||||
$manifest = $this->getManifest();
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
|
||||
if (!$extensionEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
$comparedVersion = version_compare($manifest['version'], $extensionEntity->getVersion(), '>=');
|
||||
|
||||
if ($comparedVersion <= 0) {
|
||||
$this->throwErrorAndRemovePackage('You cannot install an older version of this extension.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If extension already installed, uninstall an old version.
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
private function uninstallExtension(): void
|
||||
{
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
|
||||
if (!$extensionEntity) {
|
||||
throw new Error("Can't uninstall not existing extension.");
|
||||
}
|
||||
|
||||
$this->executeAction(Base::UNINSTALL, [
|
||||
'id' => $extensionEntity->get(Attribute::ID),
|
||||
'skipSystemRebuild' => true,
|
||||
'skipAfterScript' => true,
|
||||
'parentProcessId' => $this->getProcessId(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete extension package.
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
private function deleteExtension(): void
|
||||
{
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
|
||||
if (!$extensionEntity) {
|
||||
throw new Error("Can't delete not existing extension.");
|
||||
}
|
||||
|
||||
$this->executeAction(Base::DELETE, [
|
||||
'id' => $extensionEntity->get(Attribute::ID),
|
||||
'parentProcessId' => $this->getProcessId(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string[]|string> $dependencyList
|
||||
* @throws Error
|
||||
*/
|
||||
protected function checkDependencies(array $dependencyList): bool
|
||||
{
|
||||
return $this->getHelper()->checkDependencies($dependencyList);
|
||||
}
|
||||
}
|
||||
@@ -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\Upgrades\Actions\Extension;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Entities\Extension;
|
||||
use Throwable;
|
||||
|
||||
class Uninstall extends \Espo\Core\Upgrades\Actions\Base\Uninstall
|
||||
{
|
||||
protected ?Extension $extensionEntity = null;
|
||||
|
||||
/**
|
||||
* Get entity of this extension.
|
||||
*
|
||||
* @return Extension
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getExtensionEntity()
|
||||
{
|
||||
if (!isset($this->extensionEntity)) {
|
||||
$processId = $this->getProcessId();
|
||||
|
||||
$this->extensionEntity = $this->getEntityManager()->getEntityById(Extension::ENTITY_TYPE, $processId);
|
||||
|
||||
if (!$this->extensionEntity) {
|
||||
throw new Error('Extension entity not found.');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->extensionEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function afterRunAction(): void
|
||||
{
|
||||
/** Set extension entity, isInstalled = false */
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
$extensionEntity->set('isInstalled', false);
|
||||
|
||||
try {
|
||||
$this->getEntityManager()->saveEntity($extensionEntity);
|
||||
} catch (Throwable $e) {
|
||||
$this->getLog()->error(
|
||||
'Error saving Extension entity. The error occurred by existing Hook, more details: ' .
|
||||
$e->getMessage() .' at '. $e->getFile() . ':' . $e->getLine()
|
||||
);
|
||||
|
||||
$this->throwErrorAndRemovePackage('Error saving Extension entity. Check logs for details.', false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getRestoreFileList(): array
|
||||
{
|
||||
if (!isset($this->data['restoreFileList'])) {
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
$this->data['restoreFileList'] = $extensionEntity->get('fileList');
|
||||
}
|
||||
|
||||
return $this->data['restoreFileList'] ?? [];
|
||||
}
|
||||
}
|
||||
46
application/Espo/Core/Upgrades/Actions/Extension/Upload.php
Normal file
46
application/Espo/Core/Upgrades/Actions/Extension/Upload.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Upgrades\Actions\Extension;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class Upload extends \Espo\Core\Upgrades\Actions\Base\Upload
|
||||
{
|
||||
/**
|
||||
* Check dependencies.
|
||||
*
|
||||
* @param array<string, string[]|string> $dependencyList
|
||||
* @throws Error
|
||||
*/
|
||||
protected function checkDependencies($dependencyList): bool
|
||||
{
|
||||
return $this->getHelper()->checkDependencies($dependencyList);
|
||||
}
|
||||
}
|
||||
96
application/Espo/Core/Upgrades/Actions/Helper.php
Normal file
96
application/Espo/Core/Upgrades/Actions/Helper.php
Normal file
@@ -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\Upgrades\Actions;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Entities\Extension;
|
||||
use Espo\ORM\EntityManager;
|
||||
use RuntimeException;
|
||||
|
||||
class Helper
|
||||
{
|
||||
private ?Base $actionObject = null;
|
||||
|
||||
public function __construct(private EntityManager $entityManager)
|
||||
{}
|
||||
|
||||
public function setActionObject(Base $actionObject): void
|
||||
{
|
||||
$this->actionObject = $actionObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check dependencies.
|
||||
*
|
||||
* @param array<string, string[]|string> $dependencyList
|
||||
* @throws Error
|
||||
*/
|
||||
public function checkDependencies(mixed $dependencyList): bool
|
||||
{
|
||||
if (!$this->actionObject) {
|
||||
throw new RuntimeException("No action passed.");
|
||||
}
|
||||
|
||||
if (!is_array($dependencyList)) {
|
||||
$dependencyList = (array) $dependencyList;
|
||||
}
|
||||
|
||||
/** @var array<string, string[]|string> $dependencyList */
|
||||
|
||||
foreach ($dependencyList as $extensionName => $extensionVersion) {
|
||||
$entity = $this->entityManager
|
||||
->getRDBRepositoryByClass(Extension::class)
|
||||
->where([
|
||||
'name' => trim($extensionName),
|
||||
'isInstalled' => true,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
$versionString = is_array($extensionVersion) ?
|
||||
implode(', ', $extensionVersion) :
|
||||
$extensionVersion;
|
||||
|
||||
$errorMessage = "Dependency error: Extension '$extensionName' with version '$versionString' is missing.";
|
||||
|
||||
if (
|
||||
!$entity ||
|
||||
!$this->actionObject->checkVersions(
|
||||
$extensionVersion,
|
||||
$entity->getVersion(),
|
||||
$errorMessage
|
||||
)
|
||||
) {
|
||||
throw new Error($errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
44
application/Espo/Core/Upgrades/Actions/Upgrade/Delete.php
Normal file
44
application/Espo/Core/Upgrades/Actions/Upgrade/Delete.php
Normal file
@@ -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\Upgrades\Actions\Upgrade;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class Delete extends \Espo\Core\Upgrades\Actions\Base\Delete
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function run(mixed $data): mixed
|
||||
{
|
||||
throw new Error('The operation is not permitted.');
|
||||
}
|
||||
}
|
||||
139
application/Espo/Core/Upgrades/Actions/Upgrade/Install.php
Normal file
139
application/Espo/Core/Upgrades/Actions/Upgrade/Install.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?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\Actions\Upgrade;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Upgrades\Migration\Script;
|
||||
use Espo\Core\Upgrades\Migration\VersionUtil;
|
||||
use RuntimeException;
|
||||
|
||||
class Install extends \Espo\Core\Upgrades\Actions\Base\Install
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function stepBeforeUpgradeScript(array $data): void
|
||||
{
|
||||
$this->stepBeforeInstallScript($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function stepAfterUpgradeScript(array $data): void
|
||||
{
|
||||
$this->stepAfterInstallScript($data);
|
||||
$this->runMigrationAfterInstallScript();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function finalize(): void
|
||||
{
|
||||
$configWriter = $this->createConfigWriter();
|
||||
$configWriter->set('version', $this->getTargetVersion());
|
||||
$configWriter->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete temporary package files.
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
protected function deletePackageFiles(): bool
|
||||
{
|
||||
$res = parent::deletePackageFiles();
|
||||
$res &= $this->deletePackageArchive();
|
||||
|
||||
return (bool) $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
private function getTargetVersion(): string
|
||||
{
|
||||
$version = $this->getManifest()['version'];
|
||||
|
||||
if (!$version) {
|
||||
throw new RuntimeException("No 'version' in manifest.");
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
private function runMigrationAfterInstallScript(): void
|
||||
{
|
||||
$targetVersion = $this->getTargetVersion();
|
||||
$version = $this->getConfig()->get('version');
|
||||
|
||||
if (!$version || !is_string($version)) {
|
||||
throw new RuntimeException("No or bad 'version' in config.");
|
||||
}
|
||||
|
||||
$script = $this->getMigrationAfterInstallScript($version, $targetVersion);
|
||||
|
||||
if (!$script) {
|
||||
return;
|
||||
}
|
||||
|
||||
$script->run();
|
||||
}
|
||||
|
||||
private function getMigrationAfterInstallScript(string $version, string $targetVersion): ?Script
|
||||
{
|
||||
$isPatch = VersionUtil::isPatch($version, $targetVersion);
|
||||
$a = VersionUtil::split($targetVersion);
|
||||
|
||||
$dir = $isPatch ?
|
||||
'V' . $a[0] . '_' . $a[1] . '_' . $a[2] :
|
||||
'V' . $a[0] . '_' . $a[1];
|
||||
|
||||
$className = "Espo\\Core\\Upgrades\\Migrations\\$dir\\AfterUpgrade";
|
||||
|
||||
if (!class_exists($className)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$script = $this->getInjectableFactory()->createWith($className, ['isUpgrade' => true]);
|
||||
|
||||
if (!$script instanceof Script) {
|
||||
throw new RuntimeException("$className does not implement Script interface.");
|
||||
}
|
||||
|
||||
return $script;
|
||||
}
|
||||
}
|
||||
44
application/Espo/Core/Upgrades/Actions/Upgrade/Uninstall.php
Normal file
44
application/Espo/Core/Upgrades/Actions/Upgrade/Uninstall.php
Normal file
@@ -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\Upgrades\Actions\Upgrade;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class Uninstall extends \Espo\Core\Upgrades\Actions\Base\Uninstall
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function run(mixed $data): mixed
|
||||
{
|
||||
throw new Error('The operation is not permitted.');
|
||||
}
|
||||
}
|
||||
34
application/Espo/Core/Upgrades/Actions/Upgrade/Upload.php
Normal file
34
application/Espo/Core/Upgrades/Actions/Upgrade/Upload.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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\Actions\Upgrade;
|
||||
|
||||
class Upload extends \Espo\Core\Upgrades\Actions\Base\Upload
|
||||
{
|
||||
}
|
||||
147
application/Espo/Core/Upgrades/Base.php
Normal file
147
application/Espo/Core/Upgrades/Base.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Upgrades;
|
||||
|
||||
use Espo\Core\Container;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
abstract class Base
|
||||
{
|
||||
protected ActionManager $actionManager;
|
||||
|
||||
protected ?string $name = null;
|
||||
/** @var array<string, mixed> */
|
||||
protected array $params = [];
|
||||
|
||||
const UPLOAD = 'upload';
|
||||
const INSTALL = 'install';
|
||||
const UNINSTALL = 'uninstall';
|
||||
const DELETE = 'delete';
|
||||
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->actionManager = new ActionManager($this->name ?? '', $container, $this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
* @throws Error
|
||||
*/
|
||||
public function getManifest(): array
|
||||
{
|
||||
return $this->actionManager->getManifest();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
* @throws Error
|
||||
*/
|
||||
public function getManifestById(string $processId): array
|
||||
{
|
||||
$actionClass = $this->actionManager->getActionClass(self::INSTALL);
|
||||
$actionClass->setProcessId($processId);
|
||||
|
||||
return $actionClass->getManifest();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function upload(string $data): string
|
||||
{
|
||||
$this->actionManager->setAction(self::UPLOAD);
|
||||
|
||||
return $this->actionManager->run($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function install(array $data): mixed
|
||||
{
|
||||
$this->actionManager->setAction(self::INSTALL);
|
||||
|
||||
return $this->actionManager->run($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function uninstall(array $data): mixed
|
||||
{
|
||||
$this->actionManager->setAction(self::UNINSTALL);
|
||||
|
||||
return $this->actionManager->run($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @throws Error
|
||||
*/
|
||||
public function delete(array $data): mixed
|
||||
{
|
||||
$this->actionManager->setAction(self::DELETE);
|
||||
|
||||
return $this->actionManager->run($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
* @throws Error
|
||||
*/
|
||||
public function runInstallStep(string $stepName, array $params = []): void
|
||||
{
|
||||
$this->runActionStep(self::INSTALL, $stepName, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
* @throws Error
|
||||
* @noinspection PhpSameParameterValueInspection
|
||||
*/
|
||||
private function runActionStep(string $actionName, string $stepName, array $params = []): void
|
||||
{
|
||||
$actionClass = $this->actionManager->getActionClass($actionName);
|
||||
$methodName = 'step' . ucfirst($stepName);
|
||||
|
||||
if (!method_exists($actionClass, $methodName)) {
|
||||
if (!empty($params['id'])) {
|
||||
$actionClass->setProcessId($params['id']);
|
||||
$actionClass->throwErrorAndRemovePackage("Step \"$stepName\" is not found.");
|
||||
}
|
||||
|
||||
throw new Error('Step "'. $stepName .'" is not found.');
|
||||
}
|
||||
|
||||
$actionClass->$methodName($params); // throw an Exception on error
|
||||
}
|
||||
}
|
||||
52
application/Espo/Core/Upgrades/ExtensionManager.php
Normal file
52
application/Espo/Core/Upgrades/ExtensionManager.php
Normal 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\Upgrades;
|
||||
|
||||
class ExtensionManager extends Base
|
||||
{
|
||||
protected ?string $name = 'Extension';
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
protected array $params = [
|
||||
'packagePath' => 'data/upload/extensions',
|
||||
'backupPath' => 'data/.backup/extensions',
|
||||
|
||||
'scriptNames' => [
|
||||
'before' => 'BeforeInstall',
|
||||
'after' => 'AfterInstall',
|
||||
'beforeUninstall' => 'BeforeUninstall',
|
||||
'afterUninstall' => 'AfterUninstall',
|
||||
],
|
||||
'customDirNames' => [
|
||||
'before' => 'beforeInstallFiles',
|
||||
'after' => 'afterInstallFiles',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -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\Upgrades\Migration;
|
||||
|
||||
use Espo\Core\DataManager;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use RuntimeException;
|
||||
|
||||
class AfterUpgradeRunner
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private DataManager $dataManager
|
||||
) {}
|
||||
|
||||
public function run(string $step): void
|
||||
{
|
||||
$dir = 'V' . str_replace('.', '_', $step);
|
||||
|
||||
$className = "Espo\\Core\\Upgrades\\Migrations\\$dir\\AfterUpgrade";
|
||||
|
||||
if (!class_exists($className)) {
|
||||
throw new RuntimeException("No after-upgrade script $step.");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->dataManager->rebuild();
|
||||
} catch (Error $e) {
|
||||
throw new RuntimeException("Error while rebuild: " . $e->getMessage());
|
||||
}
|
||||
|
||||
/** @var Script $script */
|
||||
$script = $this->injectableFactory->createWith($className, ['isUpgrade' => false]);
|
||||
$script->run();
|
||||
|
||||
try {
|
||||
$this->dataManager->rebuild();
|
||||
} catch (Error $e) {
|
||||
throw new RuntimeException("Error while rebuild: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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\Migration;
|
||||
|
||||
class ExtractedStepsProvider
|
||||
{
|
||||
public function __construct(
|
||||
private StepsProvider $stepsProvider
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAfterUpgrade(string $version, string $targetVersion): array
|
||||
{
|
||||
return VersionUtil::extractSteps($version, $targetVersion, $this->stepsProvider->getAfterUpgrade());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPrepare(string $version, string $targetVersion): array
|
||||
{
|
||||
return VersionUtil::extractSteps($version, $targetVersion, $this->stepsProvider->getPrepare());
|
||||
}
|
||||
}
|
||||
127
application/Espo/Core/Upgrades/Migration/Runner.php
Normal file
127
application/Espo/Core/Upgrades/Migration/Runner.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Upgrades\Migration;
|
||||
|
||||
use Espo\Core\Console\IO;
|
||||
use Espo\Core\DataManager;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Utils\Config\ConfigWriter;
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
class Runner
|
||||
{
|
||||
public function __construct(
|
||||
private ExtractedStepsProvider $stepsProvider,
|
||||
private VersionDataProvider $versionDataProvider,
|
||||
private StepRunner $stepRunner,
|
||||
private DataManager $dataManager,
|
||||
private ConfigWriter $configWriter
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function run(IO $io): void
|
||||
{
|
||||
$this->dataManager->clearCache();
|
||||
|
||||
$version = $this->versionDataProvider->getPreviousVersion();
|
||||
$targetVersion = $this->versionDataProvider->getTargetVersion();
|
||||
|
||||
$prepareSteps = $this->stepsProvider->getPrepare($version, $targetVersion);
|
||||
|
||||
if ($prepareSteps !== []) {
|
||||
$io->writeLine("Running prepare migrations...");
|
||||
|
||||
foreach ($prepareSteps as $step) {
|
||||
$this->runPrepareStep($io, $step);
|
||||
}
|
||||
}
|
||||
|
||||
$afterSteps = $this->stepsProvider->getAfterUpgrade($version, $targetVersion);
|
||||
|
||||
if ($afterSteps === []) {
|
||||
$io->writeLine("No migrations to run.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$io->writeLine("Running after-upgrade migrations...");
|
||||
|
||||
foreach ($afterSteps as $step) {
|
||||
$this->runAfterUpgradeStep($io, $step);
|
||||
$this->updateVersion(VersionUtil::stepToVersion($step));
|
||||
}
|
||||
|
||||
$this->updateVersion($targetVersion);
|
||||
$this->dataManager->updateAppTimestamp();
|
||||
|
||||
$io->writeLine("Completed.");
|
||||
}
|
||||
|
||||
private function runAfterUpgradeStep(IO $io, string $step): void
|
||||
{
|
||||
$io->write(" $step...");
|
||||
|
||||
$isSuccessful = $this->stepRunner->runAfterUpgrade($step);
|
||||
|
||||
if ($isSuccessful) {
|
||||
$io->writeLine(" DONE");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$io->writeLine(" FAIL");
|
||||
|
||||
throw new RuntimeException("Step process failed.");
|
||||
}
|
||||
|
||||
private function runPrepareStep(IO $io, string $step): void
|
||||
{
|
||||
$io->write(" $step...");
|
||||
|
||||
try {
|
||||
$this->stepRunner->runPrepare($step);
|
||||
} catch (Exception $e) {
|
||||
$io->writeLine(" FAIL");
|
||||
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
|
||||
$io->writeLine(" DONE");
|
||||
}
|
||||
|
||||
private function updateVersion(string $targetVersion): void
|
||||
{
|
||||
$this->configWriter->set('version', $targetVersion);
|
||||
$this->configWriter->save();
|
||||
}
|
||||
}
|
||||
35
application/Espo/Core/Upgrades/Migration/Script.php
Normal file
35
application/Espo/Core/Upgrades/Migration/Script.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\Upgrades\Migration;
|
||||
|
||||
interface Script
|
||||
{
|
||||
public function run(): void;
|
||||
}
|
||||
100
application/Espo/Core/Upgrades/Migration/StepRunner.php
Normal file
100
application/Espo/Core/Upgrades/Migration/StepRunner.php
Normal file
@@ -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\Upgrades\Migration;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Log;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class StepRunner
|
||||
{
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Log $log
|
||||
) {}
|
||||
|
||||
public function runAfterUpgrade(string $step): bool
|
||||
{
|
||||
$phpExecutablePath = $this->getPhpExecutablePath();
|
||||
|
||||
$command = "command.php";
|
||||
|
||||
$process = new Process([$phpExecutablePath, $command, 'migration-version-step', "--step=$step"]);
|
||||
$process->setTimeout(null);
|
||||
$process->run();
|
||||
|
||||
$this->processLogging($process);
|
||||
|
||||
return $process->isSuccessful();
|
||||
}
|
||||
|
||||
public function runPrepare(string $step): void
|
||||
{
|
||||
$dir = 'V' . str_replace('.', '_', $step);
|
||||
|
||||
$className = "Espo\\Core\\Upgrades\\Migrations\\$dir\\Prepare";
|
||||
|
||||
if (!class_exists($className)) {
|
||||
throw new RuntimeException("No prepare script $step.");
|
||||
}
|
||||
|
||||
/** @var Script $script */
|
||||
$script = $this->injectableFactory->create($className);
|
||||
$script->run();
|
||||
}
|
||||
|
||||
private function getPhpExecutablePath(): string
|
||||
{
|
||||
$phpExecutablePath = $this->config->get('phpExecutablePath');
|
||||
|
||||
if (!$phpExecutablePath) {
|
||||
$phpExecutablePath = (new PhpExecutableFinder)->find();
|
||||
}
|
||||
|
||||
return $phpExecutablePath;
|
||||
}
|
||||
|
||||
private function processLogging(Process $process): void
|
||||
{
|
||||
if ($process->isSuccessful()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$output = $process->getOutput();
|
||||
|
||||
if ($output) {
|
||||
$this->log->error("Migration step command: " . $output);
|
||||
}
|
||||
}
|
||||
}
|
||||
79
application/Espo/Core/Upgrades/Migration/StepsProvider.php
Normal file
79
application/Espo/Core/Upgrades/Migration/StepsProvider.php
Normal file
@@ -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\Upgrades\Migration;
|
||||
|
||||
use Espo\Core\Utils\File\Manager;
|
||||
use const SORT_STRING;
|
||||
|
||||
class StepsProvider
|
||||
{
|
||||
private string $dir = 'application/Espo/Core/Upgrades/Migrations';
|
||||
|
||||
public function __construct(
|
||||
private Manager $fileManager
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPrepare(): array
|
||||
{
|
||||
return $this->get('Prepare');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAfterUpgrade(): array
|
||||
{
|
||||
return $this->get('AfterUpgrade');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function get(string $name): array
|
||||
{
|
||||
$list = $this->fileManager->getDirList($this->dir);
|
||||
|
||||
$list = array_filter($list, function ($item) use ($name) {
|
||||
$dir = $this->dir . '/' . $item;
|
||||
|
||||
return $this->fileManager->isFile("$dir/$name.php");
|
||||
});
|
||||
|
||||
$list = array_values($list);
|
||||
$list = array_map(fn ($item) => substr(str_replace('_', '.', $item), 1), $list);
|
||||
|
||||
sort($list, SORT_STRING);
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Upgrades\Migration;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\File\Manager;
|
||||
use RuntimeException;
|
||||
|
||||
class VersionDataProvider
|
||||
{
|
||||
private string $defaultConfigPath = 'application/Espo/Resources/defaults/config.php';
|
||||
|
||||
public function __construct(
|
||||
private Manager $fileManager,
|
||||
private Config\SystemConfig $systemConfig,
|
||||
) {}
|
||||
|
||||
public function getPreviousVersion(): string
|
||||
{
|
||||
$version = $this->systemConfig->getVersion();
|
||||
|
||||
if (!$version) {
|
||||
throw new RuntimeException("No or bad 'version' in config.");
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
public function getTargetVersion(): string
|
||||
{
|
||||
$data = $this->fileManager->getPhpContents($this->defaultConfigPath);
|
||||
|
||||
if (!is_array($data)) {
|
||||
throw new RuntimeException("No default config.");
|
||||
}
|
||||
|
||||
$version = $data['version'] ?? null;
|
||||
|
||||
if (!$version || !is_string($version)) {
|
||||
throw new RuntimeException("No or bad 'version' parameter in default config.");
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
}
|
||||
119
application/Espo/Core/Upgrades/Migration/VersionUtil.php
Normal file
119
application/Espo/Core/Upgrades/Migration/VersionUtil.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?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\Migration;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class VersionUtil
|
||||
{
|
||||
/**
|
||||
* @param string[] $fullList
|
||||
* @return string[]
|
||||
*/
|
||||
public static function extractSteps(string $from, string $to, array $fullList): array
|
||||
{
|
||||
usort($fullList, fn ($v1, $v2) => version_compare($v1, $v2));
|
||||
|
||||
$isPatch = self::isPatch($from, $to);
|
||||
|
||||
$list = [];
|
||||
$nextMinorIsPassed = false;
|
||||
|
||||
foreach ($fullList as $item) {
|
||||
$a = self::split($item);
|
||||
|
||||
if ($isPatch && $a[2] === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$v1 = $a[0] . '.' . $a[1] . '.' . ($a[2] ?? '0');
|
||||
|
||||
if (version_compare($v1, $from) <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (version_compare($v1, $to) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isItemPatch = $a[2] !== null;
|
||||
|
||||
if (!$isPatch && $isItemPatch && $nextMinorIsPassed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$nextMinorIsPassed && !self::isPatch($from, $v1)) {
|
||||
$nextMinorIsPassed = true;
|
||||
}
|
||||
|
||||
$list[] = $item;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{string, string, ?string}
|
||||
*/
|
||||
public static function split(string $version): array
|
||||
{
|
||||
$array = explode('.', $version, 3);
|
||||
|
||||
if (count($array) === 2) {
|
||||
return [$array[0], $array[1], null];
|
||||
}
|
||||
|
||||
if (count($array) !== 3) {
|
||||
throw new RuntimeException("Bad version number $version.");
|
||||
}
|
||||
|
||||
/** @var array{string, string, string} */
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function isPatch(string $from, string $to): bool
|
||||
{
|
||||
$aFrom = self::split($from);
|
||||
$aTo = self::split($to);
|
||||
|
||||
return $aFrom[0] === $aTo[0] && $aFrom[1] === $aTo[1];
|
||||
}
|
||||
|
||||
public static function stepToVersion(string $step): string
|
||||
{
|
||||
$a = self::split($step);
|
||||
|
||||
if ($a[2] === null) {
|
||||
$a[2] = '0';
|
||||
}
|
||||
|
||||
return implode('.', $a);
|
||||
}
|
||||
}
|
||||
193
application/Espo/Core/Upgrades/Migrations/V7_2/AfterUpgrade.php
Normal file
193
application/Espo/Core/Upgrades/Migrations/V7_2/AfterUpgrade.php
Normal 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);
|
||||
}
|
||||
}
|
||||
118
application/Espo/Core/Upgrades/Migrations/V7_4/AfterUpgrade.php
Normal file
118
application/Espo/Core/Upgrades/Migrations/V7_4/AfterUpgrade.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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}"]]
|
||||
],
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
application/Espo/Core/Upgrades/Migrations/V7_5/Prepare.php
Normal file
53
application/Espo/Core/Upgrades/Migrations/V7_5/Prepare.php
Normal 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);
|
||||
}
|
||||
}
|
||||
120
application/Espo/Core/Upgrades/Migrations/V8_0/AfterUpgrade.php
Normal file
120
application/Espo/Core/Upgrades/Migrations/V8_0/AfterUpgrade.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
125
application/Espo/Core/Upgrades/Migrations/V8_2/AfterUpgrade.php
Normal file
125
application/Espo/Core/Upgrades/Migrations/V8_2/AfterUpgrade.php
Normal 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();
|
||||
}
|
||||
}
|
||||
147
application/Espo/Core/Upgrades/Migrations/V8_3/AfterUpgrade.php
Normal file
147
application/Espo/Core/Upgrades/Migrations/V8_3/AfterUpgrade.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\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');
|
||||
}
|
||||
}
|
||||
102
application/Espo/Core/Upgrades/Migrations/V8_4/AfterUpgrade.php
Normal file
102
application/Espo/Core/Upgrades/Migrations/V8_4/AfterUpgrade.php
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
205
application/Espo/Core/Upgrades/Migrations/V9_0/AfterUpgrade.php
Normal file
205
application/Espo/Core/Upgrades/Migrations/V9_0/AfterUpgrade.php
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
180
application/Espo/Core/Upgrades/Migrations/V9_1/AfterUpgrade.php
Normal file
180
application/Espo/Core/Upgrades/Migrations/V9_1/AfterUpgrade.php
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
50
application/Espo/Core/Upgrades/UpgradeManager.php
Normal file
50
application/Espo/Core/Upgrades/UpgradeManager.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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;
|
||||
|
||||
class UpgradeManager extends Base
|
||||
{
|
||||
protected ?string $name = 'Upgrade';
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
protected array $params = [
|
||||
'packagePath' => 'data/upload/upgrades',
|
||||
'backupPath' => 'data/.backup/upgrades',
|
||||
'scriptNames' => [
|
||||
'before' => 'BeforeUpgrade',
|
||||
'after' => 'AfterUpgrade',
|
||||
],
|
||||
'customDirNames' => [
|
||||
'before' => 'beforeUpgradeFiles',
|
||||
'after' => 'afterUpgradeFiles',
|
||||
'vendor' => 'vendorFiles',
|
||||
],
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user