Compare commits

...

3 Commits

Author SHA1 Message Date
Stephen Abello
0e7e87ff8b Correctly handle separators 2026-08-21 14:53:04 +02:00
Stephen Abello
7e3b4af093 Fix JS file handling, add TODO for what's still missing 2026-08-21 10:06:19 +02:00
Stephen Abello
aff09a731f N°9152 - Add first bit of UI extensibility. Needs a lot of polishing. API endpoints are working and displaying in the UI 2026-08-20 15:05:00 +02:00
53 changed files with 1641 additions and 12 deletions

View File

@@ -58,6 +58,17 @@ abstract class ApplicationPopupMenuItem
return $this->sLabel;
}
/**
* @param string $sLabel
*
* @api
* @since 3.3.0
*/
public function SetLabel($sLabel)
{
$this->sLabel = $sLabel;
}
/**
* Get the CSS classes
*

View File

@@ -95,7 +95,21 @@ interface iPopupMenuExtension
* @todo
*/
public const PORTAL_MENU_ACTIONS = 10;
/**
* Get the list of items to be added to the backoffice top-bar items
* $param is null for now, in a distant future we want to give a complete context
*/
public const MENU_TOPBAR_ACTIONS = 11;
/**
* Get the list of items to be added to the actions on a given form field (i.e. the attribute of an object)
* $param is an array: ['object' => DBObject, att_code => attribute code , mode => edit/read]
*/
public const MENU_OBJDETAILS_FIELD_ACTIONS = 12;
/**
* Get the list of items to be added to the actions in the activity panel (read-only mode)
* $param = ['object' => DBObject, caselog_att_code => caselog attribute code or 'activity']
*/
public const MENU_OBJDETAILS_ACTIVITY_PANEL_ACTIONS = 13;
/**
* Get the list of items to be added to a menu.
*

View File

@@ -1065,6 +1065,14 @@ HTML
}
}
$val['value_raw'] = ($bExcludeRawValue === false) ? $this->Get($sAttCode) : '';
$val['actions'] = [];
/** @var \iPopupMenuExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance) {
foreach ($oExtensionInstance::EnumItems(iPopupMenuExtension::MENU_OBJDETAILS_FIELD_ACTIONS, ['object' => $this, 'att_code' => $sAttCode, 'mode' => $bEditMode ? 'edit' : 'read']) as $oMenuItem) {
$val['actions'][] = $oMenuItem;
}
}
// The field is visible, add it to the current column
$oField = FieldUIBlockFactory::MakeFromParams($val);

View File

@@ -5,6 +5,7 @@
@import "alert";
@import "button";
@import "button-separator";
@import "button-group";
@import "breadcrumbs";
@import "quick-create";

View File

@@ -0,0 +1,20 @@
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-button-separator--width: 1px !default;
$ibo-button-separator--height: 16px !default;
$ibo-button-separator--margin-x: 0.75rem !default;
$ibo-button-separator--margin-y: $ibo-spacing-0 !default;
$ibo-button-separator--border-size: 1px !default;
$ibo-button-separator--border-color: $ibo-color-grey-500 !default;
.ibo-button-separator {
display: inline-flex;
border-right: $ibo-button-separator--border-size solid $ibo-button-separator--border-color;
width: $ibo-button-separator--width;
height: $ibo-button-separator--height;
margin: $ibo-button-separator--margin-y $ibo-button-separator--margin-x;
}

View File

@@ -264,4 +264,22 @@ $ibo-field--enable-bulk--checkbox--margin-left: $ibo-spacing-300 !default;
.ibo-input-select--action-buttons a {
@extend %ibo-hyperlink-inherited-colors;
}
}
.ibo-field--label {
.ibo-field--action:not(.ibo-button-separator) {
width: 20px;
height: 20px;
padding: 0;
border-radius: $ibo-field--fullscreen-toggler--border-radius;
font-size: $ibo-font-size-50;
vertical-align: center;
~ .ibo-field--action {
margin-left: 2px;
}
}
.ibo-field--action.ibo-button-separator {
margin: 0 2px;
vertical-align: center;
}
}

View File

@@ -4,7 +4,6 @@
*/
@import "navigation-menu";
@import "top-bar";
@import "content";
@import "details";
@import "tab-container/tab-container";
@@ -15,4 +14,5 @@
@import "wizard-container/wizard-container";
@import "object/all";
@import "activity-panel/all";
@import "extension/all";
@import "extension/all";
@import "top-bar/all";

View File

@@ -0,0 +1,73 @@
$ibo-activity-panel--activity-actions--right: 12px !default;
$ibo-activity-panel--activity-actions--top: 76px + $ibo-activity-panel--activity-actions--right +36px + 8px !default;
$ibo-activity-panel--activity-action--diameter: 36px !default;
$ibo-activity-panel--activity-action--background-color: $ibo-color-primary-600 !default;
$ibo-activity-panel--activity-action--background-color--on-hover: $ibo-color-primary-500 !default;
$ibo-activity-panel--activity-action--background-color--is-active: $ibo-color-primary-700 !default;
$ibo-activity-panel--activity-action--color: $ibo-color-white-100 !default;
$ibo-activity-panel--activity-action--border-radius: $ibo-border-radius-full !default;
$ibo-activity-panel--activity-action--box-shadow: $ibo-elevation-100 !default;
$ibo-activity-panel--activity-action--hover--box-shadow: $ibo-elevation-200 !default;
$ibo-activity-panel--activity-action--icon--height: 100% !default;
$ibo-activity-panel--activity-action--icon--width: $ibo-activity-panel--activity-action--icon--height !default;
$ibo-activity-panel--activity-action--icon--font-size: $ibo-font-size-200 !default;
$ibo-activity-panel--activity-action--icon--line-height: 33px !default;
$ibo-activity-panel--activity-action-separator--width: 24px !default;
$ibo-activity-panel--activity-action-separator--height: 1px !default;
$ibo-activity-panel--activity-action-separator--background-color: $ibo-color-grey-500 !default;
.ibo-activity-panel--activity-actions {
display: flex;
flex-direction: column;
gap: 8px;
position: absolute;
z-index: 1;
right: $ibo-activity-panel--activity-actions--right;
top: $ibo-activity-panel--activity-actions--top;
&.ibo-is-hidden{
display: none;
}
}
.ibo-activity-panel--activity-action {
@extend %ibo-baseline-centered-content;
width: $ibo-activity-panel--activity-action--diameter;
height: $ibo-activity-panel--activity-action--diameter;
background-color: $ibo-activity-panel--activity-action--background-color;
color: $ibo-activity-panel--activity-action--color;
border-radius: $ibo-activity-panel--activity-action--border-radius;
box-shadow: $ibo-activity-panel--activity-action--box-shadow;
> i{
text-align: center;
height: $ibo-activity-panel--activity-action--icon--height;
width: $ibo-activity-panel--activity-action--icon--width;
font-size: $ibo-activity-panel--activity-action--icon--font-size;
line-height: $ibo-activity-panel--activity-action--icon--line-height;
}
&:hover {
color: $ibo-activity-panel--activity-action--color;
background-color: $ibo-activity-panel--activity-action--background-color--on-hover;
box-shadow: $ibo-activity-panel--activity-action--hover--box-shadow;
}
&:active {
color: $ibo-activity-panel--activity-action--color;
background-color: $ibo-activity-panel--activity-action--background-color--is-active;
}
&.ibo-is-hidden{
display: none;
}
}
.ibo-activity-panel--activity-action-separator {
display: block;
width: $ibo-activity-panel--activity-action-separator--width;
height: $ibo-activity-panel--activity-action-separator--height;
margin: auto;
background-color: $ibo-activity-panel--activity-action-separator--background-color;
}

View File

@@ -10,3 +10,4 @@
@import "transition-entry";
@import "edits-entry";
@import "notification-entry";
@import "activity-action";

View File

@@ -59,4 +59,8 @@ $ibo-caselog-entry-form--lock-message--margin-left: 1rem !default;
}
.ibo-caselog-entry-form--action-buttons--main-actions > .ibo-popover-menu {
z-index: 1;
}
.ibo-caselog-entry-form--action-buttons--extra-actions .ibo-button-separator{
vertical-align: center ;
}

View File

@@ -0,0 +1,2 @@
@import "top-bar";
@import "top-bar-action";

View File

@@ -0,0 +1,36 @@
$ibo-top-bar-quick-action--head--background-color: $ibo-color-white-100 !default;
$ibo-top-bar-quick-action--icon-padding-x: $ibo-spacing-500 !default;
$ibo-top-bar-quick-action--icon-padding-y: $ibo-spacing-0 !default;
$ibo-top-bar-quick-action--icon--color: $ibo-color-primary-600 !default;
$ibo-top-bar-quick-action--icon--color--on-hover: $ibo-color-primary-700 !default;
$ibo-top-bar-quick-action--icon--color--on-active: $ibo-color-primary-800 !default;
.ibo-top-bar-quick-action{
position: relative;
@extend %ibo-full-height-content;
}
.ibo-top-bar-quick-action--head{
@extend %ibo-full-height-content;
height: 100%;
background-color: $ibo-top-bar-quick-action--head--background-color;
}
.ibo-top-bar-quick-action--icon{
color: $ibo-top-bar-quick-action--icon--color;
align-self: center;
padding: $ibo-top-bar-quick-action--icon-padding-y $ibo-top-bar-quick-action--icon-padding-x;
@extend %ibo-font-ral-nor-400;
&:hover{
color: $ibo-top-bar-quick-action--icon--color--on-hover;
}
&:active{
color: $ibo-top-bar-quick-action--icon--color--on-active;
}
}
.ibo-top-bar-quick-action-separator {
width: 2px;
background-color: $ibo-color-grey-300;
margin: 0 8px;
}

View File

@@ -71,6 +71,8 @@ $(function()
authors_count: '[data-role="ibo-activity-panel--tab-toolbar-info-authors-count"]',
messages_count: '[data-role="ibo-activity-panel--tab-toolbar-info-messages-count"]',
compose_button: '[data-role="ibo-activity-panel--add-caselog-entry-button"]',
activity_actions: '[data-role="ibo-activity-panel--activity-actions"]',
activity_action: '[data-role="ibo-activity-panel--activity-action"]',
compose_menu: '#ibo-activity-panel--compose-menu',
compose_menu_item: '#ibo-activity-panel--compose-menu [data-role="ibo-popover-menu--item"]',
caselog_entry_form: '[data-role="ibo-caselog-entry-form"]',
@@ -194,6 +196,11 @@ $(function()
this.element.find(this.js_selectors.compose_button).on('click', function (oEvent) {
me._onComposeButtonClick(oEvent);
});
this.element.find('[data-role="ibo-activity-panel--activity-action"]').on('click', function (oEvent) {
me._onActivityActionClick(oEvent, $(this));
});
// - Click on the compose menu items
this.element.find(this.js_selectors.compose_menu_item).on('click', function (oEvent) {
me._onComposeMenuItemClick(oEvent, $(this));
@@ -375,6 +382,25 @@ $(function()
// Else, the compose menu will open automatically
},
_onActivityActionClick: function (oEvent, oActionElem) {
oEvent.preventDefault();
const oActiveTabData = this._GetActiveTabData();
const oActionElement = $(oEvent.target).closest(this.js_selectors.activity_action);
const oActionElementMenu = $('#'+oActionElement.attr('id')+'-menu');
const oTabCode = oActiveTabData.type === 'caselog' ? oActiveTabData.att_code : oActiveTabData.type;
const oActionElementMenuItem = oActionElementMenu.find('[data-caselog-attribute-code="'+oTabCode+'"]');
// If the current tab has a corresponding menu item, click on it to trigger it directly
if (oActionElementMenuItem.length > 0) {
// Note: Stop propagation to avoid the menu to be opened automatically by the popover handler
oEvent.stopImmediatePropagation();
// TODO: Need fix for URL items as synthetic clicks doesn't open URLs
oActionElementMenuItem.click();
}
// Else let the popover menu open automatically, the user will have to choose a case log
},
/**
* @param oEvent {Object}
* @param oItemElem {Object} jQuery object representing the clicked item
@@ -784,6 +810,7 @@ $(function()
_ShowCaseLogsEntryForms: function () {
this.element.find(this.js_selectors.caselog_entry_form).trigger('show_form.caselog_entry_form.itop');
this.element.find(this.js_selectors.compose_button).addClass(this.css_classes.is_hidden);
this.element.find(this.js_selectors.activity_actions).addClass(this.css_classes.is_hidden);
},
/**
* Hide all case logs entry forms.
@@ -795,6 +822,7 @@ $(function()
_HideCaseLogsEntryForms: function () {
this.element.find(this.js_selectors.caselog_entry_form).trigger('hide_form.caselog_entry_form.itop');
this.element.find(this.js_selectors.compose_button).removeClass(this.css_classes.is_hidden);
this.element.find(this.js_selectors.activity_actions).removeClass(this.css_classes.is_hidden);
},
/**
* Empty all case logs entry forms

View File

@@ -157,6 +157,9 @@ return array(
'Combodo\\iTop\\Application\\TwigBase\\UI\\UIBlockNode' => $baseDir . '/sources/Application/TwigBase/UI/UIBlockNode.php',
'Combodo\\iTop\\Application\\TwigBase\\UI\\UIBlockParser' => $baseDir . '/sources/Application/TwigBase/UI/UIBlockParser.php',
'Combodo\\iTop\\Application\\UI\\Base\\AbstractUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/AbstractUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Common\\Action\\tActionCommon' => $baseDir . '/sources/Application/UI/Base/Common/Action/tActionCommon.php',
'Combodo\\iTop\\Application\\UI\\Base\\Common\\Action\\tActionJs' => $baseDir . '/sources/Application/UI/Base/Common/Action/tActionJs.php',
'Combodo\\iTop\\Application\\UI\\Base\\Common\\Action\\tActionURL' => $baseDir . '/sources/Application/UI/Base/Common/Action/tActionURL.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Alert\\Alert' => $baseDir . '/sources/Application/UI/Base/Component/Alert/Alert.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Alert\\AlertUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Alert/AlertUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Badge\\Badge' => $baseDir . '/sources/Application/UI/Base/Component/Badge/Badge.php',
@@ -166,6 +169,7 @@ return array(
'Combodo\\iTop\\Application\\UI\\Base\\Component\\ButtonGroup\\ButtonGroupUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/ButtonGroup/ButtonGroupUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\Button' => $baseDir . '/sources/Application/UI/Base/Component/Button/Button.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\ButtonJS' => $baseDir . '/sources/Application/UI/Base/Component/Button/ButtonJS.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\ButtonSeparator' => $baseDir . '/sources/Application/UI/Base/Component/Button/ButtonSeparator.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\ButtonUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\ButtonURL' => $baseDir . '/sources/Application/UI/Base/Component/Button/ButtonURL.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\CollapsibleSection\\CollapsibleSection' => $baseDir . '/sources/Application/UI/Base/Component/CollapsibleSection/CollapsibleSection.php',
@@ -269,6 +273,11 @@ return array(
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityEntry\\NotificationEntry' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityEntry/NotificationEntry.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityEntry\\TransitionEntry' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityEntry/TransitionEntry.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanel' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanel.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelAction\\ActivityPanelAction' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelAction.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelAction\\ActivityPanelActionFactory' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelAction\\ActivityPanelActionJS' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionJS.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelAction\\ActivityPanelActionSeparator' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionSeparator.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelAction\\ActivityPanelActionURL' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionURL.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelFactory' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelHelper' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelHelper.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelPrint' => $baseDir . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelPrint.php',
@@ -295,6 +304,11 @@ return array(
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TabContainer\\Tab\\AjaxTab' => $baseDir . '/sources/Application/UI/Base/Layout/TabContainer/Tab/AjaxTab.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TabContainer\\Tab\\Tab' => $baseDir . '/sources/Application/UI/Base/Layout/TabContainer/Tab/Tab.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBar' => $baseDir . '/sources/Application/UI/Base/Layout/TopBar/TopBar.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarAction\\TopBarQuickAction' => $baseDir . '/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickAction.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarAction\\TopBarQuickActionFactory' => $baseDir . '/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarAction\\TopBarQuickActionJS' => $baseDir . '/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionJS.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarAction\\TopBarQuickActionSeparator' => $baseDir . '/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionSeparator.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarAction\\TopBarQuickActionURL' => $baseDir . '/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionURL.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarFactory' => $baseDir . '/sources/Application/UI/Base/Layout/TopBar/TopBarFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\UIContentBlock' => $baseDir . '/sources/Application/UI/Base/Layout/UIContentBlock.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\UIContentBlockUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Layout/UIContentBlockUIBlockFactory.php',

View File

@@ -558,6 +558,9 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
'Combodo\\iTop\\Application\\TwigBase\\UI\\UIBlockNode' => __DIR__ . '/../..' . '/sources/Application/TwigBase/UI/UIBlockNode.php',
'Combodo\\iTop\\Application\\TwigBase\\UI\\UIBlockParser' => __DIR__ . '/../..' . '/sources/Application/TwigBase/UI/UIBlockParser.php',
'Combodo\\iTop\\Application\\UI\\Base\\AbstractUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/AbstractUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Common\\Action\\tActionCommon' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Common/Action/tActionCommon.php',
'Combodo\\iTop\\Application\\UI\\Base\\Common\\Action\\tActionJs' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Common/Action/tActionJs.php',
'Combodo\\iTop\\Application\\UI\\Base\\Common\\Action\\tActionURL' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Common/Action/tActionURL.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Alert\\Alert' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Alert/Alert.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Alert\\AlertUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Alert/AlertUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Badge\\Badge' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Badge/Badge.php',
@@ -567,6 +570,7 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
'Combodo\\iTop\\Application\\UI\\Base\\Component\\ButtonGroup\\ButtonGroupUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/ButtonGroup/ButtonGroupUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\Button' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Button/Button.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\ButtonJS' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Button/ButtonJS.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\ButtonSeparator' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Button/ButtonSeparator.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\ButtonUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\ButtonURL' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Button/ButtonURL.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\CollapsibleSection\\CollapsibleSection' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/CollapsibleSection/CollapsibleSection.php',
@@ -670,6 +674,11 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityEntry\\NotificationEntry' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityEntry/NotificationEntry.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityEntry\\TransitionEntry' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityEntry/TransitionEntry.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanel' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanel.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelAction\\ActivityPanelAction' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelAction.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelAction\\ActivityPanelActionFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelAction\\ActivityPanelActionJS' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionJS.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelAction\\ActivityPanelActionSeparator' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionSeparator.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelAction\\ActivityPanelActionURL' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionURL.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelHelper' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelHelper.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\ActivityPanel\\ActivityPanelPrint' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelPrint.php',
@@ -696,6 +705,11 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TabContainer\\Tab\\AjaxTab' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/TabContainer/Tab/AjaxTab.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TabContainer\\Tab\\Tab' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/TabContainer/Tab/Tab.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBar' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/TopBar/TopBar.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarAction\\TopBarQuickAction' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickAction.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarAction\\TopBarQuickActionFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarAction\\TopBarQuickActionJS' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionJS.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarAction\\TopBarQuickActionSeparator' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionSeparator.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarAction\\TopBarQuickActionURL' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionURL.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\TopBar\\TopBarFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/TopBar/TopBarFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\UIContentBlock' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/UIContentBlock.php',
'Combodo\\iTop\\Application\\UI\\Base\\Layout\\UIContentBlockUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Layout/UIContentBlockUIBlockFactory.php',

View File

@@ -0,0 +1,100 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Common\Action;
/**
* Trait tActionCommon
*
* Shared properties and accessors for UI actions.
*
* @package Combodo\iTop\Application\UI\Base\Common\Action
* @internal
* @since 3.3.0
*/
trait tActionCommon
{
protected string $sUid;
protected string $sLabel;
protected string $sIconClass = '';
protected ?string $sTooltip = null;
protected array $aCssClasses = [];
public function GetUID(): string
{
return $this->sUid;
}
public function SetUID(string $sUid)
{
$this->sUid = $sUid;
return $this;
}
public function GetLabel(): string
{
return $this->sLabel;
}
public function SetLabel(string $sLabel)
{
$this->sLabel = $sLabel;
return $this;
}
public function GetIconClass(): string
{
return $this->sIconClass;
}
public function SetIconClass(string $sIconClass)
{
$this->sIconClass = $sIconClass;
return $this;
}
public function GetTooltip(): ?string
{
return $this->sTooltip;
}
public function SetTooltip(?string $sTooltip)
{
$this->sTooltip = $sTooltip;
return $this;
}
public function GetCssClasses(): array
{
return $this->aCssClasses;
}
public function SetCssClasses(array $aCssClasses)
{
$this->aCssClasses = $aCssClasses;
return $this;
}
public function AddCssClass(string $sCssClass)
{
$this->aCssClasses[] = $sCssClass;
return $this;
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Common\Action;
/**
* Trait tActionJs
*
* Shared properties and accessors for JS-based actions.
*
* @package Combodo\iTop\Application\UI\Base\Common\Action
* @internal
* @since 3.3.0
*/
trait tActionJs
{
protected string $sJsCode;
protected string $sUrl = '#';
protected array $aIncludeJSFiles = [];
public function GetJsCode(): string
{
return $this->sJsCode;
}
public function SetJsCode(string $sJsCode)
{
$this->sJsCode = $sJsCode;
return $this;
}
public function GetUrl(): string
{
return $this->sUrl;
}
public function SetUrl(string $sUrl)
{
$this->sUrl = $sUrl;
return $this;
}
public function GetIncludeJSFiles(): array
{
return $this->aIncludeJSFiles;
}
public function SetIncludeJSFiles(array $aIncludeJSFiles)
{
$this->aIncludeJSFiles = $aIncludeJSFiles;
return $this;
}
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Common\Action;
/**
* Trait tActionURL
*
* Shared properties and accessors for URL-based actions.
*
* @package Combodo\iTop\Application\UI\Base\Common\Action
* @internal
* @since 3.3.0
*/
trait tActionURL
{
protected string $sUrl;
protected ?string $sTarget = null;
public function GetUrl(): string
{
return $this->sUrl;
}
public function SetUrl(string $sUrl)
{
$this->sUrl = $sUrl;
return $this;
}
public function GetTarget(): ?string
{
return $this->sTarget;
}
public function SetTarget(?string $sTarget)
{
$this->sTarget = $sTarget;
return $this;
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Component\Button;
use Combodo\iTop\Application\UI\Base\UIBlock;
/**
* Class ButtonSeparator
*
* @internal
* @since 3.3.0
*/
class ButtonSeparator extends UIBlock
{
public const BLOCK_CODE = 'ibo-button-separator';
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/button/separator/layout';
public const DEFAULT_JS_TEMPLATE_REL_PATH = null;
}

View File

@@ -20,8 +20,12 @@
namespace Combodo\iTop\Application\UI\Base\Component\Button;
use ApplicationPopupMenuItem;
use Combodo\iTop\Application\UI\Base\AbstractUIBlockFactory;
use Dict;
use JSPopupMenuItem;
use SeparatorPopupMenuItem;
use URLPopupMenuItem;
use utils;
/**
@@ -376,6 +380,122 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
return $oButton;
}
/**
* @param ApplicationPopupMenuItem $oPopupItem
* @param string $sColor
* @param string $sActionType
*
* @return \Combodo\iTop\Application\UI\Base\Component\Button\Button|\Combodo\iTop\Application\UI\Base\Component\Button\ButtonSeparator|null
*/
public static function MakeIconButtonFromApplicationPopupMenuItem(ApplicationPopupMenuItem $oPopupItem, $sColor = Button::ENUM_COLOR_SCHEME_NEUTRAL, $sActionType = Button::ENUM_ACTION_TYPE_ALTERNATIVE): Button|ButtonSeparator|null
{
if ($oPopupItem instanceof JSPopupMenuItem) {
$sTooltip = $oPopupItem->GetTooltip();
if (!utils::IsNotNullOrEmptyString($sTooltip)) {
$sTooltip = $oPopupItem->GetLabel();
}
$oButton = self::MakeIconAction(
$oPopupItem->GetIconClass(),
$sTooltip
);
$oButton->SetOnClickJsCode($oPopupItem->GetJsCode());
foreach ($oPopupItem->GetLinkedScripts() as $sLinkedScript) {
$oButton->AddJsFileRelPath($sLinkedScript);
}
$oButton->SetActionType($sActionType);
$oButton->SetColor($sColor);
return $oButton;
} elseif ($oPopupItem instanceof URLPopupMenuItem) {
$sTooltip = $oPopupItem->GetTooltip();
if (!utils::IsNotNullOrEmptyString($sTooltip)) {
$sTooltip = $oPopupItem->GetLabel();
}
$oButton = self::MakeIconLink(
$oPopupItem->GetIconClass(),
$sTooltip,
$oPopupItem->GetURL(),
$oPopupItem->GetTarget(),
);
$oButton->SetActionType($sActionType);
$oButton->SetColor($sColor);
return $oButton;
} elseif ($oPopupItem instanceof SeparatorPopupMenuItem) {
$oSeparator = new ButtonSeparator($oPopupItem->GetUID());
$oSeparator->SetCSSClasses($oPopupItem->GetCssClasses());
return $oSeparator;
}
// Should never happen, but just in case a new type is added, return null
return null;
}
/**
* @param ApplicationPopupMenuItem $oPopupItem
* @param string $sColor
* @param string $sActionType
*
* @return \Combodo\iTop\Application\UI\Base\Component\Button\Button|\Combodo\iTop\Application\UI\Base\Component\Button\ButtonSeparator|null
*/
public static function MakeButtonFromApplicationPopupMenuItem(ApplicationPopupMenuItem $oPopupItem, $sColor = Button::ENUM_COLOR_SCHEME_NEUTRAL, $sActionType = Button::ENUM_ACTION_TYPE_ALTERNATIVE): Button|ButtonSeparator|null
{
if ($oPopupItem instanceof JSPopupMenuItem) {
$oButton = self::MakeForAction(
$oPopupItem->GetLabel(),
$sColor,
$sActionType,
);
if (utils::IsNotNullOrEmptyString($oPopupItem->GetIconClass())) {
$oButton->SetIconClass($oPopupItem->GetIconClass());
}
if (utils::IsNotNullOrEmptyString($oPopupItem->GetTooltip())) {
$oButton->SetTooltip($oPopupItem->GetTooltip());
}
$oButton->SetOnClickJsCode($oPopupItem->GetJsCode());
foreach ($oPopupItem->GetLinkedScripts() as $sLinkedScript) {
$oButton->AddJsFileRelPath($sLinkedScript);
}
return $oButton;
} elseif ($oPopupItem instanceof URLPopupMenuItem) {
$oButton = self::MakeForLink(
$oPopupItem->GetLabel(),
$oPopupItem->GetURL(),
$sColor,
$sActionType,
$oPopupItem->GetTarget()
);
if (utils::IsNotNullOrEmptyString($oPopupItem->GetIconClass())) {
$oButton->SetIconClass($oPopupItem->GetIconClass());
}
if (utils::IsNotNullOrEmptyString($oPopupItem->GetTooltip())) {
$oButton->SetTooltip($oPopupItem->GetTooltip());
}
if (utils::IsNotNullOrEmptyString($oPopupItem->GetTarget())) {
$oButton->SetTarget($oPopupItem->GetTarget());
}
return $oButton;
} elseif ($oPopupItem instanceof SeparatorPopupMenuItem) {
$oSeparator = new ButtonSeparator($oPopupItem->GetUID());
$oSeparator->SetCSSClasses($oPopupItem->GetCssClasses());
return $oSeparator;
}
// Should never happen, but just in case a new type is added, return null
return null;
}
//----------------------------------------------------------------------------------------------
// Link buttons, mostly used outside forms, to redirect somewhere whilst keeping a button aspect
//----------------------------------------------------------------------------------------------

View File

@@ -57,14 +57,16 @@ class Field extends UIContentBlock
/** @var string */
protected $sComments;
protected $oValue;
protected $aActions = [];
public function __construct(string $sLabel, ?UIBlock $oValue = null, ?string $sId = null)
{
parent::__construct($sId);
$this->sLabel = $sLabel;
$this->sValueId = null;
if (!is_null($oValue)) {
$this->AddSubBlock($oValue);
}
$this->oValue = $oValue;
}
/**
@@ -287,6 +289,10 @@ class Field extends UIContentBlock
return $this;
}
public function HasValue(): bool
{
return !is_null($this->oValue);
}
/**
* @return \Combodo\iTop\Application\UI\Base\UIBlock
*/
@@ -390,4 +396,36 @@ class Field extends UIContentBlock
{
return utils::IsNotNullOrEmptyString($this->GetDescription());
}
public function GetActions(): array
{
return $this->aActions;
}
public function AddAction(UIBlock $oAction)
{
$this->aActions[] = $oAction;
return $this;
}
public function HasActions(): bool
{
return !empty($this->aActions);
}
public function SetActions(array $aActions)
{
$this->aActions = $aActions;
return $this;
}
public function GetSubBlocks(): array
{
$aSubBlocks = [];
if (!is_null($this->oValue)) {
$aSubBlocks[$this->oValue->GetId()] = $this->oValue;
}
foreach ($this->aActions as $oAction) {
$aSubBlocks[$oAction->GetId()] = $oAction;
}
return $aSubBlocks;
}
}

View File

@@ -8,7 +8,10 @@
namespace Combodo\iTop\Application\UI\Base\Component\Field;
use Combodo\iTop\Application\UI\Base\AbstractUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuFactory;
use Combodo\iTop\Application\UI\Base\UIBlock;
/**
@@ -67,6 +70,36 @@ class FieldUIBlockFactory extends AbstractUIBlockFactory
}
}
if (isset($aParams['actions'])) {
//TODO make this a configuration parameter
$iMaxActions = 50;
$aActions = [];
// Create a button for a few action or a kebab button for multiple actions that a more than what's configured
if (count($aParams['actions']) > $iMaxActions) {
$oKebabButton = ButtonUIBlockFactory::MakeIconAction('fas fa-ellipsis-v', 'More actions', 'kebab');
$oKebabButton->AddCSSClass('ibo-field--action');
$aKebabActions = [];
foreach ($aParams['actions'] as $oAction) {
$aKebabActions[] = $oAction->GetMenuItem();
}
//TODO: Make a dict entry for the kebab menu label
$oKebabPopoverMenu = PopoverMenuFactory::MakeMenuForActions($oField->GetId().'--kebab-menu', $aKebabActions);
$oKebabPopoverMenu->SetTogglerFromBlock($oKebabButton);
$oKebabPopoverMenu->SetContainer(PopoverMenu::ENUM_CONTAINER_BODY);
$aActions[] = $oKebabButton;
$aActions[] = $oKebabPopoverMenu;
} else {
foreach ($aParams['actions'] as $oAction) {
$oActionButton = ButtonUIBlockFactory::MakeIconButtonFromApplicationPopupMenuItem($oAction);
$oActionButton->AddCSSClass('ibo-field--action');
$aActions[] = $oActionButton;
}
}
$oField->SetActions($aActions);
}
return $oField;
}

View File

@@ -101,7 +101,7 @@ class ActivityPanel extends UIBlock
protected $bPrefilterStateChangesOnLogs;
/** @var bool */
protected $bPrefilterEditsOnLogs;
protected array $aActivityActions = [];
/**
* ActivityPanel constructor.
*
@@ -939,6 +939,10 @@ class ActivityPanel extends UIBlock
$aSubBlocks[$this->GetComposeMenu()->GetId()] = $this->GetComposeMenu();
foreach ($this->GetActivityActions() as $oActivityAction) {
$aSubBlocks[$oActivityAction->GetId()] = $oActivityAction;
}
return $aSubBlocks;
}
@@ -950,4 +954,26 @@ class ActivityPanel extends UIBlock
{
return \UserRights::IsActionAllowed($this->GetObjectClass(), UR_ACTION_MODIFY);
}
public function HasActivityActions(): bool
{
return count($this->aActivityActions) > 0;
}
public function GetActivityActions(): array
{
return $this->aActivityActions;
}
public function SetActivityActions(array $aActivityActions)
{
$this->aActivityActions = $aActivityActions;
return $this;
}
public function AddActivityAction($oActivityAction)
{
$this->aActivityActions[] = $oActivityAction;
return $this;
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAction;
use Combodo\iTop\Application\UI\Base\Common\Action\tActionCommon;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
use Combodo\iTop\Application\UI\Base\UIBlock;
/**
* Class ActivityPanelAction
*
* @package Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAction
* @internal
* @since 3.3.0
*/
abstract class ActivityPanelAction extends UIBlock
{
use tActionCommon;
// Overloaded constants
public const BLOCK_CODE = 'ibo-activity-panel-action';
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/activity-panel/activity-panel-action/layout';
public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/layouts/activity-panel/activity-panel-action/layout';
public const DEFAULT_JS_FILES_REL_PATH = [
];
public const DEFAULT_CSS_FILES_REL_PATH = [
];
/** @var array<string, array<\ApplicationPopupMenuItem>> */
protected array $aPopupItems = [];
protected ?PopoverMenu $oPopoverMenu = null;
public function __construct(string $sLabel, string $sIconClass, ?string $sTooltip = null, ?string $sId = null)
{
parent::__construct($sId);
$this->sUid = $sId ?? $this->GetId();
$this->sLabel = $sLabel;
$this->sIconClass = $sIconClass;
$this->sTooltip = $sTooltip;
}
public function SetPopupItems(array $aPopupItems)
{
$this->aPopupItems = $aPopupItems;
return $this;
}
public function AddPopupItem(string $sSectionId, \ApplicationPopupMenuItem $oPopupItem)
{
if (!array_key_exists($sSectionId, $this->aPopupItems)) {
$this->aPopupItems[$sSectionId] = [];
}
$this->aPopupItems[$sSectionId][] = $oPopupItem;
return $this;
}
public function GetPopupItems(): array
{
return $this->aPopupItems;
}
public function SetPopoverMenu(PopoverMenu $oPopoverMenu)
{
$this->oPopoverMenu = $oPopoverMenu;
return $this;
}
public function GetPopoverMenu(): ?PopoverMenu
{
return $this->oPopoverMenu;
}
public function HasPopoverMenu(): bool
{
return $this->oPopoverMenu !== null;
}
public function GetSubBlocks(): array
{
if (false === $this->HasPopoverMenu()) {
return [];
}
return [$this->oPopoverMenu->GetId() => $this->oPopoverMenu];
}
}

View File

@@ -0,0 +1,152 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAction;
use ApplicationPopupMenuItem;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItemFactory;
use JSPopupMenuItem;
use SeparatorPopupMenuItem;
use URLPopupMenuItem;
/**
* Class ActivityPanelActionJS
*
* @package Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAction
* @internal
* @since 3.3.0
*/
class ActivityPanelActionFactory
{
/**
* @param array $aPopupItems as follow:
* [ 'case-log-attribute-code' => [ ApplicationPopupMenuItem1, ... ], ... ]
* or
* [ 'case-log-attribute-code' => ['label' => 'Displayed label', 'items' => [ ApplicationPopupMenuItem1, ... ]], ... ]
* @return ActivityPanelAction[]
* @throws \Exception
*/
public static function MakeFromApplicationPopupItems(array $aPopupItems)
{
$aActionsByUid = [];
foreach ($aPopupItems as $sSectionId => $aSectionData) {
$sSectionLabel = (string) $sSectionId;
$aSectionPopupItems = $aSectionData;
if (is_array($aSectionData) && array_key_exists('items', $aSectionData)) {
$aSectionPopupItems = $aSectionData['items'];
if (array_key_exists('label', $aSectionData)) {
$sSectionLabel = (string) $aSectionData['label'];
}
}
if (!is_array($aSectionPopupItems)) {
$aSectionPopupItems = [$aSectionPopupItems];
}
// Special index as separators aren't meant to be multiple action across different caselogs
$iSectionSeparatorIndex = 0;
foreach ($aSectionPopupItems as $oPopupItem) {
if (!$oPopupItem instanceof ApplicationPopupMenuItem) {
continue;
}
$sUid = $oPopupItem->GetUID();
if ($oPopupItem instanceof SeparatorPopupMenuItem) {
$iSectionSeparatorIndex++;
$sUid = 'ibo-activity-panel--activity-action-separator--'.$iSectionSeparatorIndex;
}
if (!isset($aActionsByUid[$sUid])) {
$oActivityAction = static::MakeFromApplicationPopupItem($oPopupItem);
if ($oActivityAction === null) {
continue;
}
if (false === ($oActivityAction instanceof ActivityPanelActionSeparator)) {
$oPopoverMenu = new PopoverMenu($oActivityAction->GetId().'-menu');
$oPopoverMenu->SetContainer(PopoverMenu::ENUM_CONTAINER_BODY);
$oPopoverMenu->SetTogglerFromBlock($oActivityAction);
$oActivityAction->SetPopoverMenu($oPopoverMenu);
}
$aActionsByUid[$sUid] = $oActivityAction;
}
if ($aActionsByUid[$sUid] instanceof ActivityPanelActionSeparator) {
continue;
}
$oPopupItemForPopoverMenuItem = clone $oPopupItem;
$oPopupItemForPopoverMenuItem->SetLabel($sSectionLabel);
$oPopupItemForPopoverMenuItem->SetIconClass('');
$oPopoverMenuItem = PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem($oPopupItemForPopoverMenuItem);
$oPopoverMenuItem->AddDataAttribute('caselog-attribute-code', (string) $sSectionId);
$aActionsByUid[$sUid]->GetPopoverMenu()->AddItem((string) $sSectionId, $oPopoverMenuItem);
}
}
return array_values($aActionsByUid);
}
public static function MakeFromApplicationPopupItem($oPopupItem): ?ActivityPanelAction
{
if ($oPopupItem instanceof JSPopupMenuItem) {
$oAction = new ActivityPanelActionJS(
$oPopupItem->GetLabel(),
$oPopupItem->GetIconClass(),
$oPopupItem->GetTooltip(),
$oPopupItem->GetUID()
);
$oAction->SetJsCode($oPopupItem->GetJsCode());
$oAction->SetUrl($oPopupItem->GetUrl());
$oAction->SetIncludeJSFiles($oPopupItem->GetLinkedScripts());
return $oAction;
} elseif ($oPopupItem instanceof URLPopupMenuItem) {
$oAction = new ActivityPanelActionURL(
$oPopupItem->GetLabel(),
$oPopupItem->GetIconClass(),
$oPopupItem->GetTooltip(),
$oPopupItem->GetUID()
);
$oAction->SetUrl($oPopupItem->GetUrl());
$oAction->SetTarget($oPopupItem->GetTarget());
return $oAction;
} elseif ($oPopupItem instanceof SeparatorPopupMenuItem) {
return new ActivityPanelActionSeparator($oPopupItem->GetUID());
}
// Should never happen, but just in case a new type is added, return null
return null;
}
protected static function ClonePopupItemWithLabel(ApplicationPopupMenuItem $oPopupItem, string $sLabel): ApplicationPopupMenuItem
{
$oClonedPopupItem = clone $oPopupItem;
$oClonedPopupItem->SetLabel($sLabel);
return $oClonedPopupItem;
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAction;
use Combodo\iTop\Application\UI\Base\Common\Action\tActionJs;
/**
* Class ActivityPanelActionJS
*
* @package Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAction
* @internal
* @since 3.3.0
*/
class ActivityPanelActionJS extends ActivityPanelAction
{
use tActionJs;
// Overloaded constants
public const BLOCK_CODE = 'ibo-activity-panel-action-js';
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/activity-panel/activity-action/activity-action-js';
public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/layouts/activity-panel/activity-action/activity-action-js';
public const DEFAULT_JS_FILES_REL_PATH = [
];
public const DEFAULT_CSS_FILES_REL_PATH = [
];
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAction;
/**
* Class ActivityPanelActionSeparator
*
* @package Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAction
* @internal
* @since 3.3.0
*/
class ActivityPanelActionSeparator extends ActivityPanelAction
{
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/activity-panel/activity-action/activity-action-separator';
public const DEFAULT_JS_TEMPLATE_REL_PATH = null;
/**
* @param string|null $sId
*/
public function __construct(?string $sId = null)
{
parent::__construct('', '', null, $sId);
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAction;
use Combodo\iTop\Application\UI\Base\Common\Action\tActionURL;
/**
* Class ActivityPanelActionURL
*
* @package Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAction
* @internal
* @since 3.3.0
*/
class ActivityPanelActionURL extends ActivityPanelAction
{
use tActionURL;
// Overloaded constants
public const BLOCK_CODE = 'ibo-activity-panel-action-url';
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/activity-panel/activity-action/activity-action-url';
public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/layouts/activity-panel/activity-action/activity-action-url';
public const DEFAULT_JS_FILES_REL_PATH = [
];
public const DEFAULT_CSS_FILES_REL_PATH = [
];
}

View File

@@ -22,12 +22,15 @@ namespace Combodo\iTop\Application\UI\Base\Layout\ActivityPanel;
use cmdbAbstractObject;
use Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityEntry\ActivityEntryFactory;
use Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAction\ActivityPanelActionFactory;
use Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\CaseLogEntryForm\CaseLogEntryFormFactory;
use DBObject;
use DBObjectSearch;
use DBObjectSet;
use Dict;
use Exception;
use ExecutionKPI;
use iPopupMenuExtension;
use IssueLog;
use MetaModel;
@@ -172,6 +175,53 @@ class ActivityPanelFactory
}
}
if ($sMode === cmdbAbstractObject::ENUM_DISPLAY_MODE_VIEW) {
$aActionItems = [];
/** @var \iPopupMenuExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance) {
// Iterate over every case log tab to retrieve the actions for each of them
foreach ($aCaseLogTabs as $sCaseLogAttCode => $aCaseLogData) {
$aCaselogActions = [];
// Let's iterate over the menu items for this case log
foreach ($oExtensionInstance::EnumItems(iPopupMenuExtension::MENU_OBJDETAILS_ACTIVITY_PANEL_ACTIONS, ['object' => $oObject, 'caselog_att_code' => $sCaseLogAttCode]) as $oMenuItem) {
$aCaselogActions[] = $oMenuItem;
}
if (empty($aCaselogActions)) {
continue;
}
// Create a new section for the case log if it doesn't exist yet
if (!array_key_exists($sCaseLogAttCode, $aActionItems)) {
$aActionItems[$sCaseLogAttCode] = [
'label' => MetaModel::GetLabel($sObjClass, $sCaseLogAttCode),
'items' => [],
];
}
$aActionItems[$sCaseLogAttCode]['items'] = array_merge($aActionItems[$sCaseLogAttCode]['items'], $aCaselogActions);
}
$aActivityItems = [];
// Don't forget 1 last iteration for the activity tab
foreach ($oExtensionInstance::EnumItems(iPopupMenuExtension::MENU_OBJDETAILS_ACTIVITY_PANEL_ACTIONS, ['object' => $oObject, 'caselog_att_code' => 'activity']) as $oMenuItem) {
$aActivityItems[] = $oMenuItem;
}
if (!empty($aActivityItems)) {
if (!array_key_exists('activity', $aActionItems)) {
$aActionItems['activity'] = [
'label' => Dict::S('UI:Layout:ActivityPanel:Tab:Activity:Title'),
'items' => [],
];
}
$aActionItems['activity']['items'] = array_merge($aActionItems['activity']['items'], $aActivityItems);
}
}
$oActivityPanel->SetActivityActions(ActivityPanelActionFactory::MakeFromApplicationPopupItems($aActionItems));
}
$oKPI->ComputeStatsForExtension(new ActivityPanelFactory(), 'MakeForObjectDetails');
return $oActivityPanel;

View File

@@ -12,10 +12,12 @@ use Combodo\iTop\Application\UI\Base\Component\Button\Button;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\ButtonGroup\ButtonGroupUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuFactory;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItemFactory;
use DBObject;
use DBObjectSet;
use Dict;
use iPopupMenuExtension;
use JSPopupMenuItem;
use MetaModel;
use UserRights;
@@ -48,6 +50,30 @@ class CaseLogEntryFormFactory
$oCaseLogEntryForm->AddMainActionButtons($oSaveButton);
}
// Build extra actions
$aPopupItems = [];
/** @var \iPopupMenuExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance) {
foreach ($oExtensionInstance::EnumItems(iPopupMenuExtension::MENU_OBJDETAILS_FIELD_ACTIONS, ['object' => $oObject, 'att_code' => $sCaseLogAttCode, 'mode' => $sObjectMode === cmdbAbstractObject::ENUM_DISPLAY_MODE_EDIT ? 'edit' : 'read']) as $oMenuItem) {
$aPopupItems[] = $oMenuItem;
}
}
if (!empty($aPopupItems)) {
$aActions = [];
// Create a button for each extra action, the UI will be responsible for displaying them in a dropdown if there are too many
foreach ($aPopupItems as $oAction) {
$oActionButton = ButtonUIBlockFactory::MakeButtonFromApplicationPopupMenuItem($oAction);
$oActionButton->AddCSSClass('ibo-field--action');
$aActions[] = $oActionButton;
}
foreach ($aActions as $oAction) {
$oAction->AddCSSClass('ibo-caselog-entry-form--extra-action');
$oCaseLogEntryForm->AddExtraActionButtons($oAction);
}
}
return $oCaseLogEntryForm;
}

View File

@@ -24,6 +24,8 @@ use Combodo\iTop\Application\UI\Base\Component\Breadcrumbs\Breadcrumbs;
use Combodo\iTop\Application\UI\Base\Component\GlobalSearch\GlobalSearch;
use Combodo\iTop\Application\UI\Base\Component\QuickCreate\QuickCreate;
use Combodo\iTop\Application\UI\Base\Component\Toolbar\Toolbar;
use Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction\TopBarQuickAction;
use Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction\TopBarQuickActionJS;
use Combodo\iTop\Application\UI\Base\UIBlock;
/**
@@ -48,6 +50,8 @@ class TopBar extends UIBlock
protected $oBreadcrumbs;
/** @var Toolbar|null */
protected $oToolbar;
/** @var array $aActions */
protected array $aActions = [];
/**
* TopBar constructor.
@@ -205,7 +209,8 @@ class TopBar extends UIBlock
{
$aSubBlocks = [];
$aSubBlocksNames = ['QuickCreate', 'GlobalSearch', 'Breadcrumbs', 'Toolbar'];
// Add single objects
$aSubBlocksNames = ['QuickCreate', 'GlobalSearch', 'Breadcrumbs', 'Toolbar', ];
foreach ($aSubBlocksNames as $sSubBlockName) {
$sHasMethodName = 'Has'.$sSubBlockName;
if (true === call_user_func_array([$this, $sHasMethodName], [])) {
@@ -214,6 +219,40 @@ class TopBar extends UIBlock
}
}
// Add object arrays
$aSubBlocksNames = ['Actions'];
foreach ($aSubBlocksNames as $sSubBlockName) {
$sHasMethodName = 'Has'.$sSubBlockName;
if (true === call_user_func_array([$this, $sHasMethodName], [])) {
$sPropertyName = 'a'.$sSubBlockName;
foreach ($this->$sPropertyName as $oSubBlock) {
$aSubBlocks[$oSubBlock->GetId()] = $oSubBlock;
}
}
}
return $aSubBlocks;
}
public function HasActions(): bool
{
return count($this->GetActions()) > 0;
}
public function GetActions(): array
{
return $this->aActions;
}
public function SetActions(array $aActions)
{
$this->aActions = $aActions;
return $this;
}
public function AddAction(TopBarQuickAction $oAction)
{
$this->aActions[] = $oAction;
return $this;
}
}

View File

@@ -23,6 +23,9 @@ namespace Combodo\iTop\Application\UI\Base\Layout\TopBar;
use Combodo\iTop\Application\UI\Base\Component\Breadcrumbs\Breadcrumbs;
use Combodo\iTop\Application\UI\Base\Component\GlobalSearch\GlobalSearchFactory;
use Combodo\iTop\Application\UI\Base\Component\QuickCreate\QuickCreateFactory;
use Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction\TopBarQuickActionFactory;
use iPopupMenuExtension;
use MetaModel;
use utils;
/**
@@ -58,6 +61,13 @@ class TopBarFactory
$oTopBar->SetGlobalSearch(GlobalSearchFactory::MakeFromUserHistory());
}
/** @var \iPopupMenuExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance) {
foreach ($oExtensionInstance::EnumItems(iPopupMenuExtension::MENU_TOPBAR_ACTIONS, []) as $oMenuItem) {
$oTopBar->AddAction(TopBarQuickActionFactory::MakeFromApplicationPopupItem($oMenuItem));
}
}
if (utils::GetConfig()->Get('breadcrumb.enabled') === true) {
$oBreadcrumbs = new Breadcrumbs($aBreadcrumbsEntry, Breadcrumbs::BLOCK_CODE);

View File

@@ -0,0 +1,99 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction;
use Combodo\iTop\Application\UI\Base\UIBlock;
/**
* Class TopBarAction
*
* @package Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction
* @internal
* @since 3.3.0
*/
abstract class TopBarQuickAction extends UIBlock
{
// Overloaded constants
public const BLOCK_CODE = 'ibo-top-bar-quick-action';
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/top-bar/top-bar-quick-action/layout';
public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/layouts/top-bar/top-bar-quick-action/layout';
public const DEFAULT_JS_FILES_REL_PATH = [
];
public const DEFAULT_CSS_FILES_REL_PATH = [
];
protected string $sLabel;
protected string $sIconClass;
protected ?string $sTooltip = null;
/**
* TopBarAction constructor.
*
* @param string $sLabel
* @param string $sIconClass
* @param string|null $sTooltip
* @param string|null $sId
*
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public function __construct(string $sLabel, string $sIconClass, ?string $sTooltip = null, ?string $sId = null)
{
parent::__construct($sId);
$this->sLabel = $sLabel;
$this->sIconClass = $sIconClass;
$this->sTooltip = $sTooltip;
}
public function GetLabel(): string
{
return $this->sLabel;
}
public function SetLabel(string $sLabel): TopBarQuickAction
{
$this->sLabel = $sLabel;
return $this;
}
public function GetIconClass(): string
{
return $this->sIconClass;
}
public function SetIconClass(string $sIconClass): TopBarQuickAction
{
$this->sIconClass = $sIconClass;
return $this;
}
public function GetTooltip(): ?string
{
return $this->sTooltip;
}
public function SetTooltip(?string $sTooltip): TopBarQuickAction
{
$this->sTooltip = $sTooltip;
return $this;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction;
use JSPopupMenuItem;
use SeparatorPopupMenuItem;
use URLPopupMenuItem;
/**
* Class TopBarActionFactory
*
* @package Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction
* @internal
* @since 3.0.0
*/
class TopBarQuickActionFactory
{
public static function MakeFromApplicationPopupItem($oApplicationPopupItem): ?TopBarQuickAction
{
if ($oApplicationPopupItem instanceof JSPopupMenuItem) {
$oTopBarAction = new TopBarQuickActionJS(
$oApplicationPopupItem->GetLabel(),
$oApplicationPopupItem->GetIconClass(),
$oApplicationPopupItem->GetTooltip(),
$oApplicationPopupItem->GetUID()
);
$oTopBarAction->SetJsCode($oApplicationPopupItem->GetJsCode());
$oTopBarAction->SetUrl($oApplicationPopupItem->GetUrl());
$oTopBarAction->SetIncludeJSFiles($oApplicationPopupItem->GetLinkedScripts());
return $oTopBarAction;
} elseif ($oApplicationPopupItem instanceof URLPopupMenuItem) {
$oTopBarAction = new TopBarQuickActionURL(
$oApplicationPopupItem->GetLabel(),
$oApplicationPopupItem->GetIconClass(),
$oApplicationPopupItem->GetTooltip(),
$oApplicationPopupItem->GetUID()
);
$oTopBarAction->SetUrl($oApplicationPopupItem->GetUrl());
$oTopBarAction->SetTarget($oApplicationPopupItem->GetTarget());
return $oTopBarAction;
} elseif ($oApplicationPopupItem instanceof SeparatorPopupMenuItem) {
$oTopBarAction = new TopBarQuickActionSeparator($oApplicationPopupItem->GetUID());
$oTopBarAction->SetCSSClasses($oApplicationPopupItem->GetCssClasses());
return $oTopBarAction;
}
// Should never happen, but just in case a new type is added, return null
return null;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction;
use Combodo\iTop\Application\UI\Base\Common\Action\tActionJs;
use Combodo\iTop\Application\UI\Base\UIBlock;
/**
* Class TopBarActionJS
*
* @package Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction
* @internal
* @since 3.3.0
*/
class TopBarQuickActionJS extends TopBarQuickAction
{
use tActionJs;
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/top-bar/top-bar-quick-action/top-bar-quick-action-js';
public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/layouts/top-bar/top-bar-quick-action/top-bar-quick-action-js';
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction;
/**
* Class TopBarQuickActionSeparator
*
* @package Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction
* @internal
* @since 3.3.0
*/
class TopBarQuickActionSeparator extends TopBarQuickAction
{
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/top-bar/top-bar-quick-action/top-bar-quick-action-separator';
public const DEFAULT_JS_TEMPLATE_REL_PATH = null;
/**
* @param string|null $sId
*
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public function __construct(?string $sId = null)
{
parent::__construct('', '', null, $sId);
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* Copyright (C) 2013-2026 Combodo SAS
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction;
use Combodo\iTop\Application\UI\Base\Common\Action\tActionURL;
/**
* Class TopBarActionURL
*
* @package Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction
* @internal
* @since 3.3.0
*/
class TopBarQuickActionURL extends TopBarQuickAction
{
use tActionURL;
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/top-bar/top-bar-quick-action/top-bar-quick-action-url';
public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/layouts/top-bar/top-bar-quick-action/top-bar-quick-action-url';
}

View File

@@ -0,0 +1,11 @@
{# @copyright Copyright (C) 2010-2026 Combodo SAS #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% apply spaceless %}
<span id="{{ oUIBlock.GetId() }}"
class="{{ oUIBlock.GetBlocksInheritanceCSSClassesAsString() }}
{% if oUIBlock.GetAdditionalCSSClassesAsString() %}{{ oUIBlock.GetAdditionalCSSClassesAsString() }}{% endif %}
{% if oUIBlock.IsHidden() %}ibo-is-hidden{% endif %}"
data-role="ibo-button-separator">
</span>
{% endapply %}

View File

@@ -26,6 +26,13 @@
{% if oUIBlock.HasDescription() %}
<span class="ibo-has-description" data-tooltip-content="{{ oUIBlock.GetDescription() }}" data-tooltip-max-width="600px" ></span>
{% endif %}
{% if oUIBlock.HasActions() %}
<span class="ibo-field--actions">
{% for oAction in oUIBlock.GetActions() %}
{{ render_block(oAction, {aPage: aPage}) }}
{% endfor %}
</span>
{% endif %}
{% if oUIBlock.GetLayout() == constant("Combodo\\iTop\\Application\\UI\\Base\\Component\\Field\\Field::ENUM_FIELD_LAYOUT_LARGE") %}
{% if oUIBlock.GetComments() %}
<div class="ibo-field--comments">{{ oUIBlock.GetComments()|raw }}</div>
@@ -33,9 +40,9 @@
{% endif %}
</div>
<div class="ibo-field--value" {% if oUIBlock.GetValueId() %}id="{{ oUIBlock.GetValueId() }}"{% endif %}>
{% for oSubBlock in oUIBlock.GetSubBlocks() %}
{{ render_block(oSubBlock, {aPage: aPage}) }}
{% endfor %}
{% if oUIBlock.HasValue() %}
{{ render_block(oUIBlock.GetValue(), {aPage: aPage}) }}
{% endif %}
</div>
{% if oUIBlock.GetLayout() != constant("Combodo\\iTop\\Application\\UI\\Base\\Component\\Field\\Field::ENUM_FIELD_LAYOUT_LARGE") %}
{% if oUIBlock.GetComments() %}

View File

@@ -0,0 +1,17 @@
<a href="{{ oUIBlock.GetUrl() }}"
id="{{ oUIBlock.GetId() }}"
class="{{ oUIBlock.GetBlocksInheritanceCSSClassesAsString() }} {{ oUIBlock.GetAdditionalCSSClassesAsString() }} ibo-activity-panel--activity-action"
data-role="ibo-activity-panel--activity-action"
{% if oUIBlock.GetTooltip() is not empty %}
data-tooltip-content="{{ oUIBlock.GetTooltip() }}"
aria-label="{{ oUIBlock.GetTooltip() }}"
{% endif %}
>
{% if oUIBlock.GetIconClass() is not empty %}
<i class="{{ oUIBlock.GetIconClass() }}"></i>
{% endif %}
{# <span>{{ oUIBlock.GetLabel() }}</span>#}
</a>
{% if oUIBlock.HasPopoverMenu() %}
{{ render_block(oUIBlock.GetPopoverMenu()) }}
{% endif %}

View File

@@ -0,0 +1 @@
{# Probably not needed, activity panel will handle interactions with popovermenu #}

View File

@@ -0,0 +1,4 @@
<span id="{{ oUIBlock.GetId() }}"
class="{{ oUIBlock.GetBlocksInheritanceCSSClassesAsString() }} {{ oUIBlock.GetAdditionalCSSClassesAsString() }} ibo-activity-panel--activity-action-separator"
data-role="ibo-activity-panel--activity-action-separator">
</span>

View File

@@ -0,0 +1,17 @@
<a href="{{ oUIBlock.GetUrl() }}"
id="{{ oUIBlock.GetId() }}"
class="{{ oUIBlock.GetBlocksInheritanceCSSClassesAsString() }} {{ oUIBlock.GetAdditionalCSSClassesAsString() }} ibo-activity-panel--activity-action"
data-role="ibo-activity-panel--activity-action"
{% if oUIBlock.GetTooltip() is not empty %}
data-tooltip-content="{{ oUIBlock.GetTooltip() }}"
aria-label="{{ oUIBlock.GetTooltip() }}"
{% endif %}
>
{% if oUIBlock.GetIconClass() is not empty %}
<i class="{{ oUIBlock.GetIconClass() }}"></i>
{% endif %}
{# <span>{{ oUIBlock.GetLabel() }}</span>#}
</a>
{% if oUIBlock.HasPopoverMenu() %}
{{ render_block(oUIBlock.GetPopoverMenu()) }}
{% endif %}

View File

@@ -0,0 +1 @@
{# Probably not needed, activity panel will handle interactions with popovermenu #}

View File

@@ -84,6 +84,13 @@
{{ render_block(oUIBlock.GetComposeMenu()) }}
{% endif %}
{% endif %}
{% if oUIBlock.HasActivityActions() %}
<div class="ibo-activity-panel--activity-actions" data-role="ibo-activity-panel--activity-actions">
{% for oActivityAction in oUIBlock.GetActivityActions() %}
{{ render_block(oActivityAction) }}
{% endfor %}
</div>
{% endif %}
<div class="ibo-activity-panel--body" data-role="ibo-activity-panel--body">
{% if oUIBlock.GetGroupedEntries()|length > 0 %}
{% for aEntryGroup in oUIBlock.GetGroupedEntries() %}

View File

@@ -6,6 +6,9 @@
{% if oUIBlock.HasGlobalSearch() %}
{{ render_block(oUIBlock.GetGlobalSearch(), {aPage: aPage}) }}
{% endif %}
{% for oAction in oUIBlock.GetActions() %}
{{ render_block(oAction, {aPage: aPage}) }}
{% endfor %}
</div>
{% if oUIBlock.HasBreadcrumbs() %}
{{ render_block(oUIBlock.GetBreadcrumbs(), {aPage: aPage}) }}

View File

@@ -0,0 +1 @@
{# Probably not needed, abstract #}

View File

@@ -0,0 +1,13 @@
<div id="{{ oUIBlock.GetId() }}"
data-role="ibo-top-bar-quick-action">
<div class="ibo-top-bar-quick-action--head" data-role="ibo-top-bar-quick-action--head">
<a href="{{ oUIBlock.GetURL() }}" class="ibo-top-bar-quick-action--icon" data-role="ibo-top-bar-quick-action--icon"
aria-label="{{ oUIBlock.GetTooltip() }}"
data-tooltip-content="{{ oUIBlock.GetTooltip() }}"
data-tooltip-placement="bottom-start"
data-tooltip-distance-offset="25"
>
<span class="{{ oUIBlock.GetIconClass() }}"></span>
</a>
</div>
</div>

View File

@@ -0,0 +1,4 @@
$('#{{ oUIBlock.GetId() }} [data-role="ibo-top-bar-quick-action--icon"]').on('click', function (event) {
event.preventDefault();
{{ oUIBlock.GetJsCode()|raw }}
});

View File

@@ -0,0 +1,4 @@
<div id="{{ oUIBlock.GetId() }}"
class="ibo-top-bar-quick-action ibo-top-bar-quick-action-separator"
data-role="ibo-top-bar-quick-action-separator">
</div>

View File

@@ -0,0 +1,14 @@
<div id="{{ oUIBlock.GetId() }}"
data-role="ibo-top-bar-quick-action">
<div class="ibo-top-bar-quick-action--head" data-role="ibo-top-bar-quick-action--head">
<a href="{{ oUIBlock.GetURL() }}" class="ibo-top-bar-quick-action--icon" data-role="ibo-top-bar-quick-action--icon"
target="{{ oUIBlock.GetTarget() }}"
aria-label="{{ oUIBlock.GetTooltip() }}"
data-tooltip-content="{{ oUIBlock.GetTooltip() }}"
data-tooltip-placement="bottom-start"
data-tooltip-distance-offset="25"
>
<span class="{{ oUIBlock.GetIconClass() }}"></span>
</a>
</div>
</div>

View File

@@ -0,0 +1 @@
{# Probably not needed #}