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)) );
+ }
}
-?>