diff --git a/application/Espo/Core/Console/Commands/Upgrade.php b/application/Espo/Core/Console/Commands/Upgrade.php index 95df4a9c37..344f73818b 100644 --- a/application/Espo/Core/Console/Commands/Upgrade.php +++ b/application/Espo/Core/Console/Commands/Upgrade.php @@ -29,9 +29,137 @@ namespace Espo\Core\Console\Commands; +use Espo\Core\Exceptions\Error; + class Upgrade extends Base { - public function run() + protected $upgradeManager; + + protected $upgradeStepList = [ + 'copyBefore', + 'rebuild', + 'beforeUpgradeScript', + 'rebuild', + 'copy', + 'rebuild', + 'copyAfter', + 'rebuild', + 'afterUpgradeScript', + 'rebuild', + ]; + + public function run($options, $flagList, $argumentList) + { + $params = $this->normalizeParams($options, $flagList, $argumentList); + + switch ($params['mode']) { + case 'local': + $this->runLocalUpgrade($params); + break; + + default: + case 'remote': + $this->runRemoteUpgrade($params); + break; + } + } + + /** + * Normalize params. Permitted options and flags and $arguments: + * -y - without confirmation + * -s - single process + * --file="EspoCRM-upgrade.zip" + * --step="beforeUpgradeScript" + * @param array $options + * @param array $flagList + * @param array $argumentList + * @return array + */ + protected function normalizeParams($options, $flagList, $argumentList) + { + $params = [ + 'mode' => 'remote', + 'skipConfirmation' => false, + 'singleProcess' => false, + ]; + + if (!empty($options['file'])) { + $params['mode'] = 'local'; + $params['file'] = $options['file']; + } + + if (in_array('y', $flagList)) { + $params['skipConfirmation'] = true; + } + + if (in_array('s', $flagList)) { + $params['singleProcess'] = true; + } + + if (!empty($options['step'])) { + $params['step'] = $options['step']; + } + + return $params; + } + + protected function runLocalUpgrade(array $params) + { + if (empty($params['file']) || !file_exists($params['file'])) { + echo "Upgrade package is not found.\n"; + return; + } + + $packageFile = $params['file']; + $fromVersion = $this->getConfig()->get('version'); + + fwrite(\STDOUT, "Current version is {$fromVersion}.\n"); + + $upgradeId = $this->upload($packageFile); + $manifest = $this->getUpgradeManager()->getManifestById($upgradeId); + $nextVersion = $manifest['version']; + + if (!$params['skipConfirmation']) { + fwrite(\STDOUT, "EspoCRM will be upgraded to version {$nextVersion} now. Enter [Y] to continue.\n"); + + if (!$this->confirm()) { + echo "Upgrade canceled.\n"; + return; + } + } + + fwrite(\STDOUT, "Upgrading... This may take a while..."); + + try { + $this->runUpgradeProcess($upgradeId, $params); + } catch (\Exception $e) { + fwrite(\STDOUT, "\n"); + fwrite(\STDOUT, $e->getMessage() . "\n"); + return; + } + + fwrite(\STDOUT, "\n"); + + $app = new \Espo\Core\Application(); + $currentVerison = $app->getContainer()->get('config')->get('version'); + + fwrite(\STDOUT, "Upgrade is complete. Current version is {$currentVerison}.\n"); + + $infoData = $this->getVersionInfo(); + $lastVersion = $infoData->lastVersion ?? null; + + if ($lastVersion && $lastVersion !== $currentVerison && $fromVersion !== $currentVerison) { + fwrite(\STDOUT, "Newer version is available.\n"); + return; + } + + if ($lastVersion && $lastVersion === $currentVerison) { + fwrite(\STDOUT, "You have the latest version.\n"); + return; + } + } + + protected function runRemoteUpgrade(array $params) { $infoData = $this->getVersionInfo(); if (!$infoData) return; @@ -48,11 +176,13 @@ class Upgrade extends Base return; } - fwrite(\STDOUT, "EspoCRM will be upgraded to version {$nextVersion} now. Enter [Y] to continue.\n"); + if (!$params['skipConfirmation']) { + fwrite(\STDOUT, "EspoCRM will be upgraded to version {$nextVersion} now. Enter [Y] to continue.\n"); - if (!$this->confirm()) { - echo "Upgrade canceled.\n"; - return; + if (!$this->confirm()) { + echo "Upgrade canceled.\n"; + return; + } } fwrite(\STDOUT, "Downloading..."); @@ -64,13 +194,22 @@ class Upgrade extends Base fwrite(\STDOUT, "Upgrading... This may take a while..."); - $this->upgrade($upgradePackageFilePath); + $upgradeId = $this->upload($upgradePackageFilePath); + + try { + $this->runUpgradeProcess($upgradeId, $params); + } catch (\Exception $e) { + $error = $e->getMessage(); + } + + $this->getFileManager()->unlink($upgradePackageFilePath); fwrite(\STDOUT, "\n"); - fwrite(\STDOUT, $resultText); - - $this->getFileManager()->unlink($upgradePackageFilePath); + if (!empty($error)) { + echo $error; + return; + } $app = new \Espo\Core\Application(); $currentVerison = $app->getContainer()->get('config')->get('version'); @@ -88,22 +227,66 @@ class Upgrade extends Base } } - protected function upgrade($filePath) + protected function upload($filePath) { - $app = new \Espo\Core\Application(); - $app->setupSystemUser(); - - $upgradeManager = new \Espo\Core\UpgradeManager($app->getContainer()); - try { $fileData = file_get_contents($filePath); $fileData = 'data:application/zip;base64,' . base64_encode($fileData); - - $upgradeId = $upgradeManager->upload($fileData); - $upgradeManager->install(['id' => $upgradeId]); + $upgradeId = $this->getUpgradeManager()->upload($fileData); } catch (\Exception $e) { die("Error: " . $e->getMessage() . "\n"); } + + return $upgradeId; + } + + protected function runUpgradeProcess($upgradeId, array $params = []) + { + $useSingleProcess = array_key_exists('singleProcess', $params) ? $params['singleProcess'] : false; + + $stepList = !empty($params['step']) ? [$params['step']] : $this->upgradeStepList; + array_unshift($stepList, 'init'); + array_push($stepList, 'finalize'); + + if (!$useSingleProcess && $this->isShellEnabled()) { + return $this->runSteps($upgradeId, $stepList); + } + + return $this->runStepsInSingleProcess($upgradeId, $stepList); + } + + protected function runStepsInSingleProcess($upgradeId, array $stepList) + { + $GLOBALS['log']->debug('Installation process ['.$upgradeId.']: Single process mode.'); + + try { + foreach ($stepList as $stepName) { + $upgradeManager = $this->getUpgradeManager(true); + $upgradeManager->runInstallStep($stepName, ['id' => $upgradeId]); + } + } catch (\Exception $e) { + $GLOBALS['log']->error('Upgrade Error: ' . $e->getMessage()); + throw new Error($e->getMessage()); + } + + return true; + } + + protected function runSteps($upgradeId, array $stepList) + { + $phpExecutablePath = $this->getPhpExecutablePath(); + + foreach ($stepList as $stepName) { + $command = $phpExecutablePath . " command.php upgrade-step --step=". ucfirst($stepName) ." --id=". $upgradeId; + + $shellResult = shell_exec($command); + if ($shellResult !== 'true') { + $GLOBALS['log']->error('Upgrade Error: ' . $shellResult); + throw new Error($shellResult); + } + } + + return true; } protected function confirm() @@ -127,6 +310,29 @@ class Upgrade extends Base return $this->getContainer()->get('fileManager'); } + protected function getUpgradeManager($reload = false) + { + if (!$this->upgradeManager || $reload) { + $app = new \Espo\Core\Application(); + $app->setupSystemUser(); + + $this->upgradeManager = new \Espo\Core\UpgradeManager($app->getContainer()); + } + + return $this->upgradeManager; + } + + protected function getPhpExecutablePath() + { + $phpExecutablePath = $this->getConfig()->get('phpExecutablePath'); + + if (!$phpExecutablePath) { + $phpExecutablePath = (new \Symfony\Component\Process\PhpExecutableFinder)->find(); + } + + return $phpExecutablePath; + } + protected function getVersionInfo() { $url = 'https://s.espocrm.com/upgrade/next/'; @@ -183,4 +389,18 @@ class Upgrade extends Base return realpath($localFilePath); } + + protected function isShellEnabled() + { + if (!function_exists('exec') || !is_callable('shell_exec')) { + return false; + } + + $result = shell_exec("echo test"); + if (empty($result)) { + return false; + } + + return true; + } } diff --git a/application/Espo/Core/Console/Commands/UpgradeStep.php b/application/Espo/Core/Console/Commands/UpgradeStep.php new file mode 100644 index 0000000000..f0785fe1b9 --- /dev/null +++ b/application/Espo/Core/Console/Commands/UpgradeStep.php @@ -0,0 +1,71 @@ +runUpgradeStep($stepName, ['id' => $upgradeId]); + } + + protected function runUpgradeStep($stepName, array $params) + { + $app = new \Espo\Core\Application(); + $app->setupSystemUser(); + + $upgradeManager = new \Espo\Core\UpgradeManager($app->getContainer()); + + try { + $result = $upgradeManager->runInstallStep($stepName, $params); // throw Exception on error + } catch (\Exception $e) { + die("Error: " . $e->getMessage() . "\n"); + } + + if (is_bool($result)) { + $result = $result ? "true" : "false"; + } + + return $result; + } +} diff --git a/application/Espo/Core/UpgradeManager.php b/application/Espo/Core/UpgradeManager.php index 09e01034e3..bde391c36f 100644 --- a/application/Espo/Core/UpgradeManager.php +++ b/application/Espo/Core/UpgradeManager.php @@ -50,4 +50,4 @@ class UpgradeManager extends Upgrades\Base 'vendor' => 'vendorFiles', ) ); -} \ No newline at end of file +} diff --git a/application/Espo/Core/Upgrades/ActionManager.php b/application/Espo/Core/Upgrades/ActionManager.php index 2451dfc39c..2946e22f42 100644 --- a/application/Espo/Core/Upgrades/ActionManager.php +++ b/application/Espo/Core/Upgrades/ActionManager.php @@ -84,15 +84,23 @@ class ActionManager return $object->run($data); } + public function getActionClass($actionName) + { + return $this->getObject($actionName); + } + public function getManifest() { return $this->getObject()->getManifest(); } - protected function getObject() + protected function getObject($actionName = null) { $managerName = $this->getManagerName(); - $actionName = $this->getAction(); + + if (!$actionName) { + $actionName = $this->getAction(); + } if (!isset($this->objects[$managerName][$actionName])) { $class = '\Espo\Core\Upgrades\Actions\\' . ucfirst($managerName) . '\\' . ucfirst($actionName); @@ -106,4 +114,4 @@ class ActionManager return $this->objects[$managerName][$actionName]; } -} \ No newline at end of file +} diff --git a/application/Espo/Core/Upgrades/Actions/Base.php b/application/Espo/Core/Upgrades/Actions/Base.php index 25667b46e1..70d5723e80 100644 --- a/application/Espo/Core/Upgrades/Actions/Base.php +++ b/application/Espo/Core/Upgrades/Actions/Base.php @@ -156,7 +156,7 @@ abstract class Base return $this->entityManager; } - protected function throwErrorAndRemovePackage($errorMessage = '') + public function throwErrorAndRemovePackage($errorMessage = '') { $this->deletePackageFiles(); $this->deletePackageArchive(); @@ -186,7 +186,7 @@ abstract class Base return $this->processId; } - protected function setProcessId($processId) + public function setProcessId($processId) { $this->processId = $processId; } @@ -272,11 +272,30 @@ abstract class Base } /** - * Run scripts by type + * Run a script by a type * @param string $type Ex. "before", "after" * @return void */ protected function runScript($type) + { + $beforeInstallScript = $this->getScriptPath($type); + + if ($beforeInstallScript) { + $scriptNames = $this->getParams('scriptNames'); + $scriptName = $scriptNames[$type]; + + require_once($beforeInstallScript); + $script = new $scriptName(); + + try { + $script->run($this->getContainer(), $this->scriptParams); + } catch (\Exception $e) { + $this->throwErrorAndRemovePackage($e->getMessage()); + } + } + } + + protected function getScriptPath($type) { $packagePath = $this->getPackagePath(); $scriptNames = $this->getParams('scriptNames'); @@ -287,16 +306,8 @@ abstract class Base } $beforeInstallScript = Util::concatPath( array($packagePath, self::SCRIPTS, $scriptName) ) . '.php'; - if (file_exists($beforeInstallScript)) { - require_once($beforeInstallScript); - $script = new $scriptName(); - - try { - $script->run($this->getContainer(), $this->scriptParams); - } catch (\Exception $e) { - $this->throwErrorAndRemovePackage($e->getMessage()); - } + return $beforeInstallScript; } } @@ -474,6 +485,28 @@ abstract class Base * @return boolean */ protected function copyFiles($type = null, $dest = '') + { + $filesPath = $this->getCopyFilesPath($type); + + if ($filesPath) { + switch ($type) { + case 'vendor': + $dest = $this->vendorDirName; + break; + } + + return $this->copy($filesPath, $dest, true); + } + + return true; + } + + /** + * Get needed file list based on type. E.g. file list for "beforeCopy" action + * @param string $type + * @return boolean + */ + protected function getCopyFilesPath($type = null) { switch ($type) { case 'before': @@ -486,7 +519,6 @@ abstract class Base $dirNames = $this->getParams('customDirNames'); if (isset($dirNames['vendor'])) { $dirPath = $dirNames['vendor']; - $dest = $this->vendorDirName; } break; @@ -500,11 +532,9 @@ abstract class Base $filesPath = Util::concatPath($packagePath, $dirPath); if (file_exists($filesPath)) { - return $this->copy($filesPath, $dest, true); + return $filesPath; } } - - return true; } protected function getVendorFileList($type = 'copy') @@ -711,7 +741,9 @@ abstract class Base $result = $this->getFileManager()->isWritableList($fullFileList); if (!$result) { $permissionDeniedList = $this->getFileManager()->getLastPermissionDeniedList(); - throw new Error("Permission denied for
". implode(",
", $permissionDeniedList)); + + $delimiter = $this->isCli() ? "\n" : "
"; + throw new Error("Permission denied: " . $delimiter . implode($delimiter, $permissionDeniedList)); } } @@ -756,7 +788,7 @@ abstract class Base 'useCache' => $config->get('useCache'), ]; - $this->setParam('beforeMaintenanceModeParams', $actualParams); + $config->set('temporaryUpgradeParams', $actualParams); $save = false; @@ -783,19 +815,34 @@ abstract class Base protected function disableMaintenanceMode() { $config = $this->getConfig(); - $beforeMaintenanceModeParams = $this->getParams('beforeMaintenanceModeParams', []); + + $temporaryUpgradeParams = $config->get('temporaryUpgradeParams', []); $save = false; - foreach ($beforeMaintenanceModeParams as $paramName => $paramValue) { + foreach ($temporaryUpgradeParams as $paramName => $paramValue) { if ($config->get($paramName) != $paramValue) { $config->set($paramName, $paramValue); $save = true; } } + if ($config->has('temporaryUpgradeParams')) { + $config->remove('temporaryUpgradeParams'); + $save = true; + } + if ($save) { $config->save(); } } + + protected function isCli() + { + if (substr(php_sapi_name(), 0, 3) == 'cli') { + return true; + } + + return false; + } } diff --git a/application/Espo/Core/Upgrades/Actions/Base/Install.php b/application/Espo/Core/Upgrades/Actions/Base/Install.php index 0d00652858..c18819c51c 100644 --- a/application/Espo/Core/Upgrades/Actions/Base/Install.php +++ b/application/Espo/Core/Upgrades/Actions/Base/Install.php @@ -45,40 +45,107 @@ class Install extends \Espo\Core\Upgrades\Actions\Base $GLOBALS['log']->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); + + $GLOBALS['log']->debug('Installation process ['.$processId.']: end run.'); + } + + protected function initPackage(array $data) + { + $GLOBALS['log']->setLevel('info'); + + $processId = $data['id']; + if (empty($processId)) { throw new Error('Installation package ID was not specified.'); } $this->setProcessId($processId); - $this->initialize(); - /** check if an archive is unzipped, if no then unzip */ $packagePath = $this->getPackagePath(); if (!file_exists($packagePath)) { $this->unzipArchive(); $this->isAcceptable(); } + } - //check permissions copied and deleted files + public function stepInit(array $data) + { + $this->initPackage($data); + + $GLOBALS['log']->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(); - //beforeInstallFiles + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: End "init" step.'); + } + + public function stepCopyBefore(array $data) + { + $this->initPackage($data); + + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: Start "copyBefore" step.'); + if (!$this->copyFiles('before')) { $this->throwErrorAndRemovePackage('Cannot copy beforeInstall files.'); } - /* run before install script */ + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: End "copyBefore" step.'); + } + + public function stepBeforeInstallScript(array $data) + { + $this->initPackage($data); + + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: Start "beforeInstallScript" step.'); + if (!isset($data['skipBeforeScript']) || !$data['skipBeforeScript']) { $this->runScript('before'); } + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: End "beforeInstallScript" step.'); + } + + public function stepCopy(array $data) + { + $this->initPackage($data); + + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: Start "copy" step.'); + /* remove files defined in a manifest "deleteBeforeCopy" */ $this->deleteFiles('deleteBeforeCopy', true); @@ -93,7 +160,14 @@ class Install extends \Espo\Core\Upgrades\Actions\Base $this->deleteFiles('vendor'); $this->copyFiles('vendor'); - $this->disableMaintenanceMode(); + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: End "copy" step.'); + } + + public function stepRebuild(array $data) + { + $this->initPackage($data); + + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: Start "rebuild" step.'); if (!isset($data['skipSystemRebuild']) || !$data['skipSystemRebuild']) { if (!$this->systemRebuild()) { @@ -101,18 +175,45 @@ class Install extends \Espo\Core\Upgrades\Actions\Base } } + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: End "rebuild" step.'); + } + + public function stepCopyAfter(array $data) + { + $this->initPackage($data); + + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: Start "copyAfter" step.'); + //afterInstallFiles if (!$this->copyFiles('after')) { $this->throwErrorAndRemovePackage('Cannot copy afterInstall files.'); } + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: End "copyAfter" step.'); + } + + public function stepAfterInstallScript(array $data) + { + $this->initPackage($data); + + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: Start "afterInstallScript" step.'); + /* run after install script */ if (!isset($data['skipAfterScript']) || !$data['skipAfterScript']) { $this->runScript('after'); } - $this->afterRunAction(); + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: End "afterInstallScript" step.'); + } + public function stepFinalize(array $data) + { + $this->initPackage($data); + + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: Start "finalize" step.'); + + $this->disableMaintenanceMode(); + $this->afterRunAction(); $this->finalize(); /* delete unziped files */ @@ -122,9 +223,18 @@ class Install extends \Espo\Core\Upgrades\Actions\Base $this->getFileManager()->removeInDir([$this->getPath('backupPath'), self::FILES]); } - $GLOBALS['log']->debug('Installation process ['.$processId.']: end run.'); + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: End "finalize" step.'); + } - $this->clearCache(); + public function stepRevert(array $data) + { + $this->initPackage($data); + + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: Start "revert" step.'); + + $this->restoreFiles(); + + $GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: End "revert" step.'); } protected function restoreFiles() @@ -134,6 +244,10 @@ class Install extends \Espo\Core\Upgrades\Actions\Base $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); @@ -150,7 +264,7 @@ class Install extends \Espo\Core\Upgrades\Actions\Base return $res; } - protected function throwErrorAndRemovePackage($errorMessage = '') + public function throwErrorAndRemovePackage($errorMessage = '') { $this->restoreFiles(); parent::throwErrorAndRemovePackage($errorMessage); diff --git a/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php b/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php index 5177d2b4d1..a54b873c56 100644 --- a/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php +++ b/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php @@ -155,7 +155,7 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base return $res; } - protected function throwErrorAndRemovePackage($errorMessage = '') + public function throwErrorAndRemovePackage($errorMessage = '') { $this->restoreFiles(); throw new Error($errorMessage); diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Install.php b/application/Espo/Core/Upgrades/Actions/Extension/Install.php index c24b181717..cb4a8d3fcb 100644 --- a/application/Espo/Core/Upgrades/Actions/Extension/Install.php +++ b/application/Espo/Core/Upgrades/Actions/Extension/Install.php @@ -186,7 +186,7 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install * @param string $errorMessage * @return void */ - protected function throwErrorAndRemovePackage($errorMessage = '') + public function throwErrorAndRemovePackage($errorMessage = '') { if (!$this->isNew()) { throw new Error($errorMessage); diff --git a/application/Espo/Core/Upgrades/Actions/Upgrade/Install.php b/application/Espo/Core/Upgrades/Actions/Upgrade/Install.php index 67e866baac..2ca1f2a41e 100644 --- a/application/Espo/Core/Upgrades/Actions/Upgrade/Install.php +++ b/application/Espo/Core/Upgrades/Actions/Upgrade/Install.php @@ -28,8 +28,19 @@ ************************************************************************/ namespace Espo\Core\Upgrades\Actions\Upgrade; + class Install extends \Espo\Core\Upgrades\Actions\Base\Install { + public function stepBeforeUpgradeScript(array $data) + { + return $this->stepBeforeInstallScript($data); + } + + public function stepAfterUpgradeScript(array $data) + { + return $this->stepAfterInstallScript($data); + } + protected function finalize() { $manifest = $this->getManifest(); diff --git a/application/Espo/Core/Upgrades/Base.php b/application/Espo/Core/Upgrades/Base.php index 0cbb1bd3e1..4581612ad2 100644 --- a/application/Espo/Core/Upgrades/Base.php +++ b/application/Espo/Core/Upgrades/Base.php @@ -29,9 +29,10 @@ namespace Espo\Core\Upgrades; -use Espo\Core\Utils\Util, - Espo\Core\Utils\Json, - Espo\Core\Exceptions\Error; +use Espo\Core\Utils\Util; +use Espo\Core\Utils\Json; +use Espo\Core\Exceptions\Error; +use Espo\Core\Exceptions\NotFound; abstract class Base { @@ -71,6 +72,14 @@ abstract class Base return $this->getActionManager()->getManifest(); } + public function getManifestById($processId) + { + $actionClass = $this->getActionManager()->getActionClass(self::INSTALL); + $actionClass->setProcessId($processId); + + return $actionClass->getManifest(); + } + public function upload($data) { $this->getActionManager()->setAction(self::UPLOAD); @@ -98,4 +107,28 @@ abstract class Base return $this->getActionManager()->run($processId); } + + public function runInstallStep($stepName, array $params = []) + { + return $this->runActionStep(self::INSTALL, $stepName, $params); + } + + protected function runActionStep($actionName, $stepName, array $params = []) + { + $actionClass = $this->getActionManager()->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 + + return true; + } }