Compare commits

...

3 Commits

Author SHA1 Message Date
Eric Espie
a82aa3c666 Configuration in json files by module 2025-09-30 12:25:50 +02:00
Eric Espie
2a1fab6eb3 Configuration in json files by module 2025-09-30 10:04:24 +02:00
Anne-Cath
778c16da86 N°8663 - DoCheckToWrite fonction implode() 2025-09-29 14:33:52 +02:00
23 changed files with 216 additions and 61 deletions

View File

@@ -20,6 +20,8 @@
*/
use Combodo\iTop\Core\Configuration\ConfigManager;
define('ITOP_APPLICATION', 'iTop');
define('ITOP_APPLICATION_SHORT', 'iTop');
@@ -2096,76 +2098,21 @@ class Config
{
$this->CheckFile('configuration', $sConfigFile);
$sConfigCode = trim(file_get_contents($sConfigFile));
// Variables created when doing an eval() on the config file
/** @var array $MySettings */
$MySettings = null;
/** @var array $MyModuleSettings */
$MyModuleSettings = null;
/** @var array $MyModules */
$MyModules = null;
// This does not work on several lines
// preg_match('/^<\\?php(.*)\\?'.'>$/', $sConfigCode, $aMatches)...
// So, I've implemented a solution suggested in the PHP doc (search for phpWrapper)
try
{
ob_start();
eval('?'.'>'.trim($sConfigCode));
$sNoise = trim(ob_get_contents());
ob_end_clean();
}
catch (Error $e)
{
// PHP 7
throw new ConfigException('Error in configuration file',
array('file' => $sConfigFile, 'error' => $e->getMessage().' at line '.$e->getLine()));
}
catch (Exception $e)
{
// well, never reach in case of parsing error :-(
// will be improved in PHP 6 ?
throw new ConfigException('Error in configuration file',
array('file' => $sConfigFile, 'error' => $e->getMessage()));
}
if (strlen($sNoise) > 0)
{
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
throw new ConfigException('Syntax error in configuration file',
array('file' => $sConfigFile, 'error' => '<tt>'.utils::EscapeHtml($sNoise, ENT_QUOTES).'</tt>'));
}
if (!isset($MySettings) || !is_array($MySettings))
{
throw new ConfigException('Missing array in configuration file',
array('file' => $sConfigFile, 'expected' => '$MySettings'));
}
if (!array_key_exists('addons', $MyModules))
{
throw new ConfigException('Missing item in configuration file',
array('file' => $sConfigFile, 'expected' => '$MyModules[\'addons\']'));
}
if (!array_key_exists('user rights', $MyModules['addons']))
{
// Add one, by default
$MyModules['addons']['user rights'] = '/addons/userrights/userrightsnull.class.inc.php';
}
[$MySettings, $MyModuleSettings, $MyModules] = ConfigManager::GetInstance()->Load($sConfigFile);
$this->m_aAddons = $MyModules['addons'];
foreach ($MySettings as $sPropCode => $rawvalue)
foreach ($MySettings as $sPropCode => $rawValue)
{
if ($this->IsProperty($sPropCode))
{
if (is_string($rawvalue))
if (is_string($rawValue))
{
$value = trim($rawvalue);
$value = trim($rawValue);
}
else
{
$value = $rawvalue;
$value = $rawValue;
}
$this->Set($sPropCode, $value, $sConfigFile, true);
}

View File

@@ -745,6 +745,17 @@ abstract class LogAPI
static::Log(self::LEVEL_TRACE, $sMessage, $sChannel, $aContext);
}
public static function Exception(string $sMessage, throwable $previous): void
{
if (is_null($previous)) {
$previous = new Exception('');
}
$aContext['error'] = $previous->getMessage();
$aContext['stack'] = $previous->getTraceAsString();
static::Error($sMessage, static::CHANNEL_DEFAULT, $aContext);
}
/**
* @throws \ConfigException if log wrongly configured
*/

View File

@@ -61,6 +61,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
'Portal:Form:Close:Warning' => 'Opravdu chcete opustit tento formulář? Data vložená do formuláře budou ztracena ',
'Portal:Error:ObjectCannotBeCreated' => 'Chyba: objekt nelze vytvořit. Před opětovným odesláním tohoto formuláře zkontrolujte související objekty a přílohy.',
'Portal:Error:ObjectCannotBeUpdated' => 'Chyba: objekt nelze vytvořit. Před opětovným odesláním tohoto formuláře zkontrolujte související objekty a přílohy.',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -61,6 +61,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
'Portal:Form:Close:Warning' => 'Do you want to leave this form? Data entered may be lost~~',
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -61,6 +61,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Portal:Form:Close:Warning' => 'Soll diese Eingabemaske verlassen werden? Eingegebene Daten werden nicht gespeichert.',
'Portal:Error:ObjectCannotBeCreated' => 'Error: Objekt kann nicht erzeugt werden. Prüfen Sie verknüpfte Objekte und Anhänge bevor Sie dieses Formular erneut abschicken.',
'Portal:Error:ObjectCannotBeUpdated' => 'Error: Objekt kann nicht geupdated werden. Prüfen Sie verknüpfte Objekte und Anhänge bevor Sie dieses Formular erneut abschicken.',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -70,6 +70,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Portal:Form:Close:Warning' => 'Do you want to leave this form? Data entered may be lost',
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting this form again.',
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting this form again.',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s',
));
// UserProfile brick

View File

@@ -57,6 +57,7 @@ Dict::Add('EN GB', 'British English', 'British English', array(
'Portal:Form:Close:Warning' => 'Do you want to leave this form? Data entered may be lost',
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting this form again.',
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting this form again.',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s',
));
// UserProfile brick

View File

@@ -59,6 +59,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Portal:Form:Close:Warning' => '¿Desea abandorar este formulario? Datos modificados se perderan',
'Portal:Error:ObjectCannotBeCreated' => 'Error: no se puede crear el objeto. Verifique los objetos asociados y archivos adjuntos antes de enviar nuevamente este formulario.',
'Portal:Error:ObjectCannotBeUpdated' => 'Error: no se puede actualizar el objeto. Verifique los objetos asociados y archivos adjuntos antes de enviar nuevamente este formulario.',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -61,6 +61,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Portal:Form:Close:Warning' => 'Voulez-vous quitter ce formulaire ? Les données saisies seront perdues',
'Portal:Error:ObjectCannotBeCreated' => 'Erreur: L\'objet n\'a pas été créé. Vérifiez les objets liés et les attachements avant de soumettre à nouveau le formulaire.',
'Portal:Error:ObjectCannotBeUpdated' => 'Erreur: L\'objet n\'a pas été modifié. Vérifiez les objets liés et les attachements avant de soumettre à nouveau le formulaire.',
'Portal:Error:CheckToWriteFailed' => 'Erreur durant la validation du champ \'%1$s\' : %2$s',
));
// UserProfile brick

View File

@@ -61,6 +61,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'Portal:Form:Close:Warning' => 'Szeretné elhagyni ezt az űrlapot? A megadott adatok elveszhetnek',
'Portal:Error:ObjectCannotBeCreated' => 'Hiba: az objektum nem hozható létre. Ellenőrizze a kapcsolódó objektumokat és mellékleteket, mielőtt újra elküldi ezt az űrlapot.',
'Portal:Error:ObjectCannotBeUpdated' => 'Hiba: az objektum nem frissíthető. Ellenőrizze a kapcsolódó objektumokat és mellékleteket, mielőtt újra elküldi ezt az űrlapot.',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -61,6 +61,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
'Portal:Form:Close:Warning' => 'Do you want to leave this form? Data entered may be lost~~',
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -61,6 +61,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
'Portal:Form:Close:Warning' => 'Do you want to leave this form? Data entered may be lost~~',
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -62,6 +62,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'Portal:Form:Close:Warning' => 'Ben je zeker dat je dit venster wil sluiten? Ingevoerde gegevens kunnen verloren gaan.',
'Portal:Error:ObjectCannotBeCreated' => 'Fout: object kan niet worden aangemaakt. Kijk verwante objecten en bijlagen na vooraleer dit formulier opnieuw te versturen.',
'Portal:Error:ObjectCannotBeUpdated' => 'Fout: object kan niet worden aangepast. Kijk verwante objecten en bijlagen na vooraleer dit formulier opnieuw te versturen.',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -61,6 +61,7 @@ Dict::Add('PL PL', 'Polish', 'Polski', array(
'Portal:Form:Close:Warning' => 'Chcesz opuścić ten formularz? Wprowadzone dane mogą zostać utracone',
'Portal:Error:ObjectCannotBeCreated' => 'Błąd: nie można utworzyć obiektu. Sprawdź powiązane obiekty i załączniki przed ponownym przesłaniem tego formularza.',
'Portal:Error:ObjectCannotBeUpdated' => 'Błąd: nie można zaktualizować obiektu. Sprawdź powiązane obiekty i załączniki przed ponownym przesłaniem tego formularza.',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -61,6 +61,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'Portal:Form:Close:Warning' => 'Você deseja abandonar esta página? Os dados digitados podem ser perdidos',
'Portal:Error:ObjectCannotBeCreated' => 'Erro: objeto não pode ser criado. Verifique os objetos e anexos associados antes de enviar novamente este formulário',
'Portal:Error:ObjectCannotBeUpdated' => 'Erro: objeto não pode ser atualizado. Verifique os objetos e anexos associados antes de enviar novamente este formulário',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -62,6 +62,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
'Portal:Form:Close:Warning' => 'Вы действительно хотите закрыть эту форму? Введённые данные могут быть утеряны.',
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -61,6 +61,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
'Portal:Form:Close:Warning' => 'Do you want to leave this form? Data entered may be lost~~',
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -61,6 +61,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
'Portal:Form:Close:Warning' => 'Do you want to leave this form? Data entered may be lost~~',
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting this form again.~~',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -70,6 +70,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Portal:Form:Close:Warning' => '确定要离开表单吗? 已输入数据会丢失',
'Portal:Error:ObjectCannotBeCreated' => '错误: 无法创建对象. 请在再次提交表单前检查相关对象和附件.',
'Portal:Error:ObjectCannotBeUpdated' => '错误: 无法更新对象. 请在再次提交表单前检查相关对象和附件.',
'Portal:Error:CheckToWriteFailed' => 'Error during validation of field \'%1$s\': %2$s~~',
));
// UserProfile brick

View File

@@ -337,7 +337,18 @@ class ObjectFormHandlerHelper
);
}
} else {
throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, implode('<br/>', $aFormData['validation']['messages']['error']['_main']));
$sErrorMessages = '';
foreach ($aFormData['validation']['messages']['error'] as $sFieldId => $aMessages) {
if ($sFieldId == '_main') {
$sErrorMessages .= implode(' - ', $aFormData['validation']['messages']['error']['_main']);
} else {
$oObj = $oFormManager->GetObject();
$sLabel = $oObj->GetLabel($sFieldId);
$sErrorMessages .= Dict::Format('Portal:Error:CheckToWriteFailed', $sLabel, (is_array($aMessages) ? implode(' - ', $aMessages) : $aMessages));
}
}
throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, $sErrorMessages);
}
break;

View File

@@ -414,6 +414,7 @@ return array(
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderFactory' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php',
'Combodo\\iTop\\Core\\CMDBChange\\CMDBChangeOrigin' => $baseDir . '/sources/Core/CMDBChange/CMDBChangeOrigin.php',
'Combodo\\iTop\\Core\\Configuration\\ConfigManager' => $baseDir . '/sources/Core/Configuration/ConfigManager.php',
'Combodo\\iTop\\Core\\DbConnectionWrapper' => $baseDir . '/core/DbConnectionWrapper.php',
'Combodo\\iTop\\Core\\Email\\EMailSymfony' => $baseDir . '/sources/Core/Email/EmailSymfony.php',
'Combodo\\iTop\\Core\\Email\\EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php',

View File

@@ -779,6 +779,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderFactory' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php',
'Combodo\\iTop\\Core\\CMDBChange\\CMDBChangeOrigin' => __DIR__ . '/../..' . '/sources/Core/CMDBChange/CMDBChangeOrigin.php',
'Combodo\\iTop\\Core\\Configuration\\ConfigManager' => __DIR__ . '/../..' . '/sources/Core/Configuration/ConfigManager.php',
'Combodo\\iTop\\Core\\DbConnectionWrapper' => __DIR__ . '/../..' . '/core/DbConnectionWrapper.php',
'Combodo\\iTop\\Core\\Email\\EMailSymfony' => __DIR__ . '/../..' . '/sources/Core/Email/EmailSymfony.php',
'Combodo\\iTop\\Core\\Email\\EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php',

View File

@@ -0,0 +1,167 @@
<?php
/**
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\Configuration;
use ConfigException;
use Error;
use Exception;
use IssueLog;
use SetupUtils;
use utils;
class ConfigManager
{
private static ConfigManager $oInstance;
private ?array $aMySettings = null;
private ?array $aMyModuleSettings = null;
private ?array $aMyModules = null;
protected function __construct()
{
}
final public static function GetInstance(): ConfigManager
{
if (!isset(static::$oInstance)) {
static::$oInstance = new ConfigManager();
}
return static::$oInstance;
}
/**
* Load iTop configuration from regular php file and json files
* @param string $sConfigFile
*
* @return array returns [$MySettings, $MyModuleSettings, $MyModules]
* @throws \ConfigException
*/
public function Load(string $sConfigFile): array
{
$sConfigCode = trim(file_get_contents($sConfigFile));
if (!is_null($this->aMySettings)) {
return [$this->aMySettings, $this->aMyModuleSettings, $this->aMyModules];
}
// Variables created when doing an eval() on the config file
/** @var array $MySettings */
$MySettings = null;
/** @var array $MyModuleSettings */
$MyModuleSettings = [];
/** @var array $MyModules */
$MyModules = null;
// This does not work on several lines
// preg_match('/^<\\?php(.*)\\?'.'>$/', $sConfigCode, $aMatches)...
// So, I've implemented a solution suggested in the PHP doc (search for phpWrapper)
try
{
ob_start();
eval('?'.'>'.trim($sConfigCode));
$sNoise = trim(ob_get_contents());
ob_end_clean();
}
catch (Error $e)
{
// PHP 7
throw new ConfigException('Error in configuration file',
array('file' => $sConfigFile, 'error' => $e->getMessage().' at line '.$e->getLine()));
}
catch (Exception $e)
{
// well, never reach in case of parsing error :-(
// will be improved in PHP 6 ?
throw new ConfigException('Error in configuration file',
array('file' => $sConfigFile, 'error' => $e->getMessage()));
}
if (strlen($sNoise) > 0)
{
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
throw new ConfigException('Syntax error in configuration file',
array('file' => $sConfigFile, 'error' => '<tt>'.utils::EscapeHtml($sNoise, ENT_QUOTES).'</tt>'));
}
if (!is_array($MySettings))
{
throw new ConfigException('Missing array in configuration file',
array('file' => $sConfigFile, 'expected' => '$MySettings'));
}
if (!array_key_exists('addons', $MyModules))
{
throw new ConfigException('Missing item in configuration file',
array('file' => $sConfigFile, 'expected' => '$MyModules[\'addons\']'));
}
if (!array_key_exists('user rights', $MyModules['addons']))
{
// Add one, by default
$MyModules['addons']['user rights'] = '/addons/userrights/userrightsnull.class.inc.php';
}
$this->aMySettings = $MySettings;
$this->aMyModuleSettings = $MyModuleSettings;
$this->aMyModules = $MyModules;
$this->LoadModulesConfig(dirname($sConfigFile));
return [$this->aMySettings, $this->aMyModuleSettings, $this->aMyModules];
}
private function LoadModulesConfig(string $sConfigDir): void
{
foreach ($this->ListConfigFiles($sConfigDir) as $sJSONModuleFiles) {
$aConf = json_decode(file_get_contents($sJSONModuleFiles), true);
if (is_null($aConf)) {
IssueLog::Exception('Error reading configuration file: '.$sJSONModuleFiles, new Exception());
continue;
}
if (is_array($aConf['MySettings'] ?? null)) {
$this->aMySettings = array_merge($this->aMySettings, $aConf['MySettings']);
}
if (is_array($aConf['MyModuleSettings'] ?? null)) {
$this->aMyModuleSettings = array_merge($this->aMyModuleSettings, $aConf['MyModuleSettings']);
}
if (is_array($aConf['MyModules'] ?? null)) {
$this->aMyModules = array_merge($this->aMyModules, $aConf['MyModules']);
}
}
}
/**
* Find all json files under $sRootDir
* @param string $sRootDir
*
* @return array
*/
private function ListConfigFiles(string $sRootDir): array
{
$aConfigFiles = [];
while($aDirs = glob($sRootDir . '/*', GLOB_ONLYDIR)) {
$sRootDir .= '/*';
foreach ($aDirs as $sConfDir) {
$aConfigFiles = array_merge($aConfigFiles, glob($sConfDir.'/*.json'));
}
}
return $aConfigFiles;
}
public function SaveModuleConfig(string $sModule, array $MyModuleSettings, array $MySettings = null, array $MyModules = null): void
{
$sConfigFile = dirname(utils::GetConfigFilePath()).'/modules/'.$sModule.'/'.$sModule.'.json';
SetupUtils::builddir(dirname($sConfigFile));
$aConf = ['MyModuleSettings' => $MyModuleSettings];
if (is_array($MySettings)) {
$aConf['MySettings'] = $MySettings;
}
if (is_array($MyModules)) {
$aConf['MyModules'] = $MyModules;
}
file_put_contents($sConfigFile, json_encode($aConf, JSON_PRETTY_PRINT));
}
}