From af804a9dc15127a9bf5bbba688743995b6d642e3 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Mon, 12 Dec 2022 23:44:31 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B05655=20-=20Enable=20bulk=20modify/delete?= =?UTF-8?q?=20on=20linksets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/displayblock.class.inc.php | 42 +++++++++++-------- dictionaries/en.dictionary.itop.ui.php | 3 ++ dictionaries/fr.dictionary.itop.ui.php | 3 ++ pages/UI.php | 9 ++-- .../DataTable/DataTableUIBlockFactory.php | 2 +- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 3497fc19a..e09ca91a5 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -1753,9 +1753,10 @@ class MenuBlock extends DisplayBlock $this->m_sStyle = 'list'; } - $sClass = $this->m_oFilter->GetClass(); + $sClass = $this->GetFilter()->GetClass(); + $aSelectedClasses = $this->GetFilter()->GetSelectedClasses(); $bIsForLinkset = isset($aExtraParams['target_attr']); - $oSet = new CMDBObjectSet($this->m_oFilter); + $oSet = new CMDBObjectSet($this->GetFilter()); $iSetCount = $oSet->Count(); /** @var string $sRefreshAction JS snippet to run when clicking on the refresh button of the menu */ $sRefreshAction = $aExtraParams['refresh_action'] ?? ''; @@ -1775,7 +1776,7 @@ class MenuBlock extends DisplayBlock } $oReflectionClass = new ReflectionClass($sClass); - $sFilter = $this->m_oFilter->serialize(); + $sFilter = $this->GetFilter()->serialize(); $sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass); $sRootUrl = utils::GetAbsoluteUrlAppRoot(); @@ -1801,24 +1802,29 @@ class MenuBlock extends DisplayBlock // Any style actions // - Bulk actions on objects set if ($iSetCount > 1) { - // Check rights - $bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); - $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet); - - // TODO 3.1: Remove when finished -// $id = $aExtraParams['object_id']; -// $sTargetAttr = $aExtraParams['target_attr']; -// $oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr); -// $sTargetClass = $oAttDef->GetTargetClass(); - if ($bIsCreationAllowed) { $this->AddNewObjectMenuAction($aRegularActions, $sClass, $sDefaultValuesAsUrlParams); } - if ($bIsBulkModifyAllowed) { - $this->AddBulkModifyObjectsMenuAction($aRegularActions, $sClass, $sFilter); - } - if ($bIsBulkDeleteAllowed) { - $this->AddBulkDeleteObjectsMenuAction($aRegularActions, $sClass, $sFilter); + + // Bulk actions for each selected classes (eg. "link" and "remote" on n:n relations) + foreach ($aSelectedClasses as $sSelectedAlias => $sSelectedClass) { + $sSelectedClassName = MetaModel::GetName($sSelectedClass); + + // Check rights on class + $bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sSelectedClass)) && UserRights::IsActionAllowed($sSelectedClass, UR_ACTION_BULK_MODIFY, $oSet) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); + $bIsBulkDeleteAllowed = (bool) UserRights::IsActionAllowed($sSelectedClass, UR_ACTION_BULK_DELETE, $sSelectedClass); + + // Refine filter on selected class so bullk actions occur on the right class + $oSelectedClassFilter = $this->GetFilter()->DeepClone(); + $oSelectedClassFilter->SetSelectedClasses([$sSelectedAlias]); + + // Action identifier is using the alias on purpose so they can be used as "shortcut actions" easily for "Link" or "Remote" aliases on linksets. + if ($bIsBulkModifyAllowed) { + $this->AddBulkModifyObjectsMenuAction($aRegularActions, $sSelectedClass, $oSelectedClassFilter->serialize(), 'UI:Menu:ModifyAll:'.$sSelectedAlias, Dict::Format('UI:Menu:ModifyAll_Class', $sSelectedClassName)); + } + if ($bIsBulkDeleteAllowed) { + $this->AddBulkDeleteObjectsMenuAction($aRegularActions, $sSelectedClass, $oSelectedClassFilter->serialize(), 'UI:Menu:BulkDelete:'.$sSelectedAlias, Dict::Format('UI:Menu:BulkDelete_Class', $sSelectedClassName)); + } } // Stimuli diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php index 2363137d1..0fe549cea 100644 --- a/dictionaries/en.dictionary.itop.ui.php +++ b/dictionaries/en.dictionary.itop.ui.php @@ -525,6 +525,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Menu:Modify' => 'Modify...', 'UI:Menu:Delete' => 'Delete...', 'UI:Menu:BulkDelete' => 'Delete...', + 'UI:Menu:BulkDelete_Class' => 'Delete %1$s objects...', 'UI:UndefinedObject' => 'undefined', 'UI:Document:OpenInNewWindow:Download' => 'Open in new window: %1$s, Download: %2$s', 'UI:SplitDateTime-Date' => 'date', @@ -1163,9 +1164,11 @@ When associated with a trigger, each action is given an "order" number, specifyi 'Enum:Undefined' => 'Undefined', 'UI:DurationForm_Days_Hours_Minutes_Seconds' => '%1$s d %2$s h %3$s min %4$s s', 'UI:ModifyAllPageTitle' => 'Modify All', + 'UI:Modify_ObjectsOf_Class' => 'Modifying objects of class %1$s', 'UI:Modify_N_ObjectsOf_Class' => 'Modifying %1$d objects of class %2$s', 'UI:Modify_M_ObjectsOf_Class_OutOf_N' => 'Modifying %1$d objects of class %2$s out of %3$d', 'UI:Menu:ModifyAll' => 'Modify...', + 'UI:Menu:ModifyAll_Class' => 'Modify %1$s objects...', 'UI:Button:ModifyAll' => 'Modify All', 'UI:Button:PreviewModifications' => 'Preview Modifications >>', 'UI:ModifiedObject' => 'Object Modified', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 7dbb6ffb6..a22bad457 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -509,6 +509,7 @@ Nous espérons que vous aimerez cette version autant que nous avons eu du plaisi 'UI:Menu:Modify' => 'Modifier...', 'UI:Menu:Delete' => 'Supprimer...', 'UI:Menu:BulkDelete' => 'Supprimer...', + 'UI:Menu:BulkDelete_Class' => 'Supprimer des %1$s...', 'UI:UndefinedObject' => 'non défini', 'UI:Document:OpenInNewWindow:Download' => 'Ouvrir dans un nouvelle fenêtre: %1$s, Télécharger: %2$s', 'UI:SplitDateTime-Date' => 'date', @@ -1139,9 +1140,11 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'Enum:Undefined' => 'Non défini', 'UI:DurationForm_Days_Hours_Minutes_Seconds' => '%1$s J %2$s H %3$s min %4$s s', 'UI:ModifyAllPageTitle' => 'Modification par lots', + 'UI:Modify_ObjectsOf_Class' => 'Modification d\'objet(s) de type %1$s', 'UI:Modify_N_ObjectsOf_Class' => 'Modification de %1$d objet(s) de type %2$s', 'UI:Modify_M_ObjectsOf_Class_OutOf_N' => 'Modification de %1$d (sur %3$d) objets de type %2$s', 'UI:Menu:ModifyAll' => 'Modifier...', + 'UI:Menu:ModifyAll_Class' => 'Modifier des %1$s...', 'UI:Button:ModifyAll' => 'Modifier', 'UI:Button:PreviewModifications' => 'Aperçu des modifications >>', 'UI:ModifiedObject' => 'Objet Modifié', diff --git a/pages/UI.php b/pages/UI.php index c1e503e42..b9f043fda 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1912,7 +1912,7 @@ class UI { $oP->DisableBreadCrumb(); $oP->set_title(Dict::S('UI:ModifyAllPageTitle')); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $sFilter = utils::ReadParam('filter', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); if (empty($sFilter)) { throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); } @@ -1921,10 +1921,11 @@ class UI $oFilter->UpdateContextFromUser(); $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_MODIFY); $sClass = $oFilter->GetClass(); + $sClassName = MetaModel::GetName($sClass); $aDisplayParams = [ 'icon' => MetaModel::GetClassIcon($sClass, false), - 'title' => Dict::S('UI:ModifyAllPageTitle'), + 'title' => Dict::Format('UI:Modify_ObjectsOf_Class', $sClassName), ]; DisplayMultipleSelectionForm($oP, $oFilter, 'form_for_modify_all', $oChecker, [], $aDisplayParams); } @@ -1944,8 +1945,8 @@ class UI public static function OperationFormForModifyAll(iTopWebPage $oP, ApplicationContext $oAppContext): void { $oP->DisableBreadCrumb(); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - $sClass = utils::ReadParam('class', '', false, 'class'); + $sFilter = utils::ReadParam('filter', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); + $sClass = utils::ReadParam('class', '', false, utils::ENUM_SANITIZATION_FILTER_CLASS); $oFullSetFilter = DBObjectSearch::unserialize($sFilter); // Add user filter $oFullSetFilter->UpdateContextFromUser(); diff --git a/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php b/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php index 2093263a1..3f923607b 100644 --- a/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php +++ b/sources/Application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php @@ -135,7 +135,6 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory $bToolkitMenu = false; } if ($bToolkitMenu) { - $aExtraParams['selection_mode'] = true; $oMenuBlock = new MenuBlock($oSet->GetFilter(), $sStyle); $oBlockMenu = $oMenuBlock->GetRenderContent($oPage, $aExtraParams, $sListId); } else { @@ -742,6 +741,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory } $aOptions['sTableId'] = $sTableId; + $aOptions['sListId'] = $sListId; $aOptions['bUseCustomSettings'] = $bUseCustomSettings; $aOptions['bViewLink'] = $bViewLink; $aOptions['oClassAliases'] = json_encode($aClassAliases);