Compare commits

...

6 Commits

Author SHA1 Message Date
Stephen Abello
a2a00cb582 N°10075 - Update configuration parameters default value (#1055) 2026-09-29 10:02:57 +02:00
jf-cbd
4d699f79e8 Add unit test for N°9740 2026-09-28 11:30:54 +02:00
Stephen Abello
873cea1a76 N°9557 - Small improvement to shortcut preferences (#1049)
* N°9557 - Small improvement to shortcut preferences

* Apply code review suggestions
2026-09-25 10:28:05 +02:00
jf-cbd
adc2596b4a Issue/9740 fix attachment removal (#1052)
(cherry picked from commit af972313d7)
2026-09-18 15:40:52 +02:00
Molkobain
2ac4229ee0 N°10070 - Loss of DroidSansFallback font which impacts exports in east asian character sets (#1048)
* N°10070 - Loss of DroidSansFallback font which impacts exports in east asian character sets

* N°10070 - Add unit test
2026-09-15 10:03:52 +02:00
Stephen Abello
4a6af15237 N°10041 - Fullscreen CKeditor in a modal can't take the focus (#1043) 2026-09-14 09:52:21 +02:00
16 changed files with 3154 additions and 26 deletions

View File

@@ -61,7 +61,7 @@ foreach ($aTcpdfFontsDirContent as $sTcpdfFontResourceName) {
* 2) Then adding the DroidSansFallback font (useful for CJK data for example)
*/
echo $sCurrentScriptFileName.": ---2) Copying font files to TCPDF ($sTcPdfFontsFolder)...\n";
$aFontFilesToCopy = glob(__DIR__.'\droidsansfallback.*');
$aFontFilesToCopy = glob(__DIR__.DIRECTORY_SEPARATOR.'droidsansfallback.*');
foreach ($aFontFilesToCopy as $sFontFileToCopy) {
$sFontFileName = basename($sFontFileToCopy);
echo $sCurrentScriptFileName.': copying '.$sFontFileName."\n";

View File

@@ -109,10 +109,18 @@ $('#shortcut_rename_dlg').dialog({
modal: true,
title: '$sDialogTitle',
buttons: [
{ text: "$sOkButtonLabel", click: ShortcutRenameOK},
{ text: "$sCancelButtonLabel", click: function() {
$(this).dialog( "close" ); $(this).remove();
} },
{
text: "$sCancelButtonLabel",
click: function() {
$(this).dialog( "close" ); $(this).remove();
},
'class': 'ibo-button ibo-is-alternative ibo-is-neutral action cancel'
},
{
text: "$sOkButtonLabel",
click: ShortcutRenameOK,
'class': 'ibo-is-regular ibo-is-primary'
},
],
close: function() { $(this).remove(); }
});

View File

@@ -1750,8 +1750,8 @@ class Config
'security.disable_joined_classes_filter' => [
'type' => 'bool',
'description' => 'If true, scope filters aren\'t applied to joined classes or union classes not directly listed in the SELECT clause.',
'default' => true,
'value' => true,
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
@@ -1766,8 +1766,8 @@ class Config
'security.disable_exec_forced_login_for_all_enpoints' => [
'type' => 'bool',
'description' => 'If true, when no delegated authentication module is defined, no login will be forced on modules exec endpoints',
'default' => true,
'value' => true,
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],

View File

@@ -1938,7 +1938,7 @@ class DBObjectSearch extends DBSearch
$oSearch = $this;
$aClassesToFilter = $this->GetSelectedClasses();
// Opt-in for joined classes filtering, otherwise only filter the selected class(es)
// Joined classes filtering can be disabled through the configuration.
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === false) {
$aClassesToFilter = $this->GetJoinedClasses();
}

View File

@@ -683,7 +683,7 @@ class DBUnionSearch extends DBSearch
return $this;
}
// Opt-in for joined classes filtering, otherwise fallback on DBSearch filtering
// Joined classes filtering can be disabled through the configuration.
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === true) {
return parent::ApplyDataFilters();
}

View File

@@ -56,7 +56,7 @@ function RenderAttachments(AjaxPage $oPage, $iTransactionId)
try {
require_once APPROOT.'/application/startup.inc.php';
require_once APPROOT.'/application/loginwebpage.class.inc.php';
LoginWebPage::DoLoginEx(null /* any portal */, false);
LoginWebPage::DoLogin(); // No user portal should access this endpoint.
$oPage = new AjaxPage("");
@@ -105,15 +105,6 @@ try {
$oPage->SetData($aResult);
break;
case 'remove':
$iAttachmentId = utils::ReadParam('att_id', '');
$oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE id = :id");
$oSet = new DBObjectSet($oSearch, [], ['id' => $iAttachmentId]);
while ($oAttachment = $oSet->Fetch()) {
$oAttachment->DBDelete();
}
break;
case 'refresh_attachments_render':
$sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id');
RenderAttachments($oPage, $sTempId);

View File

@@ -0,0 +1,19 @@
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
// Overload of the default dialog widget
$.widget('ui.dialog', $.ui.dialog, {
_allowInteraction: function (oEvent) {
const oTarget = $(oEvent.target);
// If we interact with a CKEditor instance in fullscreen mode, we need to allow it
// We could check if the current instance is in the dialog, but it's easier to always allow it in fullscreen
if (oTarget.closest('.ck.ck-fullscreen__main-wrapper, .ck-body-wrapper').length > 0) {
return true;
}
// If that's not a specific case, fall back to the default behavior
return this._super(oEvent);
}
});

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1239,13 +1239,32 @@ JS
$oSearch = new DBObjectSearch('Shortcut');
$aShortcuts = utils::ReadMultipleSelection($oSearch);
$iShortcut = $aShortcuts[0];
$oShortcut = MetaModel::GetObject('Shortcut', $iShortcut);
$oShortcutSearch = new DBObjectSearch('Shortcut');
$oShortcutSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
$oShortcutSearch->AddCondition('id', $iShortcut, '=');
$oShortcutSet = new CMDBObjectSet($oShortcutSearch);
$oShortcut = $oShortcutSet->Fetch();
if ($oShortcut === null) {
throw new SecurityException(Dict::S('UI:ObjectDoesNotExist'));
}
$oShortcut->StartRenameDialog($oPage);
break;
case 'shortcut_rename_go':
$iShortcut = utils::ReadParam('id', 0);
$oShortcut = MetaModel::GetObject('Shortcut', $iShortcut);
$oShortcutSearch = new DBObjectSearch('Shortcut');
$oShortcutSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
$oShortcutSearch->AddCondition('id', $iShortcut, '=');
$oShortcutSet = new CMDBObjectSet($oShortcutSearch);
$oShortcut = $oShortcutSet->Fetch();
if ($oShortcut === null) {
throw new SecurityException(Dict::S('UI:ObjectDoesNotExist'));
}
$sName = utils::ReadParam('attr_name', '', false, 'raw_data');
if (strlen($sName) > 0) {

View File

@@ -243,7 +243,7 @@ JS
);
$oShortcutsToolBar->AddSubBlock($oShortcutsRenameButton);
// - Delete button
$oShortcutsDeleteButton = ButtonUIBlockFactory::MakeForSecondaryAction(
$oShortcutsDeleteButton = ButtonUIBlockFactory::MakeForDestructiveAction(
Dict::S('UI:Button:Delete'),
null,
null,

View File

@@ -209,6 +209,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->LinkScriptFromAppRoot('js/pages/backoffice/keyboard-shortcuts.js');
// Used throughout the app.
$this->LinkScriptFromAppRoot('js/pages/backoffice/dialog.js');
$this->LinkScriptFromAppRoot('js/pages/backoffice/toolbox.js');
$this->LinkScriptFromAppRoot('js/pages/backoffice/on-ready.js');

View File

@@ -111,7 +111,7 @@ class LoginWebPageTest extends ItopDataTestCase
$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()
public function testWithoutDelegatedAuthenticationEndpointsListRequiresLoginByDefault()
{
$sPageContent = $this->CallItopUri(
"pages/exec.php?exec_module=extension-without-delegated-authentication-endpoints-list&exec_page=src/Controller/File.php",
@@ -120,7 +120,24 @@ class LoginWebPageTest extends ItopDataTestCase
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');
$this->assertStringContainsString('<title>iTop login</title>', $sPageContent, 'by default, login should be required when no delegated authentication endpoints list is defined');
}
public function testWithoutDelegatedAuthenticationEndpointsListWithCompatibilityOptOut()
{
@chmod($this->oConfig->GetLoadedFile(), 0770);
$this->oConfig->Set('security.disable_exec_forced_login_for_all_enpoints', true, 'AnythingButEmptyOrUnknownValue');
$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",
[],
[],
true
);
$this->assertStringContainsString('Yo !', $sPageContent, 'the compatibility opt-out should allow anonymous access when no delegated authentication endpoints list is defined');
}
public function testNotInDelegatedAuthenticationEndpointsList()

View File

@@ -0,0 +1,38 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\DotMake\Dependencies\Composer\Tcpdf;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
/**
* @coversNothing
*/
class TcpdfUpdateFontsTest extends ItopTestCase
{
public function testThatDroidSansFallbackFilesAreCopiedToTcpdfFontsFolderAfterLibraryUpdate(): void
{
$sSourcePattern = APPROOT
.'.make'.DIRECTORY_SEPARATOR.'dependencies'.DIRECTORY_SEPARATOR.'composer'.DIRECTORY_SEPARATOR.'tcpdf'.DIRECTORY_SEPARATOR.'droidsansfallback.*';
$aSourceFiles = glob($sSourcePattern);
$this->assertIsArray($aSourceFiles, 'Unable to read source TCPDF custom font files.');
$this->assertNotEmpty($aSourceFiles, 'No source files found for pattern droidsansfallback.*');
foreach ($aSourceFiles as $sSourceFilePath) {
$sFontFileName = basename($sSourceFilePath);
$sDestinationFilePath = APPROOT
.'lib'.DIRECTORY_SEPARATOR.'tecnickcom'.DIRECTORY_SEPARATOR.'tcpdf'.DIRECTORY_SEPARATOR.'fonts'.DIRECTORY_SEPARATOR.$sFontFileName;
$this->assertFileExists($sDestinationFilePath, "Missing copied font file: {$sFontFileName}");
$this->assertSame(
hash_file('sha256', $sSourceFilePath),
hash_file('sha256', $sDestinationFilePath),
"Copied font file content mismatch: {$sFontFileName}"
);
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Combodo\iTop\Test\UnitTest\Module\ItopAttachment;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
class AttachmentAjaxEndpointTest extends ItopDataTestCase
{
public const USE_TRANSACTION = false;
private const AUTHENTICATION_PASSWORD = 'tagada-Secret,007';
protected function setUp(): void
{
parent::setUp();
$this->BackupConfiguration();
$this->AddLoginModeAndSaveConfiguration('url');
}
/**
* @dataProvider AjaxEndpointAccessProvider
*/
public function testAjaxEndpointAccess(string $sProfile, int $iExpectedHttpCode): void
{
$sLogin = 'user-'.uniqid();
$this->CreateUser($sLogin, self::$aURP_Profiles[$sProfile], self::AUTHENTICATION_PASSWORD);
$iHttpCode = $this->CallAttachmentEndpointAs($sLogin);
$this->assertSame($iExpectedHttpCode, $iHttpCode);
}
public function AjaxEndpointAccessProvider(): array
{
return [
'console user' => ['Service Desk Agent', 200],
'portal user' => ['Portal user', 302], // redirect to portal
];
}
private function CallAttachmentEndpointAs(string $sLogin): int
{
$this->CallItopUri(
'env-production/itop-attachments/ajax.itop-attachment.php?operation=add&'.http_build_query([
'auth_user' => $sLogin,
'auth_pwd' => self::AUTHENTICATION_PASSWORD,
]),
[],
[
CURLOPT_HTTPHEADER => ['X-Combodo-Ajax:1'],
CURLOPT_POST => 0,
],
true
);
return $this->aLastCurlGetInfo['http_code'];
}
}