From 5d0da47f2149cb2bf0d305bf33bca7b104e58b1f Mon Sep 17 00:00:00 2001 From: Lenaick Date: Wed, 8 Apr 2026 09:47:46 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B08178=20-=20Respect=20"high=5Fcardinality?= =?UTF-8?q?=5Fclasses"=20parameter=20on=20search=20operation=20(#870)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/composer/autoload_classmap.php | 1 + lib/composer/autoload_static.php | 1 + pages/UI.php | 73 +--------------- sources/Application/Helper/SearchHelper.php | 87 +++++++++++++++++++ .../application/Helper/SearchHelperTest.php | 87 +++++++++++++++++++ 5 files changed, 180 insertions(+), 69 deletions(-) create mode 100644 sources/Application/Helper/SearchHelper.php create mode 100644 tests/php-unit-tests/unitary-tests/application/Helper/SearchHelperTest.php diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index f33a4ce8f..4755653f8 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -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', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 6e18001be..64da644bb 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -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', diff --git a/pages/UI.php b/pages/UI.php index b5a503b4a..05e743b52 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -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; /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/sources/Application/Helper/SearchHelper.php b/sources/Application/Helper/SearchHelper.php new file mode 100644 index 000000000..d2ac70174 --- /dev/null +++ b/sources/Application/Helper/SearchHelper.php @@ -0,0 +1,87 @@ +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); + } + } + } +} diff --git a/tests/php-unit-tests/unitary-tests/application/Helper/SearchHelperTest.php b/tests/php-unit-tests/unitary-tests/application/Helper/SearchHelperTest.php new file mode 100644 index 000000000..efed1a857 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/application/Helper/SearchHelperTest.php @@ -0,0 +1,87 @@ +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(); + } + } +}