Merge remote-tracking branch 'origin/support/3.2' into develop

# Conflicts:
#	core/attributedef.class.inc.php
#	css/backoffice/_shame.scss
#	pages/csvimport.php
This commit is contained in:
Benjamin DALSASS
2026-04-14 08:41:54 +02:00
17 changed files with 303 additions and 97 deletions

View File

@@ -1423,12 +1423,21 @@ class ShortcutMenuNode extends MenuNode
public function GetHyperlink($aExtraParams)
{
$sContext = $this->oShortcut->Get('context');
$aContext = unserialize($sContext);
if (isset($aContext['menu'])) {
unset($aContext['menu']);
}
foreach ($aContext as $sArgName => $sArgValue) {
$aExtraParams[$sArgName] = $sArgValue;
try {
$aContext = utils::Unserialize($sContext);
if (isset($aContext['menu'])) {
unset($aContext['menu']);
}
foreach ($aContext as $sArgName => $sArgValue) {
$aExtraParams[$sArgName] = $sArgValue;
}
} catch (Exception $e) {
IssueLog::Warning("User shortcut corrupted, delete the shortcut", LogChannels::CONSOLE, [
'shortcut_name' => $this->oShortcut->GetName(),
'root_cause' => $e->getMessage(),
]);
// delete the shortcut
$this->oShortcut->DBDelete();
}
return parent::GetHyperlink($aExtraParams);
}

View File

@@ -3146,4 +3146,50 @@ TXT
return $aTrace;
}
/**
* PHP unserialize encapsulation, allow throwing exception when not allowed object class is detected (for security hardening)
*
* @param string $data data to unserialize
* @param array $aOptions PHP @unserialise options
* @param bool $bThrowNotAllowedObjectClassException flag to throw exception
*
* @return mixed PHP @unserialise return
* @throws Exception
*/
public static function Unserialize(string $data, array $aOptions = ['allowed_classes' => false], bool $bThrowNotAllowedObjectClassException = true): mixed
{
$data = unserialize($data, $aOptions);
if ($bThrowNotAllowedObjectClassException) {
try {
self::AssertNoIncompleteClassDetected($data);
} catch (Exception $e) {
throw new CoreException('Unserialization failed because an incomplete class was detected.', [], '', $e);
}
}
return $data;
}
/**
* Assert that data provided doesn't contain any incomplete class.
*
* @throws Exception
*/
public static function AssertNoIncompleteClassDetected(mixed $data): void
{
if (is_object($data)) {
if ($data instanceof __PHP_Incomplete_Class) {
throw new Exception('__PHP_Incomplete_Class_Name object detected');
}
foreach (get_object_vars($data) as $property) {
self::AssertNoIncompleteClassDetected($property);
}
} elseif (is_array($data)) {
foreach ($data as $value) {
self::AssertNoIncompleteClassDetected($value);
}
}
}
}

View File

@@ -1579,6 +1579,8 @@ class CMDBSource
if (static::GetDBVendor() === static::ENUM_DB_VENDOR_MYSQL) {
//Mysql 5.7.0 and upper deprecated --ssl and uses --ssl-mode instead
return version_compare(static::GetDBVersion(), '5.7.11', '>=');
} elseif (static::GetDBVendor() === static::ENUM_DB_VENDOR_MARIADB) {
return version_compare(static::GetDBVersion(), '10.2.6', '>=');
}
return false;
}

View File

@@ -1182,7 +1182,7 @@ class UserRights
return self::$m_oUser->GetKey();
} else {
// find the id out of the login string
$oUser = self::FindUser($sLogin);
$oUser = self::FindUser($sLogin, bAllowDisabledUsers: true);
if (is_null($oUser)) {
return null;
}

View File

@@ -56,6 +56,8 @@ $ibo-shame--slider--is-round--border-radius: 20px !default;
$ibo-shame--slider--is-round--before--border-radius: 7px !default;
$ibo-blockquote--color: $ibo-body-text-color !default;
// N°2847 - Recolor svg illustrations with iTop's primary color
.ibo-svg-illustration--container > svg *[fill="#6c63ff"]{
fill: $ibo-svg-illustration--fill;
@@ -126,3 +128,11 @@ input:checked + .slider:before {
.slider.round:before {
border-radius: $ibo-shame--slider--is-round--before--border-radius;
}
/*
Bulma sets blockquote background color through a variable, it affects ckeditor and html display.
This rule is needed harmonize the blockquote text color in both contexts.
*/
.ibo-is-html-content blockquote {
color: $ibo-blockquote--color;
}

View File

@@ -203,8 +203,9 @@ $ibo-input-select--autocomplete-item-image--border: 1px solid $ibo-color-grey-60
}
// N°7982 Default selectize stylesheet override
// N°9468 Dropdown content needs to be a few pixel shorter than the dropdown itself to avoid double scrollbar
.selectize-dropdown-content{
max-height: $ibo-input-select-selectize--dropdown--max-height;
max-height: calc(#{$ibo-input-select-selectize--dropdown--max-height} - 4px);
}
.selectize-dropdown.ui-menu .ui-state-active {

View File

@@ -120,6 +120,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
this.sFormAttCode = sFormAttCode;
var me = this;
const iDropdownContentHeightDifference = 4;
this.Init = function () {
// make sure that the form is clean
@@ -171,7 +172,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
// To avoid dropdown to be cut by the container's overflow hidden rule
dropdownParent: 'body',
onDropdownOpen: function (oDropdownElem) {
me.UpdateDropdownPosition(this.$control, oDropdownElem);
me.UpdateDropdownPosition(this.$control, oDropdownElem, this.$dropdown_content);
},
});
let $selectize = $select[0].selectize; // This stores the selectize object to a variable (with name 'selectize')
@@ -314,13 +315,14 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
};
/**
* Update the dropdown's position so it always fits in the screen
*
* @param {object} oControlElem jQuery object representing the "control" input (= where the user types) of the external key
* @param {object} oDropdownElem jQuery object representing the results dropdown
* @return {void}
*/
this.UpdateDropdownPosition = function (oControlElem, oDropdownElem) {
* Update the dropdown's position so it always fits in the screen
*
* @param {object} oControlElem jQuery object representing the "control" input (= where the user types) of the external key
* @param {object} oDropdownElem jQuery object representing the results dropdown
* @param {object|undefined} oDropdownContentElem
* @return {void}
*/
this.UpdateDropdownPosition = function (oControlElem, oDropdownElem, oDropdownContentElem) {
// First fix width to ensure it's not too long
const fControlWidth = oControlElem.outerWidth();
oDropdownElem.css('width', fControlWidth);
@@ -328,6 +330,13 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
// Then, fix height / position to ensure it's within the viewport
const fWindowHeight = window.innerHeight;
// Clear previously set rule so the comparison is done with dropdown real height
oDropdownElem.css('max-height', '');
if(oDropdownContentElem) {
oDropdownContentElem.css('max-height', '');
}
const fControlTopY = oControlElem.offset().top;
const fControlHeight = oControlElem.outerHeight();
@@ -338,14 +347,38 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
if (fDropdownBottomY > fWindowHeight) {
// Set dropdown max-height to 1/3 of the screen, this way we are sure the dropdown will fit in either the top / bottom half of the screen
oDropdownElem.css('max-height', '30vh');
oDropdownElem.css({
maxHeight: '30vh',
});
fDropdownHeight = oDropdownElem.outerHeight();
// Position dropdown above input if not enough space on the bottom part of the screen
// N°9468 Dropdown content needs to be a few pixel shorter than the dropdown itself to avoid double scrollbar
if(oDropdownContentElem) {
oDropdownContentElem.css('max-height', `calc(30vh - ${iDropdownContentHeightDifference}px)`);
}
/* Position dropdown above input if not enough space on the bottom part of the screen
Doesn't seem to work with selectize as an internal plugin "auto_position" refreshes the top position after
this method is called, input set use a custom plugin to avoid fix this issue "plugin_combodo_auto_position"
This would need to take the potential 4px difference (iDropdownContentHeightDifference) into account if this is fixed.
*/
if ((fDropdownTopY / fWindowHeight) > 0.6) {
oDropdownElem.css('top', fDropdownTopY - fDropdownHeight - fControlHeight);
}
oDropdownElem.css({
top: fDropdownTopY - fDropdownHeight - fControlHeight,
borderTop: oDropdownElem.css('border-bottom')
});
}
else {
oDropdownElem.css({
borderTop: 'none'
})
}
}
else {
oDropdownElem.css({
borderTop: 'none'
})
}
};
this.ManageScroll = function () {
if ($('#label_'+me.id).scrollParent()[0].tagName != 'HTML') {

View File

@@ -19,10 +19,11 @@ Selectize.define("combodo_auto_position", function (aOptions) {
// Selectize instance
let oSelf = this;
const iDropdownContentHeightDifference = 4;
// Plugin options
aOptions = $.extend({
maxDropDownHeight: 200,
maxDropDownHeight: '200px',
},
aOptions
);
@@ -33,28 +34,47 @@ Selectize.define("combodo_auto_position", function (aOptions) {
// Override position dropdown function
oSelf.positionDropdown = (function () {
return function () {
let iRefHeight = oSelf.$dropdown.outerHeight() < aOptions.maxDropDownHeight ?
oSelf.$dropdown.outerHeight() : aOptions.maxDropDownHeight;
// Clear previously set rules so the comparison is done with dropdown real height
oSelf.$dropdown.css({
'max-height': '',
});
if(oSelf.$control.offset().top + oSelf.$control.outerHeight() + iRefHeight > window.innerHeight){
oSelf.$dropdown_content.css({
'max-height': '',
});
oSelf.$dropdown.css({
top: oSelf.$control.offset().top - iRefHeight,
left: oSelf.$control.offset().left,
let iDropdownHeight = oSelf.$dropdown.outerHeight();
if(oSelf.$control.offset().top + oSelf.$control.outerHeight() + iDropdownHeight > window.innerHeight){
// Apply max-height as we are overflowing, that'll allow us to calculate where we should place ourselves later
oSelf.$dropdown.css({
maxHeight: `${aOptions.maxDropDownHeight}`,
})
iDropdownHeight = oSelf.$dropdown.outerHeight();
oSelf.$dropdown.css({
top: oSelf.$control.offset().top - iDropdownHeight + iDropdownContentHeightDifference, // Content will be shorter, so our real height too
left: oSelf.$control.offset().left,
width: oSelf.$wrapper.outerWidth(),
'max-height': `${aOptions.maxDropDownHeight}px`,
'overflow-y': 'auto',
'border-top': '1px solid #d0d0d0',
overflowY: 'auto',
borderTop : oSelf.$dropdown.css('border-bottom')
});
// N°9468 Dropdown content needs to be a few pixel shorter than the dropdown itself to avoid double scrollbar
oSelf.$dropdown_content.css({
'max-height': `calc(${aOptions.maxDropDownHeight} - ${iDropdownContentHeightDifference}px)`
});
}
else{
oSelf.$dropdown.css({
top: oSelf.$control.offset().top + oSelf.$control.outerHeight(),
left: oSelf.$control.offset().left,
width: oSelf.$wrapper.outerWidth(),
'max-height': `${aOptions.maxDropDownHeight}px`,
'overflow-y': 'auto'
});
overflowY: 'auto',
borderTop: 'none'
});
}
};
}());

View File

@@ -134,6 +134,7 @@ return array(
'Combodo\\iTop\\Application\\Helper\\CKEditorHelper' => $baseDir . '/sources/Application/Helper/CKEditorHelper.php',
'Combodo\\iTop\\Application\\Helper\\ExportHelper' => $baseDir . '/sources/Application/Helper/ExportHelper.php',
'Combodo\\iTop\\Application\\Helper\\FormHelper' => $baseDir . '/sources/Application/Helper/FormHelper.php',
'Combodo\\iTop\\Application\\Helper\\ImportHelper' => $baseDir . '/sources/Application/Helper/ImportHelper.php',
'Combodo\\iTop\\Application\\Helper\\SearchHelper' => $baseDir . '/sources/Application/Helper/SearchHelper.php',
'Combodo\\iTop\\Application\\Helper\\Session' => $baseDir . '/sources/Application/Helper/Session.php',
'Combodo\\iTop\\Application\\Helper\\WebResourcesHelper' => $baseDir . '/sources/Application/Helper/WebResourcesHelper.php',

View File

@@ -520,6 +520,7 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
'Combodo\\iTop\\Application\\Helper\\CKEditorHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/CKEditorHelper.php',
'Combodo\\iTop\\Application\\Helper\\ExportHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/ExportHelper.php',
'Combodo\\iTop\\Application\\Helper\\FormHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/FormHelper.php',
'Combodo\\iTop\\Application\\Helper\\ImportHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/ImportHelper.php',
'Combodo\\iTop\\Application\\Helper\\SearchHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/SearchHelper.php',
'Combodo\\iTop\\Application\\Helper\\Session' => __DIR__ . '/../..' . '/sources/Application/Helper/Session.php',
'Combodo\\iTop\\Application\\Helper\\WebResourcesHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/WebResourcesHelper.php',

View File

@@ -5,9 +5,11 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\ImportHelper;
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\Select;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\TextArea;
@@ -387,6 +389,14 @@ EOF
}
break;
case 'display_classes_select':
$oPage = new AjaxPage("");
$sClassName = utils::ReadPostedParam('class_name', '', utils::ENUM_SANITIZATION_FILTER_CLASS);
$bAdvanced = utils::ReadPostedParam('advanced', 'false');
$oClassesSelect = ImportHelper::GetClassesSelectUIBlock('class_name', $sClassName, UR_ACTION_BULK_MODIFY, $bAdvanced === 'true');
$oPage->AddSubBlock($oClassesSelect);
break;
case 'get_csv_template':
$sClassName = utils::ReadParam('class_name');
$sFormat = utils::ReadParam('format', 'csv');

View File

@@ -5,6 +5,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\ImportHelper;
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSectionUIBlockFactory;
@@ -14,7 +15,6 @@ use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Input\FileSelect\FileSelectUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\Select;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\TextArea;
@@ -30,7 +30,6 @@ use Combodo\iTop\Application\WebPage\AjaxPage;
use Combodo\iTop\Application\WebPage\ErrorPage;
use Combodo\iTop\Application\WebPage\iTopWebPage;
use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\Renderer\BlockRenderer;
use Combodo\iTop\Service\Import\CSVImportPageProcessor;
try {
@@ -52,46 +51,6 @@ try {
$oPage = new iTopWebPage(Dict::S('UI:Title:BulkImport'));
$oPage->SetBreadCrumbEntry('ui-tool-bulkimport', Dict::S('Menu:CSVImportMenu'), Dict::S('UI:Title:BulkImport+'), '', 'fas fa-file-import', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
/**
* Helper function to build a select from the list of valid classes for a given action
*
* @param string $sName The name of the select in the HTML form
* @param $sDefaultValue
* @param integer $iWidthPx The width (in pixels) of the drop-down list
* @param integer $iActionCode The ActionCode (from UserRights) to check for authorization for the classes
*
* @return \Combodo\iTop\Application\UI\Base\Component\Input\Select\
*/
function GetClassesSelectUIBlock(string $sName, $sDefaultValue, int $iActionCode, bool $bAdvanced = false): Select
{
$oSelectBlock = SelectUIBlockFactory::MakeForSelect($sName, 'select_'.$sName);
$oOption = SelectOptionUIBlockFactory::MakeForSelectOption("", Dict::S('UI:CSVImport:ClassesSelectOne'), false);
$oSelectBlock->AddSubBlock($oOption);
$aValidClasses = [];
$aClassCategories = ['bizmodel', 'addon/authentication'];
if ($bAdvanced) {
$aClassCategories[] = 'grant_by_profile';
}
if (UserRights::IsAdministrator()) {
$aClassCategories[] = 'application';
}
foreach ($aClassCategories as $sClassCategory) {
foreach (MetaModel::GetClasses($sClassCategory) as $sClassName) {
if ((is_null($iActionCode) || UserRights::IsActionAllowed($sClassName, $iActionCode)) &&
(!MetaModel::IsAbstract($sClassName))) {
$sDisplayName = ($bAdvanced) ? MetaModel::GetName($sClassName)." ($sClassName)" : MetaModel::GetName($sClassName);
$aValidClasses[$sDisplayName] = SelectOptionUIBlockFactory::MakeForSelectOption($sClassName, $sDisplayName, ($sClassName == $sDefaultValue));
}
}
}
ksort($aValidClasses);
foreach ($aValidClasses as $sValue => $oBlock) {
$oSelectBlock->AddSubBlock($oBlock);
}
return $oSelectBlock;
}
/**
* Helper to 'check' an input in an HTML form if the current value equals the value given
*
@@ -330,7 +289,7 @@ try {
$oClassesSelect->AddSubBlock($oDefaultSelect);
$aSynchroUpdate = utils::ReadParam('synchro_update', []);
} else {
$oClassesSelect = GetClassesSelectUIBlock('class_name', $sClassName, UR_ACTION_BULK_MODIFY, (bool)$bAdvanced);
$oClassesSelect = ImportHelper::GetClassesSelectUIBlock('class_name', $sClassName, UR_ACTION_BULK_MODIFY, (bool)$bAdvanced);
}
$oPanel = TitleUIBlockFactory::MakeForPage(Dict::S('UI:Title:CSVImportStep3'));
$oPage->AddSubBlock($oPanel);
@@ -354,11 +313,9 @@ try {
$oAdvancedMode->GetInput()->SetIsChecked(($bAdvanced == 1));
$oAdvancedMode->SetBeforeInput(false);
$oAdvancedMode->GetInput()->AddCSSClass('ibo-input-checkbox');
$oAdvancedMode->SetDescription(utils::EscapeHtml(Dict::S('UI:CSVImport:AdvancedMode+')));
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oAdvancedMode));
$oDivAdvancedHelp = UIContentBlockUIBlockFactory::MakeStandard("advanced_help")->AddCSSClass('ibo-is-hidden');
$oForm->AddSubBlock($oDivAdvancedHelp);
$oDivMapping = UIContentBlockUIBlockFactory::MakeStandard("mapping")->AddCSSClass('mt-5');
$oMessage = AlertUIBlockFactory::MakeForInformation(Dict::S('UI:CSVImport:SelectAClassFirst'))->SetIsClosable(false)->SetIsCollapsible(false);
$oDivMapping->AddSubBlock($oMessage);
@@ -395,7 +352,7 @@ try {
$oPage->add_ready_script(
<<<EOF
$('#select_class_name').on('change', function(ev) { DoMapping(); } );
$('#advanced').on('click', function(ev) { DoReload(); } );
$('#advanced').on('click', function(ev) { DoAdvanced(); } );
EOF
);
if ($sClassName != '') {
@@ -408,15 +365,15 @@ EOF
}
$oPage->add_script(
<<<EOF
<<<JS
var aDefaultKeys = new Array();
var aReadOnlyKeys = new Array();
function DoReload()
function DoAdvanced()
{
$('input[name=step]').val(3);
$('#wizForm').removeAttr('onsubmit'); // No need to perform validation checks when going back
$('#wizForm').trigger('submit');
$('#wizForm').submit();
}
function CSVGoBack()
@@ -441,14 +398,7 @@ EOF
{
var class_name = $('select[name=class_name]').val();
var advanced = $('input[name=advanced]:checked').val();
if (advanced != 1)
{
$('#advanced_help').hide();
}
else
{
$('#advanced_help').show();
}
if (class_name != '')
{
var separator = $('input[name=separator]').val();
@@ -496,6 +446,26 @@ EOF
}
}
function UpdateClassesSelect()
{
const aParams = {
operation: 'display_classes_select',
class_name: $('#select_class_name').val(),
advanced: $('#advanced').is(':checked'),
};
$('#select_class_name').block();
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.csvimport.php',
aParams,
function(data) {
$('#select_class_name').replaceWith($(data));
$('#select_class_name').on('change', function(ev) { DoMapping(); } );
DoMapping();
}
);
}
function CheckValues()
{
// Reset the highlight in case the check has already been executed with failure
@@ -626,7 +596,7 @@ EOF
}
}
}
EOF
JS
);
}
@@ -1044,7 +1014,7 @@ EOF
}*/
//Tab:Template
$oTabTemplate = $oTabContainer->AddTab('tabsTemplate', Dict::S('UI:CSVImport:Tab:Templates'));
$oFieldTemplate = FieldUIBlockFactory::MakeFromObject(Dict::S('UI:CSVImport:PickClassForTemplate'), GetClassesSelectUIBlock('template_class', '', UR_ACTION_BULK_MODIFY));
$oFieldTemplate = FieldUIBlockFactory::MakeFromObject(Dict::S('UI:CSVImport:PickClassForTemplate'), ImportHelper::GetClassesSelectUIBlock('template_class', '', UR_ACTION_BULK_MODIFY));
$oTabTemplate->AddSubBlock($oFieldTemplate);
$oDivTemplate = UIContentBlockUIBlockFactory::MakeStandard("template")->AddCSSClass("ibo-is-visible");
$oTabTemplate->AddSubBlock($oDivTemplate);

View File

@@ -511,7 +511,7 @@ EOF;
{
$bDbTlsEnabled = $oConfig->Get('db_tls.enabled');
if (!$bDbTlsEnabled) {
return '';
return CMDBSource::IsSslModeDBVersion() ? ' --skip-ssl' : '';
}
$sTlsOptions = '';
// Mysql 5.7.11 and upper deprecated --ssl and uses --ssl-mode instead

View File

@@ -0,0 +1,65 @@
<?php
namespace Combodo\iTop\Application\Helper;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\Select;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\SelectUIBlockFactory;
use CoreException;
use Dict;
use DictExceptionMissingString;
use MetaModel;
use UserRights;
/**
* Class
* ImportHelper
*
* @internal
* @since 3.2.3
* @package Combodo\iTop\Application\Helper
*/
class ImportHelper
{
/**
* Get classes select UI block.
*
* @param string $sName
* @param $sDefaultValue
* @param int $iActionCode
* @param bool $bAdvanced
*
* @return Select
* @throws CoreException
* @throws DictExceptionMissingString
*/
public static function GetClassesSelectUIBlock(string $sName, $sDefaultValue, int $iActionCode, bool $bAdvanced = false): Select
{
$oSelectBlock = SelectUIBlockFactory::MakeForSelect($sName, 'select_'.$sName);
$oOption = SelectOptionUIBlockFactory::MakeForSelectOption("", Dict::S('UI:CSVImport:ClassesSelectOne'), false);
$oSelectBlock->AddSubBlock($oOption);
$aValidClasses = [];
$aClassCategories = ['bizmodel', 'addon/authentication'];
if ($bAdvanced) {
$aClassCategories[] = 'grant_by_profile';
}
if (UserRights::IsAdministrator()) {
$aClassCategories[] = 'application';
}
foreach ($aClassCategories as $sClassCategory) {
foreach (MetaModel::GetClasses($sClassCategory) as $sClassName) {
if ((is_null($iActionCode) || UserRights::IsActionAllowed($sClassName, $iActionCode)) &&
(!MetaModel::IsAbstract($sClassName))) {
$sDisplayName = ($bAdvanced) ? MetaModel::GetName($sClassName)." ($sClassName)" : MetaModel::GetName($sClassName);
$aValidClasses[$sDisplayName] = SelectOptionUIBlockFactory::MakeForSelectOption($sClassName, $sDisplayName, ($sClassName == $sDefaultValue));
}
}
}
ksort($aValidClasses);
foreach ($aValidClasses as $sValue => $oBlock) {
$oSelectBlock->AddSubBlock($oBlock);
}
return $oSelectBlock;
}
}

View File

@@ -8,8 +8,13 @@ use AttributeFriendlyName;
use AttributeLinkedSet;
use cmdbAbstract;
use cmdbAbstractObject;
use CoreException;
use Dict;
use Exception;
use IssueLog;
use LogChannels;
use Metamodel;
use utils;
/**
* Class DataTableSettings
@@ -130,7 +135,10 @@ class DataTableSettings
*/
public function unserialize($sData)
{
$aData = unserialize($sData);
$aData = utils::Unserialize($sData);
if (!is_array($aData)) {
throw new CoreException('Wrong data table settings format, expected an array', ['datatable_settings_data' => $aData]);
}
$this->iDefaultPageSize = $aData['iDefaultPageSize'];
$this->aColumns = $aData['aColumns'];
foreach ($this->aClassAliases as $sAlias => $sClass) {
@@ -269,7 +277,19 @@ class DataTableSettings
return null;
}
}
$oSettings->unserialize($pref);
try {
$oSettings->unserialize($pref);
} catch (Exception $e) {
IssueLog::Warning("User table settings corrupted, back to the default values provided by the data model", LogChannels::CONSOLE, [
'table_id' => $sTableId,
'root_cause' => $e->getMessage(),
]);
// unset the preference
appUserPreferences::UnsetPref($oSettings->GetPrefsKey($sTableId));
// use the default values provided by the data model
return null;
}
return $oSettings;
}

View File

@@ -23,7 +23,7 @@ let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
},
{# PLUGIN combodo auto position #}
'combodo_auto_position' : {
maxDropDownHeight: 300, {# in px #}
maxDropDownHeight: '30vh', {# same value as external key widget #}
},
{# PLUGIN combodo add button #}
{% if oUIBlock.HasAddOptionButton() %}

View File

@@ -23,6 +23,7 @@
namespace Combodo\iTop\Test\UnitTest\Application;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use CoreException;
use ormDocument;
use utils;
@@ -983,4 +984,21 @@ INI;
unlink($sTmpFileOutsideItop);
}
public function testUnserialize()
{
// data to unserialize containing an object
$sData = 'a:2:{s:6:"string";s:9:"My string";s:6:"object";O:8:"DateTime":3:{s:4:"date";s:26:"2026-04-13 09:09:23.033175";s:13:"timezone_type";i:3;s:8:"timezone";s:16:"Europe/Amsterdam";}}';
// allow the DateTime object (no exception triggered)
utils::Unserialize($sData, ['allowed_classes' => ['DateTime']]);
// flag to avoid throwing an exception
utils::Unserialize($sData, ['allowed_classes' => false], false);
// flag to require throwing an exception
$this->expectException(CoreException::class);
utils::Unserialize($sData);
}
}