diff --git a/application/Espo/Core/ExtensionManager.php b/application/Espo/Core/ExtensionManager.php index f3c35baf7a..8613b3a958 100644 --- a/application/Espo/Core/ExtensionManager.php +++ b/application/Espo/Core/ExtensionManager.php @@ -28,7 +28,6 @@ class ExtensionManager extends Upgrades\Base protected $params = array( 'packagePath' => 'data/upload/extensions', - 'backupPath' => 'data/.backup/extensions', 'scriptNames' => array( diff --git a/application/Espo/Core/Mail/Storage/Message.php b/application/Espo/Core/Mail/Storage/Message.php index 5ac07f0ce9..f4ab45fff0 100644 --- a/application/Espo/Core/Mail/Storage/Message.php +++ b/application/Espo/Core/Mail/Storage/Message.php @@ -24,6 +24,15 @@ namespace Espo\Core\Mail\Storage; class Message extends \Zend\Mail\Storage\Message { + public function __isset($name) + { + $headers = $this->getHeaders(); + if (empty($headers) || !is_object($headers)) { + return false; + } + return $this->getHeaders()->has($name); + } + public function isMultipart() { if (!isset($this->contentType)) { diff --git a/application/Espo/Core/UpgradeManager.php b/application/Espo/Core/UpgradeManager.php index adae1d87f3..3e805b5b52 100644 --- a/application/Espo/Core/UpgradeManager.php +++ b/application/Espo/Core/UpgradeManager.php @@ -30,6 +30,7 @@ class UpgradeManager extends Upgrades\Base protected $params = array( 'packagePath' => 'data/upload/upgrades', + 'backupPath' => 'data/.backup/upgrades', 'scriptNames' => array( 'before' => 'BeforeUpgrade', diff --git a/application/Espo/Core/Upgrades/Actions/Base.php b/application/Espo/Core/Upgrades/Actions/Base.php index 52e7f7ad0a..91a1d3bba1 100644 --- a/application/Espo/Core/Upgrades/Actions/Base.php +++ b/application/Espo/Core/Upgrades/Actions/Base.php @@ -304,7 +304,7 @@ abstract class Base /** * Get a list of files defined in manifest.json * - * @return [type] [description] + * @return array */ protected function getDeleteFileList() { @@ -352,6 +352,18 @@ abstract class Base return $this->data['fileList']; } + protected function getRestoreFileList() + { + if (!isset($this->data['restoreFileList'])) { + $backupPath = $this->getPath('backupPath'); + $backupFilePath = Util::concatPath($backupPath, self::FILES); + + $this->data['restoreFileList'] = $this->getFileManager()->getFileList($backupFilePath, true, '', true, true); + } + + return $this->data['restoreFileList']; + } + protected function copy($sourcePath, $destPath, $recursively = false, array $fileList = null, $copyOnlyFiles = false) { try { @@ -492,6 +504,16 @@ abstract class Base $this->getActionManager()->setAction($currentAction); } + protected function initialize() + { + + } + + protected function finalize() + { + + } + protected function beforeRunAction() { @@ -506,4 +528,23 @@ abstract class Base { return $this->getContainer()->get('dataManager')->clearCache(); } + + protected function checkIsWritable() + { + $fullFileList = array_merge($this->getDeleteFileList(), $this->getCopyFileList()); + + $result = $this->getFileManager()->isWritableList($fullFileList); + if (!$result) { + $permissionDeniedList = $this->getFileManager()->getLastPermissionDeniedList(); + throw new Error("Permission denied in
". implode(",
", $permissionDeniedList)); + } + } + + protected function backupExistingFiles() + { + $fullFileList = array_merge($this->getDeleteFileList(), $this->getCopyFileList()); + + $backupPath = $this->getPath('backupPath'); + return $this->copy('', array($backupPath, self::FILES), false, $fullFileList); + } } \ No newline at end of file diff --git a/application/Espo/Core/Upgrades/Actions/Base/Delete.php b/application/Espo/Core/Upgrades/Actions/Base/Delete.php index edcbcb66aa..c301a04228 100644 --- a/application/Espo/Core/Upgrades/Actions/Base/Delete.php +++ b/application/Espo/Core/Upgrades/Actions/Base/Delete.php @@ -32,6 +32,8 @@ class Delete extends \Espo\Core\Upgrades\Actions\Base throw new Error('Delete package package ID was not specified.'); } + $this->initialize(); + $this->setProcessId($processId); $this->beforeRunAction(); @@ -41,6 +43,8 @@ class Delete extends \Espo\Core\Upgrades\Actions\Base $this->afterRunAction(); + $this->finalize(); + $GLOBALS['log']->debug('Delete package process ['.$processId.']: end run.'); } diff --git a/application/Espo/Core/Upgrades/Actions/Base/Install.php b/application/Espo/Core/Upgrades/Actions/Base/Install.php index 7a0f9dd93a..512b6362b8 100644 --- a/application/Espo/Core/Upgrades/Actions/Base/Install.php +++ b/application/Espo/Core/Upgrades/Actions/Base/Install.php @@ -23,6 +23,7 @@ namespace Espo\Core\Upgrades\Actions\Base; use Espo\Core\Exceptions\Error; +use Espo\Core\Utils\Util; class Install extends \Espo\Core\Upgrades\Actions\Base { @@ -49,6 +50,8 @@ class Install extends \Espo\Core\Upgrades\Actions\Base $this->setProcessId($processId); + $this->initialize(); + $this->isCopied = false; /** check if an archive is unzipped, if no then unzip */ @@ -58,22 +61,25 @@ class Install extends \Espo\Core\Upgrades\Actions\Base $this->isAcceptable(); } + //check permissions copied and deleted files + $this->checkIsWritable(); + + $this->backupExistingFiles(); + $this->beforeRunAction(); /* run before install script */ $this->runScript('before'); - /* remove files defined in a manifest */ - if (!$this->deleteFiles()) { - $this->throwErrorAndRemovePackage('Permission denied to delete files.'); - } - /* copy files from directory "Files" to EspoCRM files */ if (!$this->copyFiles()) { $this->throwErrorAndRemovePackage('Cannot copy files.'); } $this->isCopied = true; + /* remove files defined in a manifest */ + $this->deleteFiles(true); + if (!$this->systemRebuild()) { $this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.'); } @@ -88,20 +94,32 @@ class Install extends \Espo\Core\Upgrades\Actions\Base /* delete unziped files */ $this->deletePackageFiles(); + $this->finalize(); + $GLOBALS['log']->debug('Installation process ['.$processId.']: end run.'); } protected function restoreFiles() { - $backupPath = $this->getPath('backupPath'); - - $res = true; - if ($this->isCopied) { - $res &= $this->copy(array($backupPath, self::FILES), '', true); - $GLOBALS['log']->info('Restore: copy back'); + if (!$this->isCopied) { + return; } - $res &= $this->getFileManager()->removeInDir($backupPath, true); + $GLOBALS['log']->info('Installer: Restore previous files.'); + + $backupPath = $this->getPath('backupPath'); + $backupFilePath = Util::concatPath($backupPath, self::FILES); + + $backupFileList = $this->getRestoreFileList(); + $copyFileList = $this->getCopyFileList(); + $deleteFileList = array_diff($copyFileList, $backupFileList); + + $res = $this->copy($backupFilePath, '', true); + $res &= $this->getFileManager()->remove($deleteFileList, null, true); + + if ($res) { + $this->getFileManager()->removeInDir($backupPath, true); + } return $res; } diff --git a/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php b/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php index 5c84377eb8..bd835e8e35 100644 --- a/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php +++ b/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php @@ -22,8 +22,9 @@ namespace Espo\Core\Upgrades\Actions\Base; -use Espo\Core\Exceptions\Error, - Espo\Core\Utils\Util; +use Espo\Core\Exceptions\Error; +use Espo\Core\Utils\Util; +use Espo\Core\Utils\Json; class Uninstall extends \Espo\Core\Upgrades\Actions\Base { @@ -37,6 +38,10 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base $this->setProcessId($processId); + $this->initialize(); + + $this->checkIsWritable(); + $this->beforeRunAction(); /* run before install script */ @@ -45,19 +50,19 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base $backupPath = $this->getPath('backupPath'); if (file_exists($backupPath)) { - /* remove extension files, saved in fileList */ - if (!$this->deleteFiles(true)) { - throw new Error('Permission denied to delete files.'); - } - /* copy core files */ if (!$this->copyFiles()) { - throw new Error('Cannot copy files.'); + throw new $this->throwErrorAndRemovePackage('Cannot copy files.'); + } + + /* remove extension files, saved in fileList */ + if (!$this->deleteFiles(true)) { + throw new $this->throwErrorAndRemovePackage('Permission denied to delete files.'); } } if (!$this->systemRebuild()) { - throw new Error('Error occurred while EspoCRM rebuild.'); + throw new $this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.'); } /* run before install script */ @@ -70,13 +75,9 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base /* delete backup files */ $this->deletePackageFiles(); - $GLOBALS['log']->debug('Uninstallation process ['.$processId.']: end run.'); - } + $this->finalize(); - protected function getDeleteFileList() - { - $extensionEntity = $this->getExtensionEntity(); - return $extensionEntity->get('fileList'); + $GLOBALS['log']->debug('Uninstallation process ['.$processId.']: end run.'); } protected function restoreFiles() @@ -89,6 +90,13 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base } $res = $this->copy($filesPath, '', true); + + $manifestJson = $this->getFileManager()->getContents(array($packagePath, $this->manifestName)); + $manifest = Json::decode($manifestJson, true); + if (!empty($manifest['delete'])) { + $res &= $this->getFileManager()->remove($manifest['delete'], null, true); + } + $res &= $this->getFileManager()->removeInDir($packagePath, true); return $res; @@ -131,4 +139,41 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base throw new Error($errorMessage); } + protected function getCopyFileList() + { + 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']; + } + + protected function getRestoreFileList() + { + 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']; + } + + protected function getDeleteFileList() + { + $packageFileList = $this->getRestoreFileList(); + $backupFileList = $this->getCopyFileList(); + + $deleteFileList = array_diff($packageFileList, $backupFileList); + + return $deleteFileList; + } } diff --git a/application/Espo/Core/Upgrades/Actions/Base/Upload.php b/application/Espo/Core/Upgrades/Actions/Base/Upload.php index 2d10db12ec..1effbd029d 100644 --- a/application/Espo/Core/Upgrades/Actions/Base/Upload.php +++ b/application/Espo/Core/Upgrades/Actions/Base/Upload.php @@ -38,6 +38,10 @@ class Upload extends \Espo\Core\Upgrades\Actions\Base $GLOBALS['log']->debug('Installation process ['.$processId.']: start upload the package.'); + $this->initialize(); + + $this->beforeRunAction(); + $packagePath = $this->getPackagePath(); $packageArchivePath = $this->getPackagePath(true); @@ -55,6 +59,10 @@ class Upload extends \Espo\Core\Upgrades\Actions\Base $this->isAcceptable(); + $this->afterRunAction(); + + $this->finalize(); + $GLOBALS['log']->debug('Installation process ['.$processId.']: end upload the package.'); return $processId; diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Delete.php b/application/Espo/Core/Upgrades/Actions/Extension/Delete.php index d33fee95ea..8476b1c852 100644 --- a/application/Espo/Core/Upgrades/Actions/Extension/Delete.php +++ b/application/Espo/Core/Upgrades/Actions/Extension/Delete.php @@ -35,29 +35,15 @@ class Delete extends \Espo\Core\Upgrades\Actions\Base\Delete */ protected function getExtensionEntity() { - return $this->extensionEntity; - } - - /** - * Set Extension Entity - * - * @param \Espo\Entities\Extension $extensionEntity - */ - protected function setExtensionEntity(\Espo\Entities\Extension $extensionEntity) - { - $this->extensionEntity = $extensionEntity; - } - - protected function beforeRunAction() - { - $processId = $this->getProcessId(); - - /** get extension entity */ - $extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId); - if (!isset($extensionEntity)) { - throw new Error('Extension Entity not found.'); + if (!isset($this->extensionEntity)) { + $processId = $this->getProcessId(); + $this->extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId); + if (!isset($this->extensionEntity)) { + throw new Error('Extension Entity not found.'); + } } - $this->setExtensionEntity($extensionEntity); + + return $this->extensionEntity; } protected function afterRunAction() diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Install.php b/application/Espo/Core/Upgrades/Actions/Extension/Install.php index dff99f54a0..1fb605cd8a 100644 --- a/application/Espo/Core/Upgrades/Actions/Extension/Install.php +++ b/application/Espo/Core/Upgrades/Actions/Extension/Install.php @@ -37,8 +37,6 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install $this->uninstallExtension(); $this->deleteExtension(); } - - $this->copyExistingFiles(); } protected function afterRunAction() @@ -51,12 +49,11 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install * * @return bool */ - protected function copyExistingFiles() + protected function backupExistingFiles() { - $fileList = $this->getCopyFileList(); - $backupPath = $this->getPath('backupPath'); + parent::backupExistingFiles(); - $res = $this->copy('', array($backupPath, self::FILES), false, $fileList); + $backupPath = $this->getPath('backupPath'); /** copy scripts files */ $packagePath = $this->getPackagePath(); @@ -65,19 +62,6 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install return $res; } - protected function restoreFiles() - { - $res = true; - if ($this->isCopied) { - $extensionFileList = $this->getCopyFileList(); - $res &= $this->getFileManager()->remove($extensionFileList); - } - - $res &= parent::restoreFiles(); - - return $res; - } - protected function isNew() { $extensionEntity = $this->getExtensionEntity(); diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Uninstall.php b/application/Espo/Core/Upgrades/Actions/Extension/Uninstall.php index 41745c0979..a310bc319b 100644 --- a/application/Espo/Core/Upgrades/Actions/Extension/Uninstall.php +++ b/application/Espo/Core/Upgrades/Actions/Extension/Uninstall.php @@ -35,29 +35,15 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base\Uninstall */ protected function getExtensionEntity() { - return $this->extensionEntity; - } - - /** - * Set Extension Entity - * - * @param \Espo\Entities\Extension $extensionEntity [description] - */ - protected function setExtensionEntity(\Espo\Entities\Extension $extensionEntity) - { - $this->extensionEntity = $extensionEntity; - } - - protected function beforeRunAction() - { - $processId = $this->getProcessId(); - - /** get extension entity */ - $extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId); - if (!isset($extensionEntity)) { - throw new Error('Extension Entity not found.'); + if (!isset($this->extensionEntity)) { + $processId = $this->getProcessId(); + $this->extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId); + if (!isset($this->extensionEntity)) { + throw new Error('Extension Entity not found.'); + } } - $this->setExtensionEntity($extensionEntity); + + return $this->extensionEntity; } protected function afterRunAction() @@ -68,4 +54,14 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base\Uninstall $extensionEntity->set('isInstalled', false); $this->getEntityManager()->saveEntity($extensionEntity); } + + protected function getRestoreFileList() + { + if (!isset($this->data['restoreFileList'])) { + $extensionEntity = $this->getExtensionEntity(); + $this->data['restoreFileList'] = $extensionEntity->get('fileList'); + } + + return $this->data['restoreFileList']; + } } \ No newline at end of file diff --git a/application/Espo/Core/Upgrades/Actions/Upgrade/Install.php b/application/Espo/Core/Upgrades/Actions/Upgrade/Install.php index 5d3b69a8c5..026b9ff6a7 100644 --- a/application/Espo/Core/Upgrades/Actions/Upgrade/Install.php +++ b/application/Espo/Core/Upgrades/Actions/Upgrade/Install.php @@ -24,17 +24,12 @@ namespace Espo\Core\Upgrades\Actions\Upgrade; class Install extends \Espo\Core\Upgrades\Actions\Base\Install { - protected function systemRebuild() + protected function finalize() { $manifest = $this->getManifest(); - $res = $this->getConfig()->set('version', $manifest['version']); - if (method_exists($this->getConfig(), 'save')) { - $res = $this->getConfig()->save(); - } - $res &= parent::systemRebuild(); - - return $res; + $this->getConfig()->set('version', $manifest['version']); + $this->getConfig()->save(); } /** diff --git a/application/Espo/Core/Utils/File/Manager.php b/application/Espo/Core/Utils/File/Manager.php index e0cb8f4576..5a1e5a35cd 100644 --- a/application/Espo/Core/Utils/File/Manager.php +++ b/application/Espo/Core/Utils/File/Manager.php @@ -29,6 +29,8 @@ class Manager { private $permission; + private $permissionDeniedList = array(); + public function __construct(\Espo\Core\Utils\Config $config = null) { $params = null; @@ -774,5 +776,73 @@ return '.var_export($content, true).'; ?>'; } + /** + * Check if $paths are writable. Permission denied list are defined in getLastPermissionDeniedList() + * + * @param array $paths + * + * @return boolean + */ + public function isWritableList(array $paths) + { + $permissionDeniedList = array(); + + $result = true; + foreach ($paths as $path) { + $rowResult = $this->isWritable($path); + if (!$rowResult) { + $permissionDeniedList[] = $path; + } + $result &= $rowResult; + } + + if (!empty($permissionDeniedList)) { + $this->permissionDeniedList = $this->getPermissionUtils()->arrangePermissionList($permissionDeniedList); + } + + return (bool) $result; + } + + /** + * Get last permission denied list + * + * @return array + */ + public function getLastPermissionDeniedList() + { + return $this->permissionDeniedList; + } + + /** + * Check if $path is writable + * + * @param string | array $path + * + * @return boolean + */ + public function isWritable($path) + { + $existFile = $this->getExistsPath($path); + + return is_writable($existFile); + } + + /** + * Get exists path. Ex. if check /var/www/espocrm/custom/someFile.php and this file doesn't extist, result will be /var/www/espocrm/custom + * + * @param string | array $path + * + * @return string + */ + protected function getExistsPath($path) + { + $fullPath = $this->concatPaths($path); + + if (!file_exists($fullPath)) { + $fullPath = $this->getExistsPath(pathinfo($fullPath, PATHINFO_DIRNAME)); + } + + return $fullPath; + } } diff --git a/tests/Espo/Core/Utils/File/ManagerTest.php b/tests/Espo/Core/Utils/File/ManagerTest.php index f0a0db14ce..bc231ce1c5 100644 --- a/tests/Espo/Core/Utils/File/ManagerTest.php +++ b/tests/Espo/Core/Utils/File/ManagerTest.php @@ -1,4 +1,4 @@ -assertEquals($result, $this->reflection->invokeMethod('getExistsPath', array($input)) ); + } } -?>