Compare commits

..

4 Commits

Author SHA1 Message Date
Eric Espie
9f34e9a0db N°9455 - Feature removal - console screen behavior 2026-06-01 09:26:23 +02:00
Eric Espie
f2a5909b43 Setup fast track (keep current options) 2026-05-29 17:56:28 +02:00
Eric Espie
1aa249b59f Setup fast track (fix setup token) 2026-05-29 17:40:05 +02:00
Eric Espie
6f34a4799c Setup fast track 2026-05-29 17:22:59 +02:00
32 changed files with 195 additions and 1498 deletions

View File

@@ -1917,9 +1917,7 @@ SQL;
$aResponseHeaders[$sName] = $sValue;
}
}
if (PHP_VERSION_ID < 80000) {
curl_close($ch);
}
curl_close($ch);
return $response;
}

View File

@@ -20,16 +20,6 @@
*
*/
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');
@@ -94,7 +84,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
* @see utils::GetConfig() to load config from the current env, if metamodel is not loaded
* @package iTopORM
*/
#[AllowDynamicProperties] class Config
class Config
{
//protected $m_bIsLoaded = false;
protected $m_sFile = '';
@@ -2020,24 +2010,17 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
*/
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, bool $bPreserveComments = false, bool $bLegacyEvaluation = false)
public function __construct($sConfigFile = null, $bLoadConfig = true)
{
$this->bPreserveComments = $bPreserveComments;
$this->bLegacyEvaluation = $bLegacyEvaluation;
$this->oConfigPlaceholdersResolver = new ConfigPlaceholdersResolver();
$this->m_sFile = $sConfigFile;
@@ -2092,7 +2075,6 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
}
$this->Set('app_root_url', $sAppRootUrl);
*/
}
/**
@@ -2139,39 +2121,9 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
// 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(
@@ -2762,15 +2714,6 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
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");
}
@@ -2940,7 +2883,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
}
/**
* Pretty format a var_export'ed value so that (if possible) the indentation is preserved on every line
* Pretty format a var_export'ed value so that (if possible) the identation is preserved on every line
*
* @param array $aParserValue
* @param mixed $value The value to export
@@ -2957,19 +2900,12 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
}
$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 indentations at the beginning of each line
// Check if adding the identations 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;
@@ -2978,6 +2914,7 @@ define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
return $sNiceExport;
}
}
class ConfigPlaceholdersResolver

View File

@@ -340,14 +340,13 @@ 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, $bAllowAllData = false)
public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null)
{
try {
$oObj = MetaModel::GetObject($sClass, $id, false, $bAllowAllData);
$oObj = MetaModel::GetObject($sClass, $id, false, false);
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);

View File

@@ -26,7 +26,6 @@ use Dict;
use Exception;
use MetaModel;
use RunTimeEnvironment;
use SecurityException;
use SetupUtils;
use utils;
@@ -84,11 +83,10 @@ class DataFeatureRemovalController extends Controller
{
$aParams = [];
try {
if (SetupUtils::IsSessionSetupTokenValid()) {
//from setup wizard/mtp
SetupUtils::CheckSetupToken();
SetupUtils::EraseSetupToken();
} catch (SecurityException $e) {
} else {
//from same module
$this->ValidateTransactionId();
}
@@ -102,7 +100,6 @@ class DataFeatureRemovalController extends Controller
'removed_extensions' => '[]',
'extensions_not_uninstallable' => '[]',
'copy_setup_files' => 1,
'return_button_label' => '',
];
$aHiddenInputs = [];
@@ -111,10 +108,6 @@ 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);
@@ -186,10 +179,6 @@ 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);

View File

@@ -87,15 +87,11 @@
{% endif %}
{% endif %}
{% 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 %}
{% 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 %}
{% EndUIPanel %}

View File

@@ -123,6 +123,7 @@ require_once('./xmldataloader.class.inc.php');
// Never cache this page
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past
$oCtx = new ContextTag(ContextTag::TAG_SETUP);
/**
* Main program

View File

@@ -260,11 +260,6 @@ 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);

View File

@@ -81,13 +81,13 @@ class ApplicationInstallSequencer extends StepSequencer
return $this->ComputeNextStep($sStep);
case 'log-parameters':
if ($this->HasOptionalStep($sStep)) {
if (array_key_exists($sStep, $this->oParams->Get('optional_steps', []))) {
$this->DoLogParameters();
}
return $this->ComputeNextStep($sStep);
case 'backup':
if ($this->HasOptionalStep($sStep, false)) {
if (array_key_exists($sStep, $this->oParams->Get('optional_steps', []))) {
$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 ($this->HasOptionalStep($sStep)) {
if (array_key_exists($sStep, $this->oParams->Get('optional_steps', []))) {
$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 ($this->HasOptionalStep($sStep)) {
if (array_key_exists($sStep, $this->oParams->Get('optional_steps', []))) {
$this->oRunTimeEnvironment->MigrateDataAfterUpdateStructure($this->oParams->Get('mode'), $this->GetConfig());
}
return $this->ComputeNextStep($sStep);
@@ -191,17 +191,13 @@ class ApplicationInstallSequencer extends StepSequencer
public function GetStepNames(): array
{
$aStepNames = [ ''];
if ($this->HasOptionalStep('log-parameters')) {
$aStepNames [] = 'log-parameters';
}
if ($this->HasOptionalStep('backup', false)) {
$aStepNames [] = 'backup';
}
if ($this->HasOptionalStep('migrate-before')) {
$aStepNames [] = 'migrate-before';
foreach (['log-parameters', 'backup', 'migrate-before'] as $sStepName) {
if (array_key_exists($sStepName, $this->oParams->Get('optional_steps', []))) {
$aStepNames [] = $sStepName;
}
}
$aStepNames [] = 'db-schema';
if ($this->HasOptionalStep('migrate-after')) {
if (array_key_exists('migrate-after', $this->oParams->Get('optional_steps', []))) {
$aStepNames [] = 'migrate-after';
}

View File

@@ -102,7 +102,7 @@ class DataAuditSequencer extends StepSequencer
protected function IsDataAuditRequired(): bool
{
if (!$this->HasOptionalStep('setup-audit', false)) {
if (! array_key_exists('setup-audit', $this->oParams->Get('optional_steps', []))) {
return false;
}
@@ -124,7 +124,7 @@ class DataAuditSequencer extends StepSequencer
public function GetStepNames(): array
{
$aStepNames = [''];
if ($this->HasOptionalStep('copy')) {
if (array_key_exists('copy', $this->oParams->Get('optional_steps', []))) {
$aStepNames[] = 'copy';
}
$aStepNames[] = 'compile';

View File

@@ -29,9 +29,6 @@ abstract class StepSequencer
protected RunTimeEnvironment $oRunTimeEnvironment;
protected string $sSourceDesc;
protected const LABELS = [];
protected const SUCCESS_LABELS = [];
/**
* @param \Parameters $oParams
* @param \RunTimeEnvironment|null $oRunTimeEnvironment
@@ -236,26 +233,4 @@ 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');
}
}

View File

@@ -26,17 +26,17 @@ function WizardAsyncAction(sActionCode, oParams, OnErrorFunction)
function WizardUpdateButtons()
{
if (CanMoveForward()) {
$("#btn_next").removeClass('ibo-is-hidden');
$("#btn_next").prop('disabled', false);
}
else {
$("#btn_next").addClass('ibo-is-hidden');
$("#btn_next").prop('disabled', true);
}
if (CanMoveBackward()) {
$("#btn_back").removeClass('ibo-is-hidden');
$("#btn_back").prop('disabled', false);
}
else {
$("#btn_back").addClass('ibo-is-hidden');
$("#btn_back").prop('disabled', true);
}
}

View File

@@ -61,6 +61,7 @@ if (!function_exists('json_decode')) {
//N°3671 setup context: force $bForceTrustProxy to be persisted in next calls
utils::GetAbsoluteUrlAppRoot(true);
$oWizard = new WizardController('WizStepWelcome');
$oCtx = new ContextTag(ContextTag::TAG_SETUP);
//N°3952
if (SetupUtils::IsSessionSetupTokenValid()) {
// Normal operation
@@ -69,5 +70,5 @@ if (SetupUtils::IsSessionSetupTokenValid()) {
SetupUtils::ExitMaintenanceMode(false);
// Force initializing the setup
$oWizard->Start();
SetupUtils::CreateSetupToken();
//SetupUtils::CreateSetupToken();
}

View File

@@ -151,10 +151,10 @@ class WizardController
$sCurrentState = utils::ReadParam('_state', $this->sInitialState);
$oStep = $this->GetWizardStep($sCurrentStepClass, $sCurrentState);
if ($oStep->ValidateParams()) {
$aPossibleSteps = $oStep->GetPossibleSteps();
if ($oStep->CanMoveBackward()) {
if ($oStep->CanComeBack()) {
$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());
@@ -220,6 +220,8 @@ HTML;
}
}
$oPage->LinkScriptFromAppRoot('setup/setup.js');
$oStep->PreFormDisplay($oPage);
$oPage->add('<form id="wiz_form" class="ibo-setup--wizard" method="post">');
$oPage->add('<div class="ibo-setup--wizard--content">');
$oStep->Display($oPage);
@@ -237,12 +239,8 @@ 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) {
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 ((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 ($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>');
@@ -287,8 +285,8 @@ EOF
$oPage->output();
}
/**
* Make the wizard run: Start, Next or Back depending WizardUpdateButtons();
on the page's parameters
* Make the wizard run: 'Start', 'Next' or 'Back' depending WizardUpdateButtons();
* on the page's parameters
*/
public function Run()
{

View File

@@ -54,20 +54,16 @@ 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
{
@@ -112,7 +108,6 @@ JS);
'removed_extensions' => '[]',
'extensions_not_uninstallable' => '[]',
'copy_setup_files' => 1,
'return_button_label' => '',
];
$aHiddenInputs = '';
foreach ($aParams as $sParamName => $defaultValue) {
@@ -128,43 +123,23 @@ 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="authent" value="$sUID"/>
<input type="hidden" name="setup_token" 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(
<<<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>');
<<<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>');
$('#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(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>&nbsp;<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(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>&nbsp;<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");
@@ -173,7 +148,7 @@ JS
$("#wiz_form").data("installation_status", "cleanup_needed");
$('#btn_next').hide();
JS
EOF
);
}
@@ -186,10 +161,6 @@ JS
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;';
}
}

View File

@@ -144,11 +144,6 @@ 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

View File

@@ -54,13 +54,6 @@ 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);
@@ -116,20 +109,6 @@ 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
*/
@@ -146,25 +125,27 @@ 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(
<<<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
<<<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
);
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(
<<<JS
$('#progress').progression( {Current:100, Maximum: 100} );
$("#wiz_form").data("installation_status", "completed");
$("#btn_next").trigger('click');
JS
<<<EOF
$("#wiz_form").data("installation_status", "completed");
$('#progress').progression( {Current:100, Maximum: 100} );
WizardUpdateButtons();
$("#btn_next").off("click.install");
$("#btn_next").trigger('click');
EOF
);
static::AddPrevStepSuccessMessage($oPage, $aRes['prev-step-success-message']);
} else {
@@ -172,12 +153,12 @@ JS
$sMessage = addslashes(utils::EscapeHtml($aRes['message']));
$sMessage = str_replace("\n", '<br>', $sMessage);
$oPage->add_ready_script(
<<<JS
$("#wiz_form").data("installation_status", "error");
$("#progress .progress").addClass('progress-error');
WizardUpdateButtons();
$('#setup_error').html('$sMessage').show();
JS
<<<EOF
$("#wiz_form").data("installation_status", "error");
$("#progress .progress").addClass('progress-error');
WizardUpdateButtons();
$('#setup_error').html('$sMessage').show();
EOF
);
$this->AddProgressErrorScript($oPage, $aRes);
}
@@ -185,13 +166,7 @@ JS
protected function AddProgressErrorScript($oPage, $aRes)
{
$oPage->add_ready_script(
<<<JS
if ($('#return-button').length > 0) {
$('#return-button').removeClass('ibo-is-hidden');
}
JS
);
}
/**

View File

@@ -21,7 +21,7 @@ class WizStepLandingBeforeAudit extends WizStepModulesChoice
$oWizard->SetParameter('datamodel_version', ITOP_CORE_VERSION);
$oWizard->SetParameter('upgrade_type', 'use-compatible');
$oWizard->SaveParameter('use_symbolic_links', MFCompiler::UseSymbolicLinks());
$oWizard->SaveParameter('use_symbolic_links', MFCompiler::UseSymbolicLinks() ? 'on' : 'off');
$oWizard->SaveParameter('force-uninstall', '');
// should be done at the end
@@ -40,8 +40,8 @@ class WizStepLandingBeforeAudit extends WizStepModulesChoice
*/
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
{
$oProductionEnv = new RunTimeEnvironment();
$sBuildConfigFile = APPCONF.$oProductionEnv->GetBuildEnv().'/'.ITOP_CONFIG_FILE;
$oRuntimeEnv = new RunTimeEnvironment();
$sBuildConfigFile = APPCONF.$oRuntimeEnv->GetBuildEnv().'/'.ITOP_CONFIG_FILE;
@chmod($sBuildConfigFile, 0770); // In case it exists: RWX for owner and group, nothing for others
$oConfig = new Config($sBuildConfigFile);
@@ -56,6 +56,14 @@ class WizStepLandingBeforeAudit extends WizStepModulesChoice
$this->oWizard->SetParameter('display_choices', '[]');
$this->oWizard->SetParameter('extensions_not_uninstallable', '[]');
if ($this->oWizard->GetParameter('skip_wizard', false)) {
$oExtensionMap = new iTopExtensionsMap($oRuntimeEnv->GetBuildEnv());
$aExtensionsFromDatabase = $oExtensionMap->GetChoicesFromDatabase($oConfig);
$this->oWizard->SetParameter('selected_extensions', json_encode($aExtensionsFromDatabase));
$adModulesFromDatabase = ModuleInstallationRepository::GetInstance()->ReadComputeInstalledModules($oConfig);
$this->oWizard->SetParameter('selected_modules', json_encode(array_keys($adModulesFromDatabase)));
}
$aWizardSteps = $this->GetWizardSteps();
$this->oWizard->SetWizardSteps($aWizardSteps);
$this->sCurrentState = count($aWizardSteps) - 1;
@@ -84,7 +92,7 @@ class WizStepLandingBeforeAudit extends WizStepModulesChoice
return 'Next';
}
public function CanMoveBackward()
public function CanComeBack()
{
return false;
}

View File

@@ -57,14 +57,6 @@ 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);
@@ -250,19 +242,6 @@ 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
@@ -278,11 +257,7 @@ JS
*/
public function JSCanMoveBackward()
{
if ($this->oWizard->GetParameter('return_button_label', '') === '') {
return 'return true;';
}
return 'return false;';
return 'return true;';
}
}

View File

@@ -24,6 +24,17 @@
class WizStepWelcome extends WizardStep
{
protected $bCanMoveForward;
private array $aInfo;
private array $aWarnings;
private array $aErrors;
private string $sUID;
public function __construct(WizardController $oWizard, $sCurrentState)
{
parent::__construct($oWizard, $sCurrentState);
$this->CheckInstallation();
$this->sUID = SetupUtils::CreateSetupToken();
}
public function GetTitle()
{
@@ -46,8 +57,7 @@ class WizStepWelcome extends WizardStep
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
{
$sUID = SetupUtils::CreateSetupToken();
$this->oWizard->SetParameter('authent', $sUID);
$this->oWizard->SetParameter('authent', $this->sUID);
return new WizardState(WizStepInstallOrUpgrade::class);
}
@@ -68,39 +78,14 @@ class WizStepWelcome extends WizardStep
EOF
);
$oPage->add('<h1>'.ITOP_APPLICATION.' Installation Wizard</h1>');
$aResults = SetupUtils::CheckPhpAndExtensions();
$this->bCanMoveForward = true;
$aInfo = [];
$aWarnings = [];
$aErrors = [];
foreach ($aResults as $oCheckResult) {
switch ($oCheckResult->iSeverity) {
case CheckResult::ERROR:
$aErrors[] = $oCheckResult->sLabel;
$this->bCanMoveForward = false;
break;
case CheckResult::WARNING:
$aWarnings[] = $oCheckResult->sLabel;
break;
case CheckResult::INFO:
$aInfo[] = $oCheckResult->sLabel;
break;
case CheckResult::TRACE:
SetupLog::Ok($oCheckResult->sLabel);
break;
}
}
$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 = 'style="overflow:auto;"';
$sTitle = count($aErrors).' Error(s), '.count($aWarnings).' Warning(s).';
if (count($this->aErrors) > 0) {
$sStyle = 'overflow:auto;"';
$sTitle = count($this->aErrors).' Error(s), '.count($this->aWarnings).' Warning(s).';
$sH2Class = 'text-error';
} elseif (count($aWarnings) > 0) {
$sTitle = count($aWarnings).' Warning(s) '.$sToggleButtons;
} elseif (count($this->aWarnings) > 0) {
$sTitle = count($this->aWarnings).' Warning(s) '.$sToggleButtons;
$sH2Class = 'text-warning';
} else {
$sTitle = 'Ok. '.$sToggleButtons;
@@ -112,13 +97,13 @@ EOF
<div id="details" $sStyle>
HTML
);
foreach ($aErrors as $sText) {
foreach ($this->aErrors as $sText) {
$oPage->error($sText);
}
foreach ($aWarnings as $sText) {
foreach ($this->aWarnings as $sText) {
$oPage->warning($sText);
}
foreach ($aInfo as $sText) {
foreach ($this->aInfo as $sText) {
$oPage->ok($sText);
}
$oPage->add('</div>');
@@ -129,8 +114,70 @@ HTML
$oPage->add_ready_script('CheckDirectoryConfFilesPermissions("'.utils::GetItopVersionWikiSyntax().'")');
}
/**
* Add post display stuff to the setup screen
* @param \SetupPage $oPage
*
* @return void
*/
public function PostFormDisplay(SetupPage $oPage)
{
if ($this->bCanMoveForward) {
$sBuildConfigFile = APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE;
if (file_exists($sBuildConfigFile)) {
$oPage->add(
<<<HTML
<form method="post">
<input type="hidden" name="_class" value="WizStepLandingBeforeAudit"/>
<input type="hidden" name="operation" value="next"/>
<input type="hidden" name="_params[skip_wizard]" value="1"/>
<input type="hidden" name="authent" value="{$this->sUID}"/>
<input type="hidden" name="_params[authent]" value="{$this->sUID}"/>
<table style="width:100%;" class="ibo-setup--wizard--buttons-container">
<tr>
<td style="text-align: right"><button type="submit" class="ibo-button ibo-is-regular ibo-is-secondary">Keep current choices</button></td>
</tr>
</table>
</form>
HTML
);
}
}
}
public function CanMoveForward()
{
return $this->bCanMoveForward;
}
/**
*/
public function CheckInstallation(): void
{
$aResults = SetupUtils::CheckPhpAndExtensions();
$this->bCanMoveForward = true;
$this->aInfo = [];
$this->aWarnings = [];
$this->aErrors = [];
foreach ($aResults as $oCheckResult) {
switch ($oCheckResult->iSeverity) {
case CheckResult::ERROR:
$this->aErrors[] = $oCheckResult->sLabel;
$this->bCanMoveForward = false;
break;
case CheckResult::WARNING:
$this->aWarnings[] = $oCheckResult->sLabel;
break;
case CheckResult::INFO:
$this->aInfo[] = $oCheckResult->sLabel;
break;
case CheckResult::TRACE:
SetupLog::Ok($oCheckResult->sLabel);
break;
}
}
}
}

View File

@@ -76,6 +76,10 @@ abstract class WizardStep
{
}
public function PreFormDisplay(SetupPage $oPage)
{
}
protected function CheckDependencies()
{
if (is_null($this->bDependencyCheck)) {
@@ -151,6 +155,15 @@ 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

View File

@@ -13,6 +13,7 @@ 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;
@@ -38,9 +39,7 @@ class CaseLogEntryFormFactory
->AddMainActionButtons(static::PrepareCancelButton());
$oSaveButton = static::PrepareSaveButton();
$oTransitionsMenu = static::PrepareTransitionsSelectionPopoverMenu($oObject, $sCaseLogAttCode, $oCaseLogEntryForm->GetId());
// Prevent popover menu from landing behind caselog editor
$oTransitionsMenu->SetContainer(PopoverMenu::ENUM_CONTAINER_BODY);
$oTransitionsMenu = static::PrepareTransitionsSelectionPopoverMenu($oObject, $sCaseLogAttCode);
if (true === $oTransitionsMenu->HasItems()) {
$oButtonGroup = ButtonGroupUIBlockFactory::MakeButtonWithOptionsMenu($oSaveButton, $oTransitionsMenu);
$oCaseLogEntryForm->AddMainActionButtons($oButtonGroup);
@@ -70,16 +69,7 @@ class CaseLogEntryFormFactory
return $oButton;
}
/**
* @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
protected static function PrepareTransitionsSelectionPopoverMenu(DBObject $oObject, string $sCaseLogAttCode): PopoverMenu
{
$sObjClass = get_class($oObject);
@@ -87,6 +77,8 @@ 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)) {
@@ -105,7 +97,7 @@ class CaseLogEntryFormFactory
CaseLogEntryForm::BLOCK_CODE.'--add-action--'.$sCaseLogAttCode.'--stimulus--'.$sStimulusCode,
Dict::Format('UI:Button:SendAnd', $aStimuli[$sStimulusCode]->GetLabel()),
<<<JS
$('#$sCaseLogEntryFormId').trigger('save_entry.caselog_entry_form.itop', {stimulus_code: '{$sStimulusCode}'});
$(this).closest('[data-role="{$sCaseLogEntryFormDataRole}"]').trigger('save_entry.caselog_entry_form.itop', {stimulus_code: '{$sStimulusCode}'});
JS
)
);

View File

@@ -16,12 +16,10 @@ 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;
@@ -29,7 +27,6 @@ use DBObjectSearch;
use DBObjectSet;
use Dict;
use EventNotificationNewsroom;
use Exception;
use MetaModel;
use SecurityException;
use UserRights;
@@ -382,10 +379,9 @@ JS
$oEventBlock->SetCSSColorClass($sReadColor);
$oEventBlock->SetSubTitle($sReadLabel);
$oEventBlock->SetClassLabel('');
/** @var \ormDocument $oImage */
$oImage = $oEvent->Get('icon');
if (!$oImage->IsEmpty()) {
$sIconUrl = self::GetDisplayIconUrl($iEventId, $oImage->GetSignature());
$sIconUrl = $oImage->GetDisplayURL(get_class($oEvent), $iEventId, 'icon');
$oEventBlock->SetIcon($sIconUrl, Panel::ENUM_ICON_COVER_METHOD_COVER, true);
}
@@ -549,7 +545,7 @@ $sMessage
HTML;
$sIcon = $oMessage->Get('icon') !== null ?
$this->GetDisplayIconUrl($oMessage->GetKey(), $oMessage->Get('icon')->GetSignature()) :
$oMessage->Get('icon')->GetDisplayURL(EventNotificationNewsroom::class, $oMessage->GetKey(), 'icon') :
Branding::GetCompactMainLogoAbsoluteUrl();
$aMessages[] = [
'id' => $oMessage->GetKey(),
@@ -696,35 +692,6 @@ 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
*
@@ -817,9 +784,4 @@ 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";
}
}

View File

@@ -4,11 +4,9 @@ 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;
/**
@@ -57,36 +55,4 @@ 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;
}
}

View File

@@ -4,10 +4,8 @@ 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;
/**
@@ -73,31 +71,4 @@ 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;
}
}

View File

@@ -706,9 +706,7 @@ abstract class ItopTestCase extends KernelTestCase
$this->aLastCurlGetInfo = $info;
$sErrorMsg = curl_error($ch);
$iErrorCode = curl_errno($ch);
if (PHP_VERSION_ID < 80000) {
curl_close($ch);
}
curl_close($ch);
\IssueLog::Info(__METHOD__, null, ['url' => $sUrl, 'error' => $sErrorMsg, 'error_code' => $iErrorCode, 'post_fields' => $aPostFields, 'info' => $info]);

View File

@@ -306,21 +306,6 @@ 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 [

View File

@@ -24,7 +24,6 @@ namespace Combodo\iTop\Test\UnitTest\Module\iTopConfig;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use Config;
use http\Encoding\Stream\Inflate;
class ConfigTest extends ItopTestCase
{
@@ -73,6 +72,7 @@ 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,58 +91,4 @@ 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));
}
}

View File

@@ -1,272 +0,0 @@
<?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(
);
?>

View File

@@ -1,272 +0,0 @@
<?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(
);
?>

View File

@@ -1,292 +0,0 @@
<?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(
);
?>

View File

@@ -39,9 +39,7 @@ class UnattendedInstallTest extends ItopDataTestCase
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$sJson = curl_exec($ch);
if (PHP_VERSION_ID < 80000) {
curl_close($ch);
}
curl_close($ch);
return $sJson;
}
public function testCallUnattendedInstallFromHttp()

View File

@@ -1,154 +0,0 @@
<?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);
}
}