From 373942f922b8104b9dd3500e7c62a968e63b0eea Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Mon, 31 Aug 2026 16:03:36 +0200 Subject: [PATCH] =?UTF-8?q?=20N=C2=B09152=20-=20Add=20extensibility=20API?= =?UTF-8?q?=20endpoints=20for=20UI=20elements=20(#1017)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Co-authored-by: Molkobain Co-authored-by: Benjamin DALSASS --- application/applicationextension.inc.php | 16 +- .../ApplicationPopupMenuItem.php | 90 +- .../{backoffice => common}/JSButtonItem.php | 0 .../JSPopupMenuItem.php | 6 +- .../SeparatorPopupMenuItem.php | 8 +- .../{backoffice => common}/URLButtonItem.php | 0 .../URLPopupMenuItem.php | 6 +- .../common/iAriaAttributesProvider.php | 15 + .../common/iDataAttributesProvider.php | 14 + .../common/iGroupingProvider.php | 13 + .../common/iHtmlAttributesSource.php | 14 + .../iPopupMenuExtension.php | 19 +- application/cmdbabstract.class.inc.php | 9 + application/dashboard.class.inc.php | 4 +- application/displayblock.class.inc.php | 11 + application/forms.class.inc.php | 43 +- application/utils.inc.php | 7 +- core/config.class.inc.php | 8 + css/backoffice/components/_all.scss | 2 + css/backoffice/components/_button-bar.scss | 43 + .../components/_button-separator.scss | 20 + css/backoffice/components/_field.scss | 34 + css/backoffice/layout/_all.scss | 4 +- .../activity-panel/_activity-action.scss | 78 + .../activity-panel/_activity-panel.scss | 3 +- .../layout/activity-panel/_all.scss | 1 + .../activity-panel/_caselog-entry-form.scss | 5 + css/backoffice/layout/top-bar/_all.scss | 2 + .../layout/top-bar/_top-bar-action.scss | 40 + .../layout/{ => top-bar}/_top-bar.scss | 0 .../scss/scss-variables.scss | 2 + .../field/cs.dictionary.itop.field.php | 1 + .../field/da.dictionary.itop.field.php | 1 + .../field/de.dictionary.itop.field.php | 1 + .../field/en.dictionary.itop.field.php | 1 + .../field/en_gb.dictionary.itop.field.php | 1 + .../field/es_cr.dictionary.itop.field.php | 1 + .../field/fr.dictionary.itop.field.php | 1 + .../field/hu.dictionary.itop.field.php | 1 + .../field/it.dictionary.itop.field.php | 1 + .../field/ja.dictionary.itop.field.php | 1 + .../field/nl.dictionary.itop.field.php | 1 + .../field/pl.dictionary.itop.field.php | 1 + .../field/pt_br.dictionary.itop.field.php | 1 + .../field/ru.dictionary.itop.field.php | 1 + .../field/sk.dictionary.itop.field.php | 1 + .../field/tr.dictionary.itop.field.php | 1 + .../field/zh_cn.dictionary.itop.field.php | 1 + js/components/button-bar.js | 308 +++ js/dashboard.js | 2 +- js/layouts/activity-panel/activity-panel.js | 34 + js/property_field.js | 1 + lib/composer/autoload_classmap.php | 37 +- lib/composer/autoload_static.php | 37 +- .../UI/Base/Common/Action/tActionCommon.php | 82 + .../UI/Base/Common/Action/tActionJs.php | 58 + .../UI/Base/Common/Action/tActionURL.php | 58 + .../Base/Common/Metadata/AriaAttributes.php | 65 + .../Base/Common/Metadata/DataAttributes.php | 82 + .../UI/Base/Common/Metadata/Grouping.php | 24 + .../UI/Base/Component/Button/Button.php | 19 + .../Base/Component/Button/ButtonSeparator.php | 37 + .../Component/Button/ButtonUIBlockFactory.php | 130 ++ .../UI/Base/Component/ButtonBar/ButtonBar.php | 292 +++ .../ButtonBar/ButtonBarUIBlockFactory.php | 135 ++ .../UI/Base/Component/Field/Field.php | 58 +- .../Component/Field/FieldUIBlockFactory.php | 38 + .../PopoverMenuItem/PopoverMenuItem.php | 25 + .../PopoverMenuItemFactory.php | 60 + .../Layout/ActivityPanel/ActivityPanel.php | 28 +- .../ActivityPanelAction.php | 130 ++ .../ActivityPanelActionFactory.php | 149 ++ .../ActivityPanelActionJS.php | 42 + .../ActivityPanelActionSeparator.php | 42 + .../ActivityPanelActionURL.php | 42 + .../ActivityPanel/ActivityPanelFactory.php | 51 + .../CaseLogEntryForm/CaseLogEntryForm.php | 40 +- .../CaseLogEntryFormFactory.php | 29 + .../UI/Base/Layout/TopBar/TopBar.php | 40 +- .../UI/Base/Layout/TopBar/TopBarFactory.php | 14 + .../TopBarQuickAction/TopBarQuickAction.php | 92 + .../TopBarQuickActionFactory.php | 74 + .../TopBarQuickAction/TopBarQuickActionJS.php | 38 + .../TopBarQuickActionSeparator.php | 45 + .../TopBarQuickActionURL.php | 36 + sources/Application/UI/Base/UIBlock.php | 63 + .../components/button-bar/layout.html.twig | 19 + .../base/components/button/buttonjs.html.twig | 10 +- .../components/button/buttonurl.html.twig | 10 +- .../base/components/button/layout.html.twig | 12 +- .../button/separator/layout.html.twig | 15 + .../base/components/field/layout.html.twig | 12 +- .../popover-menu/item/mode_js.html.twig | 5 + .../item/mode_separator.html.twig | 5 + .../popover-menu/item/mode_url.html.twig | 6 +- .../activity-action-js.html.twig | 27 + .../activity-action-separator.html.twig | 5 + .../activity-action-url.html.twig | 27 + .../caselog-entry-form/layout.html.twig | 4 +- .../layouts/activity-panel/layout.html.twig | 7 + .../base/layouts/top-bar/layout.html.twig | 3 + .../top-bar-quick-action/layout.html.twig | 31 + .../top-bar-quick-action-js.html.twig | 1 + .../top-bar-quick-action-js.js.twig | 4 + .../top-bar-quick-action-separator.html.twig | 16 + .../top-bar-quick-action-url.html.twig | 5 + .../Backoffice/RenderAllUiBlocks.php | 1704 ++++++++++++----- 107 files changed, 4425 insertions(+), 574 deletions(-) rename application/applicationextension/{backoffice => common}/ApplicationPopupMenuItem.php (52%) rename application/applicationextension/{backoffice => common}/JSButtonItem.php (100%) rename application/applicationextension/{backoffice => common}/JSPopupMenuItem.php (90%) rename application/applicationextension/{backoffice => common}/SeparatorPopupMenuItem.php (72%) rename application/applicationextension/{backoffice => common}/URLButtonItem.php (100%) rename application/applicationextension/{backoffice => common}/URLPopupMenuItem.php (86%) create mode 100644 application/applicationextension/common/iAriaAttributesProvider.php create mode 100644 application/applicationextension/common/iDataAttributesProvider.php create mode 100644 application/applicationextension/common/iGroupingProvider.php create mode 100644 application/applicationextension/common/iHtmlAttributesSource.php rename application/applicationextension/{backoffice => common}/iPopupMenuExtension.php (80%) create mode 100644 css/backoffice/components/_button-bar.scss create mode 100644 css/backoffice/components/_button-separator.scss create mode 100644 css/backoffice/layout/activity-panel/_activity-action.scss create mode 100644 css/backoffice/layout/top-bar/_all.scss create mode 100644 css/backoffice/layout/top-bar/_top-bar-action.scss rename css/backoffice/layout/{ => top-bar}/_top-bar.scss (100%) create mode 100644 js/components/button-bar.js create mode 100644 sources/Application/UI/Base/Common/Action/tActionCommon.php create mode 100644 sources/Application/UI/Base/Common/Action/tActionJs.php create mode 100644 sources/Application/UI/Base/Common/Action/tActionURL.php create mode 100644 sources/Application/UI/Base/Common/Metadata/AriaAttributes.php create mode 100644 sources/Application/UI/Base/Common/Metadata/DataAttributes.php create mode 100644 sources/Application/UI/Base/Common/Metadata/Grouping.php create mode 100644 sources/Application/UI/Base/Component/Button/ButtonSeparator.php create mode 100644 sources/Application/UI/Base/Component/ButtonBar/ButtonBar.php create mode 100644 sources/Application/UI/Base/Component/ButtonBar/ButtonBarUIBlockFactory.php create mode 100644 sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelAction.php create mode 100644 sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionFactory.php create mode 100644 sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionJS.php create mode 100644 sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionSeparator.php create mode 100644 sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionURL.php create mode 100644 sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickAction.php create mode 100644 sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionFactory.php create mode 100644 sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionJS.php create mode 100644 sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionSeparator.php create mode 100644 sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionURL.php create mode 100644 templates/base/components/button-bar/layout.html.twig create mode 100644 templates/base/components/button/separator/layout.html.twig create mode 100644 templates/base/layouts/activity-panel/activity-action/activity-action-js.html.twig create mode 100644 templates/base/layouts/activity-panel/activity-action/activity-action-separator.html.twig create mode 100644 templates/base/layouts/activity-panel/activity-action/activity-action-url.html.twig create mode 100644 templates/base/layouts/top-bar/top-bar-quick-action/layout.html.twig create mode 100644 templates/base/layouts/top-bar/top-bar-quick-action/top-bar-quick-action-js.html.twig create mode 100644 templates/base/layouts/top-bar/top-bar-quick-action/top-bar-quick-action-js.js.twig create mode 100644 templates/base/layouts/top-bar/top-bar-quick-action/top-bar-quick-action-separator.html.twig create mode 100644 templates/base/layouts/top-bar/top-bar-quick-action/top-bar-quick-action-url.html.twig diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php index 5759db1a60..d1a66149dc 100644 --- a/application/applicationextension.inc.php +++ b/application/applicationextension.inc.php @@ -30,6 +30,15 @@ require_once(APPROOT.'application/newsroomprovider.class.inc.php'); * @license http://opensource.org/licenses/AGPL-3.0 * @since 2.7.0 */ + +require_once(APPROOT.'application/applicationextension/common/ApplicationPopupMenuItem.php'); +require_once(APPROOT.'application/applicationextension/common/iPopupMenuExtension.php'); +require_once(APPROOT.'application/applicationextension/common/JSButtonItem.php'); +require_once(APPROOT.'application/applicationextension/common/JSPopupMenuItem.php'); +require_once(APPROOT.'application/applicationextension/common/SeparatorPopupMenuItem.php'); +require_once(APPROOT.'application/applicationextension/common/URLButtonItem.php'); +require_once(APPROOT.'application/applicationextension/common/URLPopupMenuItem.php'); + require_once(APPROOT.'application/applicationextension/backoffice/iApplicationUIExtension.php'); require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeDictEntriesExtension.php'); require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeDictEntriesPrefixesExtension.php'); @@ -43,19 +52,12 @@ require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeScr require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeStyleExtension.php'); require_once(APPROOT.'application/applicationextension/backoffice/iFieldRendererMappingsExtension.php'); require_once(APPROOT.'application/applicationextension/backoffice/iPageUIBlockExtension.php'); -require_once(APPROOT.'application/applicationextension/backoffice/iPopupMenuExtension.php'); require_once(APPROOT.'application/applicationextension/backoffice/iPreferencesExtension.php'); require_once(APPROOT.'application/applicationextension/backoffice/iWelcomePopupExtension.php'); require_once(APPROOT.'application/applicationextension/backoffice/AbstractApplicationUIExtension.php'); require_once(APPROOT.'application/applicationextension/backoffice/AbstractPageUIBlockExtension.php'); require_once(APPROOT.'application/applicationextension/backoffice/AbstractPreferencesExtension.php'); require_once(APPROOT.'application/applicationextension/backoffice/AbstractWelcomePopupExtension.php'); -require_once(APPROOT.'application/applicationextension/backoffice/ApplicationPopupMenuItem.php'); -require_once(APPROOT.'application/applicationextension/backoffice/JSButtonItem.php'); -require_once(APPROOT.'application/applicationextension/backoffice/JSPopupMenuItem.php'); -require_once(APPROOT.'application/applicationextension/backoffice/SeparatorPopupMenuItem.php'); -require_once(APPROOT.'application/applicationextension/backoffice/URLButtonItem.php'); -require_once(APPROOT.'application/applicationextension/backoffice/URLPopupMenuItem.php'); require_once(APPROOT.'application/applicationextension/iBackupExtraFilesExtension.php'); require_once(APPROOT.'application/applicationextension/iKPILoggerExtension.php'); diff --git a/application/applicationextension/backoffice/ApplicationPopupMenuItem.php b/application/applicationextension/common/ApplicationPopupMenuItem.php similarity index 52% rename from application/applicationextension/backoffice/ApplicationPopupMenuItem.php rename to application/applicationextension/common/ApplicationPopupMenuItem.php index 0a1baaf6ba..f0ecaccbe6 100644 --- a/application/applicationextension/backoffice/ApplicationPopupMenuItem.php +++ b/application/applicationextension/common/ApplicationPopupMenuItem.php @@ -1,5 +1,9 @@ sLabel; } + /** + * @param string $sLabel + * + * @api + * @since 3.3.0 + */ + public function SetLabel(string $sLabel): void + { + $this->sLabel = $sLabel; + } + /** * Get the CSS classes * @@ -141,9 +162,76 @@ abstract class ApplicationPopupMenuItem */ abstract public function GetMenuItem(); + /** + * Get additional data, child classes can use this without having to worry about this class interfaces + * @return array + * @since 3.3.0 + */ + protected function GetMenuItemAdditionalData(): array + { + $aAdditionalData = []; + + $aDataAttributes = $this->GetDataAttributes()->GetAttributes(); + if (!empty($aDataAttributes)) { + $aAdditionalData['data_attributes'] = $aDataAttributes; + } + + $aAriaAttributes = $this->GetAriaAttributes()->GetAttributes(); + if (!empty($aAriaAttributes)) { + $aAdditionalData['aria_attributes'] = $aAriaAttributes; + } + + $oGrouping = $this->GetGrouping(); + if ($oGrouping !== null) { + $aAdditionalData['grouping_uid'] = $oGrouping->GetUID(); + } + + return $aAdditionalData; + } + /** @ignore */ public function GetLinkedScripts() { return []; } + + /** + * @api + * @since 3.3.0 + */ + public function GetAriaAttributes(): AriaAttributes + { + return $this->oAriaAttributes ??= new AriaAttributes(); + } + + /** + * @api + * @since 3.3.0 + */ + public function GetDataAttributes(): DataAttributes + { + return $this->oDataAttributes ??= new DataAttributes(); + } + + /** + * @api + * @since 3.3.0 + */ + public function GetGrouping(): ?Grouping + { + return $this->oGrouping; + } + + /** + * @param Grouping|null $oGrouping + * @return static + * @api + * @since 3.3.0 + */ + public function SetGrouping(?Grouping $oGrouping): static + { + $this->oGrouping = $oGrouping; + + return $this; + } } diff --git a/application/applicationextension/backoffice/JSButtonItem.php b/application/applicationextension/common/JSButtonItem.php similarity index 100% rename from application/applicationextension/backoffice/JSButtonItem.php rename to application/applicationextension/common/JSButtonItem.php diff --git a/application/applicationextension/backoffice/JSPopupMenuItem.php b/application/applicationextension/common/JSPopupMenuItem.php similarity index 90% rename from application/applicationextension/backoffice/JSPopupMenuItem.php rename to application/applicationextension/common/JSPopupMenuItem.php index 03f68af617..b3308e53b2 100644 --- a/application/applicationextension/backoffice/JSPopupMenuItem.php +++ b/application/applicationextension/common/JSPopupMenuItem.php @@ -3,7 +3,7 @@ /** * Class for adding an item into a popup menu that triggers some Javascript code * - * Note: This works only in the backoffice, {@see \JSButtonItem} for the end-user portal + * Note: This works only in the backoffice, {@see \common\JSButtonItem} for the end-user portal * * @api * @package UIExtensibilityAPI @@ -40,7 +40,7 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem public function GetMenuItem() { // Note: the semicolumn is a must here! - return [ + $aData = [ 'label' => $this->GetLabel(), 'onclick' => $this->GetJsCode().'; return false;', 'url' => $this->GetUrl(), @@ -48,6 +48,8 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem 'icon_class' => $this->sIconClass, 'tooltip' => $this->sTooltip, ]; + + return array_merge($aData, $this->GetMenuItemAdditionalData()); } /** @ignore */ diff --git a/application/applicationextension/backoffice/SeparatorPopupMenuItem.php b/application/applicationextension/common/SeparatorPopupMenuItem.php similarity index 72% rename from application/applicationextension/backoffice/SeparatorPopupMenuItem.php rename to application/applicationextension/common/SeparatorPopupMenuItem.php index 3be00a0f78..f1cf27c9a1 100644 --- a/application/applicationextension/backoffice/SeparatorPopupMenuItem.php +++ b/application/applicationextension/common/SeparatorPopupMenuItem.php @@ -24,6 +24,12 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem /** @ignore */ public function GetMenuItem() { - return ['label' => '', 'url' => '', 'css_classes' => $this->aCssClasses]; + $aData = [ + 'label' => '', + 'url' => '', + 'css_classes' => $this->aCssClasses, + ]; + + return array_merge($aData, $this->GetMenuItemAdditionalData()); } } diff --git a/application/applicationextension/backoffice/URLButtonItem.php b/application/applicationextension/common/URLButtonItem.php similarity index 100% rename from application/applicationextension/backoffice/URLButtonItem.php rename to application/applicationextension/common/URLButtonItem.php diff --git a/application/applicationextension/backoffice/URLPopupMenuItem.php b/application/applicationextension/common/URLPopupMenuItem.php similarity index 86% rename from application/applicationextension/backoffice/URLPopupMenuItem.php rename to application/applicationextension/common/URLPopupMenuItem.php index bd330bf6a5..4276627fda 100644 --- a/application/applicationextension/backoffice/URLPopupMenuItem.php +++ b/application/applicationextension/common/URLPopupMenuItem.php @@ -3,7 +3,7 @@ /** * Class for adding an item into a popup menu that browses to the given URL * - * Note: This works only in the backoffice, {@see \URLButtonItem} for the end-user portal + * Note: This works only in the backoffice, {@see \common\URLButtonItem} for the end-user portal * * @api * @package UIExtensibilityAPI @@ -35,13 +35,15 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem /** @ignore */ public function GetMenuItem() { - return ['label' => $this->GetLabel(), + $aData = ['label' => $this->GetLabel(), 'url' => $this->GetUrl(), 'target' => $this->GetTarget(), 'css_classes' => $this->aCssClasses, 'icon_class' => $this->sIconClass, 'tooltip' => $this->sTooltip, ]; + + return array_merge($aData, $this->GetMenuItemAdditionalData()); } /** @ignore */ diff --git a/application/applicationextension/common/iAriaAttributesProvider.php b/application/applicationextension/common/iAriaAttributesProvider.php new file mode 100644 index 0000000000..8f7298706b --- /dev/null +++ b/application/applicationextension/common/iAriaAttributesProvider.php @@ -0,0 +1,15 @@ + Attribute name => value, ready to be escaped and printed */ + public function GetHtmlAttributes(): array; +} diff --git a/application/applicationextension/backoffice/iPopupMenuExtension.php b/application/applicationextension/common/iPopupMenuExtension.php similarity index 80% rename from application/applicationextension/backoffice/iPopupMenuExtension.php rename to application/applicationextension/common/iPopupMenuExtension.php index 0f9f507ff6..87a28efb6a 100644 --- a/application/applicationextension/backoffice/iPopupMenuExtension.php +++ b/application/applicationextension/common/iPopupMenuExtension.php @@ -95,7 +95,24 @@ 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 an array: [] for now, in a distant future we want to give a complete context + * @since 3.3.0 + */ + 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] + * @since 3.3.0 + */ + 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 is an array: ['object' => DBObject, caselog_att_code => caselog attribute code or 'activity'] + * @since 3.3.0 + */ + public const MENU_OBJDETAILS_ACTIVITY_PANEL_ACTIONS = 13; /** * Get the list of items to be added to a menu. * diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 9836c31027..dda98b793f 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -49,6 +49,7 @@ use Combodo\iTop\Application\WebPage\WebPage; use Combodo\iTop\Renderer\BlockRenderer; use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer; use Combodo\iTop\Renderer\Console\ConsoleFormRenderer; +use Combodo\iTop\Service\InterfaceDiscovery\InterfaceDiscovery; use Combodo\iTop\Service\Links\LinkSetDataTransformer; use Combodo\iTop\Service\Links\LinkSetModel; use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectHelper; @@ -1065,6 +1066,14 @@ HTML } } $val['value_raw'] = ($bExcludeRawValue === false) ? $this->Get($sAttCode) : ''; + $val['actions'] = []; + + /** @var \iPopupMenuExtension $oExtensionInstance */ + foreach (InterfaceDiscovery::GetInstance()->FindItopClasses('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); diff --git a/application/dashboard.class.inc.php b/application/dashboard.class.inc.php index 967fc92ad5..b42f29223d 100644 --- a/application/dashboard.class.inc.php +++ b/application/dashboard.class.inc.php @@ -586,7 +586,7 @@ JS /** @var \Dashlet $oDashlet */ foreach ($aCell as $oDashlet) { if ($oDashlet->IsVisible()) { - $oPage->add('