mirror of
https://github.com/Combodo/iTop.git
synced 2026-06-23 08:16:35 +02:00
Compare commits
14 Commits
feature/95
...
feature/96
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38533ffcd2 | ||
|
|
38b5414ab9 | ||
|
|
fd518a7eaa | ||
|
|
b557cb6eff | ||
|
|
eedcdf32fe | ||
|
|
2321d89981 | ||
|
|
3bf47d1e8c | ||
|
|
feb89650e4 | ||
|
|
b672dfc9f2 | ||
|
|
eef06ee032 | ||
|
|
d6ce202fa8 | ||
|
|
1c38d989e4 | ||
|
|
e65542f978 | ||
|
|
829857ec85 |
@@ -1917,7 +1917,9 @@ SQL;
|
||||
$aResponseHeaders[$sName] = $sValue;
|
||||
}
|
||||
}
|
||||
curl_close($ch);
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,16 @@
|
||||
*
|
||||
*/
|
||||
|
||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Error;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
define('ITOP_APPLICATION', 'iTop');
|
||||
define('ITOP_APPLICATION_SHORT', 'iTop');
|
||||
|
||||
@@ -84,7 +94,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
|
||||
* @see utils::GetConfig() to load config from the current env, if metamodel is not loaded
|
||||
* @package iTopORM
|
||||
*/
|
||||
class Config
|
||||
#[AllowDynamicProperties] class Config
|
||||
{
|
||||
//protected $m_bIsLoaded = false;
|
||||
protected $m_sFile = '';
|
||||
@@ -2010,17 +2020,24 @@ class Config
|
||||
*/
|
||||
protected $m_sAppSecret;
|
||||
|
||||
private bool $bPreserveComments;
|
||||
private bool $bLegacyEvaluation;
|
||||
|
||||
/**
|
||||
* Config constructor.
|
||||
*
|
||||
* @param string|null $sConfigFile
|
||||
* @param bool $bLoadConfig
|
||||
* @param bool $bPreserveComments
|
||||
* @param bool $bLegacyEvaluation
|
||||
*
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function __construct($sConfigFile = null, $bLoadConfig = true)
|
||||
public function __construct($sConfigFile = null, $bLoadConfig = true, bool $bPreserveComments = false, bool $bLegacyEvaluation = false)
|
||||
{
|
||||
$this->bPreserveComments = $bPreserveComments;
|
||||
$this->bLegacyEvaluation = $bLegacyEvaluation;
|
||||
$this->oConfigPlaceholdersResolver = new ConfigPlaceholdersResolver();
|
||||
|
||||
$this->m_sFile = $sConfigFile;
|
||||
@@ -2075,6 +2092,7 @@ class Config
|
||||
}
|
||||
$this->Set('app_root_url', $sAppRootUrl);
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2121,9 +2139,39 @@ class Config
|
||||
// So, I've implemented a solution suggested in the PHP doc (search for phpWrapper)
|
||||
try {
|
||||
ob_start();
|
||||
/*file_put_contents(APPROOT . '/bugged-conf.php', '?'.'>'.trim($sConfigCode));
|
||||
$e = new \Exception('');
|
||||
var_dump($e->getTraceAsString());*/
|
||||
eval('?'.'>'.trim($sConfigCode));
|
||||
$sNoise = trim(ob_get_contents());
|
||||
ob_end_clean();
|
||||
|
||||
if ($this->bPreserveComments || ! $this->bLegacyEvaluation) {
|
||||
try {
|
||||
$oParser = (new ParserFactory())->createForNewestSupportedVersion();
|
||||
foreach ($oParser->parse($sConfigCode) as $oNode) {
|
||||
if ($oNode instanceof \PhpParser\Node\Stmt\Expression) {
|
||||
/** @var \PhpParser\Node\Stmt\Expression $oNode */
|
||||
$oExpr = $oNode->expr;
|
||||
if ($oExpr instanceof Assign) {
|
||||
/** @var Assign $oExpr */
|
||||
$oVar = $oExpr->var;
|
||||
if ($oVar instanceof Variable && $oVar->name === "MyModuleSettings") {
|
||||
if ($oExpr->expr instanceof Array_) {
|
||||
$oPhpExpressionEvaluator = new PhpExpressionEvaluator();
|
||||
if (! $this->bLegacyEvaluation) {
|
||||
$aArrayWithComments = $oPhpExpressionEvaluator->GetArray($oExpr->expr, $this->bPreserveComments);
|
||||
$MyModuleSettings = array_replace_recursive($aArrayWithComments, $MyModuleSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Error $e) {
|
||||
var_dump($e);
|
||||
}
|
||||
}
|
||||
} catch (Error $e) {
|
||||
// PHP 7
|
||||
throw new ConfigException(
|
||||
@@ -2714,6 +2762,15 @@ class Config
|
||||
foreach ($this->m_aModuleSettings as $sModule => $aProperties) {
|
||||
fwrite($hFile, "\t'$sModule' => array (\n");
|
||||
foreach ($aProperties as $sProperty => $value) {
|
||||
if (is_string($value) && false !== strpos($value, 'PhpParserComment')) {
|
||||
$value = preg_replace(
|
||||
["/.*StartPhpParserComment/", "/EndPhpParserComment/"],
|
||||
['', ''],
|
||||
$value
|
||||
);
|
||||
fwrite($hFile, "\t\t$value\n");
|
||||
continue;
|
||||
}
|
||||
$sNiceExport = self::PrettyVarExport($this->oItopConfigParser->GetVarValue('MyModuleSettings', $sProperty), $value, "\t\t");
|
||||
fwrite($hFile, "\t\t'$sProperty' => $sNiceExport,\n");
|
||||
}
|
||||
@@ -2883,7 +2940,7 @@ class Config
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty format a var_export'ed value so that (if possible) the identation is preserved on every line
|
||||
* Pretty format a var_export'ed value so that (if possible) the indentation is preserved on every line
|
||||
*
|
||||
* @param array $aParserValue
|
||||
* @param mixed $value The value to export
|
||||
@@ -2900,12 +2957,19 @@ class Config
|
||||
}
|
||||
|
||||
$sExport = var_export($value, true);
|
||||
if (strpos($sExport, 'PhpParserComment')) {
|
||||
$sExport = preg_replace(
|
||||
["/.*StartPhpParserComment/", "/EndPhpParserComment',/"],
|
||||
['', ''],
|
||||
$sExport
|
||||
);
|
||||
}
|
||||
$sNiceExport = str_replace(["\r\n", "\n", "\r"], "\n".$sIndentation, trim($sExport));
|
||||
if (!$bForceIndentation) {
|
||||
/** @var array $aImported */
|
||||
$aImported = null;
|
||||
eval('$aImported='.$sNiceExport.';');
|
||||
// Check if adding the identations at the beginning of each line
|
||||
// Check if adding the indentations at the beginning of each line
|
||||
// did not modify the values (in case of a string containing a line break)
|
||||
if ($aImported != $value) {
|
||||
$sNiceExport = $sExport;
|
||||
@@ -2914,7 +2978,6 @@ class Config
|
||||
|
||||
return $sNiceExport;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ConfigPlaceholdersResolver
|
||||
|
||||
@@ -340,13 +340,14 @@ class ormDocument
|
||||
* @param string $sContentDisposition Either 'inline' or 'attachment'
|
||||
* @param string $sSecretField The attcode of the field containing a "secret" to be provided in order to retrieve the file
|
||||
* @param string $sSecretValue The value of the secret to be compared with the value of the attribute $sSecretField
|
||||
* @param bool $bAllowAllData If true, no rights filtering is applied
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null)
|
||||
public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null, $bAllowAllData = false)
|
||||
{
|
||||
try {
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, false);
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, $bAllowAllData);
|
||||
if (!is_object($oObj)) {
|
||||
// If access to the document is not granted, check if the access to the host object is allowed
|
||||
$oObj = MetaModel::GetObject($sClass, $id, false, true);
|
||||
|
||||
@@ -26,6 +26,7 @@ use Dict;
|
||||
use Exception;
|
||||
use MetaModel;
|
||||
use RunTimeEnvironment;
|
||||
use SecurityException;
|
||||
use SetupUtils;
|
||||
use utils;
|
||||
|
||||
@@ -83,10 +84,11 @@ class DataFeatureRemovalController extends Controller
|
||||
{
|
||||
$aParams = [];
|
||||
|
||||
if (SetupUtils::IsSessionSetupTokenValid()) {
|
||||
try {
|
||||
//from setup wizard/mtp
|
||||
SetupUtils::CheckSetupToken();
|
||||
SetupUtils::EraseSetupToken();
|
||||
} else {
|
||||
} catch (SecurityException $e) {
|
||||
//from same module
|
||||
$this->ValidateTransactionId();
|
||||
}
|
||||
@@ -100,6 +102,7 @@ class DataFeatureRemovalController extends Controller
|
||||
'removed_extensions' => '[]',
|
||||
'extensions_not_uninstallable' => '[]',
|
||||
'copy_setup_files' => 1,
|
||||
'return_button_label' => '',
|
||||
];
|
||||
|
||||
$aHiddenInputs = [];
|
||||
@@ -108,6 +111,10 @@ class DataFeatureRemovalController extends Controller
|
||||
}
|
||||
$aParams['aHiddenInputs'] = $aHiddenInputs;
|
||||
|
||||
if ($aHiddenInputs['return_button_label'] !== '') {
|
||||
$aParams['sReturnButtonURL'] = utils::GetAbsoluteUrlModulePage('itsm-designer-connector', 'launch.php');
|
||||
}
|
||||
|
||||
$aAddedExtensions = json_decode($aHiddenInputs['added_extensions'], true);
|
||||
|
||||
$aRemovedExtensions = json_decode($aHiddenInputs['removed_extensions'], true);
|
||||
@@ -179,6 +186,10 @@ class DataFeatureRemovalController extends Controller
|
||||
$aParams['aSetupParams']["_params[$sInputName]"] = $sInputValue;
|
||||
}
|
||||
|
||||
if ($aHiddenInputs['return_button_label'] !== '') {
|
||||
$aParams['sReturnButtonURL'] = utils::GetAbsoluteUrlModulePage('itsm-designer-connector', 'launch.php');
|
||||
}
|
||||
|
||||
[$aParams['aDeletionPlanSummary'], $aParams['iQueryCount'], $aParams['bDeletionPossible']] = $this->GetDeletionPlanSummaryTable($aGetRemovedClasses);
|
||||
[$aParams['aDeletionExecutionSummary'], $aParams['bHasDeletionExecution']] = $this->GetExecutionSummaryTable();
|
||||
$aParams['bDeletionNeeded'] = ($aParams['iQueryCount'] > 0);
|
||||
|
||||
@@ -87,11 +87,15 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
|
||||
{% UIInput ForHidden { sName:'operation', sValue:'Main'} %}
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:BackToMain'|dict_s, sName:'btn_back', sId:'btn_back', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
{% if aHiddenInputs.return_button_label != '' %}
|
||||
{% UIButton ForAlternativeSecondaryAction { sLabel:aHiddenInputs.return_button_label, OnClickJsCode: 'window.location.href="' ~ sReturnButtonURL ~ '"'} %}
|
||||
{% else %}
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
|
||||
{% UIInput ForHidden { sName:'operation', sValue:'Main'} %}
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:BackToMain'|dict_s, sName:'btn_back', sId:'btn_back', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
{% endif %}
|
||||
{% EndUIPanel %}
|
||||
|
||||
@@ -26,6 +26,7 @@ SetupWebPage::AddModule(
|
||||
],
|
||||
'data.struct' => [
|
||||
'data/en_us.data.itop-brand.xml',
|
||||
'data/en_us.data.itop-networkdevicetype.xml',
|
||||
'data/en_us.data.itop-osfamily.xml',
|
||||
'data/en_us.data.itop-osversion.xml',
|
||||
],
|
||||
@@ -101,8 +102,6 @@ if (!class_exists('ConfigMgmtInstaller')) {
|
||||
*/
|
||||
public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
|
||||
{
|
||||
// load localized data for NetworkDeviceType
|
||||
static::LoadLocalizedDataOnCrossingVersion($oConfiguration, $sPreviousVersion, $sCurrentVersion,'3.3.0',__DIR__."/data/{{language_code}}.data.itop-networkdevicetype.xml" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,13 @@ if (!class_exists('ServiceMgmtProviderInstaller')) {
|
||||
public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
|
||||
{
|
||||
// Load localized structural data: contract types
|
||||
static::LoadLocalizedDataOnNewInstall($oConfiguration, $sPreviousVersion, __DIR__."/data/{{language_code}}.data.itop-contracttype.xml");
|
||||
static::LoadLocalizedData(
|
||||
$oConfiguration,
|
||||
$sPreviousVersion,
|
||||
$sCurrentVersion,
|
||||
'3.3.0',
|
||||
__DIR__."/data/{{language_code}}.data.itop-contracttype.xml"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,13 @@ if (!class_exists('ServiceMgmtInstaller')) {
|
||||
public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
|
||||
{
|
||||
// Load localized structural data: contact types and document types
|
||||
static::LoadLocalizedDataOnNewInstall($oConfiguration, $sPreviousVersion, __DIR__."/data/{{language_code}}.data.itop-contracttype.xml");
|
||||
static::LoadLocalizedData(
|
||||
$oConfiguration,
|
||||
$sPreviousVersion,
|
||||
$sCurrentVersion,
|
||||
'3.3.0',
|
||||
__DIR__."/data/{{language_code}}.data.itop-contracttype.xml"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,8 +100,20 @@ if (!class_exists('StructureInstaller')) {
|
||||
public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
|
||||
{
|
||||
// Load localized structural data: contact types and document types
|
||||
static::LoadLocalizedDataOnNewInstall($oConfiguration, $sPreviousVersion, __DIR__."/data/{{language_code}}.data.itop-contacttype.xml");
|
||||
static::LoadLocalizedDataOnNewInstall($oConfiguration, $sPreviousVersion, __DIR__."/data/{{language_code}}.data.itop-documenttype.xml");
|
||||
static::LoadLocalizedData(
|
||||
$oConfiguration,
|
||||
$sPreviousVersion,
|
||||
$sCurrentVersion,
|
||||
'3.3.0',
|
||||
__DIR__."/data/{{language_code}}.data.itop-contacttype.xml"
|
||||
);
|
||||
static::LoadLocalizedData(
|
||||
$oConfiguration,
|
||||
$sPreviousVersion,
|
||||
$sCurrentVersion,
|
||||
'3.3.0',
|
||||
__DIR__."/data/{{language_code}}.data.itop-documenttype.xml"
|
||||
);
|
||||
|
||||
// Default language will be used for actions
|
||||
// Note: There is a issue when upgrading, default language cannot be retrieved from the passed configuration, we have to read it from the disk
|
||||
|
||||
@@ -61,6 +61,6 @@ class TicketsInstaller extends ModuleInstallerAPI
|
||||
}
|
||||
}
|
||||
// Load localized structural data: predefined query phrases for notifications
|
||||
static::LoadLocalizedDataOnCrossingVersion($oConfiguration, $sPreviousVersion, $sCurrentVersion, '3.0.0', __DIR__."/data/{{language_code}}.data.itop-tickets.xml");
|
||||
static::LoadLocalizedData($oConfiguration, $sPreviousVersion, $sCurrentVersion, '3.0.0', __DIR__."/data/{{language_code}}.data.itop-tickets.xml");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,6 +260,11 @@ class ModuleFileReader
|
||||
}
|
||||
|
||||
$aModuleConfig = $this->oPhpExpressionEvaluator->EvaluateExpression($oModuleConfigInfo->value);
|
||||
/*if (isset($aModuleConfig['settings'])) {
|
||||
$oPhpExpressionEvaluator = new PhpExpressionEvaluator();
|
||||
$aArrayWithComments = $oPhpExpressionEvaluator->GetArrayWithComments($oModuleConfigInfo->value);
|
||||
$aModuleConfig['settings'] = array_replace_recursive($aArrayWithComments['settings'], $aModuleConfig['settings']);
|
||||
}*/
|
||||
|
||||
if (! is_array($aModuleConfig)) {
|
||||
throw new ModuleFileReaderException("3rd parameter to SetupWebPage::AddModule not an array: ".get_class($oModuleConfigInfo->value), 0, null, $sModuleFilePath);
|
||||
|
||||
@@ -311,7 +311,7 @@ abstract class ModuleInstallerAPI
|
||||
|
||||
/**
|
||||
* @param \Config $oConfiguration
|
||||
* @param string $sPreviousVersion The previous version of the module (empty string in case of first install)
|
||||
* @param string $sPreviousVersion The previous version of the module (empty string will force the loading)
|
||||
* @param string $sCurrentVersion The current version of the module
|
||||
* @param string $sFirstLoadingVersion The first module version for which the data loading should be performed (e.g. '3.0.0')
|
||||
* @param string $sFilePattern The pattern of the file to load, with {{language_code}} as placeholder for the language code (e.g. 'data.sample.{{language_code}}.xml')
|
||||
@@ -321,51 +321,74 @@ abstract class ModuleInstallerAPI
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
*/
|
||||
public static function LoadLocalizedDataOnCrossingVersion(Config $oConfiguration, ?string $sPreviousVersion, ?string $sCurrentVersion, string $sFirstLoadingVersion, string $sFilePattern): void
|
||||
public static function LoadLocalizedData(Config $oConfiguration, string $sPreviousVersion, string $sCurrentVersion, string $sFirstLoadingVersion, string $sFilePattern): void
|
||||
{
|
||||
self::AssertLoadLocalizedDataParametersAreValid($sPreviousVersion, $sCurrentVersion, $sFirstLoadingVersion);
|
||||
self::AssertLoadLocalizedDataParametersAreValid($sPreviousVersion, $sCurrentVersion, $sFirstLoadingVersion, $sFilePattern);
|
||||
|
||||
// The loading is done only if
|
||||
// - it's a first install of the module
|
||||
// - or it's an upgrade of that module (PreviousVersion is less than the CurrentVersion), which means that we are really upgrading (and not reinstalling the same version or downgrading), and
|
||||
// - either the FirstLoadingVersion is between the PreviousVersion and the CurrentVersion
|
||||
// - or the FirstLoadingVersion is empty, forcing the loading on all upgrades,
|
||||
// It's not very clear if it makes sense to test a particular version,
|
||||
// as the loading mechanism checks object existence using reconc_keys
|
||||
// and do not recreate them, nor update existing.
|
||||
// Without test, new entries added to the data files, would be automatically loaded
|
||||
if (($sPreviousVersion === '') ||
|
||||
(version_compare($sPreviousVersion, $sCurrentVersion, '<')
|
||||
&& version_compare($sPreviousVersion, $sFirstLoadingVersion, '<')
|
||||
&& version_compare($sFirstLoadingVersion, $sCurrentVersion, '<='))) {
|
||||
&& version_compare($sPreviousVersion, $sFirstLoadingVersion, '<'))) {
|
||||
|
||||
self::LoadLocalizedData($oConfiguration, $sFilePattern);
|
||||
// Note: There is an issue when upgrading, default language cannot be retrieved from the passed configuration, we have to read it from the disk
|
||||
if (utils::IsNullOrEmptyString($sPreviousVersion)) {
|
||||
// Fresh install
|
||||
$sDefaultLanguage = $oConfiguration->GetDefaultLanguage();
|
||||
} else {
|
||||
// Upgrade
|
||||
$sDefaultLanguage = utils::GetConfig(true)->GetDefaultLanguage();
|
||||
}
|
||||
|
||||
$sFileName = self::GetLocalizedFileName($sDefaultLanguage, $sFilePattern);
|
||||
if ($sFileName !== '') {
|
||||
self::XMLFileLoad($sFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Config $oConfiguration
|
||||
* @param string $sPreviousVersion The previous version of the module (empty string in case of first install)
|
||||
* @param string $sFilePattern The pattern of the file to load, with {{language_code}} as placeholder for the language code (e.g. 'data.sample.{{language_code}}.xml')
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function LoadLocalizedDataOnNewInstall(Config $oConfiguration, ?string $sPreviousVersion, string $sFilePattern): void
|
||||
* @throws \CoreUnexpectedValue
|
||||
*/
|
||||
private static function AssertLoadLocalizedDataParametersAreValid(string $sPreviousVersion, string $sCurrentVersion, string $sFirstLoadingVersion, string $sFilePattern): void
|
||||
{
|
||||
if (utils::IsNullOrEmptyString($sPreviousVersion)) {
|
||||
self::LoadLocalizedData($oConfiguration, $sFilePattern);
|
||||
if (($sPreviousVersion !== '') && !self::IsValidLocalizedDataVersion($sPreviousVersion)) {
|
||||
throw new CoreUnexpectedValue("LoadLocalizedData expects sPreviousVersion to be empty or match x.y[.z][-name], got '{$sPreviousVersion}'");
|
||||
}
|
||||
|
||||
if (!self::IsValidLocalizedDataVersion($sCurrentVersion)) {
|
||||
throw new CoreUnexpectedValue("LoadLocalizedData expects sCurrentVersion to match x.y[.z][-name], got '{$sCurrentVersion}'");
|
||||
}
|
||||
|
||||
if (!self::IsValidLocalizedDataVersion($sFirstLoadingVersion)) {
|
||||
throw new CoreUnexpectedValue("LoadLocalizedData expects sFirstLoadingVersion to match x.y[.z][-name], got '{$sFirstLoadingVersion}'");
|
||||
}
|
||||
|
||||
if (utils::IsNullOrEmptyString($sFilePattern)) {
|
||||
throw new CoreUnexpectedValue('LoadLocalizedData expects sFilePattern to be a non-empty string');
|
||||
}
|
||||
|
||||
if (substr_count($sFilePattern, '{{language_code}}') !== 1) {
|
||||
throw new CoreUnexpectedValue("LoadLocalizedData expects sFilePattern to contain the exact placeholder '{{language_code}}' exactly once");
|
||||
}
|
||||
}
|
||||
|
||||
private static function IsValidLocalizedDataVersion(string $sVersion): bool
|
||||
{
|
||||
return (preg_match('/^\d+\.\d+(?:\.\d+)?(?:-[A-Za-z0-9]+)?$/', $sVersion) === 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Config $oConfiguration
|
||||
* @param string $sFilePattern The pattern of the file to load, with {{language_code}} as placeholder for the language code (e.g. 'data.sample.{{language_code}}.xml')
|
||||
* @param array|string $sFileName
|
||||
* @param \XMLDataLoader $oDataLoader
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected static function LoadLocalizedData(Config $oConfiguration, string $sFilePattern): void
|
||||
public static function XMLFileLoad(string $sFileName): void
|
||||
{
|
||||
if (substr_count($sFilePattern, '{{language_code}}') !== 1) {
|
||||
throw new CoreUnexpectedValue("LoadLocalizedData expects $sFilePattern to contain the exact placeholder '{{language_code}}' exactly once");
|
||||
}
|
||||
$sDefaultLanguage = $oConfiguration->GetDefaultLanguage();
|
||||
$sFileName = self::GetLocalizedFileName($sDefaultLanguage, $sFilePattern);
|
||||
if (!file_exists($sFileName)) {
|
||||
throw new Exception("File $sFileName not found");
|
||||
}
|
||||
@@ -378,39 +401,19 @@ abstract class ModuleInstallerAPI
|
||||
$oDataLoader->EndSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \CoreUnexpectedValue
|
||||
*/
|
||||
private static function AssertLoadLocalizedDataParametersAreValid(?string $sPreviousVersion, ?string $sCurrentVersion, string $sFirstLoadingVersion): void
|
||||
{
|
||||
if (($sPreviousVersion !== '') && !self::IsValidLocalizedDataVersion($sPreviousVersion)) {
|
||||
throw new CoreUnexpectedValue("LoadLocalizedData expects sPreviousVersion to be empty or match x.y[.z][-name], got '{$sPreviousVersion}'");
|
||||
}
|
||||
if (!self::IsValidLocalizedDataVersion($sCurrentVersion)) {
|
||||
throw new CoreUnexpectedValue("LoadLocalizedData expects sCurrentVersion to match x.y[.z][-name], got '{$sCurrentVersion}'");
|
||||
}
|
||||
if (($sFirstLoadingVersion !== '') && !self::IsValidLocalizedDataVersion($sFirstLoadingVersion)) {
|
||||
throw new CoreUnexpectedValue("LoadLocalizedData expects sFirstLoadingVersion to match x.y[.z][-name], got '{$sFirstLoadingVersion}'");
|
||||
}
|
||||
}
|
||||
|
||||
private static function IsValidLocalizedDataVersion(string $sVersion): bool
|
||||
{
|
||||
return (preg_match('/^\d+\.\d+(?:\.\d+)?(?:-[A-Za-z0-9]+)?$/', $sVersion) === 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sLanguage The language code to use for localization (e.g. 'EN US')
|
||||
* @param string $sFilePattern The full path+name of the file to localize, with {{language_code}} as placeholder for the language code (e.g. 'data.sample.{{language_code}}.xml')
|
||||
*
|
||||
* @return string The localized file name if found, or an empty string if not found
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
private static function GetLocalizedFileName($sLanguage, string $sFilePattern): string
|
||||
public static function GetLocalizedFileName($sLanguage, string $sFilePattern): string
|
||||
{
|
||||
$sLang = str_replace(' ', '_', strtolower($sLanguage));
|
||||
$sFileName = str_replace('{{language_code}}', $sLang, $sFilePattern);
|
||||
if (!file_exists($sFileName)) {
|
||||
SetupLog::Debug("No data file found matching the pattern $sFilePattern and language_code $sLang. Trying with 'en_us' as fallback.");
|
||||
$sLang = 'en_us';
|
||||
$sFileName = str_replace('{{language_code}}', $sLang, $sFilePattern);
|
||||
}
|
||||
|
||||
@@ -81,13 +81,13 @@ class ApplicationInstallSequencer extends StepSequencer
|
||||
return $this->ComputeNextStep($sStep);
|
||||
|
||||
case 'log-parameters':
|
||||
if (array_key_exists($sStep, $this->oParams->Get('optional_steps', []))) {
|
||||
if ($this->HasOptionalStep($sStep)) {
|
||||
$this->DoLogParameters();
|
||||
}
|
||||
return $this->ComputeNextStep($sStep);
|
||||
|
||||
case 'backup':
|
||||
if (array_key_exists($sStep, $this->oParams->Get('optional_steps', []))) {
|
||||
if ($this->HasOptionalStep($sStep, false)) {
|
||||
$aBackupOptions = $this->oParams->Get('optional_steps')['backup'];
|
||||
// __DB__-%Y-%m-%d
|
||||
$sDestination = $aBackupOptions['destination'];
|
||||
@@ -100,7 +100,7 @@ class ApplicationInstallSequencer extends StepSequencer
|
||||
|
||||
case 'migrate-before':
|
||||
$this->oRunTimeEnvironment->EnterMaintenanceMode($this->GetConfig());
|
||||
if (array_key_exists($sStep, $this->oParams->Get('optional_steps', []))) {
|
||||
if ($this->HasOptionalStep($sStep)) {
|
||||
$this->oRunTimeEnvironment->MigrateDataBeforeUpdateStructure($this->oParams->Get('mode'), $this->GetConfig());
|
||||
}
|
||||
return $this->ComputeNextStep($sStep);
|
||||
@@ -111,7 +111,7 @@ class ApplicationInstallSequencer extends StepSequencer
|
||||
return $this->ComputeNextStep($sStep);
|
||||
|
||||
case 'migrate-after':
|
||||
if (array_key_exists($sStep, $this->oParams->Get('optional_steps', []))) {
|
||||
if ($this->HasOptionalStep($sStep)) {
|
||||
$this->oRunTimeEnvironment->MigrateDataAfterUpdateStructure($this->oParams->Get('mode'), $this->GetConfig());
|
||||
}
|
||||
return $this->ComputeNextStep($sStep);
|
||||
@@ -191,13 +191,17 @@ class ApplicationInstallSequencer extends StepSequencer
|
||||
public function GetStepNames(): array
|
||||
{
|
||||
$aStepNames = [ ''];
|
||||
foreach (['log-parameters', 'backup', 'migrate-before'] as $sStepName) {
|
||||
if (array_key_exists($sStepName, $this->oParams->Get('optional_steps', []))) {
|
||||
$aStepNames [] = $sStepName;
|
||||
}
|
||||
if ($this->HasOptionalStep('log-parameters')) {
|
||||
$aStepNames [] = 'log-parameters';
|
||||
}
|
||||
if ($this->HasOptionalStep('backup', false)) {
|
||||
$aStepNames [] = 'backup';
|
||||
}
|
||||
if ($this->HasOptionalStep('migrate-before')) {
|
||||
$aStepNames [] = 'migrate-before';
|
||||
}
|
||||
$aStepNames [] = 'db-schema';
|
||||
if (array_key_exists('migrate-after', $this->oParams->Get('optional_steps', []))) {
|
||||
if ($this->HasOptionalStep('migrate-after')) {
|
||||
$aStepNames [] = 'migrate-after';
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ class DataAuditSequencer extends StepSequencer
|
||||
|
||||
protected function IsDataAuditRequired(): bool
|
||||
{
|
||||
if (! array_key_exists('setup-audit', $this->oParams->Get('optional_steps', []))) {
|
||||
if (!$this->HasOptionalStep('setup-audit', false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ class DataAuditSequencer extends StepSequencer
|
||||
public function GetStepNames(): array
|
||||
{
|
||||
$aStepNames = [''];
|
||||
if (array_key_exists('copy', $this->oParams->Get('optional_steps', []))) {
|
||||
if ($this->HasOptionalStep('copy')) {
|
||||
$aStepNames[] = 'copy';
|
||||
}
|
||||
$aStepNames[] = 'compile';
|
||||
|
||||
@@ -29,6 +29,9 @@ abstract class StepSequencer
|
||||
protected RunTimeEnvironment $oRunTimeEnvironment;
|
||||
protected string $sSourceDesc;
|
||||
|
||||
protected const LABELS = [];
|
||||
protected const SUCCESS_LABELS = [];
|
||||
|
||||
/**
|
||||
* @param \Parameters $oParams
|
||||
* @param \RunTimeEnvironment|null $oRunTimeEnvironment
|
||||
@@ -233,4 +236,26 @@ abstract class StepSequencer
|
||||
* @return array (status => , message => , percentage-completed => , next-step => , next-step-label => )
|
||||
*/
|
||||
abstract public function ExecuteStep($sStep = '', $sInstallComment = null): array;
|
||||
|
||||
/**
|
||||
* Check whether an optional step is enabled in the "optional_steps" parameters. If optional_steps is not set, use $bDefaultValue as its default value
|
||||
*
|
||||
* @param $sStep
|
||||
* @param bool $bDefaultValue
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function HasOptionalStep($sStep, bool $bDefaultValue = true)
|
||||
{
|
||||
$aOptionalSteps = $this->oParams->Get('optional_steps', null);
|
||||
if (is_null($aOptionalSteps)) {
|
||||
return $bDefaultValue;
|
||||
}
|
||||
if (is_array($aOptionalSteps)) {
|
||||
return array_key_exists($sStep, $aOptionalSteps);
|
||||
}
|
||||
throw new Exception('Incorrect value for parameter optional_steps');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,17 +26,17 @@ function WizardAsyncAction(sActionCode, oParams, OnErrorFunction)
|
||||
function WizardUpdateButtons()
|
||||
{
|
||||
if (CanMoveForward()) {
|
||||
$("#btn_next").prop('disabled', false);
|
||||
$("#btn_next").removeClass('ibo-is-hidden');
|
||||
}
|
||||
else {
|
||||
$("#btn_next").prop('disabled', true);
|
||||
$("#btn_next").addClass('ibo-is-hidden');
|
||||
}
|
||||
|
||||
if (CanMoveBackward()) {
|
||||
$("#btn_back").prop('disabled', false);
|
||||
$("#btn_back").removeClass('ibo-is-hidden');
|
||||
}
|
||||
else {
|
||||
$("#btn_back").prop('disabled', true);
|
||||
$("#btn_back").addClass('ibo-is-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -151,10 +151,10 @@ class WizardController
|
||||
$sCurrentState = utils::ReadParam('_state', $this->sInitialState);
|
||||
$oStep = $this->GetWizardStep($sCurrentStepClass, $sCurrentState);
|
||||
if ($oStep->ValidateParams()) {
|
||||
if ($oStep->CanComeBack()) {
|
||||
$aPossibleSteps = $oStep->GetPossibleSteps();
|
||||
if ($oStep->CanMoveBackward()) {
|
||||
$this->PushStep(['class' => $sCurrentStepClass, 'state' => $sCurrentState]);
|
||||
}
|
||||
$aPossibleSteps = $oStep->GetPossibleSteps();
|
||||
$oWizardState = $oStep->UpdateWizardStateAndGetNextStep(true); // true => moving forward
|
||||
if (in_array($oWizardState->GetNextStep(), $aPossibleSteps)) {
|
||||
$oNextStep = $this->GetWizardStep($oWizardState->GetNextStep(), $oWizardState->GetState());
|
||||
@@ -237,8 +237,12 @@ HTML;
|
||||
|
||||
$oPage->add('<input type="hidden" name="_steps" value="'.utils::EscapeHtml(json_encode($this->aWizardSteps)).'"/>');
|
||||
$oPage->add('<table style="width:100%;" class="ibo-setup--wizard--buttons-container"><tr>');
|
||||
if ((count($this->aWizardSteps) > 0) && ($oStep->CanMoveBackward())) {
|
||||
$oPage->add('<td style="text-align: left"><button id="btn_back" class="ibo-button ibo-is-alternative ibo-is-neutral" type="submit" name="operation" value="back"><span class="ibo-button--label">Back</span></button></td>');
|
||||
if (count($this->aWizardSteps) > 0) {
|
||||
if ($oStep->CanMoveBackward()) {
|
||||
$oPage->add('<td style="text-align: left"><button id="btn_back" class="ibo-button ibo-is-alternative ibo-is-neutral" type="submit" name="operation" value="back"><span class="ibo-button--label">Back</span></button></td>');
|
||||
} else {
|
||||
$oPage->add('<td style="text-align: left"><button id="btn_back" class="ibo-button ibo-is-alternative ibo-is-neutral ibo-is-hidden" type="submit" name="operation" value="back"><span class="ibo-button--label">Back</span></button></td>');
|
||||
}
|
||||
}
|
||||
if ($oStep->CanMoveForward()) {
|
||||
$oPage->add('<td style="text-align:right;"><button id="btn_next" class="default ibo-button ibo-is-regular ibo-is-primary" type="submit" name="operation" value="next"><span class="ibo-button--label">'.utils::EscapeHtml($oStep->GetNextButtonLabel()).'</span></button></td>');
|
||||
|
||||
@@ -54,16 +54,20 @@ class WizStepDataAudit extends WizStepInstall
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether this step/state allows to go back or not
|
||||
* @return boolean True if the '<< Back' button should be displayed
|
||||
*/
|
||||
public function CanMoveBackward()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
return new WizardState(WizStepSummary::class);
|
||||
}
|
||||
|
||||
public function CanComeBack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function Display(SetupPage $oPage): void
|
||||
{
|
||||
|
||||
@@ -108,6 +112,7 @@ JS);
|
||||
'removed_extensions' => '[]',
|
||||
'extensions_not_uninstallable' => '[]',
|
||||
'copy_setup_files' => 1,
|
||||
'return_button_label' => '',
|
||||
];
|
||||
$aHiddenInputs = '';
|
||||
foreach ($aParams as $sParamName => $defaultValue) {
|
||||
@@ -123,23 +128,43 @@ INPUT;
|
||||
<<<HTML
|
||||
<form id="data-feature-removal" class="ibo-setup--wizard ibo-is-hidden" method="post" action="$sApplicationUrl">
|
||||
<input type="hidden" name="operation" value="AnalysisResult"/>
|
||||
<input type="hidden" name="setup_token" value="$sUID"/>
|
||||
<input type="hidden" name="authent" value="$sUID"/>
|
||||
$aHiddenInputs
|
||||
</form>
|
||||
HTML
|
||||
);
|
||||
|
||||
$sButtonLabel = $this->oWizard->GetParameter('return_button_label', '');
|
||||
if ($sButtonLabel !== '') {
|
||||
$sButtonLabel = utils::HtmlEntities($sButtonLabel);
|
||||
$sButtonUrl = utils::GetAbsoluteUrlModulePage('itsm-designer-connector', 'launch.php');
|
||||
$sButtonUrl = utils::HtmlEntities($sButtonUrl);
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
$('.ibo-setup--wizard--buttons-container tr td:nth-child(1)').after('<td style="text-align:center;"><button id="return-button" class="ibo-button ibo-is-alternative ibo-is-neutral ibo-is-hidden" type="button" onclick="window.location.href=\'$sButtonUrl\'"><span class="ibo-button--label">$sButtonLabel</span></button></td>');
|
||||
JS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function AddProgressErrorScript($oPage, $aRes)
|
||||
{
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
if ($('#return-button').length > 0) {
|
||||
$('#return-button').removeClass('ibo-is-hidden');
|
||||
}
|
||||
JS
|
||||
);
|
||||
|
||||
if (isset($aRes['error_code']) && $aRes['error_code'] === DataAuditSequencer::DATA_AUDIT_FAILED) {
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('.ibo-setup--wizard--buttons-container tr td:nth-child(2)').before('<td style="text-align:center;"><button class="ibo-button ibo-is-alternative ibo-is-neutral" type="submit" name="operation" value="next" id="ignore_and_continue"><span class="ibo-button--label">Ignore and continue</span></button></td>');
|
||||
<<<JS
|
||||
$('.ibo-setup--wizard--buttons-container tr td:nth-child(2)').after('<td style="text-align:center;"><button class="ibo-button ibo-is-alternative ibo-is-neutral" type="submit" name="operation" value="next" id="ignore_and_continue"><span class="ibo-button--label">Ignore and continue</span></button></td>');
|
||||
$('#ignore_and_continue').on('click', function() {
|
||||
return confirm("If you skip the cleanup you won't be able to run the process later. You'll have to migrate or delete unconsistent data manually.");
|
||||
});
|
||||
$('.ibo-setup--wizard--buttons-container tr td:nth-child(2)').after('<td style="text-align:center;"><span id="submit-wait" class="ibo-spinner ibo-is-inline ibo-is-hidden ibo-spinner ibo-block" data-role="ibo-spinner"><i class="ibo-spinner--icon fas fa-sync-alt fa-spin" aria-hidden="true"></i></span> <button id="goto-data-feature-removal" class="default ibo-button ibo-is-regular ibo-is-primary" type="button"><span class="ibo-button--label">Cleanup my data</span></button></td>');
|
||||
$('.ibo-setup--wizard--buttons-container tr td:nth-child(3)').after('<td style="text-align:center;"><span id="submit-wait" class="ibo-spinner ibo-is-inline ibo-is-hidden ibo-spinner ibo-block" data-role="ibo-spinner"><i class="ibo-spinner--icon fas fa-sync-alt fa-spin" aria-hidden="true"></i></span> <button id="goto-data-feature-removal" class="default ibo-button ibo-is-regular ibo-is-primary" type="button"><span class="ibo-button--label">Cleanup my data</span></button></td>');
|
||||
$('#goto-data-feature-removal').on("click", function() {
|
||||
$('#goto-data-feature-removal').prop('disabled', true);
|
||||
$('#submit-wait').removeClass("ibo-is-hidden");
|
||||
@@ -148,7 +173,7 @@ HTML
|
||||
|
||||
$("#wiz_form").data("installation_status", "cleanup_needed");
|
||||
$('#btn_next').hide();
|
||||
EOF
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,6 +186,10 @@ EOF
|
||||
|
||||
public function JSCanMoveBackward()
|
||||
{
|
||||
if ($this->oWizard->GetParameter('return_button_label', '') !== '') {
|
||||
return 'return false;';
|
||||
}
|
||||
|
||||
return 'return ["not started", "error", "cleanup_needed"].indexOf($("#wiz_form").data("installation_status")) !== -1;';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,11 @@ class WizStepDone extends WizardStep
|
||||
return false;
|
||||
}
|
||||
|
||||
public function JSCanMoveBackward()
|
||||
{
|
||||
return 'return false;';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether this step of the wizard requires that the configuration file be writable
|
||||
* @return bool True if the wizard will possibly need to modify the configuration at some point
|
||||
|
||||
@@ -54,6 +54,13 @@ class WizStepInstall extends AbstractWizStepInstall
|
||||
}
|
||||
}
|
||||
|
||||
public function CanMoveBackward()
|
||||
{
|
||||
$sLabel = $this->oWizard->GetParameter('return_button_label', '');
|
||||
SetupLog::Info(__METHOD__.": return_button_label [$sLabel]");
|
||||
return $sLabel === '';
|
||||
}
|
||||
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
return new WizardState(WizStepDone::class);
|
||||
@@ -109,6 +116,20 @@ JS);
|
||||
JS);
|
||||
}
|
||||
|
||||
public function PostFormDisplay(SetupPage $oPage)
|
||||
{
|
||||
$sButtonLabel = $this->oWizard->GetParameter('return_button_label', '');
|
||||
SetupLog::Info(__METHOD__.": return_button_label [$sButtonLabel]");
|
||||
if ($sButtonLabel !== '') {
|
||||
$sButtonUrl = utils::GetAbsoluteUrlModulePage('itsm-designer-connector', 'launch.php');
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
$('.ibo-setup--wizard--buttons-container tr td:nth-child(1)').after('<td style="text-align:center;"><button id="return-button" class="ibo-button ibo-is-alternative ibo-is-neutral ibo-is-hidden" type="button" onclick="window.location.href=\'$sButtonUrl\'"><span class="ibo-button--label">$sButtonLabel</span></button></td>');
|
||||
JS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
@@ -125,27 +146,25 @@ JS);
|
||||
// Tell the web page to move the progress bar and to launch the next step
|
||||
$sMessage = addslashes(utils::EscapeHtml($aRes['next-step-label']));
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#wiz_form").data("installation_status", "running");
|
||||
WizardUpdateButtons();
|
||||
$('#setup_msg').html('$sMessage');
|
||||
$('#progress').progression( {Current:{$aRes['percentage-completed']}, Maximum: 100} );
|
||||
|
||||
//$("#percentage").html('{$aRes['percentage-completed']} % completed<br/>{$aRes['next-step-label']}');
|
||||
ExecuteStep('{$aRes['next-step']}');
|
||||
EOF
|
||||
<<<JS
|
||||
$("#wiz_form").data("installation_status", "running");
|
||||
WizardUpdateButtons();
|
||||
$('#setup_msg').html('$sMessage');
|
||||
$('#progress').progression( {Current:{$aRes['percentage-completed']}, Maximum: 100} );
|
||||
|
||||
//$("#percentage").html('{$aRes['percentage-completed']} % completed<br/>{$aRes['next-step-label']}');
|
||||
ExecuteStep('{$aRes['next-step']}');
|
||||
JS
|
||||
);
|
||||
static::AddPrevStepSuccessMessage($oPage, $aRes['prev-step-success-message']);
|
||||
} elseif ($aRes['status'] !== StepSequencer::ERROR) {
|
||||
// Installation complete, move to the next step of the wizard
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#wiz_form").data("installation_status", "completed");
|
||||
$('#progress').progression( {Current:100, Maximum: 100} );
|
||||
WizardUpdateButtons();
|
||||
$("#btn_next").off("click.install");
|
||||
$("#btn_next").trigger('click');
|
||||
EOF
|
||||
<<<JS
|
||||
$('#progress').progression( {Current:100, Maximum: 100} );
|
||||
$("#wiz_form").data("installation_status", "completed");
|
||||
$("#btn_next").trigger('click');
|
||||
JS
|
||||
);
|
||||
static::AddPrevStepSuccessMessage($oPage, $aRes['prev-step-success-message']);
|
||||
} else {
|
||||
@@ -153,12 +172,12 @@ EOF
|
||||
$sMessage = addslashes(utils::EscapeHtml($aRes['message']));
|
||||
$sMessage = str_replace("\n", '<br>', $sMessage);
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#wiz_form").data("installation_status", "error");
|
||||
$("#progress .progress").addClass('progress-error');
|
||||
WizardUpdateButtons();
|
||||
$('#setup_error').html('$sMessage').show();
|
||||
EOF
|
||||
<<<JS
|
||||
$("#wiz_form").data("installation_status", "error");
|
||||
$("#progress .progress").addClass('progress-error');
|
||||
WizardUpdateButtons();
|
||||
$('#setup_error').html('$sMessage').show();
|
||||
JS
|
||||
);
|
||||
$this->AddProgressErrorScript($oPage, $aRes);
|
||||
}
|
||||
@@ -166,7 +185,13 @@ EOF
|
||||
|
||||
protected function AddProgressErrorScript($oPage, $aRes)
|
||||
{
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
if ($('#return-button').length > 0) {
|
||||
$('#return-button').removeClass('ibo-is-hidden');
|
||||
}
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -84,7 +84,7 @@ class WizStepLandingBeforeAudit extends WizStepModulesChoice
|
||||
return 'Next';
|
||||
}
|
||||
|
||||
public function CanComeBack()
|
||||
public function CanMoveBackward()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,14 @@ class WizStepSummary extends AbstractWizStepInstall
|
||||
}
|
||||
}
|
||||
|
||||
public function CanMoveBackward()
|
||||
{
|
||||
$sLabel = $this->oWizard->GetParameter('return_button_label', '');
|
||||
SetupLog::Info(__METHOD__.": return_button_label [$sLabel]");
|
||||
|
||||
return $sLabel === '';
|
||||
}
|
||||
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
$this->oWizard->SaveParameter('db_backup', false);
|
||||
@@ -242,6 +250,19 @@ JS
|
||||
);
|
||||
}
|
||||
|
||||
public function PostFormDisplay(SetupPage $oPage)
|
||||
{
|
||||
$sButtonLabel = $this->oWizard->GetParameter('return_button_label', '');
|
||||
if ($sButtonLabel !== '') {
|
||||
$sButtonUrl = utils::GetAbsoluteUrlModulePage('itsm-designer-connector', 'launch.php');
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
$('.ibo-setup--wizard--buttons-container tr td:nth-child(1)').after('<td style="text-align:center;"><button id="return-button" class="ibo-button ibo-is-alternative ibo-is-neutral" type="button" onclick="window.location.href=\'$sButtonUrl\'"><span class="ibo-button--label">$sButtonLabel</span></button></td>');
|
||||
JS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the "Next" button should be enabled interactively
|
||||
* @return string A piece of javascript code returning either true or false
|
||||
@@ -257,7 +278,11 @@ JS
|
||||
*/
|
||||
public function JSCanMoveBackward()
|
||||
{
|
||||
return 'return true;';
|
||||
if ($this->oWizard->GetParameter('return_button_label', '') === '') {
|
||||
return 'return true;';
|
||||
}
|
||||
|
||||
return 'return false;';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
/**
|
||||
* First step of the iTop Installation Wizard: Welcome screen, requirements
|
||||
*/
|
||||
@@ -98,7 +96,7 @@ EOF
|
||||
$sStyle = 'style="display:none;overflow:auto;"';
|
||||
$sToggleButtons = '<button type="button" id="show_details" class="ibo-button ibo-is-alternative ibo-is-neutral" onclick="$(\'#details\').toggle(); $(this).toggle(); $(\'#hide_details\').toggle();"><span class="ibo-button--icon fa fa-caret-down"></span><span class="ibo-button--label">Show details</span></button><button type="button" id="hide_details" class="ibo-button ibo-is-alternative ibo-is-neutral" style="display:none;" onclick="$(\'#details\').toggle(); $(this).toggle(); $(\'#show_details\').toggle();"><span class="ibo-button--icon fa fa-caret-up"></span><span class="ibo-button--label">Hide details</span></button>';
|
||||
if (count($aErrors) > 0) {
|
||||
$sStyle = 'overflow:auto;"';
|
||||
$sStyle = 'style="overflow:auto;"';
|
||||
$sTitle = count($aErrors).' Error(s), '.count($aWarnings).' Warning(s).';
|
||||
$sH2Class = 'text-error';
|
||||
} elseif (count($aWarnings) > 0) {
|
||||
|
||||
@@ -151,15 +151,6 @@ abstract class WizardStep
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the user will come back to this step/state if he click on "Back"
|
||||
* @return boolean True if the 'Back' button should display this step
|
||||
*/
|
||||
public function CanComeBack()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the "Next" button should be enabled interactively
|
||||
* @return string A piece of javascript code returning either true or false
|
||||
|
||||
@@ -13,7 +13,6 @@ use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\ButtonGroup\ButtonGroupUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
|
||||
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItemFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\CaseLogEntryForm\CaseLogEntryForm;
|
||||
use DBObject;
|
||||
use DBObjectSet;
|
||||
use Dict;
|
||||
@@ -39,7 +38,9 @@ class CaseLogEntryFormFactory
|
||||
->AddMainActionButtons(static::PrepareCancelButton());
|
||||
|
||||
$oSaveButton = static::PrepareSaveButton();
|
||||
$oTransitionsMenu = static::PrepareTransitionsSelectionPopoverMenu($oObject, $sCaseLogAttCode);
|
||||
$oTransitionsMenu = static::PrepareTransitionsSelectionPopoverMenu($oObject, $sCaseLogAttCode, $oCaseLogEntryForm->GetId());
|
||||
// Prevent popover menu from landing behind caselog editor
|
||||
$oTransitionsMenu->SetContainer(PopoverMenu::ENUM_CONTAINER_BODY);
|
||||
if (true === $oTransitionsMenu->HasItems()) {
|
||||
$oButtonGroup = ButtonGroupUIBlockFactory::MakeButtonWithOptionsMenu($oSaveButton, $oTransitionsMenu);
|
||||
$oCaseLogEntryForm->AddMainActionButtons($oButtonGroup);
|
||||
@@ -69,7 +70,16 @@ class CaseLogEntryFormFactory
|
||||
return $oButton;
|
||||
}
|
||||
|
||||
protected static function PrepareTransitionsSelectionPopoverMenu(DBObject $oObject, string $sCaseLogAttCode): PopoverMenu
|
||||
/**
|
||||
* @param DBObject $oObject
|
||||
* @param string $sCaseLogAttCode
|
||||
* @param string $sCaseLogEntryFormId
|
||||
* @since 3.2.3 Add mandatory $sCaseLogEntryFormId parameter
|
||||
* @return PopoverMenu
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
protected static function PrepareTransitionsSelectionPopoverMenu(DBObject $oObject, string $sCaseLogAttCode, string $sCaseLogEntryFormId): PopoverMenu
|
||||
{
|
||||
$sObjClass = get_class($oObject);
|
||||
|
||||
@@ -77,8 +87,6 @@ class CaseLogEntryFormFactory
|
||||
$sSectionId = 'send-actions';
|
||||
$oMenu->AddSection($sSectionId);
|
||||
|
||||
$sCaseLogEntryFormDataRole = CaseLogEntryForm::BLOCK_CODE;
|
||||
|
||||
// Note: This code is inspired from cmdbAbstract::DisplayModifyForm(), it might be better to factorize it
|
||||
$aTransitions = $oObject->EnumTransitions();
|
||||
if (!isset($aExtraParams['custom_operation']) && count($aTransitions)) {
|
||||
@@ -97,7 +105,7 @@ class CaseLogEntryFormFactory
|
||||
CaseLogEntryForm::BLOCK_CODE.'--add-action--'.$sCaseLogAttCode.'--stimulus--'.$sStimulusCode,
|
||||
Dict::Format('UI:Button:SendAnd', $aStimuli[$sStimulusCode]->GetLabel()),
|
||||
<<<JS
|
||||
$(this).closest('[data-role="{$sCaseLogEntryFormDataRole}"]').trigger('save_entry.caselog_entry_form.itop', {stimulus_code: '{$sStimulusCode}'});
|
||||
$('#$sCaseLogEntryFormId').trigger('save_entry.caselog_entry_form.itop', {stimulus_code: '{$sStimulusCode}'});
|
||||
JS
|
||||
)
|
||||
);
|
||||
|
||||
@@ -16,10 +16,12 @@ use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\Object\ObjectSummary;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
|
||||
use Combodo\iTop\Application\WebPage\DownloadPage;
|
||||
use Combodo\iTop\Application\WebPage\iTopWebPage;
|
||||
use Combodo\iTop\Application\WebPage\JsonPage;
|
||||
use Combodo\iTop\Application\WebPage\JsonPPage;
|
||||
use Combodo\iTop\Controller\Notifications\NotificationsCenterController;
|
||||
use Combodo\iTop\Service\Notification\Event\EventNotificationNewsroomService;
|
||||
use Combodo\iTop\Service\Notification\NotificationsRepository;
|
||||
use Combodo\iTop\Service\Router\Router;
|
||||
use CoreException;
|
||||
@@ -27,6 +29,7 @@ use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use Dict;
|
||||
use EventNotificationNewsroom;
|
||||
use Exception;
|
||||
use MetaModel;
|
||||
use SecurityException;
|
||||
use UserRights;
|
||||
@@ -379,9 +382,10 @@ JS
|
||||
$oEventBlock->SetCSSColorClass($sReadColor);
|
||||
$oEventBlock->SetSubTitle($sReadLabel);
|
||||
$oEventBlock->SetClassLabel('');
|
||||
/** @var \ormDocument $oImage */
|
||||
$oImage = $oEvent->Get('icon');
|
||||
if (!$oImage->IsEmpty()) {
|
||||
$sIconUrl = $oImage->GetDisplayURL(get_class($oEvent), $iEventId, 'icon');
|
||||
$sIconUrl = self::GetDisplayIconUrl($iEventId, $oImage->GetSignature());
|
||||
$oEventBlock->SetIcon($sIconUrl, Panel::ENUM_ICON_COVER_METHOD_COVER, true);
|
||||
}
|
||||
|
||||
@@ -545,7 +549,7 @@ $sMessage
|
||||
HTML;
|
||||
|
||||
$sIcon = $oMessage->Get('icon') !== null ?
|
||||
$oMessage->Get('icon')->GetDisplayURL(EventNotificationNewsroom::class, $oMessage->GetKey(), 'icon') :
|
||||
$this->GetDisplayIconUrl($oMessage->GetKey(), $oMessage->Get('icon')->GetSignature()) :
|
||||
Branding::GetCompactMainLogoAbsoluteUrl();
|
||||
$aMessages[] = [
|
||||
'id' => $oMessage->GetKey(),
|
||||
@@ -692,6 +696,35 @@ HTML;
|
||||
return $oPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the icon of an EventNotificationNewsroom
|
||||
* (copy of ajax.render.php?operation=display_document but with the bAllowAllData parameter set to true in order to bypass the data access restrictions since the icon is not a critical information)
|
||||
* @return void
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function OperationViewIcon(): void
|
||||
{
|
||||
$sId = utils::ReadParam('id', '');
|
||||
if (!empty($sId)) {
|
||||
$oPage = new DownloadPage('');
|
||||
// X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page
|
||||
// so we're resetting its value ! (see N°3416)
|
||||
$oPage->add_xframe_options('');
|
||||
$iCacheSec = (int)utils::ReadParam('cache', 0);
|
||||
$oPage->set_cache($iCacheSec);
|
||||
|
||||
// N°4129 - Prevent XSS attacks & other script executions
|
||||
if (utils::GetConfig()->Get('security.disable_inline_documents_sandbox') === false) {
|
||||
$oPage->add_header('Content-Security-Policy: sandbox;');
|
||||
}
|
||||
|
||||
if (EventNotificationNewsroomService::DownloadIcon($oPage, $sId, UserRights::GetContactId()) === true) {
|
||||
$oPage->Output();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sAction
|
||||
*
|
||||
@@ -784,4 +817,9 @@ HTML;
|
||||
|
||||
return $aReturnData;
|
||||
}
|
||||
|
||||
protected function GetDisplayIconUrl(string $sId, string $sSignature): string
|
||||
{
|
||||
return utils::GetAbsoluteUrlAppRoot()."pages/UI.php?route=itopnewsroom.view_icon&id=$sId&s=$sSignature&cache=86400";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ namespace Combodo\iTop\PhpParser\Evaluation;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileParser;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\ConstExprEvaluator;
|
||||
use PhpParser\ExprEvaluator;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
/**
|
||||
@@ -55,4 +57,36 @@ PHP;
|
||||
throw new ModuleFileReaderException("Eval of '$sExpr' caused an error:".$t->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function GetArray(Array_ $oArray, bool $bPreserveComments = true): array
|
||||
{
|
||||
$aRes = [];
|
||||
$i = 0;
|
||||
foreach ($oArray->items as $oItem) {
|
||||
/** @var \PhpParser\Node\ArrayItem $oItem **/
|
||||
if (is_null($oItem->key)) {
|
||||
$sKey = $i;
|
||||
$i++;
|
||||
} else {
|
||||
$sKey = $this->EvaluateExpression($oItem->key);
|
||||
}
|
||||
|
||||
if ($bPreserveComments) {
|
||||
foreach ($oItem->getComments() as $oComment) {
|
||||
/** @var \PhpParser\Comment $oComment */
|
||||
$aRes[] = 'StartPhpParserComment'.$oComment->getText().'EndPhpParserComment';
|
||||
}
|
||||
}
|
||||
if ($oItem->value instanceof Array_) {
|
||||
$aRes[$sKey] = $this->GetArray($oItem->value, $bPreserveComments);
|
||||
} elseif ($oItem->value instanceof Comment) {
|
||||
if ($bPreserveComments) {
|
||||
$aRes[$sKey] = $oItem->value;
|
||||
}
|
||||
} else {
|
||||
$aRes[$sKey] = $this->EvaluateExpression($oItem->value);
|
||||
}
|
||||
}
|
||||
return $aRes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ namespace Combodo\iTop\Service\Notification\Event;
|
||||
|
||||
use Action;
|
||||
use Combodo\iTop\Application\Branding;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use EventNotificationNewsroom;
|
||||
use MetaModel;
|
||||
use ormDocument;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
@@ -71,4 +73,31 @@ class EventNotificationNewsroomService
|
||||
|
||||
return $oEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Combodo\iTop\Application\WebPage\WebPage $oPage
|
||||
* @param string $sId
|
||||
* @param int $iContactId
|
||||
*
|
||||
* @return bool Returns true if the download has been launched, false otherwise (e.g. if the event doesn't exist or doesn't belong to the current user)
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public static function DownloadIcon(WebPage $oPage, string $sId, int $iContactId): bool
|
||||
{
|
||||
$oEvent = MetaModel::GetObject(EventNotificationNewsroom::class, $sId, false, true);
|
||||
if (($oEvent !== null) && ($oEvent->Get('contact_id') === $iContactId)) {
|
||||
ormDocument::DownloadDocument(
|
||||
$oPage,
|
||||
EventNotificationNewsroom::class,
|
||||
$sId,
|
||||
'icon',
|
||||
ormDocument::ENUM_CONTENT_DISPOSITION_INLINE,
|
||||
bAllowAllData: true
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,7 +706,9 @@ abstract class ItopTestCase extends KernelTestCase
|
||||
$this->aLastCurlGetInfo = $info;
|
||||
$sErrorMsg = curl_error($ch);
|
||||
$iErrorCode = curl_errno($ch);
|
||||
curl_close($ch);
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
\IssueLog::Info(__METHOD__, null, ['url' => $sUrl, 'error' => $sErrorMsg, 'error_code' => $iErrorCode, 'post_fields' => $aPostFields, 'info' => $info]);
|
||||
|
||||
|
||||
@@ -306,6 +306,21 @@ class ormDocumentTest extends ItopDataTestCase
|
||||
$this->assertStringNotContainsString('the object does not exist or you are not allowed to view it', $sAllowedHtml, 'Unexpected error message when rights are sufficient.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider DownloadDocumentRightsProvider
|
||||
*/
|
||||
public function testAllowsDownloadingDocumentWhenBypassingRightsChecksWithAllowAllData(string $sTargetClass, string $sAttCode, string $sData, string $sFileName, ?string $sHostClass)
|
||||
{
|
||||
$iDeniedDocumentId = $this->CreateDownloadTargetInOrg($sTargetClass, $sAttCode, $this->iOrgDifferentFromUser, $sData, $sFileName, $sHostClass);
|
||||
|
||||
$oPageAllowed = new CaptureWebPage();
|
||||
ormDocument::DownloadDocument($oPageAllowed, $sTargetClass, $iDeniedDocumentId, $sAttCode, ormDocument::ENUM_CONTENT_DISPOSITION_INLINE, bAllowAllData: true);
|
||||
$sAllowedHtml = $oPageAllowed->GetHtml();
|
||||
|
||||
$this->assertStringContainsString($sData, $sAllowedHtml, 'Expected file data present when bypassing rights checks.');
|
||||
$this->assertStringNotContainsString("Invalid id ($iDeniedDocumentId) for class '$sTargetClass' - the object does not exist or you are not allowed to view it", $sAllowedHtml, 'Unexpected invalid id error message when bypassing rights checks.');
|
||||
}
|
||||
|
||||
public function DownloadDocumentRightsProvider(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Combodo\iTop\Test\UnitTest\Module\iTopConfig;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use Config;
|
||||
use http\Encoding\Stream\Inflate;
|
||||
|
||||
class ConfigTest extends ItopTestCase
|
||||
{
|
||||
@@ -72,7 +73,6 @@ class ConfigTest extends ItopTestCase
|
||||
'sExpectedContains' => "'app_root_url' => 'http://%server(SERVER_NAME)?:localhost%/itop/iTop/'",
|
||||
'aChanges' => [],
|
||||
],
|
||||
|
||||
'preserve set same value' => [
|
||||
'sConfigFile' => __DIR__.'/ConfigTest/config-itop-var.php',
|
||||
'sExpectedContains' => "'app_root_url' => 'http://' . (isset(\$_SERVER['SERVER_NAME']) ? \$_SERVER['SERVER_NAME'] : 'localhost') . '/itop/iTop/'",
|
||||
@@ -91,4 +91,58 @@ class ConfigTest extends ItopTestCase
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function GetModuleSettingSection(string $sFilePath): string
|
||||
{
|
||||
preg_match('/\$MyModuleSettings[\w\W]*\/\*\*/m', file_get_contents($sFilePath), $aMatches);
|
||||
return preg_replace(['/[ ]+/', '/[ ]+/'], [' ', ' '], $aMatches[0]);
|
||||
}
|
||||
|
||||
public static function ConfEvaluationIsTheSameWithPreviousAndCurrentAlgoProvider() {
|
||||
return [
|
||||
'comments in module settings' => ['config-with-comments.php'],
|
||||
'nominal case' => ['config-without-comments.php'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ConfEvaluationIsTheSameWithPreviousAndCurrentAlgoProvider
|
||||
*/
|
||||
public function ConfEvaluationIsTheSameWithPreviousAndCurrentAlgo($sFile, $sExpectedContentFile)
|
||||
{
|
||||
$sTmpFile = $this->GetTemporaryFilePath();
|
||||
$sConfigFile = __DIR__."/ConfigTest/$sFile";
|
||||
$oConfig = new Config($sConfigFile, true, false, false);
|
||||
$oConfig->WriteToFile($sTmpFile);
|
||||
|
||||
$sExpected = file_get_contents(__DIR__."/ConfigTest/$sExpectedContentFile");
|
||||
$sExpected = preg_replace('|\?\>\n|', '?>', $sExpected);
|
||||
|
||||
$this->assertEquals($sExpected, file_get_contents($sTmpFile));
|
||||
}
|
||||
|
||||
public function testConfEvaluationIsTheSameWithPreviousAndCurrentAlgo()
|
||||
{
|
||||
$sFile = 'config-without-comments.php';
|
||||
$this->ConfEvaluationIsTheSameWithPreviousAndCurrentAlgo($sFile, $sFile);
|
||||
}
|
||||
|
||||
public function testConfEvaluationIsTheSameWithPreviousAndCurrentAlgoEvenWithCommentsInMopduleSettings()
|
||||
{
|
||||
$this->ConfEvaluationIsTheSameWithPreviousAndCurrentAlgo('config-without-comments.php', 'config-with-comments-afterevaluatonwithoutcomments.php');
|
||||
}
|
||||
|
||||
public function testConfSavePreserveCommentsInModuleSettings()
|
||||
{
|
||||
$sTmpFile = $this->GetTemporaryFilePath();
|
||||
$sConfigFile = __DIR__.'/ConfigTest/config-with-comments.php';
|
||||
$oConfig = new Config($sConfigFile, true, true);
|
||||
$oConfig->WriteToFile($sTmpFile);
|
||||
|
||||
$sExpected = file_get_contents($sConfigFile);
|
||||
$sExpected = preg_replace('|\?\>\n|', '?>', $sExpected);
|
||||
$sExpected = preg_replace('|.*COMMENT NOT PRESERVED HERE.*|', '', $sExpected);
|
||||
|
||||
$this->assertEquals($sExpected, file_get_contents($sTmpFile));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* Configuration file, generated by the iTop configuration wizard
|
||||
*
|
||||
* The file is used in MetaModel::LoadConfig() which does all the necessary initialization job
|
||||
*
|
||||
*/
|
||||
$MySettings = array(
|
||||
|
||||
// access_message: Message displayed to the users when there is any access restriction
|
||||
// default: 'iTop is temporarily frozen, please wait... (the admin team)'
|
||||
'access_message' => 'iTop is temporarily frozen, please wait... (the admin team)',
|
||||
|
||||
// access_mode: Access mode: ACCESS_READONLY = 0, ACCESS_ADMIN_WRITE = 2, ACCESS_FULL = 3
|
||||
// default: 3
|
||||
'access_mode' => 3,
|
||||
|
||||
// activity_panel.entry_form_opened_by_default: Whether or not the new entry form will be automatically opened when viewing an object.
|
||||
// default: false
|
||||
'activity_panel.entry_form_opened_by_default' => false,
|
||||
|
||||
// activity_panel.show_author_name_below_entries: Whether or not to show the author friendlyname next to the date on the last entry.
|
||||
// default: false
|
||||
'activity_panel.show_author_name_below_entries' => false,
|
||||
|
||||
// allowed_login_types: List of login types allowed (separated by | ): form, external, basic, token
|
||||
// default: 'form|external|basic|token'
|
||||
'allowed_login_types' => 'form|external|basic|token',
|
||||
|
||||
// apc_cache.enabled: If set, the APC cache is allowed (the PHP extension must also be active)
|
||||
// default: true
|
||||
'apc_cache.enabled' => true,
|
||||
|
||||
// apc_cache.query_ttl: Time to live set in APC for the prepared queries (seconds - 0 means no timeout)
|
||||
// default: 3600
|
||||
'apc_cache.query_ttl' => 3600,
|
||||
|
||||
// app_root_url: Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server's name)
|
||||
// default: ''
|
||||
'app_root_url' => '',
|
||||
|
||||
// behind_reverse_proxy: If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)
|
||||
// default: false
|
||||
'behind_reverse_proxy' => false,
|
||||
|
||||
// cron_max_execution_time: Duration (seconds) of the cron.php script : if exceeded the script will exit even if there are remaining tasks to process. Must be shorter than php max_execution_time setting (note than when using CLI, this is set to 0 by default which means unlimited). If cron.php is ran via web, it must be shorter than the web server response timeout.
|
||||
// default: 600
|
||||
'cron_max_execution_time' => 600,
|
||||
|
||||
// csv_file_default_charset: Character set used by default for downloading and uploading data as a CSV file. Warning: it is case sensitive (uppercase is preferable).
|
||||
// default: 'ISO-8859-1'
|
||||
'csv_file_default_charset' => 'ISO-8859-1',
|
||||
|
||||
'csv_import_charsets' => array (
|
||||
),
|
||||
|
||||
// csv_import_history_display: Display the history tab in the import wizard
|
||||
// default: false
|
||||
'csv_import_history_display' => false,
|
||||
|
||||
// date_and_time_format: Format for date and time display (per language)
|
||||
// default: array (
|
||||
// 'default' =>
|
||||
// array (
|
||||
// 'date' => 'Y-m-d',
|
||||
// 'time' => 'H:i:s',
|
||||
// 'date_time' => '$date $time',
|
||||
// ),
|
||||
// )
|
||||
'date_and_time_format' => ['default' => ['date' => 'Y-m-d', 'time' => 'H:i:s', 'date_time' => '$date $time']],
|
||||
|
||||
'db_host' => null,
|
||||
|
||||
'db_name' => null,
|
||||
|
||||
'db_pwd' => null,
|
||||
|
||||
'db_subname' => null,
|
||||
|
||||
'db_user' => null,
|
||||
|
||||
// deadline_format: The format used for displaying "deadline" attributes: any string with the following placeholders: $date$, $difference$
|
||||
// default: '$difference$'
|
||||
'deadline_format' => '$difference$',
|
||||
|
||||
'default_language' => 'EN US',
|
||||
|
||||
// email_asynchronous: If set, the emails are sent off line, which requires cron.php to be activated. Exception: some features like the email test utility will force the serialized mode
|
||||
// default: false
|
||||
'email_asynchronous' => false,
|
||||
|
||||
// email_default_sender_address: Default address provided in the email from header field.
|
||||
// default: ''
|
||||
'email_default_sender_address' => '',
|
||||
|
||||
// email_default_sender_label: Default label provided in the email from header field.
|
||||
// default: ''
|
||||
'email_default_sender_label' => '',
|
||||
|
||||
// email_transport: Mean to send emails: PHPMail (uses the function mail()), SMTP (implements the client protocol) or SMTP_OAuth (connect to the server using OAuth 2.0)
|
||||
// default: 'PHPMail'
|
||||
'email_transport' => 'PHPMail',
|
||||
|
||||
// email_validation_pattern: Regular expression to validate/detect the format of an eMail address
|
||||
// default: '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]{2,}'
|
||||
'email_validation_pattern' => '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,}',
|
||||
|
||||
'encryption_key' => 'af1615adfaa712f0efa19e4d8c14ecb4a1d6d0cdecb3f9e611d3b6f2109c5469',
|
||||
|
||||
'encryption_library' => 'Sodium',
|
||||
|
||||
'fast_reload_interval' => '60',
|
||||
|
||||
// graphviz_path: Path to the Graphviz "dot" executable for graphing objects lifecycle
|
||||
// default: '/usr/bin/dot'
|
||||
'graphviz_path' => '/usr/bin/dot',
|
||||
|
||||
// high_cardinality_classes: List of classes with high cardinality (Force manual submit of search)
|
||||
// default: array (
|
||||
// )
|
||||
'high_cardinality_classes' => [],
|
||||
|
||||
// inline_image_max_display_width: The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.
|
||||
// default: '250'
|
||||
'inline_image_max_display_width' => '250',
|
||||
|
||||
// inline_image_max_storage_width: The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database.
|
||||
// default: '1600'
|
||||
'inline_image_max_storage_width' => '1600',
|
||||
|
||||
// lifecycle.transitions_sort_type: How transitions will be sorted in the GUI. Possible values are "xml", "alphabetical", "fixed" or "relative"
|
||||
// default: 'relative'
|
||||
'lifecycle.transitions_sort_type' => 'relative',
|
||||
|
||||
// link_set_attribute_qualifier: Link set from string: attribute qualifier (encloses both the attcode and the value)
|
||||
// default: '\''
|
||||
'link_set_attribute_qualifier' => '\'',
|
||||
|
||||
// link_set_attribute_separator: Link set from string: attribute separator
|
||||
// default: ';'
|
||||
'link_set_attribute_separator' => ';',
|
||||
|
||||
// link_set_item_separator: Link set from string: line separator
|
||||
// default: '|'
|
||||
'link_set_item_separator' => '|',
|
||||
|
||||
// link_set_max_edit_ext_key: Maximum number of items in the link that allow editing the remote external key. Above that limit, remote external key cannot be edited. Mind that setting this limit too high can have a negative impact on performances.
|
||||
// default: 50
|
||||
'link_set_max_edit_ext_key' => 50,
|
||||
|
||||
// link_set_value_separator: Link set from string: value separator (between the attcode and the value itself
|
||||
// default: ':'
|
||||
'link_set_value_separator' => ':',
|
||||
|
||||
'log_global' => true,
|
||||
|
||||
'log_issue' => true,
|
||||
|
||||
'log_notification' => true,
|
||||
|
||||
'log_web_service' => true,
|
||||
|
||||
'max_display_limit' => '30',
|
||||
|
||||
// max_linkset_output: Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.
|
||||
// default: 100
|
||||
'max_linkset_output' => 100,
|
||||
|
||||
// mentions.allowed_classes: Classes which can be mentioned through the autocomplete in the caselogs. Key of the array must be a single character that will trigger the autocomplete, value must be a DM class (eg. "@" => "Person", "?" => "FAQ")
|
||||
// default: array (
|
||||
// '@' => 'Person',
|
||||
// )
|
||||
'mentions.allowed_classes' => ['@' => 'Person'],
|
||||
|
||||
'min_display_limit' => '20',
|
||||
|
||||
// online_help: Hyperlink to the online-help web page
|
||||
// default: 'http://www.combodo.com/itop-help'
|
||||
'online_help' => 'http://www.combodo.com/itop-help',
|
||||
|
||||
// optimize_requests_for_join_count: Optimize request joins to minimize the count (default is true, try to set it to false in case of performance issues)
|
||||
// default: true
|
||||
'optimize_requests_for_join_count' => true,
|
||||
|
||||
'password_hash_algo' => '2y',
|
||||
|
||||
// php_path: Path to the php executable in CLI mode
|
||||
// default: 'php'
|
||||
'php_path' => 'php',
|
||||
|
||||
// search_manual_submit: Force manual submit of search all requests
|
||||
// default: false
|
||||
'search_manual_submit' => false,
|
||||
|
||||
'secure_connection_required' => false,
|
||||
|
||||
// session_name: The name of the cookie used to store the PHP session id
|
||||
// default: 'iTop'
|
||||
'session_name' => 'iTop',
|
||||
|
||||
// shortcut_actions: Actions that are available as direct buttons next to the "Actions" menu
|
||||
// default: 'UI:Menu:Modify,UI:Menu:New,UI:Menu:impacts_down,UI:Menu:impacts_up'
|
||||
'shortcut_actions' => 'UI:Menu:Modify,UI:Menu:New,UI:Menu:impacts_down,UI:Menu:impacts_up',
|
||||
|
||||
// source_dir: Source directory for the datamodel files. (which gets compiled to env-production).
|
||||
// default: ''
|
||||
'source_dir' => '',
|
||||
|
||||
'standard_reload_interval' => '300',
|
||||
|
||||
// synchro_obsolete_replica_locks_object: Obsolete synchro replicas prevent object modification by any mean (eg. anonymization)
|
||||
// default: true
|
||||
'synchro_obsolete_replica_locks_object' => true,
|
||||
|
||||
// synchro_trace: Synchronization details: none, display, save (includes 'display')
|
||||
// default: 'none'
|
||||
'synchro_trace' => 'none',
|
||||
|
||||
// tag_set_item_separator: Tag set from string: tag label separator
|
||||
// default: '|'
|
||||
'tag_set_item_separator' => '|',
|
||||
|
||||
// timezone: Timezone (reference: http://php.net/manual/en/timezones.php). If empty, it will be left unchanged and MUST be explicitly configured in PHP
|
||||
// default: 'Europe/Paris'
|
||||
'timezone' => 'Europe/Paris',
|
||||
|
||||
// url_validation_pattern: Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)
|
||||
// default: '(https?|ftp)\\://([a-zA-Z0-9+!*(),;?&=\\$_.-]+(\\:[a-zA-Z0-9+!*(),;?&=\\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\\:[0-9]{2,5})?(/([a-zA-Z0-9:%@+\\$_-]\\.?)+)*/?(\\?[a-zA-Z+&\\$_.-][a-zA-Z0-9;:[\\]@&%=+/\\$_.,-]*)?(#[a-zA-Z0-9_.-][a-zA-Z0-9+\\$_.-]*)?'
|
||||
'url_validation_pattern' => '(https?|ftp)\://([a-zA-Z0-9+!*(),;?&=\$_.-]+(\:[a-zA-Z0-9+!*(),;?&=\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\:[0-9]{2,5})?(/([a-zA-Z0-9:%@+\$_-]\.?)+)*/?(\?[a-zA-Z+&\$_.-][a-zA-Z0-9;:[\]@&%=+/\$_.,-]*)?(#[a-zA-Z0-9_.-][a-zA-Z0-9+\$_.-]*)?',
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Modules specific settings
|
||||
*
|
||||
*/
|
||||
$MyModuleSettings = array(
|
||||
'combodo-hybridauth' => array (
|
||||
//debug to add traces...
|
||||
'debug' => false,
|
||||
'providers' => array (
|
||||
'Keycloak' =>
|
||||
/* COMMENT NOT PRESERVED HERE*/
|
||||
array (
|
||||
'keys' =>
|
||||
array (
|
||||
/**
|
||||
* sha
|
||||
*
|
||||
* dok
|
||||
*/
|
||||
'id' => 'my-clientid',
|
||||
'secret' => 'my-secret',
|
||||
),
|
||||
'enabled' => false,
|
||||
),
|
||||
//url to access IdP
|
||||
'url' => 'keycloak_url',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Data model modules to be loaded. Names are specified as relative paths
|
||||
*
|
||||
*/
|
||||
$MyModules = array(
|
||||
);
|
||||
?>
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* Configuration file, generated by the iTop configuration wizard
|
||||
*
|
||||
* The file is used in MetaModel::LoadConfig() which does all the necessary initialization job
|
||||
*
|
||||
*/
|
||||
$MySettings = array(
|
||||
|
||||
// access_message: Message displayed to the users when there is any access restriction
|
||||
// default: 'iTop is temporarily frozen, please wait... (the admin team)'
|
||||
'access_message' => 'iTop is temporarily frozen, please wait... (the admin team)',
|
||||
|
||||
// access_mode: Access mode: ACCESS_READONLY = 0, ACCESS_ADMIN_WRITE = 2, ACCESS_FULL = 3
|
||||
// default: 3
|
||||
'access_mode' => 3,
|
||||
|
||||
// activity_panel.entry_form_opened_by_default: Whether or not the new entry form will be automatically opened when viewing an object.
|
||||
// default: false
|
||||
'activity_panel.entry_form_opened_by_default' => false,
|
||||
|
||||
// activity_panel.show_author_name_below_entries: Whether or not to show the author friendlyname next to the date on the last entry.
|
||||
// default: false
|
||||
'activity_panel.show_author_name_below_entries' => false,
|
||||
|
||||
// allowed_login_types: List of login types allowed (separated by | ): form, external, basic, token
|
||||
// default: 'form|external|basic|token'
|
||||
'allowed_login_types' => 'form|external|basic|token',
|
||||
|
||||
// apc_cache.enabled: If set, the APC cache is allowed (the PHP extension must also be active)
|
||||
// default: true
|
||||
'apc_cache.enabled' => true,
|
||||
|
||||
// apc_cache.query_ttl: Time to live set in APC for the prepared queries (seconds - 0 means no timeout)
|
||||
// default: 3600
|
||||
'apc_cache.query_ttl' => 3600,
|
||||
|
||||
// app_root_url: Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server's name)
|
||||
// default: ''
|
||||
'app_root_url' => '',
|
||||
|
||||
// behind_reverse_proxy: If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)
|
||||
// default: false
|
||||
'behind_reverse_proxy' => false,
|
||||
|
||||
// cron_max_execution_time: Duration (seconds) of the cron.php script : if exceeded the script will exit even if there are remaining tasks to process. Must be shorter than php max_execution_time setting (note than when using CLI, this is set to 0 by default which means unlimited). If cron.php is ran via web, it must be shorter than the web server response timeout.
|
||||
// default: 600
|
||||
'cron_max_execution_time' => 600,
|
||||
|
||||
// csv_file_default_charset: Character set used by default for downloading and uploading data as a CSV file. Warning: it is case sensitive (uppercase is preferable).
|
||||
// default: 'ISO-8859-1'
|
||||
'csv_file_default_charset' => 'ISO-8859-1',
|
||||
|
||||
'csv_import_charsets' => array (
|
||||
),
|
||||
|
||||
// csv_import_history_display: Display the history tab in the import wizard
|
||||
// default: false
|
||||
'csv_import_history_display' => false,
|
||||
|
||||
// date_and_time_format: Format for date and time display (per language)
|
||||
// default: array (
|
||||
// 'default' =>
|
||||
// array (
|
||||
// 'date' => 'Y-m-d',
|
||||
// 'time' => 'H:i:s',
|
||||
// 'date_time' => '$date $time',
|
||||
// ),
|
||||
// )
|
||||
'date_and_time_format' => ['default' => ['date' => 'Y-m-d', 'time' => 'H:i:s', 'date_time' => '$date $time']],
|
||||
|
||||
'db_host' => null,
|
||||
|
||||
'db_name' => null,
|
||||
|
||||
'db_pwd' => null,
|
||||
|
||||
'db_subname' => null,
|
||||
|
||||
'db_user' => null,
|
||||
|
||||
// deadline_format: The format used for displaying "deadline" attributes: any string with the following placeholders: $date$, $difference$
|
||||
// default: '$difference$'
|
||||
'deadline_format' => '$difference$',
|
||||
|
||||
'default_language' => 'EN US',
|
||||
|
||||
// email_asynchronous: If set, the emails are sent off line, which requires cron.php to be activated. Exception: some features like the email test utility will force the serialized mode
|
||||
// default: false
|
||||
'email_asynchronous' => false,
|
||||
|
||||
// email_default_sender_address: Default address provided in the email from header field.
|
||||
// default: ''
|
||||
'email_default_sender_address' => '',
|
||||
|
||||
// email_default_sender_label: Default label provided in the email from header field.
|
||||
// default: ''
|
||||
'email_default_sender_label' => '',
|
||||
|
||||
// email_transport: Mean to send emails: PHPMail (uses the function mail()), SMTP (implements the client protocol) or SMTP_OAuth (connect to the server using OAuth 2.0)
|
||||
// default: 'PHPMail'
|
||||
'email_transport' => 'PHPMail',
|
||||
|
||||
// email_validation_pattern: Regular expression to validate/detect the format of an eMail address
|
||||
// default: '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]{2,}'
|
||||
'email_validation_pattern' => '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,}',
|
||||
|
||||
'encryption_key' => 'af1615adfaa712f0efa19e4d8c14ecb4a1d6d0cdecb3f9e611d3b6f2109c5469',
|
||||
|
||||
'encryption_library' => 'Sodium',
|
||||
|
||||
'fast_reload_interval' => '60',
|
||||
|
||||
// graphviz_path: Path to the Graphviz "dot" executable for graphing objects lifecycle
|
||||
// default: '/usr/bin/dot'
|
||||
'graphviz_path' => '/usr/bin/dot',
|
||||
|
||||
// high_cardinality_classes: List of classes with high cardinality (Force manual submit of search)
|
||||
// default: array (
|
||||
// )
|
||||
'high_cardinality_classes' => [],
|
||||
|
||||
// inline_image_max_display_width: The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.
|
||||
// default: '250'
|
||||
'inline_image_max_display_width' => '250',
|
||||
|
||||
// inline_image_max_storage_width: The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database.
|
||||
// default: '1600'
|
||||
'inline_image_max_storage_width' => '1600',
|
||||
|
||||
// lifecycle.transitions_sort_type: How transitions will be sorted in the GUI. Possible values are "xml", "alphabetical", "fixed" or "relative"
|
||||
// default: 'relative'
|
||||
'lifecycle.transitions_sort_type' => 'relative',
|
||||
|
||||
// link_set_attribute_qualifier: Link set from string: attribute qualifier (encloses both the attcode and the value)
|
||||
// default: '\''
|
||||
'link_set_attribute_qualifier' => '\'',
|
||||
|
||||
// link_set_attribute_separator: Link set from string: attribute separator
|
||||
// default: ';'
|
||||
'link_set_attribute_separator' => ';',
|
||||
|
||||
// link_set_item_separator: Link set from string: line separator
|
||||
// default: '|'
|
||||
'link_set_item_separator' => '|',
|
||||
|
||||
// link_set_max_edit_ext_key: Maximum number of items in the link that allow editing the remote external key. Above that limit, remote external key cannot be edited. Mind that setting this limit too high can have a negative impact on performances.
|
||||
// default: 50
|
||||
'link_set_max_edit_ext_key' => 50,
|
||||
|
||||
// link_set_value_separator: Link set from string: value separator (between the attcode and the value itself
|
||||
// default: ':'
|
||||
'link_set_value_separator' => ':',
|
||||
|
||||
'log_global' => true,
|
||||
|
||||
'log_issue' => true,
|
||||
|
||||
'log_notification' => true,
|
||||
|
||||
'log_web_service' => true,
|
||||
|
||||
'max_display_limit' => '30',
|
||||
|
||||
// max_linkset_output: Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.
|
||||
// default: 100
|
||||
'max_linkset_output' => 100,
|
||||
|
||||
// mentions.allowed_classes: Classes which can be mentioned through the autocomplete in the caselogs. Key of the array must be a single character that will trigger the autocomplete, value must be a DM class (eg. "@" => "Person", "?" => "FAQ")
|
||||
// default: array (
|
||||
// '@' => 'Person',
|
||||
// )
|
||||
'mentions.allowed_classes' => ['@' => 'Person'],
|
||||
|
||||
'min_display_limit' => '20',
|
||||
|
||||
// online_help: Hyperlink to the online-help web page
|
||||
// default: 'http://www.combodo.com/itop-help'
|
||||
'online_help' => 'http://www.combodo.com/itop-help',
|
||||
|
||||
// optimize_requests_for_join_count: Optimize request joins to minimize the count (default is true, try to set it to false in case of performance issues)
|
||||
// default: true
|
||||
'optimize_requests_for_join_count' => true,
|
||||
|
||||
'password_hash_algo' => '2y',
|
||||
|
||||
// php_path: Path to the php executable in CLI mode
|
||||
// default: 'php'
|
||||
'php_path' => 'php',
|
||||
|
||||
// search_manual_submit: Force manual submit of search all requests
|
||||
// default: false
|
||||
'search_manual_submit' => false,
|
||||
|
||||
'secure_connection_required' => false,
|
||||
|
||||
// session_name: The name of the cookie used to store the PHP session id
|
||||
// default: 'iTop'
|
||||
'session_name' => 'iTop',
|
||||
|
||||
// shortcut_actions: Actions that are available as direct buttons next to the "Actions" menu
|
||||
// default: 'UI:Menu:Modify,UI:Menu:New,UI:Menu:impacts_down,UI:Menu:impacts_up'
|
||||
'shortcut_actions' => 'UI:Menu:Modify,UI:Menu:New,UI:Menu:impacts_down,UI:Menu:impacts_up',
|
||||
|
||||
// source_dir: Source directory for the datamodel files. (which gets compiled to env-production).
|
||||
// default: ''
|
||||
'source_dir' => '',
|
||||
|
||||
'standard_reload_interval' => '300',
|
||||
|
||||
// synchro_obsolete_replica_locks_object: Obsolete synchro replicas prevent object modification by any mean (eg. anonymization)
|
||||
// default: true
|
||||
'synchro_obsolete_replica_locks_object' => true,
|
||||
|
||||
// synchro_trace: Synchronization details: none, display, save (includes 'display')
|
||||
// default: 'none'
|
||||
'synchro_trace' => 'none',
|
||||
|
||||
// tag_set_item_separator: Tag set from string: tag label separator
|
||||
// default: '|'
|
||||
'tag_set_item_separator' => '|',
|
||||
|
||||
// timezone: Timezone (reference: http://php.net/manual/en/timezones.php). If empty, it will be left unchanged and MUST be explicitly configured in PHP
|
||||
// default: 'Europe/Paris'
|
||||
'timezone' => 'Europe/Paris',
|
||||
|
||||
// url_validation_pattern: Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)
|
||||
// default: '(https?|ftp)\\://([a-zA-Z0-9+!*(),;?&=\\$_.-]+(\\:[a-zA-Z0-9+!*(),;?&=\\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\\:[0-9]{2,5})?(/([a-zA-Z0-9:%@+\\$_-]\\.?)+)*/?(\\?[a-zA-Z+&\\$_.-][a-zA-Z0-9;:[\\]@&%=+/\\$_.,-]*)?(#[a-zA-Z0-9_.-][a-zA-Z0-9+\\$_.-]*)?'
|
||||
'url_validation_pattern' => '(https?|ftp)\://([a-zA-Z0-9+!*(),;?&=\$_.-]+(\:[a-zA-Z0-9+!*(),;?&=\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\:[0-9]{2,5})?(/([a-zA-Z0-9:%@+\$_-]\.?)+)*/?(\?[a-zA-Z+&\$_.-][a-zA-Z0-9;:[\]@&%=+/\$_.,-]*)?(#[a-zA-Z0-9_.-][a-zA-Z0-9+\$_.-]*)?',
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Modules specific settings
|
||||
*
|
||||
*/
|
||||
$MyModuleSettings = array(
|
||||
'combodo-hybridauth' => array (
|
||||
//debug to add traces...
|
||||
'debug' => false,
|
||||
'providers' => array (
|
||||
'Keycloak' =>
|
||||
/* COMMENT NOT PRESERVED HERE*/
|
||||
array (
|
||||
'keys' =>
|
||||
array (
|
||||
/**
|
||||
* sha
|
||||
*
|
||||
* dok
|
||||
*/
|
||||
'id' => 'my-clientid',
|
||||
'secret' => 'my-secret',
|
||||
),
|
||||
'enabled' => false,
|
||||
),
|
||||
//url to access IdP
|
||||
'url' => 'keycloak_url',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Data model modules to be loaded. Names are specified as relative paths
|
||||
*
|
||||
*/
|
||||
$MyModules = array(
|
||||
);
|
||||
?>
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* Configuration file, generated by the iTop configuration wizard
|
||||
*
|
||||
* The file is used in MetaModel::LoadConfig() which does all the necessary initialization job
|
||||
*
|
||||
*/
|
||||
$MySettings = array(
|
||||
|
||||
// access_message: Message displayed to the users when there is any access restriction
|
||||
// default: 'iTop is temporarily frozen, please wait... (the admin team)'
|
||||
'access_message' => 'iTop is temporarily frozen, please wait... (the admin team)',
|
||||
|
||||
// access_mode: Access mode: ACCESS_READONLY = 0, ACCESS_ADMIN_WRITE = 2, ACCESS_FULL = 3
|
||||
// default: 3
|
||||
'access_mode' => 3,
|
||||
|
||||
// activity_panel.entry_form_opened_by_default: Whether or not the new entry form will be automatically opened when viewing an object.
|
||||
// default: false
|
||||
'activity_panel.entry_form_opened_by_default' => false,
|
||||
|
||||
// activity_panel.show_author_name_below_entries: Whether or not to show the author friendlyname next to the date on the last entry.
|
||||
// default: false
|
||||
'activity_panel.show_author_name_below_entries' => false,
|
||||
|
||||
// allowed_login_types: List of login types allowed (separated by | ): form, external, basic, token
|
||||
// default: 'form|external|basic|token'
|
||||
'allowed_login_types' => 'form|external|basic|token',
|
||||
|
||||
// apc_cache.enabled: If set, the APC cache is allowed (the PHP extension must also be active)
|
||||
// default: true
|
||||
'apc_cache.enabled' => true,
|
||||
|
||||
// apc_cache.query_ttl: Time to live set in APC for the prepared queries (seconds - 0 means no timeout)
|
||||
// default: 3600
|
||||
'apc_cache.query_ttl' => 3600,
|
||||
|
||||
// app_env_label: Label displayed to describe the current application environment, defaults to the environment name (e.g. "production")
|
||||
// default: ''
|
||||
'app_env_label' => 'production (built on 2026-06-05)',
|
||||
|
||||
// app_root_url: Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server's name)
|
||||
// default: ''
|
||||
'app_root_url' => 'https://app_root_url/iTop/',
|
||||
|
||||
// behind_reverse_proxy: If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)
|
||||
// default: false
|
||||
'behind_reverse_proxy' => false,
|
||||
|
||||
// cron_max_execution_time: Duration (seconds) of the cron.php script : if exceeded the script will exit even if there are remaining tasks to process. Must be shorter than php max_execution_time setting (note than when using CLI, this is set to 0 by default which means unlimited). If cron.php is ran via web, it must be shorter than the web server response timeout.
|
||||
// default: 600
|
||||
'cron_max_execution_time' => 600,
|
||||
|
||||
// csv_file_default_charset: Character set used by default for downloading and uploading data as a CSV file. Warning: it is case sensitive (uppercase is preferable).
|
||||
// default: 'ISO-8859-1'
|
||||
'csv_file_default_charset' => 'ISO-8859-1',
|
||||
|
||||
'csv_import_charsets' => array (
|
||||
),
|
||||
|
||||
// csv_import_history_display: Display the history tab in the import wizard
|
||||
// default: false
|
||||
'csv_import_history_display' => false,
|
||||
|
||||
// date_and_time_format: Format for date and time display (per language)
|
||||
// default: array (
|
||||
// 'default' =>
|
||||
// array (
|
||||
// 'date' => 'Y-m-d',
|
||||
// 'time' => 'H:i:s',
|
||||
// 'date_time' => '$date $time',
|
||||
// ),
|
||||
// )
|
||||
'date_and_time_format' => array('default' => array('date' => 'Y-m-d', 'time' => 'H:i:s', 'date_time' => '$date $time')),
|
||||
|
||||
'db_host' => 'localhost',
|
||||
|
||||
'db_name' => 'db_name',
|
||||
|
||||
'db_pwd' => 'db_pwd',
|
||||
|
||||
'db_subname' => '',
|
||||
|
||||
'db_user' => 'db_user',
|
||||
|
||||
// deadline_format: The format used for displaying "deadline" attributes: any string with the following placeholders: $date$, $difference$
|
||||
// default: '$difference$'
|
||||
'deadline_format' => '$difference$',
|
||||
|
||||
'default_language' => 'EN US',
|
||||
|
||||
// email_asynchronous: If set, the emails are sent off line, which requires cron.php to be activated. Exception: some features like the email test utility will force the serialized mode
|
||||
// default: false
|
||||
'email_asynchronous' => false,
|
||||
|
||||
// email_default_sender_address: Default address provided in the email from header field.
|
||||
// default: ''
|
||||
'email_default_sender_address' => '',
|
||||
|
||||
// email_default_sender_label: Default label provided in the email from header field.
|
||||
// default: ''
|
||||
'email_default_sender_label' => '',
|
||||
|
||||
// email_transport: Mean to send emails: PHPMail (uses the function mail()), SMTP (implements the client protocol) or SMTP_OAuth (connect to the server using OAuth 2.0)
|
||||
// default: 'PHPMail'
|
||||
'email_transport' => 'PHPMail',
|
||||
|
||||
// email_validation_pattern: Regular expression to validate/detect the format of an eMail address
|
||||
// default: '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]{2,}'
|
||||
'email_validation_pattern' => '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,}',
|
||||
|
||||
'encryption_key' => 'a85b',
|
||||
|
||||
'encryption_library' => 'Sodium',
|
||||
|
||||
'fast_reload_interval' => '60',
|
||||
|
||||
// graphviz_path: Path to the Graphviz "dot" executable for graphing objects lifecycle
|
||||
// default: '/usr/bin/dot'
|
||||
'graphviz_path' => '/usr/bin/dot',
|
||||
|
||||
// high_cardinality_classes: List of classes with high cardinality (Force manual submit of search)
|
||||
// default: array (
|
||||
// )
|
||||
'high_cardinality_classes' => array(),
|
||||
|
||||
// inline_image_max_display_width: The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.
|
||||
// default: '250'
|
||||
'inline_image_max_display_width' => '250',
|
||||
|
||||
// inline_image_max_storage_width: The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database.
|
||||
// default: '1600'
|
||||
'inline_image_max_storage_width' => '1600',
|
||||
|
||||
// lifecycle.transitions_sort_type: How transitions will be sorted in the GUI. Possible values are "xml", "alphabetical", "fixed" or "relative"
|
||||
// default: 'relative'
|
||||
'lifecycle.transitions_sort_type' => 'relative',
|
||||
|
||||
// link_set_attribute_qualifier: Link set from string: attribute qualifier (encloses both the attcode and the value)
|
||||
// default: '\''
|
||||
'link_set_attribute_qualifier' => '\'',
|
||||
|
||||
// link_set_attribute_separator: Link set from string: attribute separator
|
||||
// default: ';'
|
||||
'link_set_attribute_separator' => ';',
|
||||
|
||||
// link_set_item_separator: Link set from string: line separator
|
||||
// default: '|'
|
||||
'link_set_item_separator' => '|',
|
||||
|
||||
// link_set_max_edit_ext_key: Maximum number of items in the link that allow editing the remote external key. Above that limit, remote external key cannot be edited. Mind that setting this limit too high can have a negative impact on performances.
|
||||
// default: 50
|
||||
'link_set_max_edit_ext_key' => 50,
|
||||
|
||||
// link_set_value_separator: Link set from string: value separator (between the attcode and the value itself
|
||||
// default: ':'
|
||||
'link_set_value_separator' => ':',
|
||||
|
||||
'log_global' => true,
|
||||
|
||||
'log_issue' => true,
|
||||
|
||||
'log_notification' => true,
|
||||
|
||||
'log_web_service' => true,
|
||||
|
||||
'max_display_limit' => '30',
|
||||
|
||||
// max_linkset_output: Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.
|
||||
// default: 100
|
||||
'max_linkset_output' => 100,
|
||||
|
||||
// mentions.allowed_classes: Classes which can be mentioned through the autocomplete in the caselogs. Key of the array must be a single character that will trigger the autocomplete, value must be a DM class (eg. "@" => "Person", "?" => "FAQ")
|
||||
// default: array (
|
||||
// '@' => 'Person',
|
||||
// )
|
||||
'mentions.allowed_classes' => array('@' => 'Person'),
|
||||
|
||||
'min_display_limit' => '20',
|
||||
|
||||
// online_help: Hyperlink to the online-help web page
|
||||
// default: 'http://www.combodo.com/itop-help'
|
||||
'online_help' => 'http://www.combodo.com/itop-help',
|
||||
|
||||
// optimize_requests_for_join_count: Optimize request joins to minimize the count (default is true, try to set it to false in case of performance issues)
|
||||
// default: true
|
||||
'optimize_requests_for_join_count' => true,
|
||||
|
||||
'password_hash_algo' => '2y',
|
||||
|
||||
// php_path: Path to the php executable in CLI mode
|
||||
// default: 'php'
|
||||
'php_path' => 'php',
|
||||
|
||||
// search_manual_submit: Force manual submit of search all requests
|
||||
// default: false
|
||||
'search_manual_submit' => false,
|
||||
|
||||
'secure_connection_required' => false,
|
||||
|
||||
// session_name: The name of the cookie used to store the PHP session id
|
||||
// default: 'iTop'
|
||||
'session_name' => 'iTop',
|
||||
|
||||
// shortcut_actions: Actions that are available as direct buttons next to the "Actions" menu
|
||||
// default: 'UI:Menu:Modify,UI:Menu:New,UI:Menu:impacts_down,UI:Menu:impacts_up'
|
||||
'shortcut_actions' => 'UI:Menu:Modify,UI:Menu:New,UI:Menu:impacts_down,UI:Menu:impacts_up',
|
||||
|
||||
// source_dir: Source directory for the datamodel files. (which gets compiled to env-production).
|
||||
// default: ''
|
||||
'source_dir' => 'datamodels/2.x/',
|
||||
|
||||
'standard_reload_interval' => '300',
|
||||
|
||||
// synchro_obsolete_replica_locks_object: Obsolete synchro replicas prevent object modification by any mean (eg. anonymization)
|
||||
// default: true
|
||||
'synchro_obsolete_replica_locks_object' => true,
|
||||
|
||||
// synchro_trace: Synchronization details: none, display, save (includes 'display')
|
||||
// default: 'none'
|
||||
'synchro_trace' => 'none',
|
||||
|
||||
// tag_set_item_separator: Tag set from string: tag label separator
|
||||
// default: '|'
|
||||
'tag_set_item_separator' => '|',
|
||||
|
||||
// timezone: Timezone (reference: http://php.net/manual/en/timezones.php). If empty, it will be left unchanged and MUST be explicitly configured in PHP
|
||||
// default: 'Europe/Paris'
|
||||
'timezone' => 'Europe/Paris',
|
||||
|
||||
// url_validation_pattern: Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)
|
||||
// default: '(https?|ftp)\\://([a-zA-Z0-9+!*(),;?&=\\$_.-]+(\\:[a-zA-Z0-9+!*(),;?&=\\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\\:[0-9]{2,5})?(/([a-zA-Z0-9:%@+\\$_-]\\.?)+)*/?(\\?[a-zA-Z+&\\$_.-][a-zA-Z0-9;:[\\]@&%=+/\\$_.,-]*)?(#[a-zA-Z0-9_.-][a-zA-Z0-9+\\$_.-]*)?'
|
||||
'url_validation_pattern' => '(https?|ftp)\://([a-zA-Z0-9+!*(),;?&=\$_.-]+(\:[a-zA-Z0-9+!*(),;?&=\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\:[0-9]{2,5})?(/([a-zA-Z0-9:%@+\$_-]\.?)+)*/?(\?[a-zA-Z+&\$_.-][a-zA-Z0-9;:[\]@&%=+/\$_.,-]*)?(#[a-zA-Z0-9_.-][a-zA-Z0-9+\$_.-]*)?',
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Modules specific settings
|
||||
*
|
||||
*/
|
||||
$MyModuleSettings = array(
|
||||
'authent-cas' => array (
|
||||
'cas_debug' => false,
|
||||
'cas_host' => '',
|
||||
'cas_port' => '',
|
||||
'cas_context' => '',
|
||||
'cas_version' => '',
|
||||
'service_base_url' => '',
|
||||
),
|
||||
'authent-ldap' => array (
|
||||
'uri' => 'ldap://localhost',
|
||||
'default_user' => '',
|
||||
'default_pwd' => '',
|
||||
'base_dn' => 'dc=yourcompany,dc=com',
|
||||
'user_query' => '(&(uid=%1$s)(inetuserstatus=ACTIVE))',
|
||||
'options' => array (
|
||||
17 => 3,
|
||||
8 => 0,
|
||||
),
|
||||
'start_tls' => false,
|
||||
'debug' => false,
|
||||
'servers' => array (
|
||||
),
|
||||
),
|
||||
'itop-attachments' => array (
|
||||
'allowed_classes' => array (
|
||||
0 => 'Ticket',
|
||||
),
|
||||
'position' => 'relations',
|
||||
'preview_max_width' => 290,
|
||||
'icon_preview_max_size' => 500000,
|
||||
),
|
||||
'itop-backup' => array (
|
||||
'mysql_bindir' => '',
|
||||
'week_days' => 'monday, tuesday, wednesday, thursday, friday',
|
||||
'time' => '23:30',
|
||||
'retention_count' => 5,
|
||||
'enabled' => true,
|
||||
'itop_backup_incident' => '',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Data model modules to be loaded. Names are specified as relative paths
|
||||
*
|
||||
*/
|
||||
$MyModules = array(
|
||||
);
|
||||
?>
|
||||
@@ -285,89 +285,61 @@ SQL
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion
|
||||
* @dataProvider LoadLocalizedData_RequiredLanguageProvider
|
||||
* @covers \ModuleInstallerAPI::LoadLocalizedData
|
||||
*/
|
||||
public function testLoadLocalizedData_LoadRequiredLanguageOnFirstInstall(string $sRequiredLanguage, array $aAvailableLanguages, array $aExpectedCountByLanguage): void
|
||||
public function testLoadLocalizedData_LoadsOnFirstInstall(): void
|
||||
{
|
||||
// Given
|
||||
[$oConfig, $sOrgName, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_RequiredLanguage_', $sRequiredLanguage, $aAvailableLanguages);
|
||||
|
||||
[$oConfig, $sOrgName, $sTmpDir, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_FirstInstall_', 'fr_fr');
|
||||
$this->GivenLocalizedDataFile($sTmpDir, "en_us", $sOrgName);
|
||||
$this->GivenLocalizedDataFile($sTmpDir, "fr_fr", $sOrgName);
|
||||
// When no previous version, and current version higher than the first loading version
|
||||
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, '', '3.3.0', '3.0.0', $sPattern);
|
||||
|
||||
ModuleInstallerAPI::LoadLocalizedData($oConfig, '', '3.3.0', '3.0.0', $sPattern);
|
||||
// Then data loaded
|
||||
foreach ($aExpectedCountByLanguage as $sLanguage => $iExpectedCount) {
|
||||
$this->AssertOrganizationCountByName($sOrgName, $sLanguage, $iExpectedCount);
|
||||
}
|
||||
}
|
||||
|
||||
public function LoadLocalizedData_RequiredLanguageProvider(): array
|
||||
{
|
||||
return [
|
||||
'Required fr_fr and file exists' => [
|
||||
'required language' => 'fr_fr',
|
||||
'available languages' => ['en_us', 'fr_fr'],
|
||||
'expected counts' => ['en_us' => 0, 'fr_fr' => 1],
|
||||
],
|
||||
'Required en_us and file exists' => [
|
||||
'required language' => 'en_us',
|
||||
'available languages' => ['en_us', 'fr_fr'],
|
||||
'expected counts' => ['en_us' => 1, 'fr_fr' => 0],
|
||||
],
|
||||
'Required fr_fr but fallback to en_us' => [
|
||||
'required language' => 'fr_fr',
|
||||
'available languages' => ['en_us'],
|
||||
'expected counts' => ['en_us' => 1, 'fr_fr' => 0],
|
||||
],
|
||||
'Required de_de and file exists' => [
|
||||
'required language' => 'de_de',
|
||||
'available languages' => ['en_us', 'fr_fr', 'de_de'],
|
||||
'expected counts' => ['en_us' => 0, 'fr_fr' => 0, 'de_de' => 1],
|
||||
],
|
||||
'Required de_de but fallback to en_us' => [
|
||||
'required language' => 'de_de',
|
||||
'available languages' => ['en_us', 'fr_fr'],
|
||||
'expected counts' => ['en_us' => 1, 'fr_fr' => 0, 'de_de' => 0],
|
||||
],
|
||||
];
|
||||
$this->AssertOrganizationCountByName($sOrgName, 'en_us', 0);
|
||||
$this->AssertOrganizationCountByName($sOrgName, 'fr_fr', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion
|
||||
* @dataProvider LoadLocalizedData_VersionConditionNotMetProvider
|
||||
* @covers \ModuleInstallerAPI::LoadLocalizedData
|
||||
*/
|
||||
public function testLoadLocalizedData_DoesNotLoadWhenVersionConditionIsNotMet(string $sPreviousVersion, string $sCurrentVersion, string $sFirstLoadingVersion): void
|
||||
public function testLoadLocalizedData_DoesNotLoadWhenVersionConditionIsNotMet(): void
|
||||
{
|
||||
// Given
|
||||
[$oConfig, $sOrgName, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_NoLoad_', 'en_us', ['en_us']);
|
||||
// When version gate conditions are not met
|
||||
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, $sPreviousVersion, $sCurrentVersion, $sFirstLoadingVersion, $sPattern);
|
||||
[$oConfig, $sOrgName, $sTmpDir, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_NoLoad_', 'en_us');
|
||||
$this->GivenLocalizedDataFile($sTmpDir, "en_us", $sOrgName);
|
||||
|
||||
// When a previous version that is lower than the first loading version, but higher or equal to the current version
|
||||
ModuleInstallerAPI::LoadLocalizedData($oConfig, '3.0.0', '3.1.0', '3.0.0', $sPattern);
|
||||
// Then no data loaded
|
||||
$this->AssertOrganizationCountByName($sOrgName, 'en_us', 0);
|
||||
}
|
||||
|
||||
public function LoadLocalizedData_VersionConditionNotMetProvider(): array
|
||||
/**
|
||||
* @covers \ModuleInstallerAPI::LoadLocalizedData
|
||||
*/
|
||||
public function testLoadLocalizedData_FallbacksToEnUsWhenLanguageFileIsMissing(): void
|
||||
{
|
||||
return [
|
||||
'Equal versions (reinstall)' => ['3.1.0', '3.1.0', '3.0.0'],
|
||||
'Downgrade attempt' => ['3.2.0', '3.1.0', '3.0.0'],
|
||||
'Upgrade but first loading version already passed' => ['3.1.0', '3.2.0', '3.0.0'],
|
||||
'Upgrade with boundary equality on first loading version' => ['3.0.0', '3.1.0', '3.0.0'],
|
||||
'Upgrade but first loading version empty' => ['3.1.0', '3.2.0', ''],
|
||||
[$oConfig, $sOrgName, $sTmpDir, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_Fallback_', 'fr_fr');
|
||||
// Intentionally create ONLY en_us file
|
||||
$this->GivenLocalizedDataFile($sTmpDir, 'en_us', $sOrgName);
|
||||
// When loading localized data in fr_fr, but only en_us file exists
|
||||
ModuleInstallerAPI::LoadLocalizedData($oConfig, '', '3.3.0', '3.0.0', $sPattern);
|
||||
|
||||
];
|
||||
$this->AssertOrganizationCountByName($sOrgName, 'fr_fr', 0);
|
||||
$this->AssertOrganizationCountByName($sOrgName, 'en_us', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion
|
||||
* @covers \ModuleInstallerAPI::LoadLocalizedData
|
||||
* @dataProvider LoadLocalizedData_ValidVersionFormatsProvider
|
||||
*/
|
||||
public function testLoadLocalizedData_AcceptsSupportedVersionFormats(string $sCurrentVersion, string $sFirstLoadingVersion): void
|
||||
{
|
||||
[$oConfig, $sOrgName, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_ValidVersion_', 'en_us', ['en_us']);
|
||||
[$oConfig, $sOrgName, $sTmpDir, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_ValidVersion_', 'en_us');
|
||||
$this->GivenLocalizedDataFile($sTmpDir, 'en_us', $sOrgName);
|
||||
|
||||
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, '', $sCurrentVersion, $sFirstLoadingVersion, $sPattern);
|
||||
ModuleInstallerAPI::LoadLocalizedData($oConfig, '', $sCurrentVersion, $sFirstLoadingVersion, $sPattern);
|
||||
|
||||
$this->AssertOrganizationCountByName($sOrgName, 'en_us', 1);
|
||||
}
|
||||
@@ -376,39 +348,11 @@ SQL
|
||||
{
|
||||
return [
|
||||
'Current version with suffix' => ['3.2-dev', '3.0.0'],
|
||||
'Current version x.y.z' => ['10.12.140-Tagada34', '1.0'],
|
||||
'Current version x.y.z-suffix' => ['2.3.3-beta', '2.3.3-alpha'],
|
||||
'Current version x.y.z' => ['1.2.4', '1.0'],
|
||||
'Current version x.y.z-suffix' => ['2.3.3-beta', '2.0.0'],
|
||||
'Current version x.y.z-1' => ['1.2.4-1', '1.0.3-2'],
|
||||
];
|
||||
}
|
||||
// Test when a file is loaded twice because of the version conditions, it doesn't create duplicates (idempotent loading)
|
||||
public function testLoadLocalizedData_IdempotentLoading(): void
|
||||
{
|
||||
// Given
|
||||
[$oConfig, $sOrgName, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_Idempotent_', 'en_us', ['en_us']);
|
||||
|
||||
// When LoadLocalizedData is called twice with conditions that would load the file both times
|
||||
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, '', '3.1.0', '3.0.0', $sPattern);
|
||||
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, '3.1.0', '3.2.0', '', $sPattern);
|
||||
|
||||
// Then no duplicate data loaded
|
||||
$this->AssertOrganizationCountByName($sOrgName, 'en_us', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion
|
||||
* @dataProvider LoadLocalizedData_InvalidParametersProvider
|
||||
*/
|
||||
public function testLoadLocalizedData_ThrowsOnInvalidParameters(string $sPreviousVersion, string $sCurrentVersion, string $sFirstLoadingVersion, string $sPattern, string $sExpectedMessage): void
|
||||
{
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$this->assertNotNull($oConfig);
|
||||
|
||||
$this->expectException(\CoreUnexpectedValue::class);
|
||||
$this->expectExceptionMessage($sExpectedMessage);
|
||||
|
||||
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, $sPreviousVersion, $sCurrentVersion, $sFirstLoadingVersion, $sPattern);
|
||||
}
|
||||
|
||||
public function LoadLocalizedData_InvalidParametersProvider(): array
|
||||
{
|
||||
@@ -444,6 +388,13 @@ SQL
|
||||
'pattern' => $sTmpDir.DIRECTORY_SEPARATOR.'data.{{LANGUAGE_CODE}}.xml',
|
||||
'message' => "{{language_code}}",
|
||||
],
|
||||
'Parent directory does not exist' => [
|
||||
'previous' => '',
|
||||
'current' => '3.2.0',
|
||||
'first' => '3.0.0',
|
||||
'pattern' => $sTmpDir.DIRECTORY_SEPARATOR.'missing'.DIRECTORY_SEPARATOR.'data.{{language_code}}.xml',
|
||||
'message' => 'parent directory',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -452,7 +403,7 @@ SQL
|
||||
*
|
||||
* @return array{0: Config, 1: string, 2: string, 3: string, 4: string}
|
||||
*/
|
||||
private function GivenLocalizedDataTestContext(string $sOrgNamePrefix, string $sLanguage, array $aAvailableLanguages = []): array
|
||||
private function GivenLocalizedDataTestContext(string $sOrgNamePrefix, string $sLanguage): array
|
||||
{
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$oConfig->SetDefaultLanguage($sLanguage);
|
||||
@@ -464,11 +415,7 @@ SQL
|
||||
$this->aFileToClean[] = $sTmpDir;
|
||||
$sPattern = $sTmpDir.DIRECTORY_SEPARATOR.'data.{{language_code}}.xml';
|
||||
|
||||
foreach ($aAvailableLanguages as $sAvailableLanguage) {
|
||||
$this->GivenLocalizedDataFile($sTmpDir, $sAvailableLanguage, $sOrgName);
|
||||
}
|
||||
|
||||
return [$oConfig, $sOrgName, $sPattern];
|
||||
return [$oConfig, $sOrgName, $sTmpDir, $sPattern];
|
||||
}
|
||||
|
||||
private function GivenLocalizedDataFile(string $sDir, string $sLang, string $sOrgName): string
|
||||
|
||||
@@ -39,7 +39,9 @@ class UnattendedInstallTest extends ItopDataTestCase
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
|
||||
$sJson = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
curl_close($ch);
|
||||
}
|
||||
return $sJson;
|
||||
}
|
||||
public function testCallUnattendedInstallFromHttp()
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Service\Notification\Event;
|
||||
|
||||
use Action;
|
||||
use ActionNewsroom;
|
||||
use Combodo\iTop\Application\WebPage\CaptureWebPage;
|
||||
use Combodo\iTop\Service\Notification\Event\EventNotificationNewsroomService;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use Contact;
|
||||
use EventNotificationNewsroom;
|
||||
use Person;
|
||||
use Ticket;
|
||||
use Trigger;
|
||||
use TriggerOnObjectMention;
|
||||
use UserRequest;
|
||||
use UserRights;
|
||||
|
||||
class EventNotificationNewsroomServiceTest extends ItopDataTestCase
|
||||
{
|
||||
public const CREATE_TEST_ORG = true;
|
||||
|
||||
private Contact $oContact;
|
||||
private Trigger $oTrigger;
|
||||
private Action $oAction;
|
||||
private Ticket $oTicket;
|
||||
private EventNotificationNewsroom $oEvent;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
/** @var Contact $oContact */
|
||||
$oContact = $this->createObject(Person::class, [
|
||||
'name' => 'Khalo',
|
||||
'first_name' => 'Frida',
|
||||
'org_id' => $this->getTestOrgId(),
|
||||
]);
|
||||
$this->oContact = $oContact;
|
||||
|
||||
/** @var Trigger $oTrigger */
|
||||
$oTrigger = $this->createObject(TriggerOnObjectMention::class, [
|
||||
'description' => 'Person mentioned on Ticket',
|
||||
'target_class' => 'Ticket',
|
||||
]);
|
||||
$this->oTrigger = $oTrigger;
|
||||
|
||||
/** @var Action $oAction */
|
||||
$oAction = $this->createObject(ActionNewsroom::class, [
|
||||
'name' => 'Notification to persons mentioned in logs',
|
||||
'status' => 'enabled',
|
||||
'title' => '$this->friendlyname$',
|
||||
'message' => 'You have been mentioned by $current_contact->friendlyname$',
|
||||
'recipients' => 'SELECT Person WHERE id = :mentioned->id',
|
||||
]);
|
||||
$this->oAction = $oAction;
|
||||
|
||||
/** @var Ticket $oTicket */
|
||||
$oTicket = $this->createObject(UserRequest::class, [
|
||||
'org_id' => $this->getTestOrgId(),
|
||||
'title' => 'Houston, got a problem',
|
||||
'description' => 'Test description',
|
||||
]);
|
||||
$this->oTicket = $oTicket;
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
$this->oEvent->DBDelete();
|
||||
}
|
||||
|
||||
public function testDownloadIsTriggeredWhenDownloaderIsNotificationRecipient(): void
|
||||
{
|
||||
$this->oEvent = EventNotificationNewsroomService::MakeEventFromAction(
|
||||
oAction: $this->oAction,
|
||||
iContactId: $this->oContact->GetKey(),
|
||||
iTriggerId: $this->oTrigger->GetKey(),
|
||||
sMessage: 'Test message',
|
||||
sTitle: 'Test event',
|
||||
sUrl: 'https://localhost/itop/pages/UI.php?operation=details&class=UserRequest&id=1',
|
||||
iObjectId: $this->oTicket->GetKey(),
|
||||
sObjectClass: UserRequest::class,
|
||||
);
|
||||
$this->oEvent->DBInsert();
|
||||
|
||||
$oPage = new CaptureWebPage();
|
||||
$bDownloadIcon = EventNotificationNewsroomService::DownloadIcon($oPage, $this->oEvent->GetKey(), $this->oContact->GetKey());
|
||||
$sHtml = $oPage->GetHtml();
|
||||
|
||||
$this->assertTrue($bDownloadIcon);
|
||||
$this->assertNotEquals('', $sHtml);
|
||||
}
|
||||
|
||||
public function testDownloadIsNotTriggeredWhenDownloaderIsNotNotificationRecipient(): void
|
||||
{
|
||||
$oContact = $this->createObject(Person::class, [
|
||||
'name' => 'Doe',
|
||||
'first_name' => 'John',
|
||||
'org_id' => $this->getTestOrgId(),
|
||||
]);
|
||||
$this->oEvent = EventNotificationNewsroomService::MakeEventFromAction(
|
||||
oAction: $this->oAction,
|
||||
iContactId: $oContact->GetKey(),
|
||||
iTriggerId: $this->oTrigger->GetKey(),
|
||||
sMessage: 'Test message',
|
||||
sTitle: 'Test event',
|
||||
sUrl: 'https://localhost/itop/pages/UI.php?operation=details&class=UserRequest&id=1',
|
||||
iObjectId: $this->oTicket->GetKey(),
|
||||
sObjectClass: UserRequest::class,
|
||||
);
|
||||
$this->oEvent->DBInsert();
|
||||
|
||||
$oPage = new CaptureWebPage();
|
||||
$bDownloadIcon = EventNotificationNewsroomService::DownloadIcon($oPage, $this->oEvent->GetKey(), $this->oContact->GetKey());
|
||||
$sHtml = $oPage->GetHtml();
|
||||
|
||||
$this->assertFalse($bDownloadIcon);
|
||||
$this->assertEquals('', $sHtml);
|
||||
}
|
||||
|
||||
public function testDownloadIconIsTriggeredEvenWhenUserCannotReadIconAttribute(): void
|
||||
{
|
||||
// Create a user with Support Agent Profile
|
||||
$sLogin = uniqid('EventNotificationNewsroomServiceTest');
|
||||
$oUser = $this->CreateContactlessUser($sLogin, self::$aURP_Profiles['Support Agent'], '1234@Abcdefg');
|
||||
$oUser->Set('contactid', $this->oContact->GetKey());
|
||||
UserRights::Login($sLogin);
|
||||
|
||||
$this->oEvent = EventNotificationNewsroomService::MakeEventFromAction(
|
||||
oAction: $this->oAction,
|
||||
iContactId: $this->oContact->GetKey(),
|
||||
iTriggerId: $this->oTrigger->GetKey(),
|
||||
sMessage: 'Test message',
|
||||
sTitle: 'Test event',
|
||||
sUrl: 'https://localhost/itop/pages/UI.php?operation=details&class=UserRequest&id=1',
|
||||
iObjectId: $this->oTicket->GetKey(),
|
||||
sObjectClass: UserRequest::class,
|
||||
);
|
||||
$this->oEvent->DBInsert();
|
||||
|
||||
$iURValue = UserRights::IsActionAllowedOnAttribute(EventNotificationNewsroom::class, 'icon', UR_ACTION_READ, $this->oEvent, $oUser);
|
||||
$this->assertEquals(UR_ALLOWED_NO, $iURValue);
|
||||
|
||||
$oPage = new CaptureWebPage();
|
||||
$bDownloadIcon = EventNotificationNewsroomService::DownloadIcon($oPage, $this->oEvent->GetKey(), $this->oContact->GetKey());
|
||||
$sHtml = $oPage->GetHtml();
|
||||
|
||||
$this->assertTrue($bDownloadIcon);
|
||||
$this->assertNotEquals('', $sHtml);
|
||||
$this->assertStringNotContainsString('the object does not exist or you are not allowed to view it', $sHtml);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user