Merge branch 'develop' into odain

This commit is contained in:
odain
2026-04-08 20:24:22 +02:00
302 changed files with 6640 additions and 3255 deletions

View File

@@ -0,0 +1,88 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Application\Helper;
use Combodo\iTop\Application\WebPage\iTopWebPage;
use Combodo\iTop\Application\WebPage\WebPage;
use DBSearch;
use DisplayBlock;
use MetaModel;
use utils;
class SearchHelper
{
/**
* Displays the result of a search request
* @param $oP WebPage Web page for the output
* @param $oFilter DBSearch The search of objects to display
* @param $bSearchForm boolean Whether or not to display the search form at the top the page
* @param $sBaseClass string The base class for the search (can be different from the actual class of the results)
* @param $sFormat string The format to use for the output: csv or html
* @param $bDoSearch bool True to display the search results below the search form
* @param $bSearchFormOpen bool True to display the search form fully expanded (only if $bSearchForm of course)
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public static function DisplaySearchSet($oP, $oFilter, $bSearchForm = true, $sBaseClass = '', $sFormat = '', $bDoSearch = true, $bSearchFormOpen = true, $aParams = []): void
{
//search block
$oBlockForm = null;
if ($bSearchForm) {
$aParams['open'] = $bSearchFormOpen;
if (false === isset($aParams['table_id'])) {
$aParams['table_id'] = 'result_1';
}
if (!empty($sBaseClass)) {
$aParams['baseClass'] = $sBaseClass;
}
$oBlockForm = new DisplayBlock($oFilter, 'search', false /* Asynchronous */, $aParams);
if (!$bDoSearch) {
$oBlockForm->Display($oP, 0);
}
}
if ($bDoSearch) {
if (strtolower($sFormat) == 'csv') {
$oBlock = new DisplayBlock($oFilter, 'csv', false);
// Adjust the size of the Textarea containing the CSV to fit almost all the remaining space
$oP->add_ready_script(" $('#1>textarea').height($('#1').parent().height() - $('#0').outerHeight() - 30).width( $('#1').parent().width() - 20);"); // adjust the size of the block
} else {
$oBlock = new DisplayBlock($oFilter, 'list', false);
// Breadcrumb
//$iCount = $oBlock->GetDisplayedCount();
$sPageId = "ui-search-".$oFilter->GetClass();
$sLabel = MetaModel::GetName($oFilter->GetClass());
$oP->SetBreadCrumbEntry($sPageId, $sLabel, '', '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
}
if ($bSearchForm) {
//add search block
$sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
if ($sTableId == '') {
$sTableId = 'result_1';
}
$aExtraParams['table_id'] = $sTableId;
$aExtraParams['submit_on_load'] = false;
$oUIBlockForm = $oBlockForm->GetDisplay($oP, 'search_1', $aExtraParams);
// If the class is not high cardinality, we can display the results directly in the same page
if (!utils::IsHighCardinality($oFilter->GetClass())) {
//add result block
$oUIBlock = $oBlock->GetDisplay($oP, $sTableId);
$oUIBlock->AddCSSClasses(['display_block', 'sf_results_area']);
$oUIBlock->AddDataAttribute('target', 'search_results');
$oUIBlockForm->AddSubBlock($oUIBlock);
}
$oP->AddUiBlock($oUIBlockForm);
} else {
$oBlock->Display($oP, 1);
}
}
}
}

View File

@@ -738,7 +738,7 @@ abstract class Controller extends AbstractController
*
* @param string $sCode Code of the tab
*/
public function AddAjaxTab(string $sCode, string $sURL, bool $bCache = true, string $sLabel = null): void
public function AddAjaxTab(string $sCode, string $sURL, bool $bCache = true, ?string $sLabel = null): void
{
if (is_null($sLabel)) {
$sLabel = Dict::S($sCode);

View File

@@ -340,8 +340,10 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
$aClassAliases = $oSet->GetFilter()->GetSelectedClasses();
$aAuthorizedClasses = [];
foreach ($aClassAliases as $sAlias => $sClassName) {
if ((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) &&
((count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases)))) {
if (
((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) !== UR_ALLOWED_NO) || ($aExtraParams['display_unauthorized_objects'] ?? false) === true)
&& ((count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases)))
) {
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}
@@ -520,6 +522,14 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
if ($aData['checked']) {
if ($sAttCode == '_key_') {
if ($bViewLink) {
$sRenderLink = "return row['".$sClassAlias."/hyperlink'];";
if (
($aExtraParams['display_unauthorized_objects'] ?? false) === true
&& UserRights::IsActionAllowed($sClassName, UR_ACTION_READ) !== UR_ALLOWED_YES
) {
$sRenderLink = "return row['".$sClassAlias."/friendlyname'];";
}
$aColumnDefinition[] = [
'description' => $aData['label'],
'object_class' => $sClassName,
@@ -527,7 +537,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
'attribute_code' => $sAttCode,
'attribute_type' => '_key_',
'attribute_label' => MetaModel::GetName($sClassName),
'render' => "return row['".$sClassAlias."/hyperlink'];",
'render' => $sRenderLink,
];
}
@@ -952,6 +962,8 @@ JS;
/** Handler to call when trying to create a new object in modal */
'creation_disallowed',
/** Don't provide the standard object creation feature */
'display_unauthorized_objects',
/** bool Display objects for which the user has no read rights */
];
}
}

View File

@@ -22,7 +22,7 @@ class TurboForm extends UIContentBlock
protected ?string $sAction;
private FormView $oFormView;
public function __construct(FormView $oFormView, string $sId = null)
public function __construct(FormView $oFormView, ?string $sId = null)
{
parent::__construct($sId);
$this->oFormView = $oFormView;

View File

@@ -36,7 +36,7 @@ class TurboFormUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\TurboForm\TurboForm An HTML form in which you can add UIBlocks
*/
public static function MakeStandard(FormView $oFormView, string $sAction = null, string $sId = null): TurboForm
public static function MakeStandard(FormView $oFormView, ?string $sAction = null, ?string $sId = null): TurboForm
{
$oTurboForm = new TurboForm($oFormView, $sId);
if (!is_null($sAction)) {
@@ -57,7 +57,7 @@ class TurboFormUIBlockFactory extends AbstractUIBlockFactory
* @return \Combodo\iTop\Application\UI\Base\Component\TurboForm\TurboForm
* @throws \Combodo\iTop\Forms\Block\FormBlockException
*/
public static function MakeForDashletConfiguration(string $sDashletId, array $aData = [], string $sId = null): TurboForm
public static function MakeForDashletConfiguration(string $sDashletId, array $aData = [], ?string $sId = null): TurboForm
{
$oBlockForm = FormBlockService::GetInstance()->GetFormBlockById($sDashletId, 'Dashlet');
$oController = new FormsController();

View File

@@ -16,7 +16,7 @@ class TurboStream extends UIContentBlock
private string $sTarget;
private string $sAction;
public function __construct(string $sTarget, string $sAction, string $sId = null)
public function __construct(string $sTarget, string $sAction, ?string $sId = null)
{
parent::__construct($sId);
$this->sTarget = $sTarget;

View File

@@ -31,7 +31,7 @@ class TurboStreamUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\TurboUpdate\TurboStream An HTML form in which you can add UIBlocks
*/
public static function MakeUpdate(string $sTarget, string $sId = null): TurboStream
public static function MakeUpdate(string $sTarget, ?string $sId = null): TurboStream
{
return new TurboStream($sTarget, 'update', $sId);
}
@@ -44,7 +44,7 @@ class TurboStreamUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\TurboUpdate\TurboStream An HTML form in which you can add UIBlocks
*/
public static function MakeReplace(string $sTarget, string $sId = null): TurboStream
public static function MakeReplace(string $sTarget, ?string $sId = null): TurboStream
{
return new TurboStream($sTarget, 'replace', $sId);
}
@@ -57,7 +57,7 @@ class TurboStreamUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\TurboUpdate\TurboStream An HTML form in which you can add UIBlocks
*/
public static function MakePrepend(string $sTarget, string $sId = null): TurboStream
public static function MakePrepend(string $sTarget, ?string $sId = null): TurboStream
{
return new TurboStream($sTarget, 'prepend', $sId);
}
@@ -70,7 +70,7 @@ class TurboStreamUIBlockFactory extends AbstractUIBlockFactory
*
* @return \Combodo\iTop\Application\UI\Base\Component\TurboUpdate\TurboStream An HTML form in which you can add UIBlocks
*/
public static function MakeAppend(string $sTarget, string $sId = null): TurboStream
public static function MakeAppend(string $sTarget, ?string $sId = null): TurboStream
{
return new TurboStream($sTarget, 'append', $sId);
}

View File

@@ -152,6 +152,7 @@ class ActivityPanelFactory
if (false === empty($aRelatedTriggersIDs)) {
// - Prepare query to retrieve events
$oNotifEventsSearch = DBObjectSearch::FromOQL('SELECT EN FROM EventNotification AS EN JOIN Action AS A ON EN.action_id = A.id WHERE EN.trigger_id IN (:triggers_ids) AND EN.object_id = :object_id');
$oNotifEventsSearch->AllowAllData();
$oNotifEventsSet = new DBObjectSet($oNotifEventsSearch, ['id' => false], ['triggers_ids' => $aRelatedTriggersIDs, 'object_id' => $sObjId]);
$oNotifEventsSet->SetLimit(MetaModel::GetConfig()->Get('max_history_length'));

View File

@@ -218,6 +218,7 @@ class AjaxPage extends WebPage implements iTabbedPage
'aJsInlineLive' => $this->a_scripts,
'aJsInlineOnDomReady' => $this->GetReadyScripts(),
'aJsInlineOnInit' => $this->a_init_scripts,
'sBodyDataGuiType' => static::BODY_DATA_GUI_TYPE,
'bEscapeContent' => ($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'),
// TODO 3.0.0: TEMP, used while developping, remove it.
'sSanitizedContent' => utils::FilterXSS($this->s_content),

View File

@@ -51,7 +51,7 @@ class ErrorPage extends NiceWebPage
$this->log_warning($sText);
}
public function error($sText, \Throwable $oException = null)
public function error($sText, ?\Throwable $oException = null)
{
$this->add("<div class=\"message message-error\">$sText</div>");
if (utils::IsEasterEggAllowed()) {

View File

@@ -172,6 +172,7 @@ class UnauthenticatedWebPage extends NiceWebPage
'aJsInlineLive' => $this->a_scripts,
'aJsInlineOnDomReady' => $this->GetReadyScripts(),
'aJsInlineOnInit' => $this->a_init_scripts,
'sBodyDataGuiType' => static::BODY_DATA_GUI_TYPE,
// TODO 3.0.0: TEMP, used while developing, remove it.
'sCapturedOutput' => utils::FilterXSS($s_captured_output),

View File

@@ -152,6 +152,8 @@ class WebPage implements Page
*/
public const DEFAULT_PAGE_TEMPLATE_REL_PATH = 'pages/backoffice/webpage/layout';
public const BODY_DATA_GUI_TYPE = 'backoffice';
protected $s_title;
protected $s_content;
protected $s_deferred_content;
@@ -1566,6 +1568,7 @@ JS;
'aJsInlineLive' => $this->a_scripts,
'aJsInlineOnDomReady' => $this->GetReadyScripts(),
'aJsInlineOnInit' => $this->a_init_scripts,
'sBodyDataGuiType' => static::BODY_DATA_GUI_TYPE,
// TODO 3.0.0: TEMP, used while developing, remove it.
'sCapturedOutput' => utils::FilterXSS($s_captured_output),

View File

@@ -929,6 +929,7 @@ HTML;
'aJsInlineOnInit' => $this->a_init_scripts,
'aJsInlineOnDomReady' => $this->GetReadyScripts(),
'aJsInlineLive' => $this->a_scripts,
'sBodyDataGuiType' => static::BODY_DATA_GUI_TYPE,
// TODO 3.0.0: TEMP, used while developping, remove it.
'sSanitizedContent' => utils::FilterXSS($this->s_content),
'sDeferredContent' => utils::FilterXSS($this->s_deferred_content),