config writer

This commit is contained in:
Yuri Kuznetsov
2021-01-28 13:14:40 +02:00
parent 9ec8056d89
commit 9acdfe5dcf
25 changed files with 1339 additions and 271 deletions
+40 -24
View File
@@ -41,6 +41,7 @@ use Espo\Core\{
Api\Request,
DataManager,
Utils\Config,
Utils\Config\ConfigWriter,
};
class EntityManager
@@ -49,13 +50,20 @@ class EntityManager
protected $dataManager;
protected $config;
protected $entityManagerTool;
protected $configWriter;
public function __construct(User $user, DataManager $dataManager, Config $config, EntityManagerTool $entityManagerTool)
{
public function __construct(
User $user,
DataManager $dataManager,
Config $config,
EntityManagerTool $entityManagerTool,
ConfigWriter $configWriter
) {
$this->user = $user;
$this->dataManager = $dataManager;
$this->config = $config;
$this->entityManagerTool = $entityManagerTool;
$this->configWriter = $configWriter;
$this->checkControllerAccess();
}
@@ -131,8 +139,10 @@ class EntityManager
if (!in_array($name, $tabList)) {
$tabList[] = $name;
$this->config->set('tabList', $tabList);
$this->config->save();
$this->configWriter->set('tabList', $tabList);
$this->configWriter->save();
}
$this->dataManager->rebuild();
@@ -184,15 +194,19 @@ class EntityManager
if ($result) {
$tabList = $this->config->get('tabList', []);
if (($key = array_search($name, $tabList)) !== false) {
unset($tabList[$key]);
$tabList = array_values($tabList);
}
$this->config->set('tabList', $tabList);
$this->config->save();
$this->configWriter->set('tabList', $tabList);
$this->configWriter->save();
$this->dataManager->clearCache();
} else {
}
else {
throw new Error();
}
@@ -206,11 +220,11 @@ class EntityManager
$data = get_object_vars($data);
$paramList = [
'entity',
'link',
'linkForeign',
'label',
'linkType',
'entity',
'link',
'linkForeign',
'label',
'linkType',
];
$additionalParamList = [
@@ -222,10 +236,11 @@ class EntityManager
$params = [];
foreach ($paramList as $item) {
if (empty($data[$item])) {
throw new BadRequest();
}
$params[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
if (empty($data[$item])) {
throw new BadRequest();
}
$params[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
}
foreach ($additionalParamList as $item) {
@@ -272,12 +287,12 @@ class EntityManager
$data = get_object_vars($data);
$paramList = [
'entity',
'entityForeign',
'link',
'linkForeign',
'label',
'labelForeign',
'entity',
'entityForeign',
'link',
'linkForeign',
'label',
'labelForeign',
];
$additionalParamList = [];
@@ -331,8 +346,8 @@ class EntityManager
$data = get_object_vars($data);
$paramList = [
'entity',
'link',
'entity',
'link',
];
$d = [];
@@ -382,6 +397,7 @@ class EntityManager
}
$this->entityManagerTool->resetToDefaults($data->scope);
$this->dataManager->clearCache();
return true;
+36 -34
View File
@@ -29,55 +29,57 @@
namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\{
Exceptions\Forbidden,
ServiceFactory,
Api\Request,
};
class Integration extends \Espo\Core\Controllers\Record
use Espo\{
Entities\User,
};
class Integration
{
protected $serviceFactory;
protected $user;
public function __construct(ServiceFactory $serviceFactory, User $user)
{
$this->serviceFactory = $serviceFactory;
$this->user = $user;
$this->checkControllerAccess();
}
protected function checkControllerAccess()
{
if (!$this->getUser()->isAdmin()) {
if (!$this->user->isAdmin()) {
throw new Forbidden();
}
}
public function actionIndex($params, $data, $request)
public function getActionRead(Request $request)
{
return false;
$entity = $this->serviceFactory
->create('Integration')
->read($request->getRouteParam('id'));
return $entity->getValueMap();
}
public function actionRead($params, $data, $request)
public function putActionUpdate(Request $request)
{
$entity = $this->getEntityManager()->getEntity('Integration', $params['id']);
return $entity->toArray();
return $this->patchActionPatch($request);
}
public function actionUpdate($params, $data, $request)
public function patchActionPatch(Request $request)
{
return $this->actionPatch($params, $data, $request);
}
$entity = $this->serviceFactory
->create('Integration')
->update($request->getRouteParam('id'), $request->getParsedBody());
public function actionPatch($params, $data, $request)
{
if (!$request->isPut() && !$request->isPatch()) {
throw new BadRequest();
}
$entity = $this->getEntityManager()->getEntity('Integration', $params['id']);
$entity->set($data);
$this->getEntityManager()->saveEntity($entity);
$integrationsConfigData = $this->getConfig()->get('integrations');
if (!$integrationsConfigData || !($integrationsConfigData instanceof \StdClass)) {
$integrationsConfigData = (object)[];
}
$integrationName = $params['id'];
$integrationsConfigData->$integrationName = $entity->get('enabled');
$this->getConfig()->set('integrations', $integrationsConfigData);
$this->getConfig()->save();
return $entity->toArray();
return $entity->getValueMap();
}
}
+26 -19
View File
@@ -35,6 +35,7 @@ use Espo\Core\{
Utils\Metadata,
Utils\Util,
Utils\Config,
Utils\Config\ConfigWriter,
Utils\File\Manager as FileManager,
Utils\Metadata\OrmMetadataData,
HookManager,
@@ -50,6 +51,7 @@ use Throwable;
class DataManager
{
protected $config;
protected $configWriter;
protected $entityManager;
protected $fileManager;
protected $metadata;
@@ -62,6 +64,7 @@ class DataManager
public function __construct(
EntityManager $entityManager,
Config $config,
ConfigWriter $configWriter,
FileManager $fileManager,
Metadata $metadata,
OrmMetadataData $ormMetadataData,
@@ -70,6 +73,7 @@ class DataManager
) {
$this->entityManager = $entityManager;
$this->config = $config;
$this->configWriter = $configWriter;
$this->fileManager = $fileManager;
$this->metadata = $metadata;
$this->ormMetadataData = $ormMetadataData;
@@ -125,8 +129,8 @@ class DataManager
$result = false;
$GLOBALS['log']->error(
'Fault to rebuild database schema. Details: '. $e->getMessage() .
' at ' . $e->getFile() . ':' . $e->getLine()
"Fault to rebuild database schema. Details: ". $e->getMessage() .
" at " . $e->getFile() . ":" . $e->getLine()
);
}
@@ -134,21 +138,27 @@ class DataManager
throw new Error("Error while rebuilding database. See log file for details.");
}
$config = $this->config;
$databaseType = strtolower($schema->getDatabaseHelper()->getDatabaseType());
if (!$config->get('actualDatabaseType') || $config->get('actualDatabaseType') != $databaseType) {
$config->set('actualDatabaseType', $databaseType);
if (
!$this->config->get('actualDatabaseType') ||
$this->config->get('actualDatabaseType') !== $databaseType
) {
$this->configWriter->set('actualDatabaseType', $databaseType);
}
$databaseVersion = $schema->getDatabaseHelper()->getDatabaseVersion();
if (!$config->get('actualDatabaseVersion') || $config->get('actualDatabaseVersion') != $databaseVersion) {
$config->set('actualDatabaseVersion', $databaseVersion);
if (
!$this->config->get('actualDatabaseVersion') ||
$this->config->get('actualDatabaseVersion') !== $databaseVersion
) {
$this->configWriter->set('actualDatabaseVersion', $databaseVersion);
}
$this->updateCacheTimestamp();
$this->configWriter->updateCacheTimestamp();
$this->configWriter->save();
}
/**
@@ -259,10 +269,9 @@ class DataManager
*/
public function updateCacheTimestamp()
{
$this->config->updateCacheTimestamp();
$this->configWriter->updateCacheTimestamp();
/* Fix rebuildDatabase() method when remove this line. */
$this->config->save();
$this->configWriter->save();
}
protected function populateConfigParameters()
@@ -270,15 +279,13 @@ class DataManager
$this->setFullTextConfigParameters();
$this->setCryptKeyConfigParameter();
$this->config->save();
$this->configWriter->save();
}
protected function setFullTextConfigParameters()
{
$config = $this->config;
$platform = $config->get('database.platform') ?? null;
$driver = $config->get('database.driver') ?? '';
$platform = $this->config->get('database.platform') ?? null;
$driver = $this->config->get('database.driver') ?? '';
if ($platform !== 'Mysql' && strpos($driver, 'mysql') === false) {
return;
@@ -300,7 +307,7 @@ class DataManager
$fullTextSearchMinLength = intval($row['Value']);
}
$config->set('fullTextSearchMinLength', $fullTextSearchMinLength);
$this->configWriter->set('fullTextSearchMinLength', $fullTextSearchMinLength);
}
protected function setCryptKeyConfigParameter()
@@ -311,7 +318,7 @@ class DataManager
$cryptKey = Util::generateSecretKey();
$this->config->set('cryptKey', $cryptKey);
$this->configWriter->set('cryptKey', $cryptKey);
}
protected function disableHooks()
+4 -1
View File
@@ -77,7 +77,10 @@ class Application extends BaseApplication
$portal = $entityManager->getEntity('Portal', $portalId);
if (!$portal) {
$portal = $entityManager->getRepository('Portal')->where(['customId' => $portalId])->findOne();
$portal = $entityManager
->getRepository('Portal')
->where(['customId' => $portalId])
->findOne();
}
if (!$portal) {
+14 -1
View File
@@ -31,18 +31,31 @@ namespace Espo\Core\Portal;
use Espo\Entities\Portal as PortalEntity;
use Espo\Core\Container as BaseContainer;
use Espo\Core\{
Container as BaseContainer,
Exceptions\Error,
};
class Container extends BaseContainer
{
protected $portalIsSet = false;
public function setPortal(PortalEntity $portal)
{
if ($this->portalIsSet) {
throw new Error("Can't set portal second time.");
}
$this->portalIsSet = true;
$this->setForced('portal', $portal);
$data = [];
foreach ($this->get('portal')->getSettingsAttributeList() as $attribute) {
$data[$attribute] = $this->get('portal')->get($attribute);
}
$this->get('config')->setPortalParameters($data);
$this->get('aclManager')->setPortal($portal);
+85 -19
View File
@@ -29,37 +29,103 @@
namespace Espo\Core\Portal\Utils;
use Espo\Core\Utils\Config as BaseConfig;
use Espo\Core\{
Exceptions\Error,
Utils\Config as BaseConfig,
};
class Config extends BaseConfig
{
protected $portalParamsSet = false;
protected $portalData = [];
protected $portalParamList = [
'companyLogoId',
'tabList',
'quickCreateList',
'dashboardLayout',
'dashletsOptions',
'theme',
'language',
'timeZone',
'dateFormat',
'timeFormat',
'weekStart',
'defaultCurrency',
];
public function get(string $name, $default = null)
{
if (array_key_exists($name, $this->portalData)) {
return $this->portalData[$name];
}
return parent::get($name, $default);
}
public function has(string $name) : bool
{
if (array_key_exists($name, $this->portalData)) {
return true;
}
return parent::has($name);
}
/**
* Override parameters for a portal.
* Override parameters for a portal. Can be called only once.
*/
public function setPortalParameters(array $data = [])
{
if (empty($data['language'])) unset($data['language']);
if (empty($data['theme'])) unset($data['theme']);
if (empty($data['timeZone'])) unset($data['timeZone']);
if (empty($data['dateFormat'])) unset($data['dateFormat']);
if (empty($data['timeFormat'])) unset($data['timeFormat']);
if (empty($data['defaultCurrency'])) unset($data['defaultCurrency']);
if (isset($data['weekStart']) && $data['weekStart'] === -1) unset($data['weekStart']);
if (array_key_exists('weekStart', $data) && is_null($data['weekStart'])) unset($data['weekStart']);
if ($this->portalParamsSet) {
throw new Error("Can't set portal params second time.");
}
$this->portalParamsSet = true;
if (empty($data['language'])) {
unset($data['language']);
}
if (empty($data['theme'])) {
unset($data['theme']);
}
if (empty($data['timeZone'])) {
unset($data['timeZone']);
}
if (empty($data['dateFormat'])) {
unset($data['dateFormat']);
}
if (empty($data['timeFormat'])) {
unset($data['timeFormat']);
}
if (empty($data['defaultCurrency'])) {
unset($data['defaultCurrency']);
}
if (isset($data['weekStart']) && $data['weekStart'] === -1) {
unset($data['weekStart']);
}
if (array_key_exists('weekStart', $data) && is_null($data['weekStart'])) {
unset($data['weekStart']);
}
if ($this->get('webSocketInPortalDisabled')) {
$this->set('useWebSocket', false, true);
$this->portalData['useWebSocket'] = false;
}
foreach ($data as $attribute => $value) {
$this->set($attribute, $value, true);
if (!in_array($attribute, $this->portalParamList)) {
continue;
}
$this->portalData[$attribute] = $value;
}
}
/**
* Save is disabled.
*/
public function save()
{
}
}
+91 -25
View File
@@ -34,6 +34,14 @@ use Espo\Core\Utils\System;
use Espo\Core\Utils\Json;
use Espo\Core\Exceptions\Error;
use Espo\Core\{
Container,
Upgrades\ActionManager,
Utils\File\ZipArchive,
Utils\Config\ConfigWriter,
Utils\Database\Helper as DatabaseHelper,
};
use Composer\Semver\Semver;
use Throwable;
@@ -89,13 +97,13 @@ abstract class Base
protected $vendorDirName = 'vendor';
public function __construct(\Espo\Core\Container $container, \Espo\Core\Upgrades\ActionManager $actionManager)
public function __construct(Container $container, ActionManager $actionManager)
{
$this->container = $container;
$this->actionManager = $actionManager;
$this->params = $actionManager->getParams();
$this->zipUtil = new \Espo\Core\Utils\File\ZipArchive($container->get('fileManager'));
$this->zipUtil = new ZipArchive($container->get('fileManager'));
}
public function __destruct()
@@ -136,7 +144,7 @@ abstract class Base
protected function getDatabaseHelper()
{
if (!isset($this->databaseHelper)) {
$this->databaseHelper = new \Espo\Core\Utils\Database\Helper($this->getConfig());
$this->databaseHelper = new DatabaseHelper($this->getConfig());
}
return $this->databaseHelper;
@@ -157,6 +165,11 @@ abstract class Base
return $this->getContainer()->get('entityManager');
}
public function createConfigWriter() : ConfigWriter
{
return $this->getContainer()->get('injectableFactory')->create(ConfigWriter::class);
}
public function throwErrorAndRemovePackage($errorMessage = '', $deletePackage = true, $systemRebuild = true)
{
if ($deletePackage) {
@@ -224,7 +237,10 @@ abstract class Base
//check php version
if (isset($manifest['php'])) {
$res &= $this->checkVersions($manifest['php'], System::getPhpVersion(), 'Your PHP version ({version}) is not supported. Required version: {requiredVersion}.');
$res &= $this->checkVersions(
$manifest['php'], System::getPhpVersion(),
'Your PHP version ({version}) is not supported. Required version: {requiredVersion}.'
);
}
//check database version
@@ -235,15 +251,25 @@ abstract class Base
if (isset($manifest['database'][$databaseTypeLc])) {
$databaseVersion = $databaseHelper->getDatabaseVersion();
if ($databaseVersion) {
$res &= $this->checkVersions($manifest['database'][$databaseTypeLc], $databaseVersion, 'Your '. $databaseType .' version ({version}) is not supported. Required version: {requiredVersion}.');
$res &= $this->checkVersions(
$manifest['database'][$databaseTypeLc],
$databaseVersion,
'Your '. $databaseType .
' version ({version}) is not supported. Required version: {requiredVersion}.'
);
}
}
}
//check acceptableVersions
if (isset($manifest['acceptableVersions'])) {
$res &= $this->checkVersions($manifest['acceptableVersions'], $this->getConfig()->get('version'), 'Your EspoCRM version ({version}) is not supported. Required version: {requiredVersion}.');
$res &= $this->checkVersions(
$manifest['acceptableVersions'],
$this->getConfig()->get('version'),
'Your EspoCRM version ({version}) is not supported. Required version: {requiredVersion}.'
);
}
//check dependencies
@@ -291,6 +317,7 @@ abstract class Base
/** check package type */
$type = strtolower( $this->getParams('name') );
$manifestType = isset($manifest['type']) ? strtolower($manifest['type']) : $this->defaultPackageType;
if (!in_array($manifestType, $this->packageTypes)) {
@@ -298,7 +325,9 @@ abstract class Base
}
if ($type != $manifestType) {
$this->throwErrorAndRemovePackage('Wrong package type. You cannot install '.$manifestType.' package via '.ucfirst($type).' Manager.');
$this->throwErrorAndRemovePackage(
'Wrong package type. You cannot install '.$manifestType.' package via '.ucfirst($type).' Manager.'
);
}
return true;
@@ -352,11 +381,13 @@ abstract class Base
$scriptNames = $this->getParams('scriptNames');
$scriptName = $scriptNames[$type];
if (!isset($scriptName)) {
return;
}
$beforeInstallScript = Util::concatPath( array($packagePath, self::SCRIPTS, $scriptName) ) . '.php';
if (file_exists($beforeInstallScript)) {
return $beforeInstallScript;
}
@@ -411,13 +442,19 @@ abstract class Base
protected function getDeleteFileList()
{
if (!isset($this->data['deleteFileList'])) {
$deleteFileList = array();
$deleteFileList = [];
$deleteList = array_merge(
$this->getDeleteList('delete'),
$this->getDeleteList('deleteBeforeCopy'),
$this->getDeleteList('vendor')
);
$deleteList = array_merge($this->getDeleteList('delete'), $this->getDeleteList('deleteBeforeCopy'), $this->getDeleteList('vendor'));
foreach ($deleteList as $key => $itemPath) {
if (is_dir($itemPath)) {
$fileList = $this->getFileManager()->getFileList($itemPath, true, '', true, true);
$fileList = $this->concatStringWithArray($itemPath, $fileList);
$deleteFileList = array_merge($deleteFileList, $fileList);
continue;
@@ -452,6 +489,7 @@ abstract class Base
{
if (!isset($this->data['fileList'])) {
$packagePath = $this->getPackagePath();
$this->data['fileList'] = $this->getFileList($packagePath);
}
@@ -462,6 +500,7 @@ abstract class Base
{
if (!isset($this->data['restoreFileList'])) {
$backupPath = $this->getPath('backupPath');
$this->data['restoreFileList'] = $this->getFileList($backupPath, true);
}
@@ -478,6 +517,7 @@ abstract class Base
protected function getFileDirs($parentDirPath = null)
{
$dirNames = $this->getParams('customDirNames');
$paths = array(self::FILES, $dirNames['before'], $dirNames['after']);
if (isset($parentDirPath)) {
@@ -501,6 +541,7 @@ abstract class Base
$fileList = array();
$paths = $this->getFileDirs($dirPath);
foreach ($paths as $filesPath) {
if (file_exists($filesPath)) {
$files = $this->getFileManager()->getFileList($filesPath, true, '', true, true);
@@ -518,8 +559,9 @@ abstract class Base
return $fileList;
}
protected function copy($sourcePath, $destPath, $recursively = false, array $fileList = null, $copyOnlyFiles = false)
{
protected function copy(
$sourcePath, $destPath, $recursively = false, array $fileList = null, $copyOnlyFiles = false
) {
try {
$res = $this->getFileManager()->copy($sourcePath, $destPath, $recursively, $fileList, $copyOnlyFiles);
}
@@ -565,18 +607,23 @@ abstract class Base
case 'before':
case 'after':
$dirNames = $this->getParams('customDirNames');
$dirPath = $dirNames[$type];
break;
case 'vendor':
$dirNames = $this->getParams('customDirNames');
if (isset($dirNames['vendor'])) {
$dirPath = $dirNames['vendor'];
}
break;
default:
$dirPath = self::FILES;
break;
}
@@ -596,11 +643,13 @@ abstract class Base
$packagePath = $this->getPackagePath();
$dirNames = $this->getParams('customDirNames');
if (!isset($dirNames['vendor'])) {
return $list;
}
$filesPath = Util::concatPath($packagePath, $dirNames['vendor']);
if (!file_exists($filesPath)) {
return $list;
}
@@ -608,10 +657,12 @@ abstract class Base
switch ($type) {
case 'copy':
$list = $this->getFileManager()->getFileList($filesPath, true, '', true, true);
break;
case 'delete':
$list = $this->getFileManager()->getFileList($filesPath, false, '', null, true);
break;
}
@@ -628,11 +679,13 @@ abstract class Base
$packagePath = $this->getPackagePath();
$manifestPath = Util::concatPath($packagePath, $this->manifestName);
if (!file_exists($manifestPath)) {
$this->throwErrorAndRemovePackage('It\'s not an Installation package.');
}
$manifestJson = $this->getFileManager()->getContents($manifestPath);
$this->data['manifest'] = Json::decode($manifestJson, true);
if (!$this->data['manifest']) {
@@ -686,7 +739,7 @@ abstract class Base
}
/**
* Unzip a package archieve
* Unzip a package archive.
*
* @return void
*/
@@ -706,7 +759,7 @@ abstract class Base
}
/**
* Delete temporary package files
* Delete temporary package files.
*
* @return boolean
*/
@@ -719,7 +772,7 @@ abstract class Base
}
/**
* Delete temporary package archive
* Delete temporary package archive.
*
* @return boolean
*/
@@ -750,7 +803,7 @@ abstract class Base
}
/**
* Execute an action. For ex., execute uninstall action in install
* Execute an action. For ex., execute uninstall action in install.
*
* @param string $actionName
* @param string $data
@@ -800,11 +853,15 @@ abstract class Base
$fullFileList = array_merge([$backupPath], $this->getDeleteFileList(), $this->getCopyFileList());
$result = $this->getFileManager()->isWritableList($fullFileList);
if (!$result) {
$permissionDeniedList = $this->getFileManager()->getLastPermissionDeniedList();
$delimiter = $this->isCli() ? "\n" : "<br>";
$this->throwErrorAndRemovePackage("Permission denied: " . $delimiter . implode($delimiter, $permissionDeniedList), false, false);
$this->throwErrorAndRemovePackage(
"Permission denied: " . $delimiter . implode($delimiter, $permissionDeniedList), false, false
);
}
}
@@ -842,6 +899,8 @@ abstract class Base
protected function enableMaintenanceMode()
{
$config = $this->getConfig();
$configWriter = $this->createConfigWriter();
$configParamName = $this->getTemporaryConfigParamName();
$parentConfigParamName = $this->getTemporaryConfigParamName(true);
@@ -855,36 +914,41 @@ abstract class Base
'useCache' => $config->get('useCache'),
];
$config->set($configParamName, $actualParams);
// @todo Maybe to romove this line?
$configWriter->set($configParamName, $actualParams);
$save = false;
if (!$actualParams['maintenanceMode']) {
$config->set('maintenanceMode', true);
$configWriter->set('maintenanceMode', true);
$save = true;
}
if (!$actualParams['cronDisabled']) {
$config->set('cronDisabled', true);
$configWriter->set('cronDisabled', true);
$save = true;
}
if ($actualParams['useCache']) {
$config->set('useCache', false);
$configWriter->set('useCache', false);
$save = true;
}
if ($save) {
$config->save();
$configWriter->save();
}
}
protected function disableMaintenanceMode($force = false)
{
$config = $this->getConfig();
$configWriter = $this->createConfigWriter();
$configParamList = [
$this->getTemporaryConfigParamName()
$this->getTemporaryConfigParamName(),
];
if ($force && $this->getTemporaryConfigParamName(true)) {
@@ -901,16 +965,17 @@ abstract class Base
foreach ($config->get($configParamName, []) as $paramName => $paramValue) {
if ($config->get($paramName) != $paramValue) {
$config->set($paramName, $paramValue);
$configWriter->set($paramName, $paramValue);
}
}
$config->remove($configParamName);
$configWriter->remove($configParamName);
$save = true;
}
if ($save) {
$config->save();
$configWriter->save();
}
}
@@ -920,6 +985,7 @@ abstract class Base
if ($isParentProcess) {
$processId = $this->getParentProcessId();
if (!$processId) {
return;
}
@@ -45,8 +45,11 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
{
$manifest = $this->getManifest();
$this->getConfig()->set('version', $manifest['version']);
$this->getConfig()->save();
$configWriter = $this->createConfigWriter();
$configWriter->set('version', $manifest['version']);
$configWriter->save();
}
/**
+13 -6
View File
@@ -29,15 +29,21 @@
namespace Espo\Core\Utils;
use Espo\Core\Utils\Config;
use Espo\Core\{
Utils\Config,
Utils\Config\ConfigWriter,
};
class ApiKey
{
private $config;
public function __construct(Config $config)
private $configWriter;
public function __construct(Config $config, ConfigWriter $configWriter)
{
$this->config = $config;
$this->configWriter = $configWriter;
}
public static function hash(string $secretKey, string $string = '') : string
@@ -74,9 +80,9 @@ class ApiKey
$apiSecretKeys->$id = $secretKey;
$this->config->set('apiSecretKeys', $apiSecretKeys);
$this->configWriter->set('apiSecretKeys', $apiSecretKeys);
$this->config->save();
$this->configWriter->save();
}
public function removeSecretKeyForUserId(string $id)
@@ -89,7 +95,8 @@ class ApiKey
unset($apiSecretKeys->$id);
$this->config->set('apiSecretKeys', $apiSecretKeys);
$this->config->save();
$this->configWriter->set('apiSecretKeys', $apiSecretKeys);
$this->configWriter->save();
}
}
+48 -6
View File
@@ -37,7 +37,7 @@ use Espo\Core\{
use StdClass;
/**
* Reads and writes the main config file.
* Access to the application config parameters.
*/
class Config
{
@@ -69,6 +69,11 @@ class Config
$this->fileManager = $fileManager;
}
/**
* A path to the config file.
*
* @todo Move to ConfigData.
*/
public function getConfigPath() : string
{
return $this->configPath;
@@ -125,7 +130,18 @@ class Config
}
/**
* Set a parameter or multiple parameters.
* Re-load data.
*
* @todo Get rid of this method. Use ConfigData as a dependency.
* `$configData->update();`
*/
public function update()
{
$this->loadConfig(true);
}
/**
* @deprecated since version 6.2.0
*/
public function set($name, $value = null, bool $dontMarkDirty = false)
{
@@ -151,7 +167,7 @@ class Config
}
/**
* Remove a parameter..
* @deprecated since version 6.2.0
*/
public function remove(string $name) : bool
{
@@ -167,7 +183,7 @@ class Config
}
/**
* Save config changes to the file.
* @deprecated since version 6.2.0
*/
public function save()
{
@@ -233,6 +249,9 @@ class Config
/**
* Get system default config parameters.
*
* @deprecated
* @todo Move to `Espo\Core\Utils\Config\ConfigDefaults`.
*/
public function getDefaults() : array
{
@@ -289,8 +308,7 @@ class Config
/**
* Update cache timestamp.
*
* @param $returnOnlyValue - To return an array with timestamp.
* @return bool | array
* @deprecated
*/
public function updateCacheTimestamp(bool $returnOnlyValue = false)
{
@@ -305,31 +323,55 @@ class Config
return $this->set($timestamp);
}
/**
* @todo Remove.
* @deprecated
*/
public function getAdminOnlyItemList() : array
{
return $this->get('adminItems', []);
}
/**
* @todo Remove.
* @deprecated
*/
public function getSuperAdminOnlyItemList() : array
{
return $this->get('superAdminItems', []);
}
/**
* @todo Remove.
* @deprecated
*/
public function getSystemOnlyItemList() : array
{
return $this->get('systemItems', []);
}
/**
* @todo Remove.
* @deprecated
*/
public function getSuperAdminOnlySystemItemList() : array
{
return $this->get('superAdminSystemItems', []);
}
/**
* @todo Remove.
* @deprecated
*/
public function getUserOnlyItemList() : array
{
return $this->get('userItems', []);
}
/**
* @todo Move to another class `Espo\Core\Utils\Config\ApplicationConfigProvider`.
* @deprecated
*/
public function getSiteUrl() : string
{
return rtrim($this->get('siteUrl'), '/');
@@ -34,6 +34,8 @@ use Espo\Core\{
Utils\Config,
};
use RuntimeException;
class ConfigFileManager
{
protected $fileManager;
@@ -55,9 +57,23 @@ class ConfigFileManager
return $this->fileManager->isFile($filePath);
}
public function putPhpContents(string $path, $data, bool $useRenaming = false)
protected function putPhpContentsInternal(string $path, array $data, bool $useRenaming = false)
{
return $this->fileManager->putPhpContents($path, $data, true, $useRenaming);
$result = $this->fileManager->putPhpContents($path, $data, true, $useRenaming);
if ($result === false) {
throw new RuntimeException();
}
}
public function putPhpContents(string $path, array $data)
{
$this->putPhpContentsInternal($path, $data, true);
}
public function putPhpContentsNoRenaming(string $path, array $data)
{
$this->putPhpContentsInternal($path, $data, false);
}
public function getPhpContents(string $path)
@@ -0,0 +1,182 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Utils\Config;
use Espo\Core\{
Utils\Config,
Exceptions\Error,
};
use Exception;
/**
* Writes into the config.
*/
class ConfigWriter
{
private $changedData = [];
private $removeParamList = [];
protected $associativeArrayAttributeList = [
'currencyRates',
'database',
'logger',
'defaultPermissions',
];
private $cacheTimestampParam = 'cacheTimestamp';
protected $config;
protected $fileManager;
protected $helper;
public function __construct(Config $config, ConfigWriterFileManager $fileManager, ConfigWriterHelper $helper)
{
$this->config = $config;
$this->fileManager = $fileManager;
$this->helper = $helper;
}
/**
* Set a parameter.
*/
public function set(string $name, $value)
{
if (in_array($name, $this->associativeArrayAttributeList) && is_object($value)) {
$value = (array) $value;
}
$this->changedData[$name] = $value;
}
/**
* Set multiple parameters.
*/
public function setMultiple(array $params)
{
foreach ($params as $name => $value) {
$this->set($name, $value);
}
}
/**
* Remove a parameter.
*/
public function remove(string $name)
{
$this->removeParamList[] = $name;
}
/**
* Save config changes to the file.
*/
public function save()
{
$changedData = $this->changedData;
if (!isset($changedData[$this->cacheTimestampParam])) {
$changedData[$this->cacheTimestampParam] = $this->generateCacheTimestamp();
}
$configPath = $this->config->getConfigPath();
if (!$this->fileManager->isFile($configPath)) {
throw new Error("Config file '{$configPath}' was not found.");
}
$data = $this->fileManager->getPhpContents($configPath);
if (!is_array($data)) {
$data = $this->fileManager->getPhpContents($configPath);
}
if (!is_array($data)) {
throw new Error("Could not read config.");
}
foreach ($changedData as $key => $value) {
$data[$key] = $value;
}
foreach ($this->removeParamList as $key) {
unset($data[$key]);
}
if (!is_array($data)) {
throw new Error("Invalid config data while saving.");
}
$data['microtime'] = $microtime = $this->helper->generateMicrotime();
try {
$this->fileManager->putPhpContents($configPath, $data);
}
catch (Exception $e) {
throw new Error("Could not save config.");
}
$reloadedData = $this->fileManager->getPhpContents($configPath);
if (
!is_array($reloadedData) ||
$microtime !== ($reloadedData['microtime'] ?? null)
) {
try {
$this->fileManager->putPhpContentsNoRenaming($configPath, $data);
}
catch (Exception $e) {
throw new Error("Could not save config.");
}
}
$this->changedData = [];
$this->removeParamList = [];
$this->config->update();
}
/**
* Update the cache timestamp.
*
* @todo Remove? Saving re-writes the cache timestamp anyway.
*/
public function updateCacheTimestamp()
{
$this->set($this->cacheTimestampParam, $this->generateCacheTimestamp());
}
protected function generateCacheTimestamp() : int
{
return $this->helper->generateCacheTimestamp();
}
}
@@ -0,0 +1,93 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Utils\Config;
use Espo\Core\{
Utils\File\Manager as FileManager,
Utils\Config,
};
use RuntimeException;
class ConfigWriterFileManager
{
private $fileManager;
public function __construct(?Config $config = null, ?array $defaultPermissions = null)
{
$defaultPermissionsToSet = null;
if ($defaultPermissions) {
$defaultPermissionsToSet = $defaultPermissions;
}
else if ($config) {
$defaultPermissionsToSet = $config->get('defaultPermissions');
}
$this->fileManager = new FileManager($defaultPermissionsToSet);
}
public function setConfig(Config $config)
{
$this->fileManager = new FileManager(
$config->get('defaultPermissions')
);
}
public function isFile(string $filePath) : bool
{
return $this->fileManager->isFile($filePath);
}
protected function putPhpContentsInternal(string $path, array $data, bool $useRenaming = false)
{
$result = $this->fileManager->putPhpContents($path, $data, true, $useRenaming);
if ($result === false) {
throw new RuntimeException();
}
}
public function putPhpContents(string $path, array $data)
{
$this->putPhpContentsInternal($path, $data, true);
}
public function putPhpContentsNoRenaming(string $path, array $data)
{
$this->putPhpContentsInternal($path, $data, false);
}
public function getPhpContents(string $path)
{
return $this->fileManager->getPhpContents($path);
}
}
@@ -0,0 +1,43 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Utils\Config;
class ConfigWriterHelper
{
public function generateCacheTimestamp() : int
{
return time();
}
public function generateMicrotime() : float
{
return microtime(true);
}
}
+1 -2
View File
@@ -43,12 +43,11 @@ class Portal extends \Espo\Core\ORM\Entity
'dateFormat',
'timeFormat',
'weekStart',
'defaultCurrency'
'defaultCurrency',
];
public function getSettingsAttributeList()
{
return $this->settingsAttributeList;
}
}
@@ -32,28 +32,34 @@ namespace Espo\Hooks\Integration;
use Espo\ORM\Entity;
use Espo\Core\{
Utils\Config,
Utils\Config\ConfigWriter,
};
class GoogleMaps
{
protected $config;
protected $configWriter;
public function __construct(Config $config)
public function __construct(ConfigWriter $configWriter)
{
$this->config = $config;
$this->configWriter = $configWriter;
}
public function afterSave(Entity $entity)
{
if ($entity->id === 'GoogleMaps') {
if (!$entity->get('enabled') || !$entity->get('apiKey')) {
$this->config->set('googleMapsApiKey', null);
$this->config->save();
return;
}
$this->config->set('googleMapsApiKey', $entity->get('apiKey'));
$this->config->save();
if ($entity->id !== 'GoogleMaps') {
return;
}
if (!$entity->get('enabled') || !$entity->get('apiKey')) {
$this->configWriter->set('googleMapsApiKey', null);
$this->configWriter->save();
return;
}
$this->configWriter->set('googleMapsApiKey', $entity->get('apiKey'));
$this->configWriter->save();
}
}
@@ -0,0 +1,76 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Hooks\Portal;
use Espo\ORM\Entity;
use Espo\Core\{
Utils\Config,
Utils\Config\ConfigWriter,
};
class WriteConfig
{
protected $config;
protected $configWriter;
public function __construct(Config $config, ConfigWriter $configWriter)
{
$this->config = $config;
$this->configWriter = $configWriter;
}
public function afterSave(Entity $entity)
{
if (!$entity->has('isDefault')) {
return;
}
if ($entity->get('isDefault')) {
$defaultPortalId = $this->config->get('defaultPortalId');
if ($defaultPortalId === $entity->id) {
return;
}
$this->configWriter->set('defaultPortalId', $entity->id);
$this->configWriter->save();
}
if ($entity->isAttributeChanged('isDefault') && $entity->getFetched('isDefault')) {
$this->configWriter->set('defaultPortalId', null);
$this->configWriter->save();
}
}
}
+3 -24
View File
@@ -31,11 +31,12 @@ namespace Espo\Repositories;
use Espo\ORM\Entity;
use Espo\Core\Exceptions\Error;
use Espo\Core\Repositories\Database;
use Espo\Core\Di;
class Portal extends \Espo\Core\Repositories\Database implements
class Portal extends Database implements
Di\ConfigAware
{
use Di\ConfigSetter;
@@ -67,26 +68,4 @@ class Portal extends \Espo\Core\Repositories\Database implements
$entity->set('url', $url);
}
}
protected function afterSave(Entity $entity, array $options = [])
{
parent::afterSave($entity, $options);
if ($entity->has('isDefault')) {
if ($entity->get('isDefault')) {
$defaultPortalId = $this->config->get('defaultPortalId');
if ($defaultPortalId !== $entity->id) {
$this->config->set('defaultPortalId', $entity->id);
$this->config->save();
}
} else {
if ($entity->isAttributeChanged('isDefault')) {
if ($entity->getFetched('isDefault')) {
$this->config->set('defaultPortalId', null);
$this->config->save();
}
}
}
}
}
}
@@ -29,16 +29,28 @@
namespace Espo\Services;
use Espo\Core\Di;
use Espo\Core\{
Utils\Config,
Utils\Config\ConfigWriter,
Di,
};
class AdminNotifications implements
Di\ConfigAware,
Di\EntityManagerAware
{
use Di\ConfigSetter;
use Di\EntityManagerSetter;
protected $config;
protected $configWriter;
public function __construct(Config $config, ConfigWriter $configWriter)
{
$this->config = $config;
$this->configWriter = $configWriter;
}
/**
* Job for checking a new version of EspoCRM.
*/
@@ -51,32 +63,36 @@ class AdminNotifications implements
}
$latestRelease = $this->getLatestRelease();
if (empty($latestRelease['version'])) {
$config->set('latestVersion', $latestRelease['version']);
$config->save();
$this->configWriter->set('latestVersion', $latestRelease['version']);
$this->configWriter->save();
return true;
}
if ($config->get('latestVersion') != $latestRelease['version']) {
$config->set('latestVersion', $latestRelease['version']);
$this->configWriter->set('latestVersion', $latestRelease['version']);
if (!empty($latestRelease['notes'])) {
//todo: create notification
// @todo Create a notification.
}
$config->save();
$this->configWriter->save();
return true;
}
if (!empty($latestRelease['notes'])) {
//todo: find and modify notification
// @todo Find and modify notification.
}
return true;
}
/**
* Job for cheking a new version of installed extensions.
* Job for checking a new version of installed extensions.
*/
public function jobCheckNewExtensionVersion()
{
@@ -103,6 +119,7 @@ class AdminNotifications implements
while ($row = $sth->fetch()) {
$url = !empty($row['checkVersionUrl']) ? $row['checkVersionUrl'] : null;
$extensionName = $row['name'];
$latestRelease = $this->getLatestRelease($url, [
@@ -117,11 +134,13 @@ class AdminNotifications implements
$latestExtensionVersions = $config->get('latestExtensionVersions', []);
$save = false;
foreach ($latestReleases as $extensionName => $extensionData) {
if (empty($latestExtensionVersions[$extensionName])) {
$latestExtensionVersions[$extensionName] = $extensionData['version'];
$save = true;
continue;
}
@@ -133,6 +152,7 @@ class AdminNotifications implements
}
$save = true;
continue;
}
@@ -142,37 +162,51 @@ class AdminNotifications implements
}
if ($save) {
$config->set('latestExtensionVersions', $latestExtensionVersions);
$config->save();
$this->configWriter->set('latestExtensionVersions', $latestExtensionVersions);
$this->configWriter->save();
}
return true;
}
protected function getLatestRelease(?string $url = null, array $requestData = [], string $urlPath = 'release/latest')
{
if (function_exists('curl_version')) {
$ch = curl_init();
protected function getLatestRelease(
?string $url = null, array $requestData = [], string $urlPath = 'release/latest'
) : ?array {
$requestUrl = $url ? trim($url) : base64_decode('aHR0cHM6Ly9zLmVzcG9jcm0uY29tLw==');
$requestUrl = (substr($requestUrl, -1) == '/') ? $requestUrl : $requestUrl . '/';
$requestUrl .= empty($requestData) ? $urlPath . '/' : $urlPath . '/?' . http_build_query($requestData);
curl_setopt($ch, CURLOPT_URL, $requestUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
$result = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($result, true);
if (is_array($data)) {
return $data;
}
}
if (!function_exists('curl_version')) {
return null;
}
return null;
$ch = curl_init();
$requestUrl = $url ? trim($url) : base64_decode('aHR0cHM6Ly9zLmVzcG9jcm0uY29tLw==');
$requestUrl = (substr($requestUrl, -1) == '/') ? $requestUrl : $requestUrl . '/';
$requestUrl .= empty($requestData) ?
$urlPath . '/' :
$urlPath . '/?' . http_build_query($requestData);
curl_setopt($ch, CURLOPT_URL, $requestUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
$result = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
return null;
}
$data = json_decode($result, true);
if (!is_array($data)) {
return null;
}
return $data;
}
}
+40 -13
View File
@@ -37,6 +37,7 @@ use Espo\Core\Exceptions\{
use Espo\Core\{
DataManager,
Utils\Config,
Utils\Config\ConfigWriter,
Acl,
};
@@ -45,55 +46,81 @@ use StdClass;
class CurrencyRate
{
protected $config;
protected $configWriter;
protected $dataManager;
protected $acl;
public function __construct(Config $config, DataManager $dataManager, Acl $acl) {
public function __construct(Config $config, ConfigWriter $configWriter, DataManager $dataManager, Acl $acl)
{
$this->config = $config;
$this->configWriter = $configWriter;
$this->dataManager = $dataManager;
$this->acl = $acl;
}
public function get() : StdClass
{
if (!$this->acl->check('Currency')) throw new Forbidden();
if ($this->acl->getLevel('Currency', 'read') !== 'yes') throw new Forbidden();
if (!$this->acl->check('Currency')) {
throw new Forbidden();
}
return (object) ($this->config->get('currencyRates') ?? []);
if ($this->acl->getLevel('Currency', 'read') !== 'yes') {
throw new Forbidden();
}
return (object) (
$this->config->get('currencyRates') ?? []
);
}
public function set(StdClass $rates) : StdClass
{
if (!$this->acl->check('Currency')) throw new Forbidden();
if ($this->acl->getLevel('Currency', 'edit') !== 'yes') throw new Forbidden();
if (!$this->acl->check('Currency')) {
throw new Forbidden();
}
if ($this->acl->getLevel('Currency', 'edit') !== 'yes') {
throw new Forbidden();
}
$config = $this->config;
$currencyList = $config->get('currencyList') ?? [];
foreach (get_object_vars($rates) as $key => $value) {
if (!is_string($key) || !in_array($key, $currencyList)) {
unset($rates->$key);
continue;
}
if (!is_numeric($value) || is_string($value)) throw new BadRequest();
if ($value < 0) throw new BadRequest();
if (!is_numeric($value) || is_string($value)) {
throw new BadRequest();
}
if ($value < 0) {
throw new BadRequest();
}
}
foreach ($currencyList as $currency) {
if ($currency == $config->get('baseCurrency')) continue;
if ($currency == $config->get('baseCurrency')) {
continue;
}
if (!isset($rates->$currency)) {
$rates->$currency = $config->get('currencyRates.' . $currency) ?? 1.0;
}
}
$data = (object) ['currencyRates' => $rates];
$this->configWriter->set('currencyRates', $rates);
$config->setData($data);
$config->save();
$this->configWriter->save();
$this->dataManager->rebuildDatabase([]);
return (object) ($config->get('currencyRates') ?? []);
return (object) (
$config->get('currencyRates') ?? []
);
}
}
+113
View File
@@ -0,0 +1,113 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Services;
use Espo\{
Core\Exceptions\Forbidden,
Core\Exceptions\NotFound,
Core\Utils\Config,
Core\Utils\Config\ConfigWriter,
ORM\EntityManager,
ORM\Entity,
Entities\User,
};
use StdClass;
class Integration
{
protected $entityManager;
protected $user;
protected $config;
protected $configWriter;
public function __construct(
EntityManager $entityManager, User $user, Config $config, ConfigWriter $configWriter
) {
$this->entityManager = $entityManager;
$this->user = $user;
$this->config = $config;
$this->configWriter = $configWriter;
}
protected function processAccessCheck()
{
if (!$this->user->isAdmin()) {
throw new Forbidden();
}
}
public function read(string $id) : Entity
{
$this->processAccessCheck();
$entity = $this->entityManager->getEntity('Integration', $id);
if (!$entity) {
throw new NotFound();
}
return $entity;
}
public function update(string $id, StdClass $data) : Entity
{
$this->processAccessCheck();
$entity = $this->entityManager->getEntity('Integration', $id);
if (!$entity) {
throw new NotFound();
}
$entity->set($data);
$this->entityManager->saveEntity($entity);
$integrationsConfigData = $this->config->get('integrations') ?? (object) [];
if (!($integrationsConfigData instanceof StdClass)) {
$integrationsConfigData = (object) [];
}
$integrationName = $id;
$integrationsConfigData->$integrationName = $entity->get('enabled');
$this->configWriter->set('integrations', $integrationsConfigData);
$this->configWriter->save();
return $entity;
}
}
+57 -26
View File
@@ -30,7 +30,6 @@
namespace Espo\Services;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\BadRequest;
use Espo\ORM\Entity;
@@ -43,6 +42,7 @@ use Espo\Core\{
Utils\Metadata,
Utils\FieldUtil,
Utils\Config,
Utils\Config\ConfigWriter,
DataManager,
Utils\FieldValidatorManager,
Currency\DatabasePopulator as CurrencyDatabasePopulator,
@@ -52,6 +52,7 @@ class Settings
{
protected $applicationState;
protected $config;
protected $configWriter;
protected $fieldUtil;
protected $metadata;
protected $acl;
@@ -63,6 +64,7 @@ class Settings
public function __construct(
ApplicationState $applicationState,
Config $config,
ConfigWriter $configWriter,
Metadata $metadata,
Acl $acl,
FieldUtil $fieldUtil,
@@ -73,6 +75,7 @@ class Settings
) {
$this->applicationState = $applicationState;
$this->config = $config;
$this->configWriter = $configWriter;
$this->metadata = $metadata;
$this->acl = $acl;
$this->fieldUtil = $fieldUtil;
@@ -114,7 +117,9 @@ class Settings
if ($this->applicationState->isPortal()) {
$portal = $this->applicationState->getPortal();
$this->entityManager->getRepository('Portal')->loadUrlField($portal);
$data->siteUrl = $portal->get('url');
}
@@ -124,6 +129,7 @@ class Settings
if ($user->isSystem()) {
$globalItemList = $this->getGlobalItemList();
foreach (get_object_vars($data) as $item => $value) {
if (!in_array($item, $globalItemList)) {
unset($data->$item);
@@ -145,21 +151,30 @@ class Settings
'streamEmailNotificationsTypeList',
'emailKeepParentTeamsEntityList',
];
$scopeList = array_keys($this->metadata->get(['entityDefs'], []));
foreach ($scopeList as $scope) {
if (!$this->metadata->get(['scopes', $scope, 'acl'])) continue;
if (!$this->acl->check($scope)) {
foreach ($entityTypeListParamList as $param) {
$list = $data->$param ?? [];
foreach ($list as $i => $item) {
if ($item === $scope) {
unset($list[$i]);
}
}
$list = array_values($list);
$data->$param = $list;
$scopeList = array_keys($this->metadata->get(['entityDefs'], []));
foreach ($scopeList as $scope) {
if (!$this->metadata->get(['scopes', $scope, 'acl'])) {
continue;
}
if ($this->acl->check($scope)) {
continue;
}
foreach ($entityTypeListParamList as $param) {
$list = $data->$param ?? [];
foreach ($list as $i => $item) {
if ($item === $scope) {
unset($list[$i]);
}
}
$list = array_values($list);
$data->$param = $list;
}
}
}
@@ -203,6 +218,7 @@ class Settings
foreach ($this->config->getSuperAdminOnlyItemList() as $item) {
$ignoreItemList[] = $item;
}
foreach ($this->config->getSuperAdminOnlySystemItemList() as $item) {
$ignoreItemList[] = $item;
}
@@ -213,7 +229,9 @@ class Settings
}
$entity = $this->entityManager->getEntity('Settings');
$entity->set($data);
$this->processValidation($entity, $data);
if (
@@ -222,13 +240,11 @@ class Settings
$this->dataManager->clearCache();
}
$this->config->setData($data);
$this->configWriter->setMultiple(
get_object_vars($data)
);
$result = $this->config->save();
if ($result === false) {
throw new Error('Cannot save settings');
}
$this->configWriter->save();
if (isset($data->personNameFormat)) {
$this->dataManager->clearCache();
@@ -237,8 +253,6 @@ class Settings
if (isset($data->defaultCurrency) || isset($data->baseCurrency) || isset($data->currencyRates)) {
$this->populateDatabaseWithCurrencyRates();
}
return $result;
}
protected function populateDatabaseWithCurrencyRates()
@@ -254,9 +268,13 @@ class Settings
unset($data->webSocketUrl);
}
if ($user->isSystem()) return;
if ($user->isSystem()) {
return;
}
if ($user->isAdmin()) return;
if ($user->isAdmin()) {
return;
}
if (
!$this->acl->checkScope('Email', 'create')
@@ -290,6 +308,7 @@ class Settings
$itemList = $this->config->getUserOnlyItemList();
$fieldDefs = $this->metadata->get(['entityDefs', 'Settings', 'fields']);
foreach ($fieldDefs as $field => $fieldParams) {
if (!empty($fieldParams['onlyUser'])) {
foreach ($this->fieldUtil->getAttributeList('Settings', $field) as $attribute) {
@@ -306,6 +325,7 @@ class Settings
$itemList = $this->config->getSystemOnlyItemList();
$fieldDefs = $this->metadata->get(['entityDefs', 'Settings', 'fields']);
foreach ($fieldDefs as $field => $fieldParams) {
if (!empty($fieldParams['onlySystem'])) {
foreach ($this->fieldUtil->getAttributeList('Settings', $field) as $attribute) {
@@ -322,6 +342,7 @@ class Settings
$itemList = $this->config->get('globalItems', []);
$fieldDefs = $this->metadata->get(['entityDefs', 'Settings', 'fields']);
foreach ($fieldDefs as $field => $fieldParams) {
if (!empty($fieldParams['global'])) {
foreach ($this->fieldUtil->getAttributeList('Settings', $field) as $attribute) {
@@ -338,7 +359,10 @@ class Settings
$fieldList = $this->fieldUtil->getEntityTypeFieldList('Settings');
foreach ($fieldList as $field) {
if (!$this->isFieldSetInData($data, $field)) continue;
if (!$this->isFieldSetInData($data, $field)) {
continue;
}
$this->processValidationField($entity, $field, $data);
}
}
@@ -352,7 +376,10 @@ class Settings
foreach ($validationList as $type) {
$value = $this->fieldUtil->getEntityTypeFieldParam('Settings', $field, $type);
if (is_null($value) && !in_array($type, $mandatoryValidationList)) continue;
if (is_null($value) && !in_array($type, $mandatoryValidationList)) {
continue;
}
if (!$fieldValidatorManager->check($entity, $field, $type, $data)) {
throw new BadRequest("Not valid data. Field: '{$field}', type: {$type}.");
@@ -363,13 +390,17 @@ class Settings
protected function isFieldSetInData($data, $field)
{
$attributeList = $this->fieldUtil->getActualAttributeList('Settings', $field);
$isSet = false;
foreach ($attributeList as $attribute) {
if (property_exists($data, $attribute)) {
$isSet = true;
break;
}
}
return $isSet;
}
}
+76 -20
View File
@@ -32,6 +32,9 @@ use Espo\Core\{
Utils\Util,
Utils\Config\ConfigFileManager,
Utils\Config,
Utils\Config\ConfigWriter,
Utils\Config\ConfigWriterFileManager,
Utils\Config\ConfigWriterHelper,
Utils\Database\Helper as DatabaseHelper,
Utils\PasswordHash,
Utils\SystemRequirements,
@@ -97,7 +100,9 @@ class Installer
protected function initialize()
{
$fileManager = new ConfigFileManager();
$config = new Config($fileManager);
$configPath = $config->getConfigPath();
if (!file_exists($configPath)) {
@@ -105,13 +110,22 @@ class Installer
}
$data = include('data/config.php');
$configWriter = new ConfigWriter(
$config,
new ConfigWriterFileManager(null, $data['defaultPermissions'] ?? null),
new ConfigWriterHelper()
);
$defaultData = $config->getDefaults();
//save default data if not exists, check by keys
if (!Util::arrayKeysExists(array_keys($defaultData), $data)) {
$defaultData = array_replace_recursive($defaultData, $data);
$config->set($defaultData);
$config->save();
$configWriter->setMultiple($defaultData);
$configWriter->save();
}
}
@@ -130,6 +144,11 @@ class Installer
return $this->app->getContainer()->get('config');
}
public function createConfigWriter() : ConfigWriter
{
return $this->app->getContainer()->get('injectableFactory')->create(ConfigWriter::class);
}
protected function getSystemHelper()
{
return $this->systemHelper;
@@ -154,7 +173,9 @@ class Installer
{
if (!isset($this->passwordHash)) {
$config = $this->getConfig();
$this->passwordHash = new PasswordHash($config);
$configWriter = $this->createConfigWriter();
$this->passwordHash = new PasswordHash($config, $configWriter);
}
return $this->passwordHash;
@@ -192,8 +213,10 @@ class Installer
if (!isset($this->language)) {
try {
$this->language = $this->app->getContainer()->get('defaultLanguage');
} catch (Throwable $e) {
}
catch (Throwable $e) {
echo "Error: " . $e->getMessage();
$GLOBALS['log']->error($e->getMessage());
die;
@@ -227,6 +250,7 @@ class Installer
public function getSystemRequirementList($type, $requiredOnly = false, array $additionalData = null)
{
$systemRequirementManager = new SystemRequirements($this->app->getContainer());
return $systemRequirementManager->getRequiredListByType($type, $requiredOnly, $additionalData);
}
@@ -240,9 +264,11 @@ class Installer
catch (Exception $e) {
if ($isCreateDatabase && $e->getCode() == '1049') {
$modParams = $params;
unset($modParams['dbname']);
$pdo = $this->getDatabaseHelper()->createPdoConnection($modParams);
$pdo->query("CREATE DATABASE IF NOT EXISTS `". $params['dbname'] ."`");
return $this->checkDatabaseConnection($params, false);
@@ -279,25 +305,31 @@ class Installer
'siteUrl' => !empty($saveData['siteUrl']) ? $saveData['siteUrl'] : $this->getSystemHelper()->getBaseUrl(),
'passwordSalt' => $this->getPasswordHash()->generateSalt(),
'cryptKey' => $this->getContainer()->get('crypt')->generateKey(),
'hashSecretKey' => \Espo\Core\Utils\Util::generateSecretKey(),
'hashSecretKey' => Util::generateSecretKey(),
];
if (empty($saveData['defaultPermissions']['user'])) {
$saveData['defaultPermissions']['user'] = $this->getFileManager()->getPermissionUtils()->getDefaultOwner(true);
$saveData['defaultPermissions']['user'] = $this->getFileManager()
->getPermissionUtils()
->getDefaultOwner(true);
}
if (empty($saveData['defaultPermissions']['group'])) {
$saveData['defaultPermissions']['group'] = $this->getFileManager()->getPermissionUtils()->getDefaultGroup(true);
$saveData['defaultPermissions']['group'] = $this->getFileManager()
->getPermissionUtils()
->getDefaultGroup(true);
}
if (!empty($saveData['defaultPermissions']['user'])) {
$data['defaultPermissions']['user'] = $saveData['defaultPermissions']['user'];
}
if (!empty($saveData['defaultPermissions']['group'])) {
$data['defaultPermissions']['group'] = $saveData['defaultPermissions']['group'];
}
$data = array_merge($data, $initData);
$result = $this->saveConfig($data);
return $result;
@@ -305,12 +337,13 @@ class Installer
public function saveConfig($data)
{
$config = $this->app->getContainer()->get('config');
$configWriter = $this->createConfigWriter();
$config->set($data);
$result = $config->save();
$configWriter->setMultiple($data);
return $result;
$configWriter->save();
return true;
}
public function buildDatabase()
@@ -446,13 +479,16 @@ class Installer
);
$data = array_intersect_key($preferences, array_flip($permittedSettingList));
if (empty($data)) {
return true;
}
$entity = $this->getEntityManager()->getEntity('Preferences', '1');
if ($entity) {
$entity->set($data);
return $this->getEntityManager()->saveEntity($entity);
}
@@ -479,12 +515,16 @@ class Installer
/** END: afterInstall scripts */
$installerConfig = $this->getInstallerConfig();
$installerConfig->set('isInstalled', true);
$installerConfig->save();
$config = $this->app->getContainer()->get('config');
$config->set('isInstalled', true);
$result &= $config->save();
$configWriter = $this->createConfigWriter();
$configWriter->set('isInstalled', true);
$configWriter->save();
return $result;
}
@@ -505,10 +545,12 @@ class Installer
switch ($fieldName) {
case 'defaultCurrency':
$settingDefs['defaultCurrency']['options'] = $this->getCurrencyList();
break;
case 'language':
$settingDefs['language']['options'] = $this->getLanguageList(false);
break;
}
@@ -526,8 +568,11 @@ class Installer
$defaultSettings = $this->getDefaultSettings();
$normalizedParams = [];
foreach ($params as $name => $value) {
if (!isset($defaultSettings[$name])) continue;
if (!isset($defaultSettings[$name])) {
continue;
}
$paramDefs = $defaultSettings[$name];
$paramType = isset($paramDefs['type']) ? $paramDefs['type'] : 'varchar';
@@ -539,23 +584,32 @@ class Installer
case 'enum':
if (isset($paramDefs['options']) && array_key_exists($value, $paramDefs['options'])) {
$normalizedParams[$name] = $value;
} else if (array_key_exists('default', $paramDefs)) {
$normalizedParams[$name] = $paramDefs['default'];
$GLOBALS['log']->warning('Incorrect value ['. $value .'] for Settings parameter ['. $name .']. Use default value ['. $paramDefs['default'] .'].');
}
else if (array_key_exists('default', $paramDefs)) {
$normalizedParams[$name] = $paramDefs['default'];
$GLOBALS['log']->warning(
'Incorrect value ['. $value .'] for Settings parameter ['. $name .']. ' .
'Use default value ['. $paramDefs['default'] .'].'
);
}
break;
case 'bool':
$normalizedParams[$name] = (bool) $value;
break;
case 'int':
$normalizedParams[$name] = (int) $value;
break;
case 'varchar':
default:
$normalizedParams[$name] = $value;
break;
}
}
@@ -573,7 +627,8 @@ class Installer
}
if ($optionLabel == $name) {
$optionLabel = array();
$optionLabel = [];
foreach ($settingDefs['options'] as $key => $value) {
$optionLabel[$value] = $value;
}
@@ -603,7 +658,8 @@ class Installer
try {
$result &= $sth->execute();
} catch (Exception $e) {
}
catch (Exception $e) {
$GLOBALS['log']->warning('Error executing the query: ' . $query);
}
@@ -0,0 +1,64 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\integration\Espo\Currency;
use Espo\Core\{
Utils\Config\ConfigWriter,
};
class CurrencyTest extends \tests\integration\Core\BaseTestCase
{
public function testSetCurrencyRates()
{
$app = $this->createApplication();
$configWriter = $app->getContainer()->get('injectableFactory')->create(ConfigWriter::class);
$configWriter->set('currencyList', ['USD', 'EUR']);
$configWriter->set('defaultCurrency', 'USD');
$configWriter->set('baseCurrency', 'USD');
$configWriter->set('currencyRates', [
'EUR' => 1.2,
]);
$configWriter->save();
$service = $app->getContainer()->get('serviceFactory')->create('CurrencyRate');
$newRates = $service->set(
(object) [
'EUR' => 1.3,
]
);
$this->assertEquals(1.3, $newRates->EUR);
}
}
@@ -0,0 +1,124 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\{
Utils\Config,
Utils\Config\ConfigWriter,
Utils\Config\ConfigWriterFileManager,
Utils\Config\ConfigWriterHelper,
};
class ConfigWriterTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
$this->fileManager = $this->createMock(ConfigWriterFileManager::class);
$this->config = $this->createMock(Config::class);
$this->helper = $this->createMock(ConfigWriterHelper::class);
$this->configWriter = new ConfigWriter($this->config, $this->fileManager, $this->helper);
$this->configPath = 'somepath';
$this->config
->expects($this->any())
->method('getConfigPath')
->willReturn($this->configPath);
}
public function testSave1()
{
$this->configWriter->set('k1', 'v1');
$this->configWriter->setMass([
'k2' => 'v2',
'k3' => 'v3',
]);
$this->configWriter->remove('k4');
$previousData = [
'k3' => 'e3',
'k4' => 'e4',
'microtime' => 0.0,
'cacheTimestamp' => 0,
];
$newData = [
'k1' => 'v1',
'k2' => 'v2',
'k3' => 'v3',
'microtime' => 1.0,
'cacheTimestamp' => 1,
];
$this->helper
->expects($this->once())
->method('generateMicrotime')
->willReturn(1.0);
$this->helper
->expects($this->once())
->method('generateCacheTimestamp')
->willReturn(1);
$this->config
->expects($this->once())
->method('update');
$this->fileManager
->expects($this->at(0))
->method('isFile')
->with($this->configPath)
->willReturn(true);
$this->fileManager
->expects($this->at(1))
->method('getPhpContents')
->with($this->configPath)
->willReturn($previousData);
$this->fileManager
->expects($this->at(2))
->method('putPhpContents')
->with($this->configPath, $newData);
$this->fileManager
->expects($this->at(3))
->method('getPhpContents')
->with($this->configPath)
->willReturn($previousData);
$this->configWriter->save();
}
}