diff --git a/application/applicationextension/common/ApplicationPopupMenuItem.php b/application/applicationextension/common/ApplicationPopupMenuItem.php
index 17972cf60e..f0ecaccbe6 100644
--- a/application/applicationextension/common/ApplicationPopupMenuItem.php
+++ b/application/applicationextension/common/ApplicationPopupMenuItem.php
@@ -1,5 +1,9 @@
+ * @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/common/JSPopupMenuItem.php b/application/applicationextension/common/JSPopupMenuItem.php
index 7e1bea2443..b3308e53b2 100644
--- a/application/applicationextension/common/JSPopupMenuItem.php
+++ b/application/applicationextension/common/JSPopupMenuItem.php
@@ -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/common/SeparatorPopupMenuItem.php b/application/applicationextension/common/SeparatorPopupMenuItem.php
index 3be00a0f78..f1cf27c9a1 100644
--- a/application/applicationextension/common/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/common/URLPopupMenuItem.php b/application/applicationextension/common/URLPopupMenuItem.php
index adf737f56b..4276627fda 100644
--- a/application/applicationextension/common/URLPopupMenuItem.php
+++ b/application/applicationextension/common/URLPopupMenuItem.php
@@ -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/displayblock.class.inc.php b/application/displayblock.class.inc.php
index b7940b90dc..e4da37dfac 100644
--- a/application/displayblock.class.inc.php
+++ b/application/displayblock.class.inc.php
@@ -2292,6 +2292,17 @@ class MenuBlock extends DisplayBlock
}
// ResourceId should not be sanitized
$oActionButton->AddDataAttribute('resource-id', $sActionId);
+ if (isset($aAction['data_attributes']) && is_array($aAction['data_attributes'])) {
+ foreach ($aAction['data_attributes'] as $sName => $sValue) {
+ $sDataAttributeName = $sName;
+ $oActionButton->AddDataAttribute($sDataAttributeName, (string) $sValue);
+ }
+ }
+ if (isset($aAction['aria_attributes']) && is_array($aAction['aria_attributes'])) {
+ foreach ($aAction['aria_attributes'] as $sName => $sValue) {
+ $oActionButton->AddAriaAttribute((string) $sName, (string) $sValue);
+ }
+ }
$oActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
if (empty($sLabel)) {
if (empty($aAction['tooltip'])) {
diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php
index b54be5396b..253375f5f3 100644
--- a/lib/composer/autoload_classmap.php
+++ b/lib/composer/autoload_classmap.php
@@ -160,6 +160,9 @@ return array(
'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\\Common\\Metadata\\AriaAttributes' => $baseDir . '/sources/Application/UI/Base/Common/Metadata/AriaAttributes.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Common\\Metadata\\DataAttributes' => $baseDir . '/sources/Application/UI/Base/Common/Metadata/DataAttributes.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Common\\Metadata\\Grouping' => $baseDir . '/sources/Application/UI/Base/Common/Metadata/Grouping.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',
@@ -225,8 +228,6 @@ return array(
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => $baseDir . '/sources/Application/UI/Base/Component/Input/tInputLabel.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => $baseDir . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Modal\\DoNotShowAgainOptionBlock' => $baseDir . '/sources/Application/UI/Base/Component/Modal/DoNotShowAgainOptionBlock.php',
- 'Combodo\\iTop\\Application\\UI\\Base\\Component\\OverflowLine\\OverflowLine' => $baseDir . '/sources/Application/UI/Base/Component/OverflowLine/OverflowLine.php',
- 'Combodo\\iTop\\Application\\UI\\Base\\Component\\OverflowLine\\OverflowLineUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/OverflowLine/OverflowLineUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Panel\\Panel' => $baseDir . '/sources/Application/UI/Base/Component/Panel/Panel.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Panel\\PanelUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Panel/PanelUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Pill\\Pill' => $baseDir . '/sources/Application/UI/Base/Component/Pill/Pill.php',
@@ -3967,6 +3968,7 @@ return array(
'cmdbAbstractObject' => $baseDir . '/application/cmdbabstract.class.inc.php',
'cmdbDataGenerator' => $baseDir . '/core/data.generator.class.inc.php',
'iApplicationUIExtension' => $baseDir . '/application/applicationextension/backoffice/iApplicationUIExtension.php',
+ 'iAriaAttributesProvider' => $baseDir . '/application/applicationextension/common/iAriaAttributesProvider.php',
'iBackgroundProcess' => $baseDir . '/core/backgroundprocess.inc.php',
'iBackofficeDictEntriesExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeDictEntriesExtension.php',
'iBackofficeDictEntriesPrefixesExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeDictEntriesPrefixesExtension.php',
@@ -3982,8 +3984,11 @@ return array(
'iCMDBChangeOp' => $baseDir . '/core/cmdbchangeop.class.inc.php',
'iDBObjectSetIterator' => $baseDir . '/core/dbobjectiterator.php',
'iDBObjectURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php',
+ 'iDataAttributesProvider' => $baseDir . '/application/applicationextension/common/iDataAttributesProvider.php',
'iDisplay' => $baseDir . '/core/dbobject.class.php',
'iFieldRendererMappingsExtension' => $baseDir . '/application/applicationextension/backoffice/iFieldRendererMappingsExtension.php',
+ 'iGroupingProvider' => $baseDir . '/application/applicationextension/common/iGroupingProvider.php',
+ 'iHtmlAttributesSource' => $baseDir . '/application/applicationextension/common/iHtmlAttributesSource.php',
'iKPILoggerExtension' => $baseDir . '/application/applicationextension/iKPILoggerExtension.php',
'iLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php',
'iLoginExtension' => $baseDir . '/application/applicationextension/login/iLoginExtension.php',
@@ -4028,5 +4033,5 @@ return array(
'privUITransactionFile' => $baseDir . '/application/transaction.class.inc.php',
'privUITransactionSession' => $baseDir . '/application/transaction.class.inc.php',
'utils' => $baseDir . '/application/utils.inc.php',
- '�' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
+ '©' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
);
diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php
index bd53a856b3..eb7bd78b88 100644
--- a/lib/composer/autoload_static.php
+++ b/lib/composer/autoload_static.php
@@ -561,6 +561,9 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
'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\\Common\\Metadata\\AriaAttributes' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Common/Metadata/AriaAttributes.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Common\\Metadata\\DataAttributes' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Common/Metadata/DataAttributes.php',
+ 'Combodo\\iTop\\Application\\UI\\Base\\Common\\Metadata\\Grouping' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Common/Metadata/Grouping.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',
@@ -626,8 +629,6 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/tInputLabel.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Modal\\DoNotShowAgainOptionBlock' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Modal/DoNotShowAgainOptionBlock.php',
- 'Combodo\\iTop\\Application\\UI\\Base\\Component\\OverflowLine\\OverflowLine' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/OverflowLine/OverflowLine.php',
- 'Combodo\\iTop\\Application\\UI\\Base\\Component\\OverflowLine\\OverflowLineUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/OverflowLine/OverflowLineUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Panel\\Panel' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Panel/Panel.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Panel\\PanelUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Panel/PanelUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Pill\\Pill' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Pill/Pill.php',
@@ -4368,6 +4369,7 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
'cmdbAbstractObject' => __DIR__ . '/../..' . '/application/cmdbabstract.class.inc.php',
'cmdbDataGenerator' => __DIR__ . '/../..' . '/core/data.generator.class.inc.php',
'iApplicationUIExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iApplicationUIExtension.php',
+ 'iAriaAttributesProvider' => __DIR__ . '/../..' . '/application/applicationextension/common/iAriaAttributesProvider.php',
'iBackgroundProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
'iBackofficeDictEntriesExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeDictEntriesExtension.php',
'iBackofficeDictEntriesPrefixesExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeDictEntriesPrefixesExtension.php',
@@ -4383,8 +4385,11 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
'iCMDBChangeOp' => __DIR__ . '/../..' . '/core/cmdbchangeop.class.inc.php',
'iDBObjectSetIterator' => __DIR__ . '/../..' . '/core/dbobjectiterator.php',
'iDBObjectURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php',
+ 'iDataAttributesProvider' => __DIR__ . '/../..' . '/application/applicationextension/common/iDataAttributesProvider.php',
'iDisplay' => __DIR__ . '/../..' . '/core/dbobject.class.php',
'iFieldRendererMappingsExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iFieldRendererMappingsExtension.php',
+ 'iGroupingProvider' => __DIR__ . '/../..' . '/application/applicationextension/common/iGroupingProvider.php',
+ 'iHtmlAttributesSource' => __DIR__ . '/../..' . '/application/applicationextension/common/iHtmlAttributesSource.php',
'iKPILoggerExtension' => __DIR__ . '/../..' . '/application/applicationextension/iKPILoggerExtension.php',
'iLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php',
'iLoginExtension' => __DIR__ . '/../..' . '/application/applicationextension/login/iLoginExtension.php',
@@ -4429,7 +4434,7 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
'privUITransactionFile' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
'privUITransactionSession' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
'utils' => __DIR__ . '/../..' . '/application/utils.inc.php',
- '�' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
+ '©' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
);
public static function getInitializer(ClassLoader $loader)
diff --git a/sources/Application/UI/Base/Common/Metadata/AriaAttributes.php b/sources/Application/UI/Base/Common/Metadata/AriaAttributes.php
new file mode 100644
index 0000000000..6c7e31c641
--- /dev/null
+++ b/sources/Application/UI/Base/Common/Metadata/AriaAttributes.php
@@ -0,0 +1,65 @@
+sLabel = $sLabel;
+
+ return $this;
+ }
+
+ public function GetLabel(): ?string
+ {
+ return $this->sLabel;
+ }
+
+ /**
+ * Set several attributes at once, by ARIA attribute name ("label", "description", ...)
+ *
+ * @param array $aAttributes
+ * @since 3.3.0
+ */
+ public function SetMultiple(array $aAttributes): self
+ {
+ foreach ($aAttributes as $sName => $sValue) {
+ $sMethod = 'Set'.$sName;
+ if ($sMethod !== 'SetMultiple' && method_exists($this, $sMethod)) {
+ $this->$sMethod($sValue);
+ }
+ }
+
+ return $this;
+ }
+
+ public function GetAttributes(): array
+ {
+ $aAttributes = [];
+ if ($this->sLabel !== null) {
+ $aAttributes['label'] = $this->sLabel;
+ }
+
+ return $aAttributes;
+ }
+
+ public function GetHtmlAttributes(): array
+ {
+ $aAttributes = [];
+ if ($this->sLabel !== null) {
+ $aAttributes['aria-label'] = $this->sLabel;
+ }
+
+ return $aAttributes;
+ }
+}
diff --git a/sources/Application/UI/Base/Common/Metadata/DataAttributes.php b/sources/Application/UI/Base/Common/Metadata/DataAttributes.php
new file mode 100644
index 0000000000..095b8d44b3
--- /dev/null
+++ b/sources/Application/UI/Base/Common/Metadata/DataAttributes.php
@@ -0,0 +1,82 @@
+ */
+ private array $aValues = [];
+
+ public function Set(string $sName, string $sValue): self
+ {
+ $this->aValues[static::NormalizeName($sName)] = $sValue;
+
+ return $this;
+ }
+
+ /** @param array $aValues */
+ public function SetMultiple(array $aValues): self
+ {
+ foreach ($aValues as $sName => $sValue) {
+ $this->Set($sName, $sValue);
+ }
+
+ return $this;
+ }
+
+ public function Get(string $sName): ?string
+ {
+ return $this->aValues[static::NormalizeName($sName)] ?? null;
+ }
+
+ public function Has(string $sName): bool
+ {
+ return array_key_exists(static::NormalizeName($sName), $this->aValues);
+ }
+
+ public function Remove(string $sName): self
+ {
+ unset($this->aValues[static::NormalizeName($sName)]);
+
+ return $this;
+ }
+
+ /** @return array Names without the "data-" prefix */
+ public function GetAttributes(): array
+ {
+ return $this->aValues;
+ }
+
+ public function GetHtmlAttributes(): array
+ {
+ $aAttributes = [];
+ foreach ($this->aValues as $sName => $sValue) {
+ $aAttributes['data-'.$sName] = $sValue;
+ }
+
+ return $aAttributes;
+ }
+
+ /** @throws \InvalidArgumentException */
+ private static function NormalizeName(string $sName): string
+ {
+ $sNormalized = strtolower(trim($sName));
+ if (str_starts_with($sNormalized, 'data-')) {
+ $sNormalized = substr($sNormalized, 5);
+ }
+ if (preg_match(static::NAME_PATTERN, $sNormalized) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Invalid data attribute name "%s"', $sName));
+ }
+
+ return $sNormalized;
+ }
+}
diff --git a/sources/Application/UI/Base/Common/Metadata/Grouping.php b/sources/Application/UI/Base/Common/Metadata/Grouping.php
new file mode 100644
index 0000000000..ad6ff28e72
--- /dev/null
+++ b/sources/Application/UI/Base/Common/Metadata/Grouping.php
@@ -0,0 +1,24 @@
+sUID = $sUID;
+ }
+
+ public function GetUID(): string
+ {
+ return $this->sUID;
+ }
+}
diff --git a/sources/Application/UI/Base/Component/Button/Button.php b/sources/Application/UI/Base/Component/Button/Button.php
index 7921aa5664..ac814ae271 100644
--- a/sources/Application/UI/Base/Component/Button/Button.php
+++ b/sources/Application/UI/Base/Component/Button/Button.php
@@ -21,6 +21,7 @@
namespace Combodo\iTop\Application\UI\Base\Component\Button;
use Combodo\iTop\Application\UI\Base\UIBlock;
+use utils;
/**
* Class Button
@@ -271,4 +272,20 @@ class Button extends UIBlock
return $this;
}
+
+ public function GetAriaAttributes(): array
+ {
+ $aDefaultValues = [];
+
+ // Default aria-label is the tooltip
+ if (utils::IsNotNullOrEmptyString($this->sTooltip)) {
+ $aDefaultValues['label'] = $this->sTooltip;
+ }
+ return array_merge($aDefaultValues, parent::GetAriaAttributes());
+ }
+
+ public function HasAriaAttributes(): bool
+ {
+ return !empty($this->GetAriaAttributes());
+ }
}
diff --git a/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php b/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php
index e688bb8752..37a9de0295 100644
--- a/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php
+++ b/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php
@@ -407,6 +407,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
}
$oButton->SetActionType($sActionType);
$oButton->SetColor($sColor);
+ $oButton->SetAriaAttributes($oPopupItem->GetAriaAttributes()->GetAttributes());
+ $oButton->SetDataAttributes($oPopupItem->GetDataAttributes()->GetAttributes());
return $oButton;
} elseif ($oPopupItem instanceof URLPopupMenuItem) {
@@ -424,6 +426,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
$oButton->SetActionType($sActionType);
$oButton->SetColor($sColor);
+ $oButton->SetAriaAttributes($oPopupItem->GetAriaAttributes()->GetAttributes());
+ $oButton->SetDataAttributes($oPopupItem->GetDataAttributes()->GetAttributes());
return $oButton;
} elseif ($oPopupItem instanceof SeparatorPopupMenuItem) {
@@ -460,6 +464,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
if (utils::IsNotNullOrEmptyString($oPopupItem->GetTooltip())) {
$oButton->SetTooltip($oPopupItem->GetTooltip());
}
+ $oButton->SetAriaAttributes($oPopupItem->GetAriaAttributes()->GetAttributes());
+ $oButton->SetDataAttributes($oPopupItem->GetDataAttributes()->GetAttributes());
$oButton->SetOnClickJsCode($oPopupItem->GetJsCode());
@@ -486,6 +492,8 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory
if (utils::IsNotNullOrEmptyString($oPopupItem->GetTarget())) {
$oButton->SetTarget($oPopupItem->GetTarget());
}
+ $oButton->SetAriaAttributes($oPopupItem->GetAriaAttributes()->GetAttributes());
+ $oButton->SetDataAttributes($oPopupItem->GetDataAttributes()->GetAttributes());
return $oButton;
} elseif ($oPopupItem instanceof SeparatorPopupMenuItem) {
diff --git a/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItem.php b/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItem.php
index 9e07716b21..3d0a6f0813 100644
--- a/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItem.php
+++ b/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItem.php
@@ -159,4 +159,29 @@ class PopoverMenuItem extends UIBlock
return $this->oPopupMenuItem->GetUID();
}
+ public function GetDataAttributes(): array
+ {
+ // Merge attributes from the embedded popup item and from the UI block itself
+ return array_merge($this->oPopupMenuItem->GetDataAttributes()->GetAttributes(), parent::GetDataAttributes());
+ }
+
+ public function HasDataAttributes(): bool
+ {
+ return !empty($this->GetDataAttributes());
+ }
+
+ public function GetAriaAttributes(): array
+ {
+ return array_merge($this->oPopupMenuItem->GetAriaAttributes()->GetAttributes(), parent::GetAriaAttributes());
+ }
+
+ public function HasAriaAttribute(string $sName): bool
+ {
+ return array_key_exists($sName, $this->GetAriaAttributes());
+ }
+
+ public function HasAriaAttributes(): bool
+ {
+ return !empty($this->GetAriaAttributes());
+ }
}
diff --git a/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItemFactory.php b/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItemFactory.php
index aea86c2ade..445c6dc530 100644
--- a/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItemFactory.php
+++ b/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItemFactory.php
@@ -88,6 +88,8 @@ class PopoverMenuItemFactory
'label' => $aActionData['label'],
'icon_class' => isset($aActionData['icon_class']) ? $aActionData['icon_class'] : '',
'tooltip' => isset($aActionData['tooltip']) ? $aActionData['tooltip'] : '',
+ 'data_attributes' => isset($aActionData['data_attributes']) && is_array($aActionData['data_attributes']) ? $aActionData['data_attributes'] : [],
+ 'aria_attributes' => isset($aActionData['aria_attributes']) && is_array($aActionData['aria_attributes']) ? $aActionData['aria_attributes'] : [],
];
// Avoid meaningless tooltips which are identical to the label
@@ -128,6 +130,12 @@ class PopoverMenuItemFactory
if (!empty($aRefactoredItem['tooltip'])) {
$oPopoverMenuItem->SetTooltip($aRefactoredItem['tooltip']);
}
+ if (!empty($aRefactoredItem['data_attributes'])) {
+ $oPopoverMenuItem->SetDataAttributes($aRefactoredItem['data_attributes']);
+ }
+ if (!empty($aRefactoredItem['aria_attributes'])) {
+ $oPopoverMenuItem->SetAriaAttributes($aRefactoredItem['aria_attributes']);
+ }
return $oPopoverMenuItem;
}
diff --git a/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelAction.php b/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelAction.php
index ad5670c857..c11952e10b 100644
--- a/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelAction.php
+++ b/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelAction.php
@@ -23,6 +23,7 @@ namespace Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanelAct
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;
+use utils;
/**
* Class ActivityPanelAction
@@ -93,6 +94,22 @@ abstract class ActivityPanelAction extends UIBlock
return $this->oPopoverMenu !== null;
}
+ public function GetAriaAttributes(): array
+ {
+ $aDefaultValues = [];
+ // Default value for aria-label is the tooltip
+ if (utils::IsNotNullOrEmptyString($this->sTooltip)) {
+ $aDefaultValues['label'] = $this->sTooltip;
+ }
+
+ return array_merge($aDefaultValues, parent::GetAriaAttributes());
+ }
+
+ public function HasAriaAttributes(): bool
+ {
+ return !empty($this->GetAriaAttributes());
+ }
+
public function GetSubBlocks(): array
{
if (false === $this->HasPopoverMenu()) {
diff --git a/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionFactory.php b/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionFactory.php
index 0193c79e94..1bba93529e 100644
--- a/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionFactory.php
+++ b/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanelAction/ActivityPanelActionFactory.php
@@ -49,12 +49,12 @@ class ActivityPanelActionFactory
$aActionsByUid = [];
foreach ($aPopupItems as $sSectionId => $aSectionData) {
- $sSectionLabel = (string) $sSectionId;
+ $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'];
+ $sSectionLabel = (string)$aSectionData['label'];
}
}
@@ -99,8 +99,8 @@ class ActivityPanelActionFactory
$oPopupItemForPopoverMenuItem->SetIconClass('');
$oPopoverMenuItem = PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem($oPopupItemForPopoverMenuItem);
- $oPopoverMenuItem->AddDataAttribute('caselog-attribute-code', (string) $sSectionId);
- $aActionsByUid[$sUid]->GetPopoverMenu()->AddItem((string) $sSectionId, $oPopoverMenuItem);
+ $oPopoverMenuItem->AddDataAttribute('caselog-attribute-code', (string)$sSectionId);
+ $aActionsByUid[$sUid]->GetPopoverMenu()->AddItem((string)$sSectionId, $oPopoverMenuItem);
}
}
@@ -119,6 +119,8 @@ class ActivityPanelActionFactory
$oAction->SetJsCode($oPopupItem->GetJsCode());
$oAction->SetUrl($oPopupItem->GetUrl());
$oAction->SetIncludeJSFiles($oPopupItem->GetLinkedScripts());
+ $oAction->SetAriaAttributes($oPopupItem->GetAriaAttributes()->GetAttributes());
+ $oAction->SetDataAttributes($oPopupItem->GetDataAttributes()->GetAttributes());
return $oAction;
} elseif ($oPopupItem instanceof URLPopupMenuItem) {
@@ -130,6 +132,8 @@ class ActivityPanelActionFactory
);
$oAction->SetUrl($oPopupItem->GetUrl());
$oAction->SetTarget($oPopupItem->GetTarget());
+ $oAction->SetAriaAttributes($oPopupItem->GetAriaAttributes()->GetAttributes());
+ $oAction->SetDataAttributes($oPopupItem->GetDataAttributes()->GetAttributes());
return $oAction;
} elseif ($oPopupItem instanceof SeparatorPopupMenuItem) {
@@ -140,13 +144,4 @@ class ActivityPanelActionFactory
return null;
}
-
- protected static function ClonePopupItemWithLabel(ApplicationPopupMenuItem $oPopupItem, string $sLabel): ApplicationPopupMenuItem
- {
- $oClonedPopupItem = clone $oPopupItem;
- $oClonedPopupItem->SetLabel($sLabel);
-
- return $oClonedPopupItem;
- }
-
}
diff --git a/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickAction.php b/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickAction.php
index 03fed69618..b16c8101ee 100644
--- a/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickAction.php
+++ b/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickAction.php
@@ -21,6 +21,7 @@
namespace Combodo\iTop\Application\UI\Base\Layout\TopBar\TopBarAction;
use Combodo\iTop\Application\UI\Base\UIBlock;
+use utils;
/**
* Class TopBarAction
@@ -96,4 +97,19 @@ abstract class TopBarQuickAction extends UIBlock
return $this;
}
+ public function GetAriaAttributes(): array
+ {
+ $aDefaultValues = [];
+ // Default value for aria-label is the tooltip
+ if (utils::IsNotNullOrEmptyString($this->sTooltip)) {
+ $aDefaultValues['label'] = $this->sTooltip;
+ }
+
+ return array_merge($aDefaultValues, parent::GetAriaAttributes());
+ }
+
+ public function HasAriaAttributes(): bool
+ {
+ return !empty($this->GetAriaAttributes());
+ }
}
diff --git a/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionFactory.php b/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionFactory.php
index 76f5eb764c..be941e39b9 100644
--- a/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionFactory.php
+++ b/sources/Application/UI/Base/Layout/TopBar/TopBarQuickAction/TopBarQuickActionFactory.php
@@ -44,8 +44,9 @@ class TopBarQuickActionFactory
);
$oTopBarAction->SetJsCode($oApplicationPopupItem->GetJsCode());
$oTopBarAction->SetUrl($oApplicationPopupItem->GetUrl());
-
$oTopBarAction->SetIncludeJSFiles($oApplicationPopupItem->GetLinkedScripts());
+ $oTopBarAction->SetAriaAttributes($oApplicationPopupItem->GetAriaAttributes()->GetAttributes());
+ $oTopBarAction->SetDataAttributes($oApplicationPopupItem->GetDataAttributes()->GetAttributes());
return $oTopBarAction;
} elseif ($oApplicationPopupItem instanceof URLPopupMenuItem) {
$oTopBarAction = new TopBarQuickActionURL(
@@ -56,10 +57,14 @@ class TopBarQuickActionFactory
);
$oTopBarAction->SetUrl($oApplicationPopupItem->GetUrl());
$oTopBarAction->SetTarget($oApplicationPopupItem->GetTarget());
+ $oTopBarAction->SetAriaAttributes($oApplicationPopupItem->GetAriaAttributes()->GetAttributes());
+ $oTopBarAction->SetDataAttributes($oApplicationPopupItem->GetDataAttributes()->GetAttributes());
return $oTopBarAction;
} elseif ($oApplicationPopupItem instanceof SeparatorPopupMenuItem) {
$oTopBarAction = new TopBarQuickActionSeparator($oApplicationPopupItem->GetUID());
$oTopBarAction->SetCSSClasses($oApplicationPopupItem->GetCssClasses());
+ $oTopBarAction->SetAriaAttributes($oApplicationPopupItem->GetAriaAttributes()->GetAttributes());
+ $oTopBarAction->SetDataAttributes($oApplicationPopupItem->GetDataAttributes()->GetAttributes());
return $oTopBarAction;
}
diff --git a/sources/Application/UI/Base/UIBlock.php b/sources/Application/UI/Base/UIBlock.php
index 1197ad3cc1..f8f7a7d594 100644
--- a/sources/Application/UI/Base/UIBlock.php
+++ b/sources/Application/UI/Base/UIBlock.php
@@ -145,6 +145,8 @@ abstract class UIBlock implements iUIBlock
protected $aCssFilesRelPath = [];
/** @var array Array => which will be output as HTML data-xxx attributes (eg. data-="") */
protected $aDataAttributes = [];
+ /** @var array Array => which will be output as HTML aria-xxx attributes (eg. aria-="") */
+ protected $aAriaAttributes = [];
/** @var bool Whether the current block is shown or hidden */
protected $bIsHidden;
/** @var array Additional CSS classes to put on the block */
@@ -622,6 +624,67 @@ abstract class UIBlock implements iUIBlock
return !empty($this->aDataAttributes);
}
+ /**
+ * @return array
+ * @see static::$aAriaAttributes
+ * @since 3.3.0
+ */
+ public function GetAriaAttributes(): array
+ {
+ return $this->aAriaAttributes;
+ }
+
+ /**
+ * @param array $aAriaAttributes Array of aria attributes in the format ['name' => 'value']
+ *
+ * @return $this
+ * @see static::$aAriaAttributes
+ * @since 3.3.0
+ */
+ public function SetAriaAttributes(array $aAriaAttributes)
+ {
+ $this->aAriaAttributes = $aAriaAttributes;
+
+ return $this;
+ }
+
+ /**
+ * @param string $sName
+ * @param string $sValue
+ *
+ * @return $this
+ * @see static::$aAriaAttributes
+ * @since 3.3.0
+ */
+ public function AddAriaAttribute(string $sName, string $sValue)
+ {
+ $this->aAriaAttributes[$sName] = $sValue;
+
+ return $this;
+ }
+
+ /**
+ * @param string $sName Name of the aria attribute
+ *
+ * @return bool True if $sName is already defined (even as a null value) in the UIBlock aria attributes, false otherwise
+ * @see static::$aAriaAttributes
+ * @since 3.3.0
+ */
+ public function HasAriaAttribute(string $sName): bool
+ {
+ return array_key_exists($sName, $this->aAriaAttributes);
+ }
+
+ /**
+ * @return bool
+ * @see static::$aAriaAttributes
+ * @since 3.3.0
+ */
+ public function HasAriaAttributes(): bool
+ {
+ return !empty($this->aAriaAttributes);
+ }
+
/**
* @return bool
* @see static::$bIsHidden
diff --git a/templates/base/components/button/buttonjs.html.twig b/templates/base/components/button/buttonjs.html.twig
index 14523a63e6..54ee1ec138 100644
--- a/templates/base/components/button/buttonjs.html.twig
+++ b/templates/base/components/button/buttonjs.html.twig
@@ -1,4 +1,3 @@
-{% set sAriaLabel = oUIBlock.GetLabel() is not empty ? oUIBlock.GetLabel() : (oUIBlock.GetTooltip() is not empty ? oUIBlock.GetTooltip() : '') %}