Compare commits

..

1 Commits

Author SHA1 Message Date
Anne-Cath
1f860623c4 N°9383 - CheckToWrite inside a transition: Incorrect error message display 2026-03-10 18:29:08 +01:00
56 changed files with 347 additions and 659 deletions

View File

@@ -27,9 +27,6 @@ require_once(APPROOT.'/application/displayblock.class.inc.php');
class UISearchFormForeignKeys
{
private $m_sRemoteClass;
private $m_iInputId;
public function __construct($sTargetClass, $iInputId = null)
{
$this->m_sRemoteClass = $sTargetClass;
@@ -43,7 +40,7 @@ class UISearchFormForeignKeys
*
* @throws \Exception
*/
public function ShowModalSearchForeignKeys($oPage)
public function ShowModalSearchForeignKeys($oPage, $sTitle)
{
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
@@ -63,6 +60,8 @@ class UISearchFormForeignKeys
]
));
$sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sCancel = Dict::S('UI:Button:Cancel');
$sAdd = Dict::S('UI:Button:Add');
$oPage->add(
<<<HTML
@@ -74,6 +73,39 @@ class UISearchFormForeignKeys
</form>
HTML
);
$oPage->add_ready_script(
<<<JS
$('#dlg_{$this->m_iInputId}').dialog({
width: $(window).width()*0.8,
height: $(window).height()*0.8,
autoOpen: false,
modal: true,
resizeStop: oForeignKeysWidget{$this->m_iInputId}.UpdateSizes,
buttons: [
{
text: Dict.S('UI:Button:Cancel'),
class: "cancel ibo-is-alternative ibo-is-neutral",
click: function() {
$('#dlg_{$this->m_iInputId}').dialog('close');
}
},
{
text: Dict.S('UI:Button:Add'),
id: 'btn_ok_{$this->m_iInputId}',
class: "ok ibo-is-regular ibo-is-primary",
click: function() {
oForeignKeysWidget{$this->m_iInputId}.DoAddObjects(this.id);
}
},
],
});
$('#dlg_{$this->m_iInputId}').dialog('option', {title:'$sTitle'});
$('#SearchFormToAdd_{$this->m_iInputId} form').on('submit.uilinksWizard', oForeignKeysWidget{$this->m_iInputId}.SearchObjectsToAdd);
$('#SearchFormToAdd_{$this->m_iInputId}').on('resize', oForeignKeysWidget{$this->m_iInputId}.UpdateSizes);
JS
);
}
public function GetFullListForeignKeysFromSelection($oPage, $oFullSetFilter)
@@ -87,4 +119,31 @@ HTML
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
}
}
/**
* Search for objects to be linked to the current object (i.e "remote" objects)
*
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
*
* @throws \Exception
*/
public function ListResultsSearchForeignKeys(WebPage $oP, $sRemoteClass = '')
{
if ($sRemoteClass != '') {
// assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass));
$oFilter = new DBObjectSearch($sRemoteClass);
} else {
// No remote class specified use the one defined in the linkedset
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
}
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display(
$oP,
"ResultsToAdd_{$this->m_iInputId}",
['menu' => false, 'cssCount' => "#count_{$this->m_iInputId}", 'selection_mode' => true, 'table_id' => "add_{$this->m_iInputId}"]
);
}
}

View File

@@ -122,11 +122,6 @@ class utils
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_VARIABLE_NAME = 'variable_name';
/**
* @var string For module codes (e.g. `itop-portal-base`, `combodo-webhook-integration`, `some-module-code-x.y`, ...)
* @since 3.2.3 3.3.0 N°8554
*/
public const ENUM_SANITIZATION_FILTER_MODULE_CODE = 'module_code';
/**
* @var string
* @since 2.7.10 3.0.0
@@ -398,7 +393,6 @@ class utils
* @since 2.7.10 N°6606 use the utils::ENUM_SANITIZATION_* const
* @since 2.7.10 N°6606 new case for ENUM_SANITIZATION_FILTER_PHP_CLASS
* @since 3.2.1-1 N°8242 Allow value to be an array for every filter
* @since 3.2.3 3.3.0 N°8554 new case for ENUM_SANITIZATION_FILTER_MODULE_CODE
*
* @link https://www.php.net/manual/en/filter.filters.sanitize.php PHP sanitization filters
*/
@@ -486,7 +480,7 @@ class utils
);
break;
// For XML / HTML node selector
// For XML / HTML node id selector
case static::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR:
$retValue = filter_var(
$value,
@@ -499,15 +493,6 @@ class utils
$retValue = preg_replace('/[^a-zA-Z0-9_]/', '', $value);
break;
case static::ENUM_SANITIZATION_FILTER_MODULE_CODE:
// Module codes allow all alphabets letters, numbers, dash and dot characters
$retValue = filter_var(
$value,
FILTER_VALIDATE_REGEXP,
['options' => ['regexp' => '/^[\p{L}\d.-]+$/u']]
);
break;
// For URL
case static::ENUM_SANITIZATION_FILTER_URL:
$retValue = filter_var($value, FILTER_SANITIZE_URL);

View File

@@ -19,7 +19,7 @@
"pear/archive_tar": "~1.4.14",
"pelago/emogrifier": "^7.2.0",
"psr/log": "^3.0.0",
"scssphp/scssphp": "dev-combodo/1.x",
"scssphp/scssphp": "^1.12.1",
"symfony/console": "~6.4.0",
"symfony/dotenv": "~6.4.0",
"symfony/framework-bundle": "~6.4.0",
@@ -43,10 +43,6 @@
{
"type": "vcs",
"url": "https://github.com/EsupPortail/phpCAS"
},
{
"type": "vcs",
"url": "https://github.com/combodo-itop-libs/scssphp"
}
],
"suggest": {

30
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "eebbdc6c10a479b0e62fc18d88496f5c",
"content-hash": "ceac38f6033afe07b7ab977fa39fe348",
"packages": [
{
"name": "apereo/phpcas",
@@ -1588,16 +1588,16 @@
},
{
"name": "scssphp/scssphp",
"version": "dev-combodo/1.x",
"version": "v1.13.0",
"source": {
"type": "git",
"url": "https://github.com/combodo-itop-libs/scssphp.git",
"reference": "dde81c0a39d02e8e6fc81b70269747734e16d526"
"url": "https://github.com/scssphp/scssphp.git",
"reference": "63d1157457e5554edf00b0c1fabab4c1511d2520"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/combodo-itop-libs/scssphp/zipball/dde81c0a39d02e8e6fc81b70269747734e16d526",
"reference": "dde81c0a39d02e8e6fc81b70269747734e16d526",
"url": "https://api.github.com/repos/scssphp/scssphp/zipball/63d1157457e5554edf00b0c1fabab4c1511d2520",
"reference": "63d1157457e5554edf00b0c1fabab4c1511d2520",
"shasum": ""
},
"require": {
@@ -1626,8 +1626,8 @@
"type": "library",
"extra": {
"bamarni-bin": {
"forward-command": false,
"bin-links": false
"bin-links": false,
"forward-command": false
}
},
"autoload": {
@@ -1635,11 +1635,7 @@
"ScssPhp\\ScssPhp\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ScssPhp\\ScssPhp\\Tests\\": "tests/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
@@ -1665,9 +1661,10 @@
"stylesheet"
],
"support": {
"source": "https://github.com/combodo-itop-libs/scssphp/tree/combodo/1.x"
"issues": "https://github.com/scssphp/scssphp/issues",
"source": "https://github.com/scssphp/scssphp/tree/v1.13.0"
},
"time": "2026-03-23T15:26:59+00:00"
"time": "2024-08-17T21:02:11+00:00"
},
{
"name": "soundasleep/html2text",
@@ -5100,8 +5097,7 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"apereo/phpcas": 20,
"scssphp/scssphp": 20
"apereo/phpcas": 20
},
"prefer-stable": false,
"prefer-lowest": false,

View File

@@ -4344,9 +4344,7 @@ class AttributeText extends AttributeString
} else {
$sValue = self::RenderWikiHtml($sValue, true /* wiki only */);
$sImageHtml = UserRights::IsLoggedIn() ? InlineImage::FixUrls($sValue) : InlineImage::ReplaceInlineImagesWithBase64Representation($sValue);
return "<div class=\"HTML ibo-is-html-content\" $sStyle>".$sImageHtml.'</div>';
return "<div class=\"HTML ibo-is-html-content\" $sStyle>".InlineImage::FixUrls($sValue).'</div>';
}
}
@@ -8990,15 +8988,12 @@ class AttributeStopWatch extends AttributeDefinition
switch ($sThresholdCode) {
case 'deadline':
if ($value) {
if (is_numeric($value)) {
if (!is_int($value)) {
$value = intval($value);
}
if (is_int($value)) {
$sDate = date(AttributeDateTime::GetInternalFormat(), $value);
$sRet = AttributeDeadline::FormatDeadline($sDate);
} else {
$sRet = $value;
}
} else {
$sRet = $value;
}
} else {
$sRet = '';
}

View File

@@ -1738,14 +1738,6 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'security.force_login_when_no_delegated_authentication_endpoints_list' => [
'type' => 'bool',
'description' => 'If true, when no execution policy is defined, the user will be forced to log in (instead of being automatically logged in with the default profile)',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'behind_reverse_proxy' => [
'type' => 'bool',
'description' => '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)',

View File

@@ -296,46 +296,6 @@ class InlineImage extends DBObject
return $sHtml;
}
/**
* Replace <img> tags with a data-img-id attribute by the actual image in base64 representation
* so that the image can be displayed even if the download URL is not accessible (e.g. in unauthenticated approval templates)
*
* @param string $sHtml The HTML fragment to process
*
* @return String The modified HTML
* @since 3.2.3
*/
public static function ReplaceInlineImagesWithBase64Representation(string $sHtml): String
{
return preg_replace_callback(
'/<img\s+[^>]*'.static::DOM_ATTR_ID.'="(\d+)"[^>]*>/i',
function ($matches) {
// Extract inline image ID from the tag
$id = $matches[1];
try {
// Retrieve inline image
$oInline = MetaModel::GetObject(InlineImage::class, $id, true, true);
$oOrmDocument = $oInline->Get('contents');
// Replace src image by the base64 representation
$sInlineImageAsBase64 = base64_encode($oOrmDocument->GetData());
$sDataUri = 'data:'.$oOrmDocument->GetMimeType().';base64,'.$sInlineImageAsBase64;
$sImage = preg_replace('/src=["\'][^"\']+["\']/', 'src="'.$sDataUri.'"', $matches[0]);
// Remove sensitive information (the image ID and secret) from the tag
$sImage = preg_replace('/'.static::DOM_ATTR_ID.'="\d+"\s+'.static::DOM_ATTR_SECRET.'="\w+"/', '', $sImage);
} catch (Exception $e) {
$sImage = '<img src="" alt="'.Dict::S('UI:MissingInlineImage').'">';
}
return $sImage;
},
$sHtml
);
}
/**
* Add an extra attribute data-img-id for images which are based on an actual InlineImage
* so that we can later reconstruct the full "src" URL when needed

View File

@@ -41,11 +41,6 @@ SetupWebPage::AddModule(
'doc.manual_setup' => '',
'doc.more_information' => '',
// Security
'delegated_authentication_endpoints' => [
'ajax.backup.php',
],
// Default settings
//
'settings' => [

View File

@@ -242,8 +242,8 @@ try {
throw new SecurityException(Dict::S('iTopHub:FailAuthent'));
}
// First step: prepare the datamodel, if it fails, roll-back
$aSelectedExtensionCodes = utils::ReadParam('extension_codes', [], false, utils::ENUM_SANITIZATION_FILTER_MODULE_CODE);
$aSelectedExtensionDirs = utils::ReadParam('extension_dirs', [], false, utils::ENUM_SANITIZATION_FILTER_MODULE_CODE);
$aSelectedExtensionCodes = utils::ReadParam('extension_codes', []);
$aSelectedExtensionDirs = utils::ReadParam('extension_dirs', []);
$oRuntimeEnv = new HubRunTimeEnvironment('production', false); // use a temp environment: production-build
$oRuntimeEnv->MoveSelectedExtensions(APPROOT.'/data/downloaded-extensions/', $aSelectedExtensionDirs);

View File

@@ -37,10 +37,6 @@ SetupWebPage::AddModule(
// add your sample data XML files here,
],
'delegated_authentication_endpoints' => [
'ajax.php',
],
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any

View File

@@ -317,7 +317,7 @@ class BrowseBrickHelper
$aRow[$key] = [
'level_alias' => $key,
'id' => $sCurrentObjectId,
'name' => $value->Get($sNameAttCode),
'name' => utils::EscapeHtml($value->Get($sNameAttCode)),
'class' => $sCurrentObjectClass,
'action_rules_token' => $this->PrepareActionRulesForItems($aItems, $key, $aLevelsProperties),
'metadata' => [
@@ -476,7 +476,7 @@ class BrowseBrickHelper
$aItems[$sCurrentIndex] = [
'level_alias' => $aCurrentRowKeys[0],
'id' => $aCurrentRowValues[0]->GetKey(),
'name' => $aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]]['name_att']),
'name' => utils::EscapeHtml($aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]]['name_att'])),
'class' => get_class($aCurrentRowValues[0]),
'subitems' => [],
'filter_data' => $this->GetFilterData($aLevelsProperties[$aCurrentRowKeys[0]], $aCurrentRowKeys[0], $aCurrentRowValues[0]),

View File

@@ -80,11 +80,11 @@
// N°4662 - Surround tooltip with div to ensure text retrival
if( (data.tooltip !== undefined) && ($('<div>'+data.tooltip+'</div>').text() !== ''))
{
cellElem.html( $('<span></span>').attr('data-tooltip-content', data.tooltip).attr('data-tooltip-html-enabled', true).text(data.name).prop('outerHTML') );
cellElem.html( $('<span></span>').attr('data-tooltip-content', data.tooltip).attr('data-tooltip-html-enabled', true).html(data.name).prop('outerHTML') );
}
else
{
cellElem.text(data.name);
cellElem.html(data.name);
}
// Building actions

View File

@@ -197,7 +197,7 @@
if( (item.name !== undefined) && (item.name !== '') )
{
iItemFlags += 1;
textWrapperElem.append( $('<div></div>').addClass('mosaic-item-name').text(item.name) );
textWrapperElem.append( $('<div></div>').addClass('mosaic-item-name').html(item.name) );
}
// - Adding description
if( (item.description !== undefined) && (item.description !== '') )

View File

@@ -233,9 +233,7 @@
{
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_DRILLDOWN') }}':
spanElem.addClass('tree-toggle');
var iconElem = $('<span></span>').addClass('glyphicon '+sNodeCollapsedClass).attr('aria-hidden', 'true');
var textElem = $('<span></span>').addClass('list-group-item-text').text(nameElem.text());
nameElem.empty().append(iconElem).append(textElem);
nameElem.html('<span class="glyphicon '+sNodeCollapsedClass+'" aria-hidden="true"></span><span class="list-group-item-text">'+nameElem.text()+'</span>');
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);

View File

@@ -1394,7 +1394,6 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
'UI:SelectInlineImageToUpload' => 'Vyberte obrázek',
'UI:AvailableInlineImagesLegend' => 'Dostupné obrázky',
'UI:NoInlineImage' => 'Na serveru není dostupný žádný obrázek. Nahrajte nějaký pomocí tlačítka výše.',
'UI:MissingInlineImage' => 'Chybějící obrázek',
'UI:ToggleFullScreen' => 'Přepnout zobrazení',
'UI:Button:ResetImage' => 'Obnovit původní obrázek',
'UI:Button:RemoveImage' => 'Odebrat obrázek',

View File

@@ -1397,7 +1397,6 @@ Ved tilknytningen til en trigger, bliver hver handling tildelt et "rækkefølge"
'UI:SelectInlineImageToUpload' => 'Select the image to upload~~',
'UI:AvailableInlineImagesLegend' => 'Available images~~',
'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.~~',
'UI:MissingInlineImage' => 'Manglende billede',
'UI:ToggleFullScreen' => 'Toggle Maximize / Minimize~~',
'UI:Button:ResetImage' => 'Recover the previous image~~',
'UI:Button:RemoveImage' => 'Remove the image~~',

View File

@@ -1394,7 +1394,6 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm
'UI:SelectInlineImageToUpload' => 'Wähle das Bild für den Upload aus',
'UI:AvailableInlineImagesLegend' => 'Verfügbare Bilder',
'UI:NoInlineImage' => 'Es sind keine Bilder auf dem Server verfügbar. Nutze den "Durchsuchen" Button oben, um ein Bild vom Computer hochzuladen.',
'UI:MissingInlineImage' => 'Bild fehlt',
'UI:ToggleFullScreen' => 'Maximieren / Minimieren',
'UI:Button:ResetImage' => 'Vorheriges Bild wiederherstellen',
'UI:Button:RemoveImage' => 'Bild löschen',

View File

@@ -1471,7 +1471,6 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:SelectInlineImageToUpload' => 'Select the image to upload',
'UI:AvailableInlineImagesLegend' => 'Available images',
'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.',
'UI:MissingInlineImage' => 'Missing image',
'UI:ToggleFullScreen' => 'Toggle Maximize / Minimize',
'UI:Button:ResetImage' => 'Recover the previous image',

View File

@@ -1471,7 +1471,6 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:SelectInlineImageToUpload' => 'Select the image to upload',
'UI:AvailableInlineImagesLegend' => 'Available images',
'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.',
'UI:MissingInlineImage' => 'Missing image',
'UI:ToggleFullScreen' => 'Toggle Maximise / Minimise',
'UI:Button:ResetImage' => 'Recover the previous image',

View File

@@ -1397,7 +1397,6 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden",
'UI:SelectInlineImageToUpload' => 'Seleccione la imágen a subir',
'UI:AvailableInlineImagesLegend' => 'Imágenes disponibles',
'UI:NoInlineImage' => 'No hay imágenes disponibles en el servidor. Use el botón "Seleccionar archivo" para seleccionar una imágen de su equipo local y subirla al servidor.',
'UI:MissingInlineImage' => 'Imagen faltante',
'UI:ToggleFullScreen' => 'Cambiar Maximizar / Minimizar',
'UI:Button:ResetImage' => 'Recuperar imágen previa',
'UI:Button:RemoveImage' => 'Remover imágen',

View File

@@ -1398,7 +1398,6 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:SelectInlineImageToUpload' => 'Sélectionnez l\'image à ajouter',
'UI:AvailableInlineImagesLegend' => 'Images disponibles',
'UI:NoInlineImage' => 'Il n\'y a aucune image de disponible sur le serveur. Utilisez le bouton "Parcourir" (ci-dessus) pour sélectionner une image sur votre ordinateur et la télécharger sur le serveur.',
'UI:MissingInlineImage' => 'Image introuvable',
'UI:ToggleFullScreen' => 'Agrandir / Minimiser',
'UI:Button:ResetImage' => 'Récupérer l\'image initiale',
'UI:Button:RemoveImage' => 'Supprimer l\'image',

View File

@@ -1400,7 +1400,6 @@ A művelet eseményindítóhoz rendelésekor kap egy sorszámot , amely meghatá
'UI:SelectInlineImageToUpload' => 'Válasszon egy képet',
'UI:AvailableInlineImagesLegend' => 'Elérhető képek',
'UI:NoInlineImage' => 'A szerveren nincs elérhető kép. Használja a fenti "Tallózás" gombot egy kép kiválasztásához a számítógépéről, és töltse fel a szerverre.',
'UI:MissingInlineImage' => 'Hiányzó kép',
'UI:ToggleFullScreen' => 'Maximalizálás / Minimalizálás',
'UI:Button:ResetImage' => 'Az előző kép visszaállítása',
'UI:Button:RemoveImage' => 'Kép eltávolítása',

View File

@@ -1399,7 +1399,6 @@ Quando è associata a un trigger, a ogni azione è assegnato un numero "ordine",
'UI:SelectInlineImageToUpload' => 'Seleziona l\'immagine da caricare',
'UI:AvailableInlineImagesLegend' => 'Immagini disponibili',
'UI:NoInlineImage' => 'Non ci sono immagini disponibili sul server. Utilizza il pulsante "Sfoglia" sopra per selezionare un\'immagine dal tuo computer e caricarla sul server.',
'UI:MissingInlineImage' => 'Immagine mancante',
'UI:ToggleFullScreen' => 'Attiva/Disattiva a schermo intero',
'UI:Button:ResetImage' => 'Ripristina l\'immagine precedente',
'UI:Button:RemoveImage' => 'Rimuovi l\'immagine',

View File

@@ -1401,7 +1401,6 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'UI:SelectInlineImageToUpload' => 'Select the image to upload~~',
'UI:AvailableInlineImagesLegend' => 'Available images~~',
'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.~~',
'UI:MissingInlineImage' => 'Missing image~~',
'UI:ToggleFullScreen' => 'Toggle Maximize / Minimize~~',
'UI:Button:ResetImage' => 'Recover the previous image~~',
'UI:Button:RemoveImage' => 'Remove the image~~',

View File

@@ -1400,7 +1400,6 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt
'UI:SelectInlineImageToUpload' => 'Selecteer een afbeelding om te uploaden',
'UI:AvailableInlineImagesLegend' => 'Beschikbare afbeeldingen',
'UI:NoInlineImage' => 'Er is geen afbeelding beschikbaar op de server. Gebruik de "Afbeeldingen doorbladeren..." knop hierboven om een afbeelding te kiezen op je toestel.',
'UI:MissingInlineImage' => 'Ontbrekende afbeelding',
'UI:ToggleFullScreen' => 'Minimaliseren / Maximaliseren',
'UI:Button:ResetImage' => 'Vorige afbeelding herstellen',
'UI:Button:RemoveImage' => 'Afbeelding verwijderen',

View File

@@ -1408,7 +1408,6 @@ W przypadku powiązania z wyzwalaczem, każde działanie otrzymuje numer "porzą
'UI:SelectInlineImageToUpload' => 'Wybierz obraz do przesłania',
'UI:AvailableInlineImagesLegend' => 'Dostępne obrazy',
'UI:NoInlineImage' => 'Na serwerze nie ma obrazu. Użyj przycisku "Przeglądaj" powyżej, aby wybrać obraz ze swojego komputera i przesłać go na serwer.',
'UI:MissingInlineImage' => 'Brakujący obraz',
'UI:ToggleFullScreen' => 'Przełącz Maksymalizuj / Minimalizuj',
'UI:Button:ResetImage' => 'Odzyskaj poprzedni obraz',
'UI:Button:RemoveImage' => 'Usuń obraz',

View File

@@ -1393,7 +1393,6 @@ Quando associada a um gatilho, cada ação recebe um número de "ordem", especif
'UI:SelectInlineImageToUpload' => 'Selecione a imagem para enviar',
'UI:AvailableInlineImagesLegend' => 'Imagens disponíveis',
'UI:NoInlineImage' => 'Não há imagem disponível no servidor. Use o botão "Escolher arquivo" acima para selecionar uma imagem do seu computador e fazer o upload para o servidor',
'UI:MissingInlineImage' => 'Imagem ausente',
'UI:ToggleFullScreen' => 'Alternancia Maximizar / Minimizar',
'UI:Button:ResetImage' => 'Recupere a imagem anterior',
'UI:Button:RemoveImage' => 'Remover a imagem',

View File

@@ -1397,7 +1397,6 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'UI:SelectInlineImageToUpload' => 'Выберите изображение для загрузки',
'UI:AvailableInlineImagesLegend' => 'Доступные изображения',
'UI:NoInlineImage' => 'На сервере нет доступных изображений. С помощью кнопки "Обзор..." выше выберите изображение на вашем компьютере, чтобы загрузить его на сервер.',
'UI:MissingInlineImage' => 'Отсутствует изображение',
'UI:ToggleFullScreen' => 'Развернуть / Свернуть',
'UI:Button:ResetImage' => 'Восстановить предыдущее изображение',
'UI:Button:RemoveImage' => 'Удалить изображение',

View File

@@ -1398,7 +1398,6 @@ Keď sú priradené spúštačom, každej akcii je dané číslo "príkazu", šp
'UI:SelectInlineImageToUpload' => 'Select the image to upload~~',
'UI:AvailableInlineImagesLegend' => 'Available images~~',
'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.~~',
'UI:MissingInlineImage' => 'Missing image~~',
'UI:ToggleFullScreen' => 'Toggle Maximize / Minimize~~',
'UI:Button:ResetImage' => 'Recover the previous image~~',
'UI:Button:RemoveImage' => 'Remove the image~~',

View File

@@ -1401,7 +1401,6 @@ Tetikleme gerçekleştiriğinde işlemler tanımlanan sıra numarası ile gerçe
'UI:SelectInlineImageToUpload' => 'Select the image to upload~~',
'UI:AvailableInlineImagesLegend' => 'Available images~~',
'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.~~',
'UI:MissingInlineImage' => 'Missing image~~',
'UI:ToggleFullScreen' => 'Toggle Maximize / Minimize~~',
'UI:Button:ResetImage' => 'Recover the previous image~~',
'UI:Button:RemoveImage' => 'Remove the image~~',

View File

@@ -1398,7 +1398,6 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
'UI:SelectInlineImageToUpload' => '选择要上传的图片',
'UI:AvailableInlineImagesLegend' => '可用的图片',
'UI:NoInlineImage' => '服务器上没有图片. 使用上面的 "浏览" 按钮, 从您的电脑上选择并上传到服务器.',
'UI:MissingInlineImage' => '缺少图片',
'UI:ToggleFullScreen' => '切换最大化/最小化',
'UI:Button:ResetImage' => '恢复之前的图片',
'UI:Button:RemoveImage' => '移除图片',

View File

@@ -332,12 +332,6 @@ CombodoModal._ConvertButtonDefinition = function (aButtonsDefinitions) {
class: typeof(element.classes) !== 'undefined' ? element.classes.join(' ') : '',
click: element.callback_on_click
}
// id is optional, and we don't want to set it if not defined
if (typeof element.id !== 'undefined' && element.id !== null) {
aButton.id = element.id;
}
aConverted.push(aButton);
}
);

View File

@@ -35,63 +35,116 @@ function SearchFormForeignKeys(id, sTargetClass, sAttCode, oSearchWidgetElmt, sF
this.sAttCode = sAttCode;
this.oSearchWidgetElmt = oSearchWidgetElmt;
this.emptyHtml = ''; // content to be displayed when the search results are empty (when opening the dialog)
this.emptyOnClose = true; // Workaround for the JQuery dialog being very slow when opening and closing if the content contains many INPUT tags
this.ajax_request = null;
// this.bSelectMode = bSelectMode; // true if the edited field is a SELECT, false if it's an autocomplete
// this.bSearchMode = bSearchMode; // true if selecting a value in the context of a search form
var me = this;
this.Init = function()
{
// make sure that the form is clean
$('#linkedset_'+this.id+' .selection').each( function() { this.checked = false; });
$('#'+this.id+'_btnRemove').prop('disabled', false);
$('<div id="dlg_'+me.id+'"></div>').appendTo(document.body);
// me.trace(dialog);
//TODO : check and remove all unneded code bellow this line!!
$('#'+this.id+'_linksToRemove').val('');
$('#linkedset_'+me.id).on('remove', function() {
// prevent having the dlg div twice
$('#dlg_'+me.id).remove();
});
$('#'+this.iInputId).closest('form').on('submit', function() {
return me.OnFormSubmit();
});
};
this.StopPendingRequest = function()
{
if (me.ajax_request)
{
me.ajax_request.abort();
me.ajax_request = null;
}
};
this.ShowModalSearchForeignKeys = function()
{
const oModalParams = {
content: {
endpoint: AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'),
data: {
sAttCode: me.sAttCode,
iInputId: me.id,
sTargetClass: me.sTargetClass,
operation: 'ShowModalSearchForeignKeys'
},
},
title: me.sTitle,
id: 'dlg_'+me.id,
size: 'lg',
buttons: [
{
text: Dict.S('UI:Button:Cancel'),
callback_on_click: function() {
$(this).dialog("close");
},
classes: ['cancel', 'ibo-is-alternative', 'ibo-is-neutral'],
},
{
text: Dict.S('UI:Button:Add'),
id: "btn_ok_"+me.id,
classes: ['ok', 'ibo-is-regular', 'ibo-is-primary'],
callback_on_click: function() {
me.DoAddObjects();
}
}
],
callback_on_content_loaded: function(oModalContentElement){
// Update initial buttons state
me.UpdateButtons();
},
// // Query the server to get the form to search for target objects
// if (me.bSelectMode)
// {
// $('#fstatus_'+me.id).html('<img src="../images/indicator.gif" />');
// }
// else
// {
// $('#label_'+me.id).addClass('dlg_loading');
// }
$('#label_'+me.id).addClass('dlg_loading');
var theMap = {
sAttCode: me.sAttCode,
iInputId: me.id,
sTitle: me.sTitle,
sTargetClass: me.sTargetClass,
// bSearchMode: me.bSearchMode,
operation: 'ShowModalSearchForeignKeys'
};
extra_options: {
callback_on_modal_close: function () {
$(this).remove(); // destroy then remove dialog object
}
}
}
const oModal = CombodoModal.OpenModal(oModalParams);
// Bind events
oModal.on('change', '#count_'+me.id, function(){
me.UpdateButtons();
});
// Make sure that we cancel any pending request before issuing another
// since responses may arrive in arbitrary order
me.StopPendingRequest();
// Run the query and get the result back directly in HTML
me.ajax_request = $.post( AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
function(data)
{
// $('#dlg_'+me.id).html(data);
$('#dlg_'+me.id).empty().append($(data)); // $(data).filter(':not(script)'));
$('#dlg_'+me.id).dialog('open');
me.UpdateSizes();
me.UpdateButtons();
me.ajax_request = null;
me.ListResultsSearchForeignKeys();
},
'html'
);
};
this.UpdateSizes = function()
{
var dlg = $('#dlg_'+me.id);
// Adjust the dialog's size to fit into the screen
if (dlg.width() > ($(window).width()-40))
{
dlg.width($(window).width()-40);
}
if (dlg.height() > ($(window).height()-70))
{
dlg.height($(window).height()-70);
}
var searchForm = dlg.find('div.display_block:first'); // Top search form, enclosing display_block
var results = $('#SearchResultsToAdd_'+me.id);
var oPadding = {};
var aKeys = ['top', 'right', 'bottom', 'left'];
for(k in aKeys)
{
oPadding[aKeys[k]] = 0;
if (dlg.css('padding-'+aKeys[k]))
{
oPadding[aKeys[k]] = parseInt(dlg.css('padding-'+aKeys[k]).replace('px', ''));
}
}
//var width = dlg.innerWidth() - oPadding['right'] - oPadding['left'] - 22; // 5 (margin-left) + 5 (padding-left) + 5 (padding-right) + 5 (margin-right) + 2 for rounding !
var height = dlg.innerHeight()-oPadding['top']-oPadding['bottom']-22;
var form_height = searchForm.outerHeight();
results.height(height - form_height - 40); // Leave some space for the buttons
};
this.UpdateButtons = function()
@@ -107,6 +160,63 @@ function SearchFormForeignKeys(id, sTargetClass, sAttCode, oSearchWidgetElmt, sF
}
};
/**
* @return {boolean}
*/
this.ListResultsSearchForeignKeys = function ()
{
var theMap = {
sTargetClass: me.sTargetClass,
iInputId: me.id,
sFilter: me.sfilter,
// bSearchMode: me.bSearchMode
};
// Gather the parameters from the search form
$('#fs_'+me.id+' :input').each( function() {
if (this.name !== '')
{
var val = $(this).val(); // supports multiselect as well
if (val !== null)
{
theMap[this.name] = val;
}
}
});
theMap['sRemoteClass'] = theMap['class']; // swap 'class' (defined in the form) and 'remoteClass'
theMap.operation = 'ListResultsSearchForeignKeys'; // Override what is defined in the form itself
theMap.sAttCode = me.sAttCode;
var sSearchAreaId = '#SearchResultsToAdd_'+me.id;
//$(sSearchAreaId).html('<div style="text-align:center;width:100%;height:24px;vertical-align:middle;"><img src="../images/indicator.gif" /></div>');
$(sSearchAreaId).block();
me.UpdateButtons();
// Make sure that we cancel any pending request before issuing another
// since responses may arrive in arbitrary order
me.StopPendingRequest();
// Run the query and display the results
me.ajax_request = $.post(AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
function(data)
{
$(sSearchAreaId).html(data);
$('#fr_'+me.id+' input:radio').on('click', function() { me.UpdateButtons(); });
me.UpdateButtons();
me.ajax_request = null;
$('#count_'+me.id).on('change', function(){
me.UpdateButtons();
});
me.UpdateSizes();
},
'html'
);
return false; // Don't submit the form, stay in the current page !
};
/**
* @return {boolean}
*/
@@ -176,4 +286,56 @@ function SearchFormForeignKeys(id, sTargetClass, sAttCode, oSearchWidgetElmt, sF
return false;
};
// Workaround for a ui.jquery limitation: if the content of
// the dialog contains many INPUTs, closing and opening the
// dialog is very slow. So empty it each time.
this.OnClose = function()
{
me.StopPendingRequest();
// called by the dialog, so in the context 'this' points to the jQueryObject
if (me.emptyOnClose)
{
$('#SearchResultsToAdd_'+me.id).html(me.emptyHtml);
}
$('#label_'+me.id).removeClass('dlg_loading');
$('#label_'+me.id).focus();
me.ajax_request = null;
};
this.DoSelectObjectClass = function()
{
// Retrieving selected value
var oSelectedClass = $('#ac_create_'+me.id+' select');
if(oSelectedClass.length !== 1) return;
// Setting new target class
me.sTargetClass = oSelectedClass.val();
// Opening real creation form
$('#ac_create_'+me.id).dialog('close');
me.CreateObject();
};
this.Update = function()
{
if ($('#'+me.id).prop('disabled'))
{
$('#v_'+me.id).html('');
$('#label_'+me.id).prop('disabled', true);
$('#label_'+me.id).css({'background': 'transparent'});
$('#mini_add_'+me.id).hide();
$('#mini_tree_'+me.id).hide();
$('#mini_search_'+me.id).hide();
}
else
{
$('#label_'+me.id).prop('disabled', false);
$('#label_'+me.id).css({'background': '#fff url(../images/ac-background.gif) no-repeat right'});
$('#mini_add_'+me.id).show();
$('#mini_tree_'+me.id).show();
$('#mini_search_'+me.id).show();
}
};
}

View File

@@ -1654,17 +1654,17 @@
},
{
"name": "scssphp/scssphp",
"version": "dev-combodo/1.x",
"version_normalized": "dev-combodo/1.x",
"version": "v1.13.0",
"version_normalized": "1.13.0.0",
"source": {
"type": "git",
"url": "https://github.com/combodo-itop-libs/scssphp.git",
"reference": "dde81c0a39d02e8e6fc81b70269747734e16d526"
"url": "https://github.com/scssphp/scssphp.git",
"reference": "63d1157457e5554edf00b0c1fabab4c1511d2520"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/combodo-itop-libs/scssphp/zipball/dde81c0a39d02e8e6fc81b70269747734e16d526",
"reference": "dde81c0a39d02e8e6fc81b70269747734e16d526",
"url": "https://api.github.com/repos/scssphp/scssphp/zipball/63d1157457e5554edf00b0c1fabab4c1511d2520",
"reference": "63d1157457e5554edf00b0c1fabab4c1511d2520",
"shasum": ""
},
"require": {
@@ -1687,15 +1687,15 @@
"ext-iconv": "Can be used as fallback when ext-mbstring is not available",
"ext-mbstring": "For best performance, mbstring should be installed as it is faster than ext-iconv"
},
"time": "2026-03-23T15:26:59+00:00",
"time": "2024-08-17T21:02:11+00:00",
"bin": [
"bin/pscss"
],
"type": "library",
"extra": {
"bamarni-bin": {
"forward-command": false,
"bin-links": false
"bin-links": false,
"forward-command": false
}
},
"installation-source": "dist",
@@ -1704,11 +1704,7 @@
"ScssPhp\\ScssPhp\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ScssPhp\\ScssPhp\\Tests\\": "tests/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
@@ -1734,7 +1730,8 @@
"stylesheet"
],
"support": {
"source": "https://github.com/combodo-itop-libs/scssphp/tree/combodo/1.x"
"issues": "https://github.com/scssphp/scssphp/issues",
"source": "https://github.com/scssphp/scssphp/tree/v1.13.0"
},
"install-path": "../scssphp/scssphp"
},

View File

@@ -292,9 +292,9 @@
'dev_requirement' => false,
),
'scssphp/scssphp' => array(
'pretty_version' => 'dev-combodo/1.x',
'version' => 'dev-combodo/1.x',
'reference' => 'dde81c0a39d02e8e6fc81b70269747734e16d526',
'pretty_version' => 'v1.13.0',
'version' => '1.13.0.0',
'reference' => '63d1157457e5554edf00b0c1fabab4c1511d2520',
'type' => 'library',
'install_path' => __DIR__ . '/../scssphp/scssphp',
'aliases' => array(),

View File

@@ -5052,7 +5052,7 @@ EOL;
*
* @return array
*/
protected function multiplyMedia(?Environment $env = null, $childQueries = null)
protected function multiplyMedia(Environment $env = null, $childQueries = null)
{
if (
! isset($env) ||
@@ -5144,7 +5144,7 @@ EOL;
*
* @return \ScssPhp\ScssPhp\Compiler\Environment
*/
protected function pushEnv(?Block $block = null)
protected function pushEnv(Block $block = null)
{
$env = new Environment();
$env->parent = $this->env;
@@ -5208,7 +5208,7 @@ EOL;
*
* @return void
*/
protected function set($name, $value, $shadow = false, ?Environment $env = null, $valueUnreduced = null)
protected function set($name, $value, $shadow = false, Environment $env = null, $valueUnreduced = null)
{
$name = $this->normalizeName($name);
@@ -5314,7 +5314,7 @@ EOL;
*
* @return mixed|null
*/
public function get($name, $shouldThrow = true, ?Environment $env = null, $unreduced = false)
public function get($name, $shouldThrow = true, Environment $env = null, $unreduced = false)
{
$normalizedName = $this->normalizeName($name);
$specialContentKey = static::$namespaces['special'] . 'content';
@@ -5379,7 +5379,7 @@ EOL;
*
* @return bool
*/
protected function has($name, ?Environment $env = null)
protected function has($name, Environment $env = null)
{
return ! \is_null($this->get($name, false, $env));
}

View File

@@ -272,7 +272,7 @@ abstract class Formatter
*
* @return string
*/
public function format(OutputBlock $block, ?SourceMapGenerator $sourceMapGenerator = null)
public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerator = null)
{
$this->sourceMapGenerator = null;

View File

@@ -578,7 +578,7 @@ class Number extends Node implements \ArrayAccess, \JsonSerializable
*
* @return string
*/
public function output(?Compiler $compiler = null)
public function output(Compiler $compiler = null)
{
$dimension = round($this->dimension, self::PRECISION);

View File

@@ -140,7 +140,7 @@ class Parser
* @param bool $cssOnly
* @param LoggerInterface|null $logger
*/
public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', ?Cache $cache = null, $cssOnly = false, ?LoggerInterface $logger = null)
public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', Cache $cache = null, $cssOnly = false, LoggerInterface $logger = null)
{
$this->sourceName = $sourceName ?: '(stdin)';
$this->sourceIndex = $sourceIndex;

View File

@@ -59,7 +59,7 @@ final class Warn
*
* @internal
*/
public static function setCallback(?callable $callback = null)
public static function setCallback(callable $callback = null)
{
$previousCallback = self::$callback;
self::$callback = $callback;

View File

@@ -1186,6 +1186,12 @@ try {
if ($bRes) {
try {
$bApplyStimulus = $oObj->ApplyStimulus($sStimulus); // will write the object in the DB
}
catch (CoreCannotSaveObjectException $e) {
// Rollback to the previous state... by reloading the object from the database and applying the modifications again
$oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey());
$oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes);
$sIssues = implode(' ', $e->getIssues());
} catch (CoreException $e) {
// Rollback to the previous state... by reloading the object from the database and applying the modifications again
$oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey());

View File

@@ -34,7 +34,6 @@ try {
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
IssueLog::Trace('----- Request: '.utils::GetRequestUri(), LogChannels::WEB_REQUEST);
$oPage = new DownloadPage("");
@@ -44,7 +43,7 @@ try {
switch ($operation) {
case 'download_document':
LoginWebPage::DoLoginEx();
LoginWebPage::DoLoginEx('backoffice', false);
$id = utils::ReadParam('id', '');
$sField = utils::ReadParam('field', '');
if ($sClass == 'Attachment') {
@@ -64,7 +63,8 @@ try {
break;
case 'download_inlineimage':
LoginWebPage::DoLoginEx();
// No login is required because the "secret" protects us
// Benefit: the inline image can be inserted into any HTML (templating = $this->html(public_log)$)
$id = utils::ReadParam('id', '');
$sSecret = utils::ReadParam('s', '');
$iCacheSec = 31556926; // One year ahead: an inline image cannot change

View File

@@ -173,9 +173,10 @@ try {
case 'ShowModalSearchForeignKeys':
$oPage->SetContentType('text/html');
$iInputId = utils::ReadParam('iInputId', '');
$sTitle = utils::ReadParam('sTitle', '', false, 'raw_data');
$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
$oWidget = new UISearchFormForeignKeys($sTargetClass, $iInputId);
$oWidget->ShowModalSearchForeignKeys($oPage);
$oWidget->ShowModalSearchForeignKeys($oPage, $sTitle);
break;
// ui.searchformforeignkeys
@@ -186,6 +187,16 @@ try {
$oWidget->GetFullListForeignKeysFromSelection($oPage, $oFullSetFilter);
break;
// ui.searchformforeignkeys
case 'ListResultsSearchForeignKeys':
$oPage->SetContentType('text/html');
$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
$iInputId = utils::ReadParam('iInputId', '');
$sRemoteClass = utils::ReadParam('sRemoteClass', '', false, 'class');
$oWidget = new UISearchFormForeignKeys($sTargetClass, $iInputId);
$oWidget->ListResultsSearchForeignKeys($oPage, $sRemoteClass);
break;
// ui.linkswidget
case 'addObjects':
$oPage->SetContentType('text/html');
@@ -2036,17 +2047,6 @@ EOF
$sObjClass = utils::ReadParam('obj_class', '', false, 'class');
$iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer');
// Check user has access to the object before trying to acquire the lock
$oSearch = new DBObjectSearch($sObjClass);
$oSearch->AddCondition(MetaModel::DBGetKey($sObjClass), $iObjKey, '=');
$oSet = new CMDBObjectSet($oSearch);
if (
false === $oSet->CountExceeds(0) ||
UserRights::IsActionAllowed($sObjClass, UR_ACTION_MODIFY, $oSet) !== UR_ALLOWED_YES
) {
throw new SecurityException(Dict::S('UI:ObjectDoesNotExist'));
}
$aResult = iTopOwnershipLock::AcquireLock($sObjClass, $iObjKey);
if (false === $aResult['success']) {
$aLockData = iTopOwnershipLock::IsLocked($sObjClass, $iObjKey);
@@ -2502,7 +2502,8 @@ EOF
$oKPI->ComputeAndReport('Data fetch and format');
$oPage->output();
} catch (Exception $e) {
echo utils::EscapeHtml(Dict::S('UI:PageTitle:FatalError'));
// note: transform to cope with XSS attacks
echo utils::EscapeHtml($e->GetMessage());
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
}

View File

@@ -87,7 +87,7 @@ if (is_link($sPageEnvFullPath)) {
}
$sTargetPage = CheckPageExists($sPageEnvFullPath, $aPossibleBasePaths);
if ($sTargetPage === false || $sModule === 'core' || $sModule === 'dictionaries') {
if ($sTargetPage === false) {
// Do not recall the page parameters (security takes precedence)
echo "Wrong module, page name or environment...";
exit;
@@ -97,40 +97,4 @@ if ($sTargetPage === false || $sModule === 'core' || $sModule === 'dictionaries'
//
// GO!
//
// check module white list
// check conf param
// force login if needed
$aModuleDelegatedAuthenticationEndpointsList = GetModuleDelegatedAuthenticationEndpoints($sModule);
if (is_null($aModuleDelegatedAuthenticationEndpointsList)) {
$bForceLoginWhenNoDelegatedAuthenticationEndpoints = utils::GetConfig()->Get('security.force_login_when_no_delegated_authentication_endpoints_list');
if ($bForceLoginWhenNoDelegatedAuthenticationEndpoints) {
require_once(APPROOT.'/application/startup.inc.php');
LoginWebPage::DoLoginEx();
}
}
if (is_array($aModuleDelegatedAuthenticationEndpointsList) && !in_array($sPage, $aModuleDelegatedAuthenticationEndpointsList)) {
// if module defined a delegated authentication endpoints but not for the current page, we consider that the page is not allowed to be executed without login
require_once(APPROOT.'/application/startup.inc.php');
LoginWebPage::DoLoginEx();
}
if (is_null($aModuleDelegatedAuthenticationEndpointsList) && !UserRights::IsLoggedIn()) {
require_once(APPROOT.'/application/startup.inc.php');
// check if user is not logged in, if not log a warning in the log file as the page is executed without login, which is not recommended for security reason
IssueLog::Debug("The '$sPage' page is executed without logging in. This call will be blocked in the future and will likely cause unwanted behaviour in the '$sModule' module. Please define a delegated authentication endpoint for the module, as described at https://www.itophub.io/wiki/page?id=latest:customization:new_extension#security.");
}
require_once($sTargetPage);
function GetModuleDelegatedAuthenticationEndpoints(string $sModuleName): ?array
{
$sModuleFile = utils::GetAbsoluteModulePath($sModuleName).'/module.'.$sModuleName.'.php';
if (!file_exists($sModuleFile)) {
echo 'Wrong module, page name or environment...';
exit;
}
require_once APPROOT.'setup/extensionsmap.class.inc.php';
$oExtensionMap = new iTopExtensionsMap();
$aModuleParam = $oExtensionMap->GetModuleInfo($sModuleFile)[2];
return $aModuleParam['delegated_authentication_endpoints'] ?? null;
}

View File

@@ -390,7 +390,7 @@ class iTopExtensionsMap
* @param string $sModuleFile
* @return array
*/
public function GetModuleInfo($sModuleFile)
protected function GetModuleInfo($sModuleFile)
{
static $iDummyClassIndex = 0;

View File

@@ -42,7 +42,6 @@ use RunTimeEnvironment;
use ScalarExpression;
use SetupUtils;
use UILinksWidget;
use UserRights;
use utils;
use WizardHelper;
@@ -72,12 +71,6 @@ class AjaxRenderController
$bShowObsoleteData = utils::ShowObsoleteData();
}
$oSet->SetShowObsoleteData($bShowObsoleteData);
// N°8606 : Check user permissions on the main class
if (UserRights::IsActionAllowed($oSet->GetClass(), UR_ACTION_READ, $oSet) !== UR_ALLOWED_YES) {
throw new Exception(Dict::Format('UI:Error:ReadNotAllowedOn_Class', $oSet->GetClass()));
}
$aResult["draw"] = $iDrawNumber;
$aResult["recordsTotal"] = $oSet->Count();
$aResult["recordsFiltered"] = $aResult["recordsTotal"] ;
@@ -102,11 +95,6 @@ class AjaxRenderController
continue;
}
// N°8606 : Check user permissions on the current class
if (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) !== UR_ALLOWED_YES) {
throw new Exception(Dict::Format('UI:Error:ReadNotAllowedOn_Class', $sClass));
}
foreach ($aColumnsLoad[$sAlias] as $sAttCode) {
$aObj[$sAlias."/".$sAttCode] = $aObject[$sAlias]->GetAsHTML($sAttCode);
$bExcludeRawValue = false;

View File

@@ -1,179 +0,0 @@
<?php
declare(strict_types=1);
namespace Combodo\iTop\Test\UnitTest\Application;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use Config;
use Exception;
use MetaModel;
class LoginWebPageTest extends ItopDataTestCase
{
public const USE_TRANSACTION = false;
private Config $oConfig;
public const PASSWORD = 'a209320P!ù;ralùqpi,pàcqi"nr';
public function setUp(): void
{
parent::setUp();
$sConfigPath = MetaModel::GetConfig()->GetLoadedFile();
$this->oConfig = new Config($sConfigPath);
$this->BackupConfiguration();
$sFolderPath = APPROOT.'env-production/extension-with-delegated-authentication-endpoints-list';
if (file_exists($sFolderPath)) {
throw new Exception("Folder $sFolderPath already exists, please remove it before running the test");
}
mkdir($sFolderPath);
$this->RecurseCopy(__DIR__.'/extension-with-delegated-authentication-endpoints-list', $sFolderPath);
$sFolderPath = APPROOT.'env-production/extension-without-delegated-authentication-endpoints-list';
if (file_exists($sFolderPath)) {
throw new Exception("Folder $sFolderPath already exists, please remove it before running the test");
}
mkdir($sFolderPath);
$this->RecurseCopy(__DIR__.'/extension-without-delegated-authentication-endpoints-list', $sFolderPath);
}
public function tearDown(): void
{
parent::tearDown();
$sFolderPath = APPROOT.'env-production/extension-with-delegated-authentication-endpoints-list';
if (file_exists($sFolderPath)) {
$this->RecurseRmdir($sFolderPath);
} else {
throw new Exception("Folder $sFolderPath does not exist, it should have been created in setUp");
}
$sFolderPath = APPROOT.'env-production/extension-without-delegated-authentication-endpoints-list';
if (file_exists($sFolderPath)) {
$this->RecurseRmdir($sFolderPath);
} else {
throw new Exception("Folder $sFolderPath does not exist, it should have been created in setUp");
}
}
protected function GivenConfigFileAllowedLoginTypes($aAllowedLoginTypes): void
{
@chmod($this->oConfig->GetLoadedFile(), 0770);
$this->oConfig->SetAllowedLoginTypes($aAllowedLoginTypes);
$this->oConfig->WriteToFile($this->oConfig->GetLoadedFile());
@chmod($this->oConfig->GetLoadedFile(), 0444);
}
/**
*
* @throws \Exception
*/
public function testInDelegatedAuthenticationEndpoints()
{
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-with-delegated-authentication-endpoints-list&exec_page=src/Controller/FileInDelegatedAuthenticationEndpointsList.php",
[],
[],
true
);
$this->assertStringNotContainsString('<title>iTop login</title>', $sPageContent, 'File listed in delegated authentication endpoints list (in the module), login should not be requested by exec.');
}
public function testUserCanAccessAnyFile()
{
// generate random login
$sUserLogin = 'user-'.date('YmdHis');
$this->CreateUser($sUserLogin, self::$aURP_Profiles['Service Desk Agent'], self::PASSWORD);
$this->GivenConfigFileAllowedLoginTypes(explode('|', 'form'));
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-with-delegated-authentication-endpoints-list&exec_page=src/Controller/FileNotInDelegatedAuthenticationEndpointsList.php",
[
'auth_user' => $sUserLogin,
'auth_pwd' => self::PASSWORD,
],
[],
true
);
$this->assertStringContainsString('Yo', $sPageContent, 'Logged in user should access any file via exec.php even if the page isn\'t listed in delegated authentication endpoints list');
}
public function testWithoutDelegatedAuthenticationEndpointsListWithForceLoginConf()
{
@chmod($this->oConfig->GetLoadedFile(), 0770);
$this->oConfig->Set('security.force_login_when_no_delegated_authentication_endpoints_list', true, 'AnythingButEmptyOrUnknownValue'); // 3rd param to write file even if show_in_conf_sample is false
$this->oConfig->WriteToFile();
@chmod($this->oConfig->GetLoadedFile(), 0444);
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-without-delegated-authentication-endpoints-list&exec_page=src/Controller/File.php",
);
$this->assertStringContainsString('<title>iTop login</title>', $sPageContent, 'if itop is configured to force login when no there is no delegated authentication endpoints list, then login should be required.');
}
public function testWithoutDelegatedAuthenticationEndpointsListWithDefaultConfiguration()
{
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-without-delegated-authentication-endpoints-list&exec_page=src/Controller/File.php",
[],
[],
true
);
$this->assertStringContainsString('Yo', $sPageContent, 'by default (until N°9343) if no delegated authentication endpoints list is defined, not logged in persons should access pages');
}
public function testNotInDelegatedAuthenticationEndpointsList()
{
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-with-delegated-authentication-endpoints-list&exec_page=src/Controller/FileNotInDelegatedAuthenticationEndpointsList.php",
[],
[],
true
);
$this->assertStringContainsString('<title>iTop login</title>', $sPageContent, 'Since an delegated authentication endpoints list is defined and file isn\'t listed in it, login should be required');
}
/**
* @dataProvider InDelegatedAuthenticationEndpointsWithAdminRequiredProvider
*
* @throws \Exception
*/
public function testInDelegatedAuthenticationEndpointsWithAdminRequired($iProfileId, $bShouldSeeForbiddenAdminPage)
{
// generate random login
$sUserLogin = 'user-'.date('YmdHis');
$this->CreateUser($sUserLogin, $iProfileId, self::PASSWORD);
$this->GivenConfigFileAllowedLoginTypes(explode('|', 'form'));
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-with-delegated-authentication-endpoints-list&exec_page=src/Controller/FileInDelegatedAuthenticationEndpointsListAndAdminRequired.php",
[
'auth_user' => $sUserLogin,
'auth_pwd' => self::PASSWORD,
],
[],
true
);
$bShouldSeeForbiddenAdminPage ?
$this->assertStringContainsString('Access restricted to people having administrator privileges', $sPageContent, 'Should prevent non admin user to access this page') : // in delegated authentication endpoints list (in the module), login should not be required
$this->assertStringContainsString('Yo !', $sPageContent, 'Should execute the file and see its content since user has admin profile');
}
public function InDelegatedAuthenticationEndpointsWithAdminRequiredProvider()
{
return [
'Administrator profile' => [
self::$aURP_Profiles['Administrator'],
'Should see forbidden admin page' => false,
],
'ReadOnly profile' => [
self::$aURP_Profiles['Service Desk Agent'],
'Should see forbidden admin page' => true,
],
];
}
}

View File

@@ -1,51 +0,0 @@
<?php
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'extension-with-delegated-authentication-endpoints-list/0.0.1',
[
// Identification
//
'label' => 'Templates foundation',
'category' => 'business',
// Setup
//
'dependencies' => [],
'mandatory' => true,
'visible' => false,
'installer' => 'TemplatesBaseInstaller',
// Security
'delegated_authentication_endpoints' => [
'src/Controller/FileInDelegatedAuthenticationEndpointsList.php',
'src/Controller/FileInDelegatedAuthenticationEndpointsListAndAdminRequired.php',
],
// Components
//
'datamodel' => [
'model.templates-base.php',
],
'webservice' => [],
'data.struct' => [// add your 'structure' definition XML files here,
],
'data.sample' => [// add your sample data XML files here,
],
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => [
// Select where, in the main UI, the extra data should be displayed:
// tab (dedicated tab)
// properties (right after the properties, but before the log if any)
// none (extra data accessed only by programs)
'view_extra_data' => 'relations',
],
]
);

View File

@@ -1,10 +0,0 @@
<?php
if (UserRights::IsLoggedIn()) {
throw new Exception("User should not be authenticated at this point");
}
require_once(APPROOT.'/application/startup.inc.php');
LoginWebPage::DoLogin(true);
echo 'Yo !';

View File

@@ -1,45 +0,0 @@
<?php
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'extension-without-delegated-authentication-endpoints-list/0.0.1',
[
// Identification
//
'label' => 'Templates foundation',
'category' => 'business',
// Setup
//
'dependencies' => [],
'mandatory' => true,
'visible' => false,
'installer' => 'TemplatesBaseInstaller',
// Components
//
'datamodel' => [
'model.templates-base.php',
],
'webservice' => [],
'data.struct' => [// add your 'structure' definition XML files here,
],
'data.sample' => [// add your sample data XML files here,
],
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => [
// Select where, in the main UI, the extra data should be displayed:
// tab (dedicated tab)
// properties (right after the properties, but before the log if any)
// none (extra data accessed only by programs)
'view_extra_data' => 'relations',
],
]
);

View File

@@ -834,11 +834,6 @@ HTML,
'good element_identifier' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, 'AD05nb', 'AD05nb'],
'bad element_identifier' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, 'AD05nb+', 'AD05nb'],
'array' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, ['AD05nb+','apply_modify'], ['AD05nb','apply_modify']],
'good module code' => [utils::ENUM_SANITIZATION_FILTER_MODULE_CODE, 'some-module-code', 'some-module-code'],
'good module code with capitalized letters' => [utils::ENUM_SANITIZATION_FILTER_MODULE_CODE, 'SOME-module-code', 'SOME-module-code'],
'good module code with dot' => [utils::ENUM_SANITIZATION_FILTER_MODULE_CODE, 'some-module-code-for-3.2-version', 'some-module-code-for-3.2-version'],
'bad module code with underscores' => [utils::ENUM_SANITIZATION_FILTER_MODULE_CODE, 'some_module_code', null],
'bad module code with slashes' => [utils::ENUM_SANITIZATION_FILTER_MODULE_CODE, 'some-module/code', null],
'good url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://www.w3schools.com', 'https://www.w3schools.com'],
'bad url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https//www.w3schools.com', null],
'url with injection' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://demo.combodo.com/simple/pages/UI.php?operation=full_text&text=<img zzz src=x onerror=alert(1) //>', 'https://demo.combodo.com/simple/pages/UI.php?operation=full_text&text=<imgzzzsrc=xonerror=alert(1)//>'],

View File

@@ -1,47 +0,0 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Core;
use AttributeDate;
use AttributeDateTime;
use Change;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use DateTime;
use MetaModel;
use UserRequest;
class AttributeSubItemTest extends ItopDataTestCase
{
public const CREATE_TEST_ORG = true;
/**
* @param string $sAttCode
* @param string $sVerb
* @param string $sExpectedValue
*
* @return void
*/
public function testGetForTemplate()
{
$aUserRequestCustomParams = [
'title' => "Test DisplayStopwatch",
];
$oUserRequest = $this->CreateUserRequest(456, $aUserRequestCustomParams);
$iStartDate = time() - 200;
$oStopwatch = $oUserRequest->Get('ttr');
$oStopwatch->DefineThreshold(100, $iStartDate);
$oUserRequest->Set('ttr', $oStopwatch);
$sValue = $oUserRequest->Get('ttr_escalation_deadline');
$oAttDef = MetaModel::GetAttributeDef(get_class($oUserRequest), 'ttr_escalation_deadline');
self::assertEquals('Missed by 3 min', $oAttDef->GetForTemplate($sValue, 'html', $oUserRequest));
$oDateTime = new DateTime();
$oDateTime->setTimestamp($iStartDate);
$sDate = $oDateTime->format(AttributeDateTime::GetFormat());
self::assertEquals($sDate, $oAttDef->GetForTemplate($sValue, 'label', $oUserRequest), 'label() should render the date in the format specified in the configuration file, in parameter "date_and_time_format"');
self::assertEquals('Missed by 3 min', $oAttDef->GetForTemplate($sValue, 'text', $oUserRequest), 'text() should render the deadline as specified in the configuration file, in parameter "deadline_format", and depending on the user language');
self::assertEquals($iStartDate, $oAttDef->GetForTemplate($sValue, '', $oUserRequest));
}
}

View File

@@ -9,7 +9,6 @@ namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use InlineImage;
use ormDocument;
class InlineImageTest extends ItopDataTestCase
{
@@ -99,36 +98,4 @@ HTML;
$this->assertStringContainsString(\utils::EscapeHtml(\utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.'123&s=abc'), $sResult);
$this->assertStringContainsString(\utils::EscapeHtml(\utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.'456&s=def'), $sResult);
}
/**
* @covers InlineImage::ReplaceInlineImagesWithBase64Representation
*/
public function testReplaceInlineImagesWithBase64Representation()
{
// create an inline image in the database
$oInlineImage = $this->createObject(InlineImage::class, [
'expire' => (new \DateTime('+1 day'))->format('Y-m-d H:i:s'),
'item_class' => 'UserRequest',
'item_id' => 999,
'item_org_id' => 1,
'contents' => new ormDocument('0x89504E470D0A1A0A0000000D494844520000000E0000000E08060000001F482DD1000000017352474200AECE1CE90000000467414D410000B18F0BFC6105000000097048597300000EC300000EC301C76FA8640000001E49444154384F63782BA3F29F1CCC802E402C1ED588078F6AC483E9AF11008B8BA9C08A7A3F290000000049454E44AE426082', 'image/png', 'square_red.png'),
'secret' => 'a94bff3ea6a872bdbc359a1704cdddb3',
]);
$sInlineImageId = $oInlineImage->GetKey();
$sInlineImageSecret = $oInlineImage->Get('secret');
// HTML with inline image
$sHtml = <<<HTML
<img src="http://host/iTop/pages/ajax.document.php?operation=download_inlineimage&amp;id=$sInlineImageId&amp;s=$sInlineImageSecret" data-img-id="$sInlineImageId" data-img-secret="$sInlineImageSecret" />
HTML;
// expected HTML with base64 representation of the image
$sExpected = <<<HTML
<img src="data:image/png;base64,MHg4OTUwNEU0NzBEMEExQTBBMDAwMDAwMEQ0OTQ4NDQ1MjAwMDAwMDBFMDAwMDAwMEUwODA2MDAwMDAwMUY0ODJERDEwMDAwMDAwMTczNTI0NzQyMDBBRUNFMUNFOTAwMDAwMDA0Njc0MTRENDEwMDAwQjE4RjBCRkM2MTA1MDAwMDAwMDk3MDQ4NTk3MzAwMDAwRUMzMDAwMDBFQzMwMUM3NkZBODY0MDAwMDAwMUU0OTQ0NDE1NDM4NEY2Mzc4MkJBM0YyOUYxQ0NDODAyRTQwMkMxRUQ1ODgwNzhGNkFDNDgzRTlBRjExMDA4QjhCQTlDMDhBN0EzRjI5MDAwMDAwMDA0OTQ1NEU0NEFFNDI2MDgy" />
HTML;
// test the method
$sResult = InlineImage::ReplaceInlineImagesWithBase64Representation($sHtml);
$this->assertEquals($sExpected, $sResult);
}
}