diff --git a/.gitignore b/.gitignore
index b085703006..fd3fe3fbe6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
/data/cache/*
/data/upload/*
/data/preferences/*
+/data/.backup/*
/data/config.php
/custom
/application/Espo/Resources/metadata/scopes/CustomTest.json
diff --git a/application/Espo/Controllers/Admin.php b/application/Espo/Controllers/Admin.php
index f9b82cd5fd..9aa225f8a1 100644
--- a/application/Espo/Controllers/Admin.php
+++ b/application/Espo/Controllers/Admin.php
@@ -72,7 +72,7 @@ class Admin extends \Espo\Core\Controllers\Base
{
$upgradeManager = new \Espo\Core\UpgradeManager($this->getContainer());
- $upgradeManager->run($data['id']);
+ $upgradeManager->install($data['id']);
return true;
}
diff --git a/application/Espo/Controllers/Extension.php b/application/Espo/Controllers/Extension.php
index 3c59d2499b..e943bd3012 100644
--- a/application/Espo/Controllers/Extension.php
+++ b/application/Espo/Controllers/Extension.php
@@ -33,13 +33,13 @@ class Extension extends \Espo\Core\Controllers\Record
throw new Forbidden();
}
}
-
+
public function actionUpload($params, $data, $request)
{
if (!$request->isPost()) {
- throw new Forbidden();
+ throw new Forbidden();
}
-
+
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
$id = $manager->upload($data);
@@ -56,22 +56,22 @@ class Extension extends \Espo\Core\Controllers\Record
public function actionInstall($params, $data, $request)
{
if (!$request->isPost()) {
- throw new Forbidden();
+ throw new Forbidden();
}
-
+
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
- $manager->run($data['id']);
+ $manager->install($data['id']);
return true;
}
-
+
public function actionUninstall($params, $data, $request)
{
if (!$request->isPost()) {
- throw new Forbidden();
- }
-
+ throw new Forbidden();
+ }
+
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
$manager->uninstall($data['id']);
@@ -79,6 +79,19 @@ class Extension extends \Espo\Core\Controllers\Record
return true;
}
+ public function actionDeletePackage($params, $data, $request)
+ {
+ if (!$request->isPost()) {
+ throw new Forbidden();
+ }
+
+ $manager = new \Espo\Core\ExtensionManager($this->getContainer());
+
+ $manager->delete($data['id']);
+
+ return true;
+ }
+
public function actionCreate()
{
throw new Forbidden();
@@ -98,30 +111,30 @@ class Extension extends \Espo\Core\Controllers\Record
{
throw new Forbidden();
}
-
+
public function actionDelete()
{
throw new Forbidden();
}
-
+
public function actionMassUpdate()
{
throw new Forbidden();
}
-
+
public function actionMassDelete()
{
throw new Forbidden();
}
-
+
public function actionCreateLink()
{
throw new Forbidden();
}
-
+
public function actionRemoveLink()
{
throw new Forbidden();
- }
+ }
}
diff --git a/application/Espo/Core/ExtensionManager.php b/application/Espo/Core/ExtensionManager.php
index 11dbebfe49..d9b848c0ed 100644
--- a/application/Espo/Core/ExtensionManager.php
+++ b/application/Espo/Core/ExtensionManager.php
@@ -22,15 +22,20 @@
namespace Espo\Core;
-use Espo\Core\Exceptions\Error;
-
class ExtensionManager extends Upgrades\Base
{
- protected $packagePath = 'data/upload/extensions';
+ protected $name = 'Extension';
- protected $scriptNames = array(
- 'before' => 'BeforeUpgrade',
- 'after' => 'AfterUpgrade',
+ protected $params = array(
+ 'packagePath' => 'data/upload/extensions',
+
+ 'backupPath' => 'data/.backup/extensions',
+
+ 'scriptNames' => array(
+ 'before' => 'BeforeInstall',
+ 'after' => 'AfterInstall',
+ 'beforeUninstall' => 'BeforeUninstall',
+ 'afterUninstall' => 'AfterUninstall',
+ )
);
-
}
diff --git a/application/Espo/Core/UpgradeManager.php b/application/Espo/Core/UpgradeManager.php
index 3c2102c8e3..5d42afe3d7 100644
--- a/application/Espo/Core/UpgradeManager.php
+++ b/application/Espo/Core/UpgradeManager.php
@@ -26,11 +26,14 @@ use Espo\Core\Exceptions\Error;
class UpgradeManager extends Upgrades\Base
{
- protected $packagePath = 'data/upload/upgrades';
+ protected $name = 'Upgrade';
- protected $scriptNames = array(
- 'before' => 'BeforeUpgrade',
- 'after' => 'AfterUpgrade',
+ protected $params = array(
+ 'packagePath' => 'data/upload/upgrades',
+
+ 'scriptNames' => array(
+ 'before' => 'BeforeUpgrade',
+ 'after' => 'AfterUpgrade',
+ )
);
-
}
\ No newline at end of file
diff --git a/application/Espo/Core/Upgrades/ActionManager.php b/application/Espo/Core/Upgrades/ActionManager.php
new file mode 100644
index 0000000000..babb676a8e
--- /dev/null
+++ b/application/Espo/Core/Upgrades/ActionManager.php
@@ -0,0 +1,102 @@
+managerName = $managerName;
+ $this->container = $container;
+
+ $params['name'] = $managerName;
+ $this->params = $params;
+ }
+
+ protected function getManagerName()
+ {
+ return $this->managerName;
+ }
+
+ protected function getContainer()
+ {
+ return $this->container;
+ }
+
+ public function setAction($action)
+ {
+ $this->currentAction = $action;
+ }
+
+ protected function getAction()
+ {
+ return $this->currentAction;
+ }
+
+ protected function getParams()
+ {
+ return $this->params;
+ }
+
+ public function run($data)
+ {
+ $object = $this->getObject();
+
+ return $object->run($data);
+ }
+
+ public function getManifest()
+ {
+ return $this->getObject()->getManifest();
+ }
+
+ protected function getObject()
+ {
+ $managerName = $this->getManagerName();
+ $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 class ['.$class.'].');
+ }
+
+ $this->objects[$managerName][$actionName] = new $class($this->container, $this->getParams());
+ }
+
+ 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
new file mode 100644
index 0000000000..ba106cb34a
--- /dev/null
+++ b/application/Espo/Core/Upgrades/Actions/Base.php
@@ -0,0 +1,445 @@
+ 'upgrade',
+ 'extension' => 'extension',
+ );
+
+ /**
+ * Default package type
+ */
+ protected $defaultPackageType = 'extension';
+
+
+ public function __construct($container, $params)
+ {
+ $this->container = $container;
+ $this->params = $params;
+
+ $this->zipUtil = new \Espo\Core\Utils\File\ZipArchive($container->get('fileManager'));
+ }
+
+ public function __destruct()
+ {
+ $this->processId = null;
+ $this->data = null;
+ }
+
+ protected function getContainer()
+ {
+ return $this->container;
+ }
+
+ protected function getParams($name = null)
+ {
+ if (isset($this->params[$name])) {
+ return $this->params[$name];
+ }
+
+ return $this->params;
+ }
+
+ protected function getZipUtil()
+ {
+ return $this->zipUtil;
+ }
+
+ protected function getFileManager()
+ {
+ if (!isset($this->fileManager)) {
+ $this->fileManager = $this->getContainer()->get('fileManager');
+ }
+ return $this->fileManager;
+ }
+
+ protected function getConfig()
+ {
+ if (!isset($this->config)) {
+ $this->config = $this->getContainer()->get('config');
+ }
+ return $this->config;
+ }
+
+ protected function getEntityManager()
+ {
+ if (!isset($this->entityManager)) {
+ $this->entityManager = $this->getContainer()->get('entityManager');
+ }
+ return $this->entityManager;
+ }
+
+ protected function throwErrorWithDetelePackage($errorMessage = '')
+ {
+ $this->deletePackageFiles();
+ $this->deletePackageArchive();
+ throw new Error($errorMessage);
+ }
+
+ abstract public function run($data);
+
+ protected function createProcessId()
+ {
+ if (isset($this->processId)) {
+ throw new Error('Another installation process is currently running.');
+ }
+
+ $this->processId = uniqid();
+
+ return $this->processId;
+ }
+
+ protected function getProcessId()
+ {
+ if (!isset($this->processId)) {
+ throw new Error('Installation ID was not specified.');
+ }
+
+ return $this->processId;
+ }
+
+ protected function setProcessId($processId)
+ {
+ $this->processId = $processId;
+ }
+
+ /**
+ * Check if version of upgrade/extension is acceptable to current version of EspoCRM
+ *
+ * @param string $version
+ * @return boolean
+ */
+ protected function isAcceptable()
+ {
+ $res = $this->checkPackageType();
+ $res &= $this->checkVersions();
+
+ return $res;
+ }
+
+ protected function checkVersions()
+ {
+ $manifest = $this->getManifest();
+
+ /** check acceptable versions */
+ $version = $manifest['acceptableVersions'];
+ $currentVersion = $this->getConfig()->get('version');
+
+ if (is_string($version)) {
+ $version = (array) $version;
+ }
+
+ foreach ($version as $strVersion) {
+
+ $strVersion = trim($strVersion);
+
+ if ($strVersion == $currentVersion) {
+ return true;
+ }
+
+ $strVersion = str_replace('\\', '', $strVersion);
+ $strVersion = preg_quote($strVersion);
+ $strVersion = str_replace('\\*', '+', $strVersion);
+
+ if (preg_match('/^'.$strVersion.'/', $currentVersion)) {
+ return true;
+ }
+ }
+
+ $this->throwErrorWithDetelePackage('Your EspoCRM version doesn\'t match for this installation package.');
+ }
+
+ protected function checkPackageType()
+ {
+ $manifest = $this->getManifest();
+
+ /** check package type */
+ $type = strtolower( $this->getParams('name') );
+ $manifestType = isset($manifest['type']) ? strtolower($manifest['type']) : $this->defaultPackageType;
+
+ if (!in_array($manifestType, $this->packageTypes)) {
+ $this->throwErrorWithDetelePackage('Unknown package type.');
+ }
+
+ if ($type != $manifestType) {
+ $this->throwErrorWithDetelePackage('Wrong package type. You cannot install '.$manifestType.' package via '.ucfirst($type).' Manager.');
+ }
+
+ return true;
+ }
+
+ /**
+ * Run scripts by type
+ * @param string $type Ex. "before", "after"
+ * @return void
+ */
+ protected function runScript($type)
+ {
+ $packagePath = $this->getPath('packagePath');
+ $scriptNames = $this->getParams('scriptNames');
+
+ $scriptName = $scriptNames[$type];
+ if (!isset($scriptName)) {
+ return;
+ }
+
+ $beforeInstallScript = Util::concatPath( array($packagePath, self::SCRIPTS, $scriptName) ) . '.php';
+
+ if (file_exists($beforeInstallScript)) {
+ require_once($beforeInstallScript);
+ $script = new $scriptName();
+ $script->run($this->getContainer());
+ }
+ }
+
+ /**
+ * Get package path
+ *
+ * @param string $processId
+ * @return string
+ */
+ protected function getPath($name = 'packagePath', $isPackage = false)
+ {
+ $postfix = $isPackage ? $this->packagePostfix : '';
+
+ $processId = $this->getProcessId();
+ $path = Util::concatPath($this->getParams($name), $processId);
+
+ return $path . $postfix;
+ }
+
+ /**
+ * Get a list of files defined in manifest.json
+ *
+ * @return [type] [description]
+ */
+ protected function getDeleteFileList()
+ {
+ $manifest = $this->getManifest();
+
+ if (!empty($manifest['delete'])) {
+ return $manifest['delete'];
+ }
+
+ return array();
+ }
+
+ /**
+ * Delete files defined in a manifest
+ *
+ * @return boolen
+ */
+ protected function deleteFiles()
+ {
+ $deleteFileList = $this->getDeleteFileList();
+
+ if (!empty($deleteFileList)) {
+ return $this->getFileManager()->remove($deleteFileList);
+ }
+
+ return true;
+ }
+
+ protected function getCopyFileList()
+ {
+ if (!isset($this->data['fileList'])) {
+ $packagePath = $this->getPath('packagePath');
+ $filesPath = Util::concatPath($packagePath, self::FILES);
+
+ $this->data['fileList'] = $this->getFileManager()->getFileList($filesPath, true, '', 'all', true);
+ }
+
+ return $this->data['fileList'];
+ }
+
+ /**
+ * Copy files from upgrade/extension package
+ *
+ * @param string $processId
+ * @return boolean
+ */
+ protected function copyFiles()
+ {
+ $packagePath = $this->getPath('packagePath');
+ $filesPath = Util::concatPath($packagePath, self::FILES);
+
+ return $this->getFileManager()->copy($filesPath, '', true);
+ }
+
+ public function getManifest()
+ {
+ if (!isset($this->data['manifest'])) {
+ $packagePath = $this->getPath('packagePath');
+
+ $manifestPath = Util::concatPath($packagePath, $this->manifestName);
+ if (!file_exists($manifestPath)) {
+ throw new Error('It\'s not an Installation package.');
+ }
+
+ $manifestJson = $this->getFileManager()->getContents($manifestPath);
+ $this->data['manifest'] = Json::decode($manifestJson, true);
+
+ if (!$this->data['manifest']) {
+ throw new Error('Syntax error in manifest.json.');
+ }
+
+ if (!$this->checkManifest($this->data['manifest'])) {
+ throw new Error('Unsupported package.');
+ }
+ }
+
+ return $this->data['manifest'];
+ }
+
+ /**
+ * Check if the manifest is correct
+ *
+ * @param array $manifest
+ * @return boolean
+ */
+ protected function checkManifest(array $manifest)
+ {
+ $requiredFields = array(
+ 'name',
+ 'version',
+ 'acceptableVersions',
+ );
+
+ foreach ($requiredFields as $fieldName) {
+ if (empty($manifest[$fieldName])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Unzip a package archieve
+ *
+ * @return void
+ */
+ protected function unzipArchive()
+ {
+ $packagePath = $this->getPath('packagePath');
+ $packageArchivePath = $this->getPath('packagePath', true);
+
+ $res = $this->getZipUtil()->unzip($packageArchivePath, $packagePath);
+ if ($res === false) {
+ throw new Error('Unnable to unzip the file - '.$packagePath.'.');
+ }
+ }
+
+ /**
+ * Delete temporary package files
+ *
+ * @return boolean
+ */
+ protected function deletePackageFiles()
+ {
+ $packagePath = $this->getPath('packagePath');
+ $res = $this->getFileManager()->removeInDir($packagePath, true);
+
+ return $res;
+ }
+
+ /**
+ * Delete temporary package archive
+ *
+ * @return boolean
+ */
+ protected function deletePackageArchive()
+ {
+ $packageArchive = $this->getPath('packagePath', true);
+ $res = $this->getFileManager()->removeFile($packageArchive);
+
+ return $res;
+ }
+
+ protected function systemRebuild()
+ {
+ return $this->getContainer()->get('dataManager')->rebuild();
+ }
+
+ protected function beforeRunAction()
+ {
+
+ }
+
+ protected function afterRunAction()
+ {
+
+ }
+
+
+}
diff --git a/application/Espo/Core/Upgrades/Actions/Base/Delete.php b/application/Espo/Core/Upgrades/Actions/Base/Delete.php
new file mode 100644
index 0000000000..60a95b0fe5
--- /dev/null
+++ b/application/Espo/Core/Upgrades/Actions/Base/Delete.php
@@ -0,0 +1,55 @@
+debug('Delete package process ['.$processId.']: start run.');
+
+ if (empty($processId)) {
+ throw new Error('Delete package package ID was not specified.');
+ }
+
+ $this->setProcessId($processId);
+
+ $this->beforeRunAction();
+
+ /* delete a package */
+ $this->deletePackage();
+
+ $this->afterRunAction();
+
+ $GLOBALS['log']->debug('Delete package process ['.$processId.']: end run.');
+ }
+
+ protected function deletePackage()
+ {
+ $packageArchivePath = $this->getPath('packagePath', true);
+ $res = $this->getFileManager()->removeFile($packageArchivePath);
+
+ return $res;
+ }
+
+}
\ No newline at end of file
diff --git a/application/Espo/Core/Upgrades/Actions/Base/Install.php b/application/Espo/Core/Upgrades/Actions/Base/Install.php
new file mode 100644
index 0000000000..c21d3f67e5
--- /dev/null
+++ b/application/Espo/Core/Upgrades/Actions/Base/Install.php
@@ -0,0 +1,81 @@
+debug('Installation process ['.$processId.']: start run.');
+
+ if (empty($processId)) {
+ throw new Error('Installation package ID was not specified.');
+ }
+
+ $this->setProcessId($processId);
+
+ /** check if an archive is unzipped, if no then unzip */
+ $packagePath = $this->getPath('packagePath');
+ if (!file_exists($packagePath)) {
+ $this->unzipArchive();
+ $this->isAcceptable();
+ }
+
+ $this->beforeRunAction();
+
+ /* run before install script */
+ $this->runScript('before');
+
+ /* remove files defined in a manifest */
+ if (!$this->deleteFiles()) {
+ throw new Error('Permission denied to delete files.');
+ }
+
+ /* copy files from directory "Files" to EspoCRM files */
+ if (!$this->copyFiles()) {
+ throw new Error('Cannot copy files.');
+ }
+
+ if (!$this->systemRebuild()) {
+ throw new Error('Error occurred while EspoCRM rebuild.');
+ }
+
+ /* run before install script */
+ $this->runScript('after');
+
+ $this->afterRunAction();
+
+ /* delete unziped files */
+ $this->deletePackageFiles();
+
+ $GLOBALS['log']->debug('Installation process ['.$processId.']: end run.');
+ }
+}
diff --git a/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php b/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php
new file mode 100644
index 0000000000..8d0730ed4a
--- /dev/null
+++ b/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php
@@ -0,0 +1,103 @@
+debug('Uninstallation process ['.$processId.']: start run.');
+
+ if (empty($processId)) {
+ throw new Error('Uninstallation package ID was not specified.');
+ }
+
+ $this->setProcessId($processId);
+
+ $this->beforeRunAction();
+
+ /* run before install script */
+ $this->runScript('beforeUninstall');
+
+ /* remove extension files, saved in fileList */
+ if (!$this->deleteFiles()) {
+ throw new Error('Permission denied to delete files.');
+ }
+
+ /* copy core files */
+ if (!$this->copyFiles()) {
+ throw new Error('Cannot copy files.');
+ }
+
+ if (!$this->systemRebuild()) {
+ throw new Error('Error occurred while EspoCRM rebuild.');
+ }
+
+ /* run before install script */
+ $this->runScript('afterUninstall');
+
+ $this->afterRunAction();
+
+ /* delete backup files */
+ $this->deletePackageFiles();
+
+ $GLOBALS['log']->debug('Uninstallation process ['.$processId.']: end run.');
+ }
+
+ protected function getDeleteFileList()
+ {
+ $extensionEntity = $this->getExtensionEntity();
+ return $extensionEntity->get('fileList');
+ }
+
+ protected function copyFiles()
+ {
+ $backupPath = $this->getPath('backupPath');
+ $res = $this->getFileManager()->copy(array($backupPath, self::FILES), '', true);
+
+ return $res;
+ }
+
+ /**
+ * Get backup path
+ *
+ * @param string $processId
+ * @return string
+ */
+ protected function getPath($name = 'packagePath', $isPackage = false)
+ {
+ $name = ($name == 'packagePath') ? 'backupPath' : $name;
+ return parent::getPath($name, $isPackage);
+ }
+
+ protected function deletePackageFiles()
+ {
+ $backupPath = $this->getPath('backupPath');
+ $res = $this->getFileManager()->removeInDir($backupPath, true);
+
+ return $res;
+ }
+
+}
diff --git a/application/Espo/Core/Upgrades/Actions/Base/Upload.php b/application/Espo/Core/Upgrades/Actions/Base/Upload.php
new file mode 100644
index 0000000000..ee679cd13a
--- /dev/null
+++ b/application/Espo/Core/Upgrades/Actions/Base/Upload.php
@@ -0,0 +1,62 @@
+createProcessId();
+
+ $GLOBALS['log']->debug('Installation process ['.$processId.']: start upload the package.');
+
+ $packagePath = $this->getPath('packagePath');
+ $packageArchivePath = $this->getPath('packagePath', true);
+
+ if (!empty($data)) {
+ list($prefix, $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();
+
+ $GLOBALS['log']->debug('Installation process ['.$processId.']: end upload the package.');
+
+ return $processId;
+ }
+}
\ No newline at end of file
diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Delete.php b/application/Espo/Core/Upgrades/Actions/Extension/Delete.php
new file mode 100644
index 0000000000..daadecef66
--- /dev/null
+++ b/application/Espo/Core/Upgrades/Actions/Extension/Delete.php
@@ -0,0 +1,75 @@
+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.');
+ }
+ $this->setExtensionEntity($extensionEntity);
+
+ /** check if extension archive exsists */
+ $packageArchivePath = $this->getPath('packagePath', true);
+ if (!file_exists($packageArchivePath)) {
+ throw new Error('Extension Archive doesn\'t exist.');
+ }
+ }
+
+ protected function afterRunAction()
+ {
+ /** Delete extension entity */
+ $extensionEntity = $this->getExtensionEntity();
+ $this->getEntityManager()->removeEntity($extensionEntity);
+ }
+}
\ No newline at end of file
diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Install.php b/application/Espo/Core/Upgrades/Actions/Extension/Install.php
new file mode 100644
index 0000000000..9d46603707
--- /dev/null
+++ b/application/Espo/Core/Upgrades/Actions/Extension/Install.php
@@ -0,0 +1,166 @@
+findExtension();
+ $this->compareVersion();
+
+ $this->copyExistingFiles();
+ }
+
+ protected function afterRunAction()
+ {
+ $this->storeExtension();
+ }
+
+ /**
+ * Copy Existing files to backup directory
+ *
+ * @return bool
+ */
+ protected function copyExistingFiles()
+ {
+ $fileList = $this->getCopyFileList();
+ $backupPath = $this->getPath('backupPath');
+
+ $res = $this->getFileManager()->copy('', array($backupPath, self::FILES), false, $fileList);
+
+ /** copy scripts files */
+ $packagePath = $this->getPath('packagePath');
+ $res &= $this->getFileManager()->copy(array($packagePath, self::SCRIPTS), array($backupPath, self::SCRIPTS), true);
+
+ return $res;
+ }
+
+ protected function isNew()
+ {
+ $extensionEntity = $this->getExtensionEntity();
+
+ if (isset($extensionEntity)) {
+ $id = $this->getExtensionEntity()->get('id');
+ }
+
+ return isset($id) ? false : true;
+ }
+
+ /**
+ * Get extension ID. It's an ID of existing entity (if available) or Installation ID
+ *
+ * @return string
+ */
+ protected function getExtensionId()
+ {
+ $extensionEntity = $this->getExtensionEntity();
+ if (isset($extensionEntity)) {
+ $extensionEntityId = $extensionEntity->get('id');
+ }
+
+ if (!isset($extensionEntityId)) {
+ return $this->getProcessId();
+ }
+
+ return $extensionEntityId;
+ }
+
+ /**
+ * Get entity of this extension
+ *
+ * @return \Espo\Entities\Extension
+ */
+ protected function getExtensionEntity()
+ {
+ return $this->extensionEntity;
+ }
+
+ /**
+ * Find Extension entity
+ *
+ * @return \Espo\Entities\Extension
+ */
+ protected function findExtension()
+ {
+ $manifest = $this->getManifest();
+
+ $this->extensionEntity = $this->getEntityManager()->getRepository('Extension')->where(array(
+ 'name' => $manifest['name'],
+ 'isInstalled' => true,
+ ))->findOne();
+
+ return $this->extensionEntity;
+ }
+
+ /**
+ * Create a record of Extension Entity
+ *
+ * @return bool
+ */
+ protected function storeExtension()
+ {
+ $entityManager = $this->getEntityManager();
+ $extensionEntity = $entityManager->getEntity('Extension');
+
+ $manifest = $this->getManifest();
+ $fileList = $this->getCopyFileList();
+
+ $data = array(
+ 'id' => $this->getProcessId(),
+ 'name' => $manifest['name'],
+ 'status' => self::ENABLED,
+ 'isInstalled' => true,
+ 'version' => $manifest['version'],
+ 'fileList' => $fileList,
+ 'description' => $manifest['description'],
+ );
+ $extensionEntity->set($data);
+
+ return $entityManager->saveEntity($extensionEntity);
+ }
+
+ /**
+ * Compare version between installed and a new extensions
+ *
+ * @return void
+ */
+ protected function compareVersion()
+ {
+ $manifest = $this->getManifest();
+ $extensionEntity = $this->getExtensionEntity();
+
+ if (isset($extensionEntity)) {
+ $comparedVersion = version_compare($manifest['version'], $extensionEntity->get('version'));
+ if ($comparedVersion <= 0) {
+ $this->throwErrorWithDetelePackage('You cannot install an older version of this extension.');
+ }
+ }
+ }
+
+
+}
diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Uninstall.php b/application/Espo/Core/Upgrades/Actions/Extension/Uninstall.php
new file mode 100644
index 0000000000..acc277b914
--- /dev/null
+++ b/application/Espo/Core/Upgrades/Actions/Extension/Uninstall.php
@@ -0,0 +1,78 @@
+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.');
+ }
+ $this->setExtensionEntity($extensionEntity);
+
+ /** check if backup of the extension exsists */
+ $backupPath = $this->getPath('backupPath');
+ if (!file_exists($backupPath)) {
+ throw new Error('Backup files don\'t exist.');
+ }
+ }
+
+ protected function afterRunAction()
+ {
+ /** Set extension entity, isInstalled = false */
+ $extensionEntity = $this->getExtensionEntity();
+
+ $extensionEntity->set('status', self::DISABLED);
+ $extensionEntity->set('isInstalled', false);
+ $this->getEntityManager()->saveEntity($extensionEntity);
+ }
+}
\ No newline at end of file
diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Upload.php b/application/Espo/Core/Upgrades/Actions/Extension/Upload.php
new file mode 100644
index 0000000000..e54b1a67c2
--- /dev/null
+++ b/application/Espo/Core/Upgrades/Actions/Extension/Upload.php
@@ -0,0 +1,28 @@
+getManifest();
+
+ $res = $this->getConfig()->set('version', $manifest['version']);
+ if (method_exists($this->getConfig(), 'save')) {
+ $res = $this->getConfig()->save();
+ }
+ $res &= parent::systemRebuild();
+
+ return $res;
+ }
+
+ /**
+ * Delete temporary package files
+ *
+ * @return boolean
+ */
+ protected function deletePackageFiles()
+ {
+ $res = parent::deletePackageFiles();
+ $res &= $this->deletePackageArchive();
+
+ return $res;
+ }
+}
diff --git a/application/Espo/Core/Upgrades/Actions/Upgrade/Upload.php b/application/Espo/Core/Upgrades/Actions/Upgrade/Upload.php
new file mode 100644
index 0000000000..a1ee4491be
--- /dev/null
+++ b/application/Espo/Core/Upgrades/Actions/Upgrade/Upload.php
@@ -0,0 +1,28 @@
+ 'Before',
- 'after' => 'After',
- );
-
- protected $paths = array(
- 'files' => 'files',
- 'scripts' => 'scripts',
- );
+ const UNINSTALL = 'uninstall';
+ const DELETE = 'delete';
public function __construct($container)
{
$this->container = $container;
- $this->zipUtil = new \Espo\Core\Utils\File\ZipArchive($container->get('fileManager'));
- }
-
- public function __destruct()
- {
- $this->upgradeId = null;
- $this->data = null;
+ $this->actionManager = new ActionManager($this->name, $container, $this->params);
}
protected function getContainer()
@@ -75,319 +54,41 @@ abstract class Base
return $this->container;
}
- protected function getZipUtil()
+ protected function getActionManager()
{
- return $this->zipUtil;
- }
-
- protected function getFileManager()
- {
- if (!isset($this->fileManager)) {
- $this->fileManager = $this->getContainer()->get('fileManager');
- }
- return $this->fileManager;
- }
-
- protected function getConfig()
- {
- if (!isset($this->config)) {
- $this->config = $this->getContainer()->get('config');
- }
- return $this->config;
- }
-
-
- /**
- * Upload an upgrade package
- *
- * @param [type] $contents
- * @return string ID of upgrade process
- */
- public function upload($data)
- {
- $upgradeId = $this->createUpgradeId();
-
- $GLOBALS['log']->debug('Upgrade process ['.$upgradeId.']: start upload the package.');
-
- $upgradePath = $this->getUpgradePath();
- $upgradePackagePath = $this->getUpgradePath(true);
-
- if (!empty($data)) {
- list($prefix, $contents) = explode(',', $data);
- $contents = base64_decode($contents);
- }
-
- $res = $this->getFileManager()->putContents($upgradePackagePath, $contents);
- if ($res === false) {
- throw new Error('Could not upload the package.');
- }
-
- $res = $this->getZipUtil()->unzip($upgradePackagePath, $upgradePath);
- if ($res === false) {
- throw new Error('Unnable to unzip the file - '.$upgradePath.'.');
- }
-
- if (!$this->isAcceptable()) {
- throw new Error("Your EspoCRM version doesn't match for this upgrade package.");
- }
-
- $GLOBALS['log']->debug('Upgrade process ['.$upgradeId.']: end upload the package.');
-
- return $upgradeId;
- }
-
- /**
- * Main upgrade process
- *
- * @param string $upgradeId Upgrade ID, gotten in upload stage
- * @return bool
- */
- public function run($upgradeId)
- {
- $GLOBALS['log']->debug('Upgrade process ['.$upgradeId.']: start run.');
-
- if (empty($upgradeId)) {
- throw new Error('Upgrade ID was not specified.');
- }
-
- $this->setUpgradeId($upgradeId);
-
- /* run before install script */
- $this->runScript('before');
-
- /* remove files defined in a manifest */
- if (!$this->deleteFiles()) {
- throw new Error('Permission denied to delete files.');
- }
-
- /* copy files from directory "Files" to EspoCRM files */
- if (!$this->copyFiles()) {
- throw new Error('Cannot copy files.');
- }
-
- if (!$this->systemRebuild()) {
- throw new Error('Error occurred while EspoCRM rebuild.');
- }
-
- /* run before install script */
- $this->runScript('after');
-
- /* delete unziped files */
- $this->deletePackageFiles();
-
- $GLOBALS['log']->debug('Upgrade process ['.$upgradeId.']: end run.');
- }
-
-
- protected function createUpgradeId()
- {
- if (isset($this->upgradeId)) {
- throw new Error('Another upgrade process is currently running.');
- }
-
- $this->upgradeId = uniqid();
-
- return $this->upgradeId;
- }
-
- protected function getUpgradeId()
- {
- if (!isset($this->upgradeId)) {
- throw new Error("Upgrade ID was not specified.");
- }
-
- return $this->upgradeId;
- }
-
- protected function setUpgradeId($upgradeId)
- {
- $this->upgradeId = $upgradeId;
- }
-
- /**
- * Check if version of upgrade is acceptable to current version of EspoCRM
- *
- * @param string $version
- * @return boolean
- */
- protected function isAcceptable()
- {
- $manifest = $this->getManifest();
- $version = $manifest['acceptableVersions'];
-
- $currentVersion = $this->getConfig()->get('version');
-
- if (is_string($version)) {
- $version = (array) $version;
- }
-
- foreach ($version as $strVersion) {
-
- $strVersion = trim($strVersion);
-
- if ($strVersion == $currentVersion) {
- return true;
- }
-
- $strVersion = str_replace('\\', '', $strVersion);
- $strVersion = preg_quote($strVersion);
- $strVersion = str_replace('\\*', '+', $strVersion);
-
- if (preg_match('/^'.$strVersion.'/', $currentVersion)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Run scripts by type
- * @param string $type Ex. "before", "after"
- * @return void
- */
- protected function runScript($type)
- {
- $upgradePath = $this->getUpgradePath();
-
- $scriptName = $this->scriptNames[$type];
- if (!isset($scriptName)) {
- return;
- }
-
- $beforeInstallScript = Util::concatPath( array($upgradePath, $this->paths['scripts'], $scriptName) ) . '.php';
-
- if (file_exists($beforeInstallScript)) {
- require_once($beforeInstallScript);
- $script = new $scriptName();
- $script->run($this->getContainer());
- }
- }
-
- /**
- * Get upgrade path
- *
- * @param string $upgradeId
- * @return string
- */
- protected function getUpgradePath($isPackage = false)
- {
- $postfix = $isPackage ? $this->packagePostfix : '';
-
- if (!isset($this->data['upgradePath'])) {
- $upgradeId = $this->getUpgradeId();
- $this->data['upgradePath'] = Util::concatPath($this->packagePath, $upgradeId);
- }
-
- return $this->data['upgradePath'] . $postfix;
- }
-
- /**
- * Delete files defined in a manifest
- *
- * @return boolen
- */
- protected function deleteFiles()
- {
- $manifest = $this->getManifest();
-
- if (!empty($manifest['delete'])) {
- return $this->getFileManager()->remove($manifest['delete']);
- }
-
- return true;
- }
-
- /**
- * Copy files from upgrade package
- *
- * @param string $upgradeId
- * @return boolean
- */
- protected function copyFiles()
- {
- $upgradePath = $this->getUpgradePath();
- $filesPath = Util::concatPath($upgradePath, $this->paths['files']);
-
- return $this->getFileManager()->copy($filesPath, '', true);
+ return $this->actionManager;
}
public function getManifest()
{
- if (!isset($this->data['manifest'])) {
- $upgradePath = $this->getUpgradePath();
-
- $manifestPath = Util::concatPath($upgradePath, $this->manifestName);
- if (!file_exists($manifestPath)) {
- throw new Error('It\'s not an upgrade package.');
- }
-
- $manifestJson = $this->getFileManager()->getContents($manifestPath);
- $this->data['manifest'] = Json::decode($manifestJson, true);
-
- if (!$this->data['manifest']) {
- throw new Error('Syntax error in manifest.json.');
- }
-
- if (!$this->checkManifest($this->data['manifest'])) {
- throw new Error('Unsupported package.');
- }
- }
-
- return $this->data['manifest'];
+ return $this->getActionManager()->getManifest();
}
- /**
- * Check if the manifest is correct
- *
- * @param array $manifest
- * @return boolean
- */
- protected function checkManifest(array $manifest)
+ public function upload($data)
{
- $requiredFields = array(
- 'name',
- 'version',
- 'acceptableVersions',
- );
+ $this->getActionManager()->setAction(self::UPLOAD);
- foreach ($requiredFields as $fieldName) {
- if (empty($manifest[$fieldName])) {
- return false;
- }
- }
-
- return true;
+ return $this->getActionManager()->run($data);
}
- /**
- * Delete temporary package files
- *
- * @return boolean
- */
- protected function deletePackageFiles()
+ public function install($processId)
{
- $upgradePath = $this->getUpgradePath();
- $upgradePackagePath = $this->getUpgradePath(true);
+ $this->getActionManager()->setAction(self::INSTALL);
- $res = $this->getFileManager()->removeInDir($upgradePath, true);
- $res &= $this->getFileManager()->removeFile($upgradePackagePath);
-
- return $res;
+ return $this->getActionManager()->run($processId);
}
- protected function systemRebuild()
+ public function uninstall($processId)
{
- $manifest = $this->getManifest();
+ $this->getActionManager()->setAction(self::UNINSTALL);
- $res = $this->getConfig()->set('version', $manifest['version']);
- if (method_exists($this->getConfig(), 'save')) {
- $res = $this->getConfig()->save();
- }
- $res &= $this->getContainer()->get('dataManager')->rebuild();
-
- return $res;
+ return $this->getActionManager()->run($processId);
}
+ public function delete($processId)
+ {
+ $this->getActionManager()->setAction(self::DELETE);
+ return $this->getActionManager()->run($processId);
+ }
}
diff --git a/application/Espo/Core/Utils/File/Manager.php b/application/Espo/Core/Utils/File/Manager.php
index 7534e9e45b..f60a8aac3d 100644
--- a/application/Espo/Core/Utils/File/Manager.php
+++ b/application/Espo/Core/Utils/File/Manager.php
@@ -362,40 +362,67 @@ class Manager
/**
* Copy files from one direcoty to another
+ * Ex. $sourcePath = 'data/uploads/extensions/file.json', $destPath = 'data/uploads/backup', result will be data/uploads/backup/data/uploads/backup/file.json.
*
* @param string $sourcePath
* @param string $destPath
* @param boolean $recursively
+ * @param array $fileList - list of files that should be copied
+ * @param boolean $copyOnlyFiles - copy only files, instead of full path with directories, Ex. $sourcePath = 'data/uploads/extensions/file.json', $destPath = 'data/uploads/backup', result will be 'data/uploads/backup/file.json'
* @return boolen
*/
- public function copy($sourcePath, $destPath, $recursively = false)
+ public function copy($sourcePath, $destPath, $recursively = false, array $fileList = null, $copyOnlyFiles = false)
{
$sourcePath = $this->concatPaths($sourcePath);
$destPath = $this->concatPaths($destPath);
- if (is_file($sourcePath)) {
- $fileList = (array) $sourcePath;
+ if (isset($fileList)) {
+ if (!empty($sourcePath)) {
+ foreach ($fileList as &$fileName) {
+ $fileName = $this->concatPaths(array($sourcePath, $fileName));
+ }
+ }
} else {
- $fileList = $this->getFileList($sourcePath, $recursively, '', 'all', true);
+ $fileList = is_file($sourcePath) ? (array) $sourcePath : $this->getFileList($sourcePath, $recursively, '', 'all', true);
+ }
+
+ /** Check permission before copying */
+ $permissionDeniedList = array();
+ foreach ($fileList as $file) {
+
+ if ($copyOnlyFiles) {
+ $file = pathinfo($file, PATHINFO_BASENAME);
+ }
+
+ $destFile = $this->concatPaths(array($destPath, $file));
+
+ if ($this->checkCreateFile($destFile) === false) {
+ $permissionDeniedList[] = $destFile;
+ }
+ }
+ /** END */
+
+ if (!empty($permissionDeniedList)) {
+ $betterPermissionList = $this->getPermissionUtils()->arrangePermissionList($permissionDeniedList);
+ throw new Error("Permission denied in
". implode(",
", $betterPermissionList));
}
$res = true;
foreach ($fileList as $file) {
- $sourceFile = $this->concatPaths(array($sourcePath, $file));
- $destFile = $this->concatPaths(array($destPath, $file));
-
- if ($this->checkCreateFile($destFile) === false) {
- throw new Error('Permission denied in '. $destFile);
+ if ($copyOnlyFiles) {
+ $file = pathinfo($file, PATHINFO_BASENAME);
}
+ $sourceFile = is_file($sourcePath) ? $sourcePath : $this->concatPaths(array($sourcePath, $file));
+ $destFile = $this->concatPaths(array($destPath, $file));
+
$res &= copy($sourceFile, $destFile);
}
return $res;
}
-
/**
* Create a new file if not exists with all folders in the path.
*
@@ -566,7 +593,6 @@ class Manager
return $pathInfo['dirname'];
}
-
/**
* Return content of PHP file
*
diff --git a/application/Espo/Core/Utils/File/Permission.php b/application/Espo/Core/Utils/File/Permission.php
index b1fc85554f..d9597f5cb0 100644
--- a/application/Espo/Core/Utils/File/Permission.php
+++ b/application/Espo/Core/Utils/File/Permission.php
@@ -535,6 +535,71 @@ class Permission
return $this->permissionErrorRules;
}
+ /**
+ * Arrange permission file list
+ * e.g. array('application/Espo/Controllers/Email.php', 'application/Espo/Controllers/Import.php'), result is array('application/Espo/Controllers')
+ *
+ * @param array $fileList
+ * @return array
+ */
+ public function arrangePermissionList($fileList)
+ {
+ $betterList = array();
+ foreach ($fileList as $fileName) {
+
+ $pathInfo = pathinfo($fileName);
+ $dirname = $pathInfo['dirname'];
+
+ $currentPath = $fileName;
+ if ($this->getSearchCount($dirname, $fileList) > 1) {
+ $currentPath = $dirname;
+ }
+
+ if (!$this->isItemIncludes($currentPath, $betterList)) {
+ $betterList[] = $currentPath;
+ }
+ }
+
+ return $betterList;
+ }
+
+ /**
+ * Get count of a search string in a array
+ *
+ * @param string $search
+ * @param array $array
+ * @return bool
+ */
+ protected function getSearchCount($search, array $array)
+ {
+ $search = $this->getPregQuote($search);
+
+ $number = 0;
+ foreach ($array as $value) {
+ if (preg_match('/^'.$search.'/', $value)) {
+ $number++;
+ }
+ }
+
+ return $number;
+ }
+
+ protected function isItemIncludes($item, $array)
+ {
+ foreach ($array as $value) {
+ $value = $this->getPregQuote($value);
+ if (preg_match('/^'.$value.'/', $item)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected function getPregQuote($string)
+ {
+ return preg_quote($string, '/-+=.');
+ }
}
diff --git a/application/Espo/Resources/metadata/entityDefs/Extension.json b/application/Espo/Resources/metadata/entityDefs/Extension.json
index 20bb6f1847..f5af8b3114 100644
--- a/application/Espo/Resources/metadata/entityDefs/Extension.json
+++ b/application/Espo/Resources/metadata/entityDefs/Extension.json
@@ -2,7 +2,7 @@
"fields": {
"name": {
"type": "varchar",
- "required": true
+ "required": true
},
"status": {
"type": "enum",
@@ -11,13 +11,17 @@
"version": {
"type": "varchar",
"required": true,
- "maxLength": 50
+ "maxLength": 50
},
"fileList": {
- "type": "jsonArray"
- },
+ "type": "jsonArray"
+ },
"description": {
- "type": "text"
+ "type": "text"
+ },
+ "isInstalled": {
+ "type": "bool",
+ "default": false
},
"createdAt": {
"type": "datetime",
diff --git a/tests/Espo/Core/Utils/File/PermissionTest.php b/tests/Espo/Core/Utils/File/PermissionTest.php
new file mode 100644
index 0000000000..f8208a260e
--- /dev/null
+++ b/tests/Espo/Core/Utils/File/PermissionTest.php
@@ -0,0 +1,156 @@
+objects['fileManager'] = $this->getMockBuilder('\Espo\Core\Utils\File\Manager')->disableOriginalConstructor()->getMock();
+
+ $this->object = new \Espo\Core\Utils\File\Permission($this->objects['fileManager']);
+
+ $this->reflection = new ReflectionHelper($this->object);
+
+ $this->fileList = array(
+ 'application/Espo/Controllers/Email.php',
+ 'application/Espo/Controllers/EmailAccount.php',
+ 'application/Espo/Controllers/EmailAddress.php',
+ 'application/Espo/Controllers/ExternalAccount.php',
+ 'application/Espo/Controllers/Import.php',
+ 'application/Espo/Controllers/Integration.php',
+ 'application/Espo/Modules/Crm/Resources/i18n/pl_PL/Calendar.json',
+ 'application/Espo/Modules/Crm/Resources/i18n/pl_PL/Call.json',
+ 'application/Espo/Modules/Crm/Resources/i18n/pl_PL/Case.json',
+ 'application/Espo/Modules/Crm/Resources/i18n/pl_PL/Contact.json',
+ 'application/Espo/Modules/Crm/Resources/i18n/pl_PL/Global.json',
+ 'application/Espo/Resources/layouts/User/filters.json',
+ 'application/Espo/Resources/metadata/app/acl.json',
+ 'application/Espo/Resources/metadata/app/defaultDashboardLayout.json'
+ );
+ }
+
+ protected function tearDown()
+ {
+ $this->object = NULL;
+ }
+
+ public function testGetSearchCount()
+ {
+ $search = 'application/Espo/Controllers/';
+ $methodResult = $this->reflection->invokeMethod('getSearchCount', array($search, $this->fileList));
+ $result = 6;
+ $this->assertEquals($result, $methodResult);
+
+
+ $search = 'application/Espo/Controllers/Email.php';
+ $methodResult = $this->reflection->invokeMethod('getSearchCount', array($search, $this->fileList));
+ $result = 1;
+ $this->assertEquals($result, $methodResult);
+
+ $search = 'application/Espo/Controllers/NotReal';
+ $methodResult = $this->reflection->invokeMethod('getSearchCount', array($search, $this->fileList));
+ $result = 0;
+ $this->assertEquals($result, $methodResult);
+ }
+
+ public function testArrangePermissionList()
+ {
+ $result = array(
+ 'application/Espo/Controllers',
+ 'application/Espo/Modules/Crm/Resources/i18n/pl_PL',
+ 'application/Espo/Resources/layouts/User/filters.json',
+ 'application/Espo/Resources/metadata/app',
+ );
+ $this->assertEquals( $result, $this->object->arrangePermissionList($this->fileList) );
+ }
+
+ /*public function bestPossibleList()
+ {
+ $fileList = array(
+ 'application/Espo/Controllers',
+ 'application/Espo/Core',
+ 'application/Espo/Core/Cron',
+ 'application/Espo/Core/Loaders',
+ 'application/Espo/Core/Mail',
+ 'application/Espo/Core/Mail/Storage/Imap.php',
+ 'application/Espo/Core/SelectManagers/Base.php',
+ 'application/Espo/Core/Utils/Database/Orm',
+ 'application/Espo/Core/Utils/Database/Orm/Fields',
+ 'application/Espo/Core/Utils/Database/Orm/Relations',
+ 'application/Espo/Core/Utils',
+ 'application/Espo/Core/defaults/config.php',
+ 'application/Espo/Entities',
+ 'application/Espo/Hooks/Common/Stream.php',
+ 'application/Espo/Modules/Crm/Controllers/Opportunity.php',
+ 'application/Espo/Modules/Crm/Jobs/CheckInboundEmails.php',
+ 'application/Espo/Modules/Crm/Resources/i18n/de_DE',
+ 'application/Espo/Modules/Crm/Resources/i18n/en_US',
+ 'application/Espo/Modules/Crm/Resources/i18n/nl_NL',
+ 'application/Espo/Modules/Crm/Resources/i18n/pl_PL',
+ 'application/Espo/Modules/Crm/Resources/layouts/InboundEmail',
+ 'application/Espo/Modules/Crm/Resources/metadata/clientDefs/InboundEmail.json',
+ 'application/Espo/Modules/Crm/Resources/metadata/entityDefs',
+ 'application/Espo/Modules/Crm/Services',
+ 'application/Espo/Repositories',
+ 'application/Espo/Resources/i18n/de_DE',
+ 'application/Espo/Resources/i18n/en_US',
+ 'application/Espo/Resources/i18n/nl_NL',
+ 'application/Espo/Resources/i18n/pl_PL',
+ 'application/Espo/Resources/layouts/Email',
+ 'application/Espo/Resources/layouts/EmailAccount',
+ 'application/Espo/Resources/layouts/User/filters.json',
+ 'application/Espo/Resources/metadata/app',
+ 'application/Espo/Resources/metadata/clientDefs',
+ 'application/Espo/Resources/metadata/entityDefs',
+ 'application/Espo/Resources/metadata/integrations/Google.json',
+ 'application/Espo/Resources/metadata/scopes',
+ 'application/Espo/SelectManagers/EmailAccount.php',
+ 'application/Espo/Services',
+ 'install/core',
+ 'install/core/actions/settingsTest.php',
+ 'install/core/i18n/de_DE/install.json',
+ 'install/core/i18n/en_US/install.json',
+ 'install/core/i18n/es_ES/install.json',
+ 'install/core/i18n/nl_NL/install.json',
+ 'install/core/i18n/pl_PL/install.json',
+ 'install/core/i18n/ro_RO/install.json',
+ 'install/core/i18n/tr_TR/install.json',
+ 'install/js/install.js',
+ );
+
+ $result = array(
+ 'application/Espo/Controllers',
+ 'application/Espo/Core',
+ 'application/Espo/Entities',
+ 'application/Espo/Hooks/Common/Stream.php',
+ 'application/Espo/Modules/Crm',
+ 'application/Espo/Repositories',
+ 'application/Espo/Resources',
+ 'application/Espo/SelectManagers/EmailAccount.php',
+ 'application/Espo/Services',
+ 'install/core',
+ 'install/js/install.js',
+ );
+ }*/
+
+
+
+
+
+
+
+}
+
+?>