N°8178 - Respect "high_cardinality_classes" parameter on search operation (#870)

This commit is contained in:
Lenaick
2026-04-08 09:47:46 +02:00
committed by GitHub
parent 4eadff7f3b
commit 5d0da47f21
5 changed files with 180 additions and 69 deletions

View File

@@ -193,6 +193,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\\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',
'Combodo\\iTop\\Application\\Newsroom\\iTopNewsroomProvider' => $baseDir . '/sources/Application/Newsroom/iTopNewsroomProvider.php',

View File

@@ -548,6 +548,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'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\\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',
'Combodo\\iTop\\Application\\Newsroom\\iTopNewsroomProvider' => __DIR__ . '/../..' . '/sources/Application/Newsroom/iTopNewsroomProvider.php',

View File

@@ -5,6 +5,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\SearchHelper;
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
@@ -126,72 +127,6 @@ function SetObjectBreadCrumbEntry(DBObject $oObj, WebPage $oPage)
$oPage->SetBreadCrumbEntry("ui-details-$sClass-".$oObj->GetKey(), $oObj->Get('friendlyname'), MetaModel::GetName($sClass).': '.$oObj->Get('friendlyname'), '', $sIcon, $sIconType);
}
/**
* 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
*/
function DisplaySearchSet($oP, $oFilter, $bSearchForm = true, $sBaseClass = '', $sFormat = '', $bDoSearch = true, $bSearchFormOpen = true, $aParams = [])
{
//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);
//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);
$oUIBlockForm->AddSubBlock($oUIBlock);
} else {
$oBlock->Display($oP, 1);
}
}
}
/**
* Displays a form (checkboxes) to select the objects for which to apply a given action
* Only the objects for which the action is valid can be checked. By default all valid objects are checked
@@ -460,7 +395,7 @@ try {
$sOQL = "SELECT $sOQLClass $sOQLClause";
try {
$oFilter = DBObjectSearch::FromOQL($sOQL);
DisplaySearchSet($oP, $oFilter, $bSearchForm, $sBaseClass, $sFormat);
SearchHelper::DisplaySearchSet($oP, $oFilter, $bSearchForm, $sBaseClass, $sFormat);
} catch (CoreException $e) {
$oFilter = new DBObjectSearch($sOQLClass);
$oSet = new DBObjectSet($oFilter);
@@ -487,7 +422,7 @@ try {
}
$oP->set_title(Dict::S('UI:SearchResultsPageTitle'));
$oFilter = new DBObjectSearch($sClass);
DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat, $bDoSearch, true /* Search Form Expanded */);
SearchHelper::DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat, $bDoSearch, true /* Search Form Expanded */);
break;
///////////////////////////////////////////////////////////////////////////////////////////
@@ -509,7 +444,7 @@ try {
// $sParams = utils::ReadParam('aParams', '{}', false, \utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
// $aParams = json_decode($sParams, true);
DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat); //, true, true, $aParams
SearchHelper::DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat); //, true, true, $aParams
break;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,87 @@
<?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

@@ -0,0 +1,87 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Application\Helper;
use Combodo\iTop\Application\Helper\SearchHelper;
use Combodo\iTop\Application\WebPage\iTopWebPage;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use DBSearch;
use MetaModel;
class SearchHelperTest extends ItopDataTestCase
{
protected static array $aHighCardinalityClasses = [];
protected static bool $bSearchManualSubmit = false;
protected function setUp(): void
{
parent::setUp();
self::$aHighCardinalityClasses = MetaModel::GetConfig()->Get('high_cardinality_classes');
self::$bSearchManualSubmit = MetaModel::GetConfig()->Get('search_manual_submit');
}
protected function tearDown(): void
{
parent::tearDown();
MetaModel::GetConfig()->Set('high_cardinality_classes', static::$aHighCardinalityClasses);
MetaModel::GetConfig()->Set('search_manual_submit', static::$bSearchManualSubmit);
}
public function testDisplaySearchSetWithNoHighCardinalityClassesAddsResultSubBlock(): void
{
MetaModel::GetConfig()->Set('high_cardinality_classes', []);
MetaModel::GetConfig()->Set('search_manual_submit', false);
$oP = new iTopWebPage('SearchHelperTest');
$oFilter = DBSearch::FromOQL('SELECT UserRequest');
SearchHelper::DisplaySearchSet($oP, $oFilter);
$oContentLayout = $oP->GetContentLayout();
$this->assertTrue($oContentLayout->HasSubBlock('search_1'));
$oSearchBlock = $oContentLayout->getSubBlock('search_1');
$this->assertTrue($oSearchBlock->HasSubBlock('result_1'));
if (ob_get_level() > 0) {
ob_end_clean();
}
}
public function testDisplaySearchSetWithHighCardinalityClassesDoesNotAddResultSubBlock(): void
{
MetaModel::GetConfig()->Set('high_cardinality_classes', ['UserRequest']);
MetaModel::GetConfig()->Set('search_manual_submit', false);
$oP = new iTopWebPage('SearchHelperTest');
$oFilter = DBSearch::FromOQL('SELECT UserRequest');
SearchHelper::DisplaySearchSet($oP, $oFilter);
$oContentLayout = $oP->GetContentLayout();
$this->assertTrue($oContentLayout->HasSubBlock('search_1'));
$oSearchBlock = $oContentLayout->getSubBlock('search_1');
$this->assertFalse($oSearchBlock->HasSubBlock('result_1'));
if (ob_get_level() > 0) {
ob_end_clean();
}
}
public function testDisplaySearchSetWithSearchManualSubmitAndWithoutHighCardinalityClassesDoesNotAddResultSubBlock(): void
{
MetaModel::GetConfig()->Set('high_cardinality_classes', []);
MetaModel::GetConfig()->Set('search_manual_submit', true);
$oP = new iTopWebPage('SearchHelperTest');
$oFilter = DBSearch::FromOQL('SELECT UserRequest');
SearchHelper::DisplaySearchSet($oP, $oFilter);
$oContentLayout = $oP->GetContentLayout();
$this->assertTrue($oContentLayout->HasSubBlock('search_1'));
$oSearchBlock = $oContentLayout->getSubBlock('search_1');
$this->assertFalse($oSearchBlock->HasSubBlock('result_1'));
if (ob_get_level() > 0) {
ob_end_clean();
}
}
}