diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 06747971a..3497fc19a 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -1754,8 +1754,10 @@ class MenuBlock extends DisplayBlock } $sClass = $this->m_oFilter->GetClass(); + $bIsForLinkset = isset($aExtraParams['target_attr']); $oSet = new CMDBObjectSet($this->m_oFilter); $iSetCount = $oSet->Count(); + /** @var string $sRefreshAction JS snippet to run when clicking on the refresh button of the menu */ $sRefreshAction = $aExtraParams['refresh_action'] ?? ''; /** @var array $aRegularActions Any action other than a transition */ @@ -1765,7 +1767,7 @@ class MenuBlock extends DisplayBlock /** @var array $aToolkitActions Any "legacy" toolkit menu item, which are now displayed in the same menu as the $aRegularActions, after them */ $aToolkitActions = []; - if ((!isset($aExtraParams['selection_mode']) || $aExtraParams['selection_mode'] == "") && $this->m_sStyle != 'listInObject') { + if (!isset($aExtraParams['selection_mode']) || ($aExtraParams['selection_mode'] == "")) { $oAppContext = new ApplicationContext(); $sContext = $oAppContext->GetForLink(); if (utils::IsNotNullOrEmptyString($sContext)) { @@ -1778,13 +1780,10 @@ class MenuBlock extends DisplayBlock $sRootUrl = utils::GetAbsoluteUrlAppRoot(); // Common params that will be applied to actions - $aActionParams = array(); - if (isset($aExtraParams['menu_actions_target'])) { - $aActionParams['target'] = $aExtraParams['menu_actions_target']; - } + $aActionParams = $this->GetDefaultParamsForMenuAction(); // 1:n links, populate the target object as a default value when creating a new linked object - if (isset($aExtraParams['target_attr'])) { + if ($bIsForLinkset) { $aExtraParams['default'][$aExtraParams['target_attr']] = $aExtraParams['object_id']; } /** @var string $sDefaultValuesAsUrlParams Default values for the object to create, already formatted as URL params (eg. "&default[org_id]=3&default[title]=Foo") */ @@ -1794,248 +1793,221 @@ class MenuBlock extends DisplayBlock $sDefaultValuesAsUrlParams .= "&default[$sKey]=$sValue"; } } - $bIsCreationAllowed = (UserRights::IsActionAllowed($sClass, - UR_ACTION_CREATE) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); - switch ($iSetCount) { - case 0: - // No object in the set, the only possible action is "new" - if ($bIsCreationAllowed) { - $aRegularActions['UI:Menu:New'] = array( - 'label' => Dict::S('UI:Menu:New'), - 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefaultValuesAsUrlParams}", - ) + $aActionParams; + + // Check rights + $bIsCreationAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_CREATE) === UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); + $bIsModifyAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) === UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); + + // 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); + } + + // Stimuli + $aStates = MetaModel::EnumStates($sClass); + // Do not perform time-consuming computations if there are too many objects in the list + $iLimit = MetaModel::GetConfig()->Get('complex_actions_limit'); + + if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->CountWithLimit($iLimit + 1) < $iLimit))) { + // Life cycle actions may be available... if all objects are in the same state + // + // Group by + $oGroupByExp = new FieldExpression(MetaModel::GetStateAttributeCode($sClass), $this->m_oFilter->GetClassAlias()); + $aGroupBy = array('__state__' => $oGroupByExp); + $aQueryParams = array(); + if (isset($aExtraParams['query_params'])) { + $aQueryParams = $aExtraParams['query_params']; } - break; - case 1: - $oObj = $oSet->Fetch(); - if (is_null($oObj)) { - if (!isset($aExtraParams['link_attr'])) { - if ($bIsCreationAllowed) { - $aRegularActions['UI:Menu:New'] = array( - 'label' => Dict::S('UI:Menu:New'), - 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefaultValuesAsUrlParams}", - ) + $aActionParams; - } - } - } else { - $id = $oObj->GetKey(); - if (empty($sRefreshAction) && utils::ReadParam('operation') == 'details') { - if ($_SERVER['REQUEST_METHOD'] == 'GET') { - $sRefreshAction = "window.location.reload();"; - } else { - $sRefreshAction = "window.location.href='".ApplicationContext::MakeObjectUrl(get_class($oObj), $id)."';"; - } - } - - $bLocked = false; - if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) { - $aLockInfo = iTopOwnershipLock::IsLocked(get_class($oObj), $id); - if ($aLockInfo['locked']) { - $bLocked = true; - } - } - $bRawModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); - $bIsModifyAllowed = !$bLocked && $bRawModifiedAllowed; - $bIsDeleteAllowed = !$bLocked && UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet); - // Just one object in the set, possible actions are "new / clone / modify and delete" - if (!isset($aExtraParams['link_attr'])) { - if ($bIsModifyAllowed) { - $aRegularActions['UI:Menu:Modify'] = array( - 'label' => Dict::S('UI:Menu:Modify'), - 'url' => "{$sRootUrl}pages/$sUIPage?route=object.modify&class=$sClass&id=$id{$sContext}#", - ) + $aActionParams; - } - if ($bIsCreationAllowed) { - $aRegularActions['UI:Menu:New'] = array( - 'label' => Dict::S('UI:Menu:New'), - 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefaultValuesAsUrlParams}", - ) + $aActionParams; - } - if ($bIsDeleteAllowed) { - $aRegularActions['UI:Menu:Delete'] = array( - 'label' => Dict::S('UI:Menu:Delete'), - 'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}", - ) + $aActionParams; - } - - // Transitions / Stimuli - if (!$bLocked) { - $aTransitions = $oObj->EnumTransitions(); - if (count($aTransitions)) { - $aStimuli = Metamodel::EnumStimuli(get_class($oObj)); - foreach ($aTransitions as $sStimulusCode => $aTransitionDef) { - $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass, - $sStimulusCode, $oSet) : UR_ALLOWED_NO; - switch ($iActionAllowed) { - case UR_ALLOWED_YES: - $aTransitionActions[$sStimulusCode] = array( - 'label' => $aStimuli[$sStimulusCode]->GetLabel(), - 'url' => "{$sRootUrl}pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id{$sContext}", - ) + $aActionParams; - break; - - default: - // Do nothing - } - } - } - } - - // Relations... - $aRelations = MetaModel::EnumRelationsEx($sClass); - if (count($aRelations)) { - $this->AddMenuSeparator($aRegularActions); - foreach ($aRelations as $sRelationCode => $aRelationInfo) { - if (array_key_exists('down', $aRelationInfo)) { - $aRegularActions[$sRelationCode.'_down'] = array( - 'label' => $aRelationInfo['down'], - 'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}", + $sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy); + $aRes = CMDBSource::QueryToArray($sSql); + if (count($aRes) == 1) { + // All objects are in the same state... + $sState = $aRes[0]['__state__']; + $aTransitions = Metamodel::EnumTransitions($sClass, $sState); + if (count($aTransitions)) { + $aStimuli = Metamodel::EnumStimuli($sClass); + foreach ($aTransitions as $sStimulusCode => $aTransitionDef) { + $oSet->Rewind(); + // As soon as the user rights implementation will browse the object set, + // then we might consider using OptimizeColumnLoad() here + $iActionAllowed = UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet); + $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? $iActionAllowed : UR_ALLOWED_NO; + switch ($iActionAllowed) { + case UR_ALLOWED_YES: + case UR_ALLOWED_DEPENDS: + $aTransitionActions[$sStimulusCode] = array( + 'label' => $aStimuli[$sStimulusCode]->GetLabel(), + 'url' => "{$sRootUrl}pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=".urlencode($sFilter)."{$sContext}", ) + $aActionParams; - } - if (array_key_exists('up', $aRelationInfo)) { - $aRegularActions[$sRelationCode.'_up'] = array( - 'label' => $aRelationInfo['up'], - 'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}", - ) + $aActionParams; - } - } - } - - // Add a special menu to kill the lock, but only to allowed users who can also modify this object - if ($bLocked && $bRawModifiedAllowed) { - /** @var array $aAllowedProfiles */ - $aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles'); - $bCanKill = false; - - $oUser = UserRights::GetUserObject(); - $aUserProfiles = array(); - if (!is_null($oUser)) { - $oProfileSet = $oUser->Get('profile_list'); - while ($oProfile = $oProfileSet->Fetch()) { - $aUserProfiles[$oProfile->Get('profile')] = true; - } - } - - foreach ($aAllowedProfiles as $sProfile) { - if (array_key_exists($sProfile, $aUserProfiles)) { - $bCanKill = true; break; - } - } - if ($bCanKill) { - $this->AddMenuSeparator($aRegularActions); - $aRegularActions['concurrent_lock_unlock'] = array( - 'label' => Dict::S('UI:Menu:KillConcurrentLock'), - 'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}", - ); + default: + // Do nothing } } } - - $this->AddMenuSeparator($aRegularActions); - - $this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) { - $aRegularActions[$sLabel] = array('label' => $sLabel, 'url' => $data) + $aActionParams; - }); } - break; + } + } - default: - // Check rights - // New / Modify - $bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, - $oSet) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); - $bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, - $oSet) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); - $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet); - if (isset($aExtraParams['link_attr'])) { - $id = $aExtraParams['object_id']; - $sTargetAttr = $aExtraParams['target_attr']; - $oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr); - $sTargetClass = $oAttDef->GetTargetClass(); - if ($bIsModifyAllowed) { - $aRegularActions['UI:Menu:Add'] = array( - 'label' => Dict::S('UI:Menu:Add'), - 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true{$sContext}", - ) + $aActionParams; - } - if ($bIsBulkModifyAllowed) { - $aRegularActions['UI:Menu:Manage'] = array( - 'label' => Dict::S('UI:Menu:Manage'), - 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id{$sContext}", - ) + $aActionParams; - } - //if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All...', 'url' => "#") + $aActionParams; } - } else { - // many objects in the set, possible actions are: new / modify all / delete all + // NOT "listInObject" style actions + if ($this->m_sStyle !== 'listInObject') { + switch ($iSetCount) { + case 0: + // No object in the set, the only possible action is "new" if ($bIsCreationAllowed) { - $aRegularActions['UI:Menu:New'] = array( - 'label' => Dict::S('UI:Menu:New'), - 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefaultValuesAsUrlParams}", - ) + $aActionParams; - } - if ($bIsBulkModifyAllowed) { - $aRegularActions['UI:Menu:ModifyAll'] = array( - 'label' => Dict::S('UI:Menu:ModifyAll'), - 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_modify_all&class=$sClass&filter=".urlencode($sFilter)."{$sContext}", - ) + $aActionParams; - } - if ($bIsBulkDeleteAllowed) { - $aRegularActions['UI:Menu:BulkDelete'] = array( - 'label' => Dict::S('UI:Menu:BulkDelete'), - 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_deletion&filter=".urlencode($sFilter)."{$sContext}", - ) + $aActionParams; + $this->AddNewObjectMenuAction($aRegularActions, $sClass, $sDefaultValuesAsUrlParams); } + break; - // Stimuli - $aStates = MetaModel::EnumStates($sClass); - // Do not perform time consuming computations if there are too may objects in the list - $iLimit = MetaModel::GetConfig()->Get('complex_actions_limit'); - - if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->CountWithLimit($iLimit + 1) < $iLimit))) { - // Life cycle actions may be available... if all objects are in the same state - // - // Group by - $oGroupByExp = new FieldExpression(MetaModel::GetStateAttributeCode($sClass), $this->m_oFilter->GetClassAlias()); - $aGroupBy = array('__state__' => $oGroupByExp); - $aQueryParams = array(); - if (isset($aExtraParams['query_params'])) { - $aQueryParams = $aExtraParams['query_params']; + case 1: + $oObj = $oSet->Fetch(); + if (is_null($oObj)) { + if (!isset($aExtraParams['link_attr'])) { + if ($bIsCreationAllowed) { + $this->AddNewObjectMenuAction($aRegularActions, $sClass, $sDefaultValuesAsUrlParams); + } + } + } else { + $id = $oObj->GetKey(); + if (empty($sRefreshAction) && utils::ReadParam('operation') == 'details') { + if ($_SERVER['REQUEST_METHOD'] == 'GET') { + $sRefreshAction = "window.location.reload();"; + } else { + $sRefreshAction = "window.location.href='".ApplicationContext::MakeObjectUrl(get_class($oObj), $id)."';"; + } } - $sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy); - $aRes = CMDBSource::QueryToArray($sSql); - if (count($aRes) == 1) { - // All objects are in the same state... - $sState = $aRes[0]['__state__']; - $aTransitions = Metamodel::EnumTransitions($sClass, $sState); - if (count($aTransitions)) { - $aStimuli = Metamodel::EnumStimuli($sClass); - foreach ($aTransitions as $sStimulusCode => $aTransitionDef) { - $oSet->Rewind(); - // As soon as the user rights implementation will browse the object set, - // then we might consider using OptimizeColumnLoad() here - $iActionAllowed = UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet); - $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? $iActionAllowed : UR_ALLOWED_NO; - switch ($iActionAllowed) { - case UR_ALLOWED_YES: - case UR_ALLOWED_DEPENDS: - $aTransitionActions[$sStimulusCode] = array( - 'label' => $aStimuli[$sStimulusCode]->GetLabel(), - 'url' => "{$sRootUrl}pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=".urlencode($sFilter)."{$sContext}", - ) + $aActionParams; - break; + $bLocked = false; + if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) { + $aLockInfo = iTopOwnershipLock::IsLocked(get_class($oObj), $id); + if ($aLockInfo['locked']) { + $bLocked = true; + } + } + $bRawModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); + $bIsModifyAllowed = !$bLocked && $bRawModifiedAllowed; + $bIsDeleteAllowed = !$bLocked && UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet); + // Just one object in the set, possible actions are "new / clone / modify and delete" + if (!isset($aExtraParams['link_attr'])) { + if ($bIsModifyAllowed) { + $aRegularActions['UI:Menu:Modify'] = array( + 'label' => Dict::S('UI:Menu:Modify'), + 'url' => "{$sRootUrl}pages/$sUIPage?route=object.modify&class=$sClass&id=$id{$sContext}#", + ) + $aActionParams; + } + if ($bIsCreationAllowed) { + $this->AddNewObjectMenuAction($aRegularActions, $sClass, $sDefaultValuesAsUrlParams); + } + if ($bIsDeleteAllowed) { + $aRegularActions['UI:Menu:Delete'] = array( + 'label' => Dict::S('UI:Menu:Delete'), + 'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}", + ) + $aActionParams; + } - default: - // Do nothing + // Transitions / Stimuli + if (!$bLocked) { + $aTransitions = $oObj->EnumTransitions(); + if (count($aTransitions)) { + $aStimuli = Metamodel::EnumStimuli(get_class($oObj)); + foreach ($aTransitions as $sStimulusCode => $aTransitionDef) { + $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass, + $sStimulusCode, $oSet) : UR_ALLOWED_NO; + switch ($iActionAllowed) { + case UR_ALLOWED_YES: + $aTransitionActions[$sStimulusCode] = array( + 'label' => $aStimuli[$sStimulusCode]->GetLabel(), + 'url' => "{$sRootUrl}pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id{$sContext}", + ) + $aActionParams; + break; + + default: + // Do nothing + } } } } + + // Relations... + $aRelations = MetaModel::EnumRelationsEx($sClass); + if (count($aRelations)) { + $this->AddMenuSeparator($aRegularActions); + foreach ($aRelations as $sRelationCode => $aRelationInfo) { + if (array_key_exists('down', $aRelationInfo)) { + $aRegularActions[$sRelationCode.'_down'] = array( + 'label' => $aRelationInfo['down'], + 'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}", + ) + $aActionParams; + } + if (array_key_exists('up', $aRelationInfo)) { + $aRegularActions[$sRelationCode.'_up'] = array( + 'label' => $aRelationInfo['up'], + 'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}", + ) + $aActionParams; + } + } + } + + // Add a special menu to kill the lock, but only to allowed users who can also modify this object + if ($bLocked && $bRawModifiedAllowed) { + /** @var array $aAllowedProfiles */ + $aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles'); + $bCanKill = false; + + $oUser = UserRights::GetUserObject(); + $aUserProfiles = array(); + if (!is_null($oUser)) { + $oProfileSet = $oUser->Get('profile_list'); + while ($oProfile = $oProfileSet->Fetch()) { + $aUserProfiles[$oProfile->Get('profile')] = true; + } + } + + foreach ($aAllowedProfiles as $sProfile) { + if (array_key_exists($sProfile, $aUserProfiles)) { + $bCanKill = true; + break; + } + } + + if ($bCanKill) { + $this->AddMenuSeparator($aRegularActions); + $aRegularActions['concurrent_lock_unlock'] = array( + 'label' => Dict::S('UI:Menu:KillConcurrentLock'), + 'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}", + ); + } + } } + + $this->AddMenuSeparator($aRegularActions); + + $this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) { + $aRegularActions[$sLabel] = array('label' => $sLabel, 'url' => $data) + $aActionParams; + }); } - } + break; + } } $this->AddMenuSeparator($aRegularActions); @@ -2075,14 +2047,7 @@ class MenuBlock extends DisplayBlock case 'listInObject': $oSet->Rewind(); $param = $oSet; - $bToolkitMenu = true; - if (isset($aExtraParams['toolkit_menu'])) { - $bToolkitMenu = (bool)$aExtraParams['toolkit_menu']; - } - if ($bToolkitMenu) { - $sLabel = Dict::S('UI:ConfigureThisList'); - $aRegularActions['iTop::ConfigureList'] = ['label' => $sLabel, 'url' => '#', 'onclick' => "$('#datatable_dlg_datatable_{$sId}').dialog('open'); return false;"]; - } + utils::GetPopupMenuItemsBlock($oPopupMenuItemsBlock, iPopupMenuExtension::MENU_OBJLIST_ACTIONS, $param, $aRegularActions, $sId); utils::GetPopupMenuItemsBlock($oPopupMenuItemsBlock, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $param, $aToolkitActions, $sId); break; @@ -2265,6 +2230,12 @@ class MenuBlock extends DisplayBlock // Toolkit actions if (!empty($aToolkitActions)) { + // Add separator if necessary + if (count($aRegularActions) > 0) { + $oRegularActionsMenu->AddItem('separator-regular-actions-toolkit-actions', PopoverMenuItemFactory::MakeSeparator()); + } + + // Add actions foreach ($aToolkitActions as $sActionId => $aActionData) { $oRegularActionsMenu->AddItem('toolkit-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItemData($sActionId, $aActionData)); } @@ -2345,5 +2316,106 @@ class MenuBlock extends DisplayBlock $aActions['sep_'.(count($aActions)-1)] = array('label' => $sSeparator, 'url' => ''); } } - } + } + + /** + * Add a create action (to $aActions) for an $sClass object + * + * @param array &$aActions Pointer to the array in which the action will be added + * @param string $sClass Datamodel class concerned by the action + * @param string $sDefaultValuesAsUrlParams Default values for the new object form, + * + * @return void + * @since 3.1.0 + * @internal + */ + protected function AddNewObjectMenuAction(array &$aActions, string $sClass, string $sDefaultValuesAsUrlParams = ''): void + { + $aActions['UI:Menu:New'] = [ + 'label' => Dict::S('UI:Menu:New'), + 'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=new&class=$sClass{$sDefaultValuesAsUrlParams}"), + ] + $this->GetDefaultParamsForMenuAction(); + } + + /** + * Add a bulk modify action (to $aActions) on objects defined by $sFilter + * + * @param array &$aActions Pointer to the array in which the action will be added + * @param string $sClass Datamodel class concerned by the action + * @param string $sFilter OQL of the objects to propose for bulk modify + * @param string $sActionIdentifier Unique identifier for the action. Default if 'UI:Menu:ModifyAll' + * @param string $sActionLabel Label for the action, can be either a dict code to translate or an hardcoded label. Default is 'UI:Menu:ModifyAll'. + * + * @return void + * @since 3.1.0 + * @internal + */ + protected function AddBulkModifyObjectsMenuAction(array &$aActions, string $sClass, string $sFilter, string $sActionIdentifier = 'UI:Menu:ModifyAll', $sActionLabel = 'UI:Menu:ModifyAll'): void + { + $aActions[$sActionIdentifier] = [ + 'label' => Dict::S($sActionLabel), + 'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=select_for_modify_all&class=$sClass&filter=".urlencode($sFilter)), + ] + $this->GetDefaultParamsForMenuAction(); + } + + /** + * Add a bulk delete action (to $aActions) on objects defined by $sFilter + * + * @param array &$aActions Pointer to the array in which the action will be added + * @param string $sClass Datamodel class concerned by the action + * @param string $sFilter OQL of the objects to propose for bulk deletion + * @param string $sActionIdentifier Unique identifier for the action. Default if 'UI:Menu:BulkDelete' + * @param string $sActionLabel Label for the action, can be either a dict code to translate or an hardcoded label. Default is 'UI:Menu:BulkDelete'. + * + * @return void + * @since 3.1.0 + * @internal + */ + protected function AddBulkDeleteObjectsMenuAction(array &$aActions, string $sClass, string $sFilter, string $sActionIdentifier = 'UI:Menu:BulkDelete', $sActionLabel = 'UI:Menu:BulkDelete') + { + $aActions[$sActionIdentifier] = array( + 'label' => Dict::S($sActionLabel), + 'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=select_for_deletion&filter=".urlencode($sFilter)), + ) + $this->GetDefaultParamsForMenuAction(); + } + + /** + * @return array Default parameters of a menu action + * @since 3.1.0 + * @internal + */ + private function GetDefaultParamsForMenuAction(): array + { + $aDefaultParams = []; + + if (isset($aExtraParams['menu_actions_target'])) { + $aDefaultParams['target'] = $aExtraParams['menu_actions_target']; + } + + return $aDefaultParams; + } + + /** + * @param string $sClass Datamodel class for which the URL is prepared + * @param string $sUrlParams URL parameters to add to the URL, must already be concatenated as a string (eg. "foo=bar&some=thing&third=param") + * + * @return string An absolute URL for a menu action on the $sClass class with $sUrlParams + * @throws \Exception + * @internal + */ + private function PrepareUrlForStandardMenuAction(string $sClass, string $sUrlParams) + { + $sRootUrl = utils::GetAbsoluteUrlAppRoot(); + $sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass); + + $sUrl = "{$sRootUrl}pages/{$sUIPage}?{$sUrlParams}"; + + $oAppContext = new ApplicationContext(); + $sContext = $oAppContext->GetForLink(); + if (utils::IsNotNullOrEmptyString($sContext)) { + $sUrl .= '&'.$sContext; + } + + return $sUrl; + } } diff --git a/application/utils.inc.php b/application/utils.inc.php index c11dd2ee2..4331e8b92 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -1416,6 +1416,10 @@ class utils // switch($iMenuId) { + case iPopupMenuExtension::MENU_OBJLIST_ACTIONS: + // No native action there yet + break; + case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT: /** @var \DBObjectSet $param */ $oAppContext = new ApplicationContext(); @@ -1429,18 +1433,24 @@ class utils $oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js'); $oContainerBlock->AddCssFileRelPath('css/dragtable.css'); - $aResult = array(); - if (strlen($sUrl) < SERVER_MAX_URL_LENGTH) - { + // Configure this list on datatables + if (utils::IsNotNullOrEmptyString($sDataTableId)) { + $aResult[] = new JSPopupMenuItem( + 'iTop::ConfigureList', + Dict::S('UI:ConfigureThisList'), + "$('#datatable_dlg_datatable_{$sDataTableId}').dialog('open'); return false;" + ); $aResult[] = new SeparatorPopupMenuItem(); + } + + if (strlen($sUrl) < SERVER_MAX_URL_LENGTH) { // Static menus: Email this page, CSV Export & Add to Dashboard $aResult[] = new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook ); } - if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO) - { + if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO) { // Bulk export actions $aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")"); $aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")");