diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index a5f6a5751..8718669c9 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -3929,7 +3929,7 @@ HTML; } elseif ($iFlags & OPT_ATT_SLAVE) { - $aErrors[$sAttCode] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel()); + $aErrors[$sAttCode] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel(), $sAttCode); } else { diff --git a/application/dashlet.class.inc.php b/application/dashlet.class.inc.php index bf61a2785..2ba7e3b78 100644 --- a/application/dashlet.class.inc.php +++ b/application/dashlet.class.inc.php @@ -262,7 +262,7 @@ abstract class Dashlet } } catch (OqlException $e) { $oDashletContainer->AddCSSClass("dashlet-content"); - $oDashletContainer->AddHtml('
'.$e->GetUserFriendlyDescription().'
'); + $oDashletContainer->AddHtml(''.utils::HtmlEntities($e->GetUserFriendlyDescription()).'
'); } catch (Exception $e) { $oDashletContainer->AddCSSClass("dashlet-content"); $oDashletContainer->AddHtml(''.$e->getMessage().'
'); diff --git a/application/utils.inc.php b/application/utils.inc.php index 569c46299..f002ec383 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -454,6 +454,11 @@ class utils $retValue = preg_replace('/[^a-zA-Z0-9_]/', '', $value); break; + // For URL + case 'url': + $retValue = filter_var($value, FILTER_SANITIZE_URL); + break; + default: case static::ENUM_SANITIZATION_FILTER_RAW_DATA: $retValue = $value; @@ -2815,6 +2820,7 @@ HTML; /** * Helper around the native strlen() PHP method to keep allowing usage of null value when computing the length of a string as null value is no longer allowed with PHP 8.1+ + * @link https://www.php.net/releases/8.1/en.php#deprecations_and_bc_breaks "Passing null to non-nullable internal function parameters is deprecated" * * @param string|null $sString * diff --git a/core/action.class.inc.php b/core/action.class.inc.php index e5ee3ad0b..2ff7ee69c 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -277,7 +277,7 @@ class ActionEmail extends ActionNotification protected function FindRecipients($sRecipAttCode, $aArgs) { $sOQL = $this->Get($sRecipAttCode); - if (strlen($sOQL) == '') return ''; + if (strlen($sOQL) === 0) return ''; try { diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 89adc3165..b65b8967f 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1,6 +1,6 @@ $value) { $res = $this->CheckValue($sAttCode); if ($res !== true) { + $sAttLabel = $this->GetLabel($sAttCode); // $res contains the error description - $this->m_aCheckIssues[] = "Unexpected value for attribute '$sAttCode': $res"; + $this->m_aCheckIssues[] = Dict::Format('Core:CheckValueError', $sAttLabel, $sAttCode, $res); } $this->DoCheckLinkedSetDuplicates($sAttCode, $value); @@ -2265,7 +2266,7 @@ abstract class DBObject implements iDisplay if ($res !== true) { // $res contains the error description - $this->m_aCheckIssues[] = "Consistency rules not followed: $res"; + $this->m_aCheckIssues[] = Dict::Format('Core:CheckConsistencyError', $res); } // Synchronization: are we attempting to modify an attribute for which an external source is master? @@ -2278,12 +2279,10 @@ abstract class DBObject implements iDisplay if ($iFlags & OPT_ATT_SLAVE) { // Note: $aReasonInfo['name'] could be reported (the task owning the attribute) - $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); - $sAttLabel = $oAttDef->GetLabel(); if (!empty($aReasons)) { - // Todo: associate the attribute code with the error - $this->m_aCheckIssues[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $sAttLabel); + $sAttLabel = $this->GetLabel($sAttCode); + $this->m_aCheckIssues[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $sAttLabel, $sAttCode); } } } diff --git a/core/displayablegraph.class.inc.php b/core/displayablegraph.class.inc.php index fdf72bd64..330e585e8 100644 --- a/core/displayablegraph.class.inc.php +++ b/core/displayablegraph.class.inc.php @@ -1195,8 +1195,10 @@ class DisplayableGraph extends SimpleGraph * @param float $xMax Right coordinate of the bounding box to display the graph * @param float $yMin Top coordinate of the bounding box to display the graph * @param float $yMax Bottom coordinate of the bounding box to display the graph + * + * @since 2.7.7 3.0.2 3.1.0 N°4985 $sComments param is no longer optional */ - function RenderAsPDF(PDFPage $oPage, $sComments = '', $sContextKey, $xMin = -1, $xMax = -1, $yMin = -1, $yMax = -1) + function RenderAsPDF(PDFPage $oPage, $sComments, $sContextKey, $xMin = -1, $xMax = -1, $yMin = -1, $yMax = -1) { $aContextDefs = static::GetContextDefinitions($sContextKey, false); // No need to develop the parameters $oPdf = $oPage->get_tcpdf(); diff --git a/datamodels/2.x/itop-portal-base/portal/src/Brick/BrickCollection.php b/datamodels/2.x/itop-portal-base/portal/src/Brick/BrickCollection.php index a9d15e642..54e869e76 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Brick/BrickCollection.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Brick/BrickCollection.php @@ -162,12 +162,20 @@ class BrickCollection // - Home $this->aHomeOrdering = $this->aAllowedBricks; usort($this->aHomeOrdering, function (PortalBrick $a, PortalBrick $b) { - return $a->GetRankHome() > $b->GetRankHome(); + if ($a->GetRankHome() === $b->GetRankHome()) { + return 0; + } + + return $a->GetRankHome() > $b->GetRankHome() ? 1 : -1; }); // - Navigation menu $this->aNavigationMenuOrdering = $this->aAllowedBricks; usort($this->aNavigationMenuOrdering, function (PortalBrick $a, PortalBrick $b) { - return $a->GetRankNavigationMenu() > $b->GetRankNavigationMenu(); + if ($a->GetRankNavigationMenu() === $b->GetRankNavigationMenu()) { + return 0; + } + + return $a->GetRankNavigationMenu() > $b->GetRankNavigationMenu() ? 1 : -1; }); } diff --git a/datamodels/2.x/itop-portal-base/portal/src/Brick/ManageBrick.php b/datamodels/2.x/itop-portal-base/portal/src/Brick/ManageBrick.php index 917a515b9..79d3c45a4 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Brick/ManageBrick.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Brick/ManageBrick.php @@ -485,7 +485,11 @@ class ManageBrick extends PortalBrick if (!$this->IsGroupingByDistinctValues($sName)) { usort($this->aGrouping[$sName]['groups'], function ($a, $b) { - return $a['rank'] > $b['rank']; + if ($a['rank'] === $b['rank']) { + return 0; + } + + return $a['rank'] > $b['rank'] ? 1 : -1; }); } diff --git a/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Lists.php b/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Lists.php index 0bddf5f31..306e23d94 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Lists.php +++ b/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Lists.php @@ -91,7 +91,10 @@ class Lists extends AbstractConfiguration } // - Sorting list items by rank usort($aListItems, function ($a, $b) { - return $a['rank'] > $b['rank']; + if ($a['rank'] == $b['rank']) { + return 0; + } + return $a['rank'] > $b['rank'] ? 1 : -1; }); $aClassLists[$sListId] = $aListItems; } diff --git a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php index 1c5fc46e7..66df5a2a9 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php @@ -703,7 +703,7 @@ class ObjectFormManager extends FormManager /** @var Field $oField */ $oField = null; - if (is_callable(get_class($oAttDef).'::MakeFormField')) + if (is_callable([$oAttDef, 'MakeFormField'])) { $oField = $oAttDef->MakeFormField($this->oObject); } diff --git a/dictionaries/en.dictionary.itop.core.php b/dictionaries/en.dictionary.itop.core.php index 6fe9bdcf1..ede2e217b 100644 --- a/dictionaries/en.dictionary.itop.core.php +++ b/dictionaries/en.dictionary.itop.core.php @@ -2,7 +2,7 @@ /** * Localized data * - * @copyright Copyright (C) 2010-2021 Combodo SARL + * @copyright Copyright (C) 2010-2022 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 * * This file is part of iTop. @@ -29,6 +29,8 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:UnknownObjectTip' => 'The object could not be found. It may have been deleted some time ago and the log has been purged since.', 'Core:UniquenessDefaultError' => 'Uniqueness rule \'%1$s\' in error', + 'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s', + 'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s', 'Core:AttributeLinkedSet' => 'Array of objects', 'Core:AttributeLinkedSet+' => 'Any kind of objects of the same class or subclass', diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php index 6ff3a57cc..9b88bc07e 100644 --- a/dictionaries/en.dictionary.itop.ui.php +++ b/dictionaries/en.dictionary.itop.ui.php @@ -1168,7 +1168,7 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:CaseLogTypeYourTextHere' => 'Type your text here...', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => 'Initial value:', - 'UI:AttemptingToSetASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value not set.', + 'UI:AttemptingToSetASlaveAttribute_Name' => 'The field %1$s (%2$s) is not writable because it is mastered by the data synchronization. Value not set.', 'UI:ActionNotAllowed' => 'You are not allowed to perform this action on these objects.', 'UI:BulkAction:NoObjectSelected' => 'Please select at least one object to perform this operation', 'UI:AttemptingToChangeASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value remains unchanged.', diff --git a/js/ckeditor.on-init.js b/js/ckeditor.on-init.js index d48509993..25131cdcd 100644 --- a/js/ckeditor.on-init.js +++ b/js/ckeditor.on-init.js @@ -21,3 +21,24 @@ if ((CKEDITOR !== undefined) && (CKEDITOR.plugins.registered['disabler'] === und }); } + +// Rewrite the CKEditor Mentions plugin regexp to make it suitable for all Unicode alphabets. +if (CKEDITOR !== undefined && CKEDITOR.plugins.registered['mentions']) { + // from https://github.com/ckeditor/ckeditor4/blob/a3786007fb979d7d7bff3d10c34a2d422935baed/plugins/mentions/plugin.js#L147 + function createPattern(marker, minChars) { + let pattern = marker + '[\\p{L}\\p{N}_-]'; + if ( minChars ) { + pattern += '{' + minChars + ',}'; + } else { + pattern += '*'; + } + pattern += '$'; + return new RegExp(pattern, 'u'); + } + + CKEDITOR.on('instanceLoaded', event => { + event.editor.config.mentions.forEach(config => { + config.pattern = createPattern(config.marker, config.minChars); + }); + }); +} diff --git a/pages/UI.php b/pages/UI.php index d3a7abb11..9bb47f561 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1461,7 +1461,7 @@ EOF if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode)) ) { $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $aErrors[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel()); + $aErrors[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel(), $sAttCode); unset($aExpectedAttributes[$sAttCode]); } } diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 8ca6ea139..baebf3b0d 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -932,7 +932,7 @@ try $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); - $sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, 'url'); $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); $aResult = array('error' => ''); if (!is_null($oDashboard)) @@ -950,7 +950,7 @@ try $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data'); $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); - $sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, 'url'); $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); $aResult = array('error' => ''); if (!is_null($oDashboard)) @@ -967,7 +967,7 @@ try $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'context_param'); $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); - $sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, 'url'); appUserPreferences::SetPref('display_original_dashboard_'.$sDashboardId, false); $sJSExtraParams = json_encode($aExtraParams); $aParams = array(); @@ -1009,7 +1009,7 @@ JS case 'revert_dashboard': $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data'); - $sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, 'url'); appUserPreferences::UnsetPref('display_original_dashboard_'.$sDashboardId); $oDashboard = new RuntimeDashboard($sDashboardId); $oDashboard->Revert(); @@ -1039,7 +1039,7 @@ EOF $aParams['cells'] = utils::ReadParam('cells', array(), false, 'raw_data'); $aParams['auto_reload'] = utils::ReadParam('auto_reload', false); $aParams['auto_reload_sec'] = utils::ReadParam('auto_reload_sec', 300); - $sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, 'url'); $oDashboard = new RuntimeDashboard($sDashboardId); $oDashboard->FromParams($aParams); $oDashboard->SetReloadURL($sReloadURL); @@ -1051,7 +1051,7 @@ EOF $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); $aExtraParams['dashboard_div_id'] = utils::Sanitize($sId, '', 'element_identifier'); $sDashboardFile = utils::ReadParam('file', '', false, 'string'); - $sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, 'url'); $oDashboard = RuntimeDashboard::GetDashboardToEdit($sDashboardFile, $sId); if (!is_null($oDashboard)) { if (!empty($sReloadURL)) { diff --git a/pages/csvimport.php b/pages/csvimport.php index 4ecc7f48f..74ad1a6c0 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -236,6 +236,11 @@ try { throw new CoreException(Dict::S('UI:ActionNotAllowed')); } + // CSRF transaction id verification + if(!$bSimulate && !utils::IsTransactionValid(utils::ReadPostedParam('transaction_id', '', 'raw_data'))){ + throw new CoreException(Dict::S('UI:Error:InvalidToken')); + } + $aResult = array(); $sCSVData = utils::ReadParam('csvdata', '', false, 'raw_data'); $sCSVDataTruncated = utils::ReadParam('csvdata_truncated', '', false, 'raw_data'); @@ -522,6 +527,7 @@ try { $oForm = FormUIBlockFactory::MakeStandard('wizForm'); $oContainer->AddSubBlock($oForm); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("transaction_id", utils::GetNewTransactionId())); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("step", ($iCurrentStep + 1))); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("separator", htmlentities($sSeparator, ENT_QUOTES, 'UTF-8'))); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("text_qualifier", htmlentities($sTextQualifier, ENT_QUOTES, 'UTF-8'))); @@ -681,7 +687,7 @@ EOF // Add graphs dependencies WebResourcesHelper::EnableC3JSToWebPage($oPage); - $oPage->add_script( + $oPage->add_script( <<< EOF function CSVGoBack() { @@ -1178,7 +1184,7 @@ EOF } $aGuesses = GuessParameters($sUTF8Data); // Try to predict the parameters, based on the input data - + $iSkippedLines = utils::ReadParam('nb_skipped_lines', ''); $bBoxSkipLines = utils::ReadParam('box_skiplines', 0); $sTextQualifier = utils::ReadParam('text_qualifier', '', false, 'raw_data'); diff --git a/test/phpunit.xml.dist b/test/phpunit.xml.dist index fa2d9d132..14bf874d7 100644 --- a/test/phpunit.xml.dist +++ b/test/phpunit.xml.dist @@ -44,12 +44,8 @@