diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 35d36a1c9..f240e0702 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -549,10 +549,9 @@ class CMDBSource /** * @param string $sSQLQuery * - * @return \mysqli_result|null - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - * @throws \CoreException + * @return mysqli_result|null + * @throws MySQLException + * @throws MySQLHasGoneAwayException * * @since 2.7.0 N°679 handles nested transactions */ diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index 24a8ecb85..ba193b773 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -53,7 +53,7 @@ abstract class Trigger extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeEnumSet("context", array("allowed_values" => null, "possible_values" => new ValueSetEnumPadded($aTags, true), "sql" => "context", "depends_on" => array(), "is_null_allowed" => true, "max_items" => 12))); // "complement" is a computed field, fed by Trigger sub-classes, in general in ComputeValues method, for eg. the TriggerOnObject fed it with target_class info MetaModel::Init_AddAttribute(new AttributeString("complement", array("allowed_values" => null, "sql" => "complement", "default_value" => null, "is_null_allowed" => true, "depends_on" => array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("subscription_policy", array("allowed_values" => new ValueSetEnum('allow_no_channel,force_at_least_one_channel,force_all_channels'), "sql" => "subscription_policy", "default_value" => 'allow_no_channel', "is_null_allowed" => false, "depends_on" => array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("subscription_policy", array("allowed_values" => new ValueSetEnum(Combodo\iTop\Core\Trigger\Enum\SubscriptionPolicy::cases()), "sql" => "subscription_policy", "default_value" => \Combodo\iTop\Core\Trigger\Enum\SubscriptionPolicy::AllowNoChannel->value, "is_null_allowed" => false, "depends_on" => array()))); // Display lists MetaModel::Init_SetZListItems('details', array('finalclass', 'description', 'context', 'subscription_policy', 'action_list', 'complement')); // Attributes to be displayed for the complete details diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index c32766b0f..44733976d 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -476,6 +476,7 @@ class ValueSetEnum extends ValueSetDefinition * @param bool $bLocalizedSort * * @since 3.1.0 N°1646 Add $bLocalizedSort parameter + * @since 3.2.0 N°7157 $Values can be an array of backed-enum cases */ public function __construct($Values, bool $bSortByValues = false) { @@ -523,13 +524,21 @@ class ValueSetEnum extends ValueSetDefinition */ protected function LoadValues($aArgs) { + $aValues = []; if (is_array($this->m_values)) { - $aValues = $this->m_values; + foreach ($this->m_values as $value) { + // Handle backed-enum case + if (is_object($value) && enum_exists(get_class($value))) { + $aValues[] = $value->value; + continue; + } + + $aValues[] = $value; + } } elseif (is_string($this->m_values) && strlen($this->m_values) > 0) { - $aValues = array(); foreach (explode(",", $this->m_values) as $sVal) { $sVal = trim($sVal); @@ -539,7 +548,7 @@ class ValueSetEnum extends ValueSetDefinition } else { - $aValues = array(); + $aValues = []; } $this->m_aValues = $aValues; return true; diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 291822234..cb332e129 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -418,6 +418,7 @@ return array( 'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => $baseDir . '/sources/Core/Kpi/KpiLogData.php', 'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => $baseDir . '/sources/Core/MetaModel/FriendlyNameType.php', 'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => $baseDir . '/sources/Core/MetaModel/HierarchicalKey.php', + 'Combodo\\iTop\\Core\\Trigger\\Enum\\SubscriptionPolicy' => $baseDir . '/sources/Core/Trigger/Enum/SubscriptionPolicy.php', 'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\Form\\Field\\AbstractSimpleField' => $baseDir . '/sources/Form/Field/AbstractSimpleField.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 10d6f13b6..62611d320 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -793,6 +793,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => __DIR__ . '/../..' . '/sources/Core/Kpi/KpiLogData.php', 'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => __DIR__ . '/../..' . '/sources/Core/MetaModel/FriendlyNameType.php', 'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => __DIR__ . '/../..' . '/sources/Core/MetaModel/HierarchicalKey.php', + 'Combodo\\iTop\\Core\\Trigger\\Enum\\SubscriptionPolicy' => __DIR__ . '/../..' . '/sources/Core/Trigger/Enum/SubscriptionPolicy.php', 'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\Form\\Field\\AbstractSimpleField' => __DIR__ . '/../..' . '/sources/Form/Field/AbstractSimpleField.php', diff --git a/sources/Application/WebPage/WebPage.php b/sources/Application/WebPage/WebPage.php index b0c576f47..61f8572d8 100644 --- a/sources/Application/WebPage/WebPage.php +++ b/sources/Application/WebPage/WebPage.php @@ -624,7 +624,7 @@ class WebPage implements Page break; case static::ENUM_RESOURCE_TYPE_CSS: - $this->a_linked_stylesheets[$sFileAbsURI] = $sFileAbsURI; + $this->a_linked_stylesheets[$sFileAbsURI] = ['link' => $sFileAbsURI, 'condition' => '']; break; } } diff --git a/sources/Controller/Notifications/NotificationsCenterController.php b/sources/Controller/Notifications/NotificationsCenterController.php index d121af6bf..325715ffd 100644 --- a/sources/Controller/Notifications/NotificationsCenterController.php +++ b/sources/Controller/Notifications/NotificationsCenterController.php @@ -11,6 +11,7 @@ use Combodo\iTop\Application\UI\Base\Component\Input\Set\SetUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Panel\Panel; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock; use Combodo\iTop\Application\WebPage\iTopWebPage; +use Combodo\iTop\Core\Trigger\Enum\SubscriptionPolicy; use Combodo\iTop\Renderer\BlockRenderer; use Combodo\iTop\Service\Notification\NotificationsRepository; use Combodo\iTop\Service\Router\Router; @@ -82,7 +83,7 @@ class NotificationsCenterController extends Controller // Add the action notification to the list of actions notifications for the trigger $oActionsNotificationsByTrigger[$iTriggerId][] = $oSubscribedActionNotification; // Add the subscribed status to the list of subscribed actions notifications for the trigger - $aSubscribedActionsNotificationsByTrigger[$iTriggerId][$oSubscribedActionNotification->GetKey()] = $oLnkActionsNotifications->Get('subscribed') || $oTrigger->Get('subscription_policy') === 'force_all_channels'; + $aSubscribedActionsNotificationsByTrigger[$iTriggerId][$oSubscribedActionNotification->GetKey()] = $oLnkActionsNotifications->Get('subscribed') || $oTrigger->Get('subscription_policy') === SubscriptionPolicy::ForceAllChannels->value; } // Build table rows @@ -155,7 +156,7 @@ class NotificationsCenterController extends Controller $oChannelSet->SetOptionsTemplate('application/object/set/option_renderer.html.twig'); $oChannelSet->SetItemsTemplate('application/preferences/notification-center/item_renderer.html.twig'); // Disable the input set if the subscription policy is 'force_all_channels' - if($sTriggerSubscriptionPolicy === 'force_all_channels'){ + if ($sTriggerSubscriptionPolicy === SubscriptionPolicy::ForceAllChannels->value) { $oChannelSet->SetIsDisabled(true); } // Add a CSRF Token @@ -190,8 +191,8 @@ $.ajax({ }); JS ); - // Set the minimum number of channels to 1 if the subscription policy is 'force_at_least_one_channel' - if($sTriggerSubscriptionPolicy === 'force_at_least_one_channel') + // Set the minimum number of channels to 1 if the subscription policy is {@see SubscriptionPolicy::ForceAtLeastOneChannel} + if($sTriggerSubscriptionPolicy === SubscriptionPolicy::ForceAtLeastOneChannel->value) { $oChannelSet->SetMinItems(1); } @@ -289,7 +290,7 @@ JS // Add the action notification to the list of actions notifications for the trigger $oActionsNotificationsByTrigger[$iTriggerId][] = $oSubscribedActionNotification; // Add the subscribed status to the list of subscribed actions notifications for the trigger - $aSubscribedActionsNotificationsByTrigger[$iTriggerId][$oSubscribedActionNotification->GetKey()] = $oLnkActionsNotifications->Get('subscribed') || $oTrigger->Get('subscription_policy') === 'force_all_channels'; + $aSubscribedActionsNotificationsByTrigger[$iTriggerId][$oSubscribedActionNotification->GetKey()] = $oLnkActionsNotifications->Get('subscribed') || $oTrigger->Get('subscription_policy') === SubscriptionPolicy::ForceAllChannels->value; } $oPage->AddTabContainer('NotificationsCenter', '', $oNotificationsPanel); @@ -491,7 +492,7 @@ JS throw new \Exception('Invalid trigger'); } // Check the trigger subscription policy - if($oTrigger->Get('subscription_policy') === 'force_all_channels'){ + if ($oTrigger->Get('subscription_policy') === SubscriptionPolicy::ForceAllChannels->value) { throw new \Exception('You are not allowed to unsubscribe from this channel'); } @@ -501,7 +502,7 @@ JS throw new \Exception('You are not subscribed to any channel'); } // Check the trigger subscription policy and if we are subscribed to at least 1 channel if necessary - if($oTrigger->Get('subscription_policy') === 'force_at_least_one_channel') { + if($oTrigger->Get('subscription_policy') === SubscriptionPolicy::ForceAtLeastOneChannel->value) { $oTotalSubscribedActionsNotificationsSet = NotificationsRepository::GetInstance()->SearchSubscriptionByTriggerContactAndSubscription($iTriggerId, \UserRights::GetContactId(), '1'); if (($oTotalSubscribedActionsNotificationsSet->Count() - $oSubscribedActionsNotificationsSet->Count()) === 0) { throw new \Exception('You can\'t unsubscribe from this channel, you must be subscribed to at least one channel'); diff --git a/sources/Core/Trigger/Enum/SubscriptionPolicy.php b/sources/Core/Trigger/Enum/SubscriptionPolicy.php new file mode 100644 index 000000000..67e46879b --- /dev/null +++ b/sources/Core/Trigger/Enum/SubscriptionPolicy.php @@ -0,0 +1,16 @@ + + * @package Combodo\iTop\Core\Trigger\Enum + * @since 3.2.0 + */ +enum SubscriptionPolicy: string { + case AllowNoChannel = "allow_no_channel"; + case ForceAtLeastOneChannel = "force_at_least_one_channel"; + case ForceAllChannels = "force_all_channels"; +} diff --git a/sources/Service/Notification/NotificationsService.php b/sources/Service/Notification/NotificationsService.php index 810e27b1c..4beecfc13 100644 --- a/sources/Service/Notification/NotificationsService.php +++ b/sources/Service/Notification/NotificationsService.php @@ -3,6 +3,7 @@ namespace Combodo\iTop\Service\Notification; use ActionNotification; +use Combodo\iTop\Core\Trigger\Enum\SubscriptionPolicy; use Contact; use lnkActionNotificationToContact; use Trigger; @@ -99,7 +100,7 @@ class NotificationsService { public function IsSubscribed(Trigger $oTrigger, ActionNotification $oActionNotification, Contact $oRecipient): bool { // Check if the trigger subscription policy is 'force_all_channels' - if ($oTrigger->Get('subscription_policy') === 'force_all_channels') { + if ($oTrigger->Get('subscription_policy') === SubscriptionPolicy::ForceAllChannels->value) { return true; } // Check if the user is already subscribed to the action notification diff --git a/tests/php-unit-tests/unitary-tests/core/ValueSetEnum/ABCEnum.php b/tests/php-unit-tests/unitary-tests/core/ValueSetEnum/ABCEnum.php new file mode 100644 index 000000000..4e8be881b --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/core/ValueSetEnum/ABCEnum.php @@ -0,0 +1,13 @@ + + * @package Combodo\iTop\Test\UnitTest\Core + * @coves \ValueSetEnum + */ +class ValueSetEnumTest extends ItopTestCase +{ + public static function setupBeforeClass(): void + { + require_once __DIR__ . "/ValueSetEnum/ABCEnum.php"; + } + + /** + * @dataProvider LoadValuesProvider + * + * @param mixed $input + * @param array $aExpectedValues + * @param bool $bIsInputBackedEnum + * + * @return void + */ + public function testLoadValues(mixed $input, array $aExpectedValues, bool $bIsInputBackedEnum = false): void + { + if ($bIsInputBackedEnum) { + $input = $input::cases(); + } + $oValueSetEnum = new ValueSetEnum($input); + $aTestedValues = $oValueSetEnum->GetValues([]); + + $this->assertEquals($aExpectedValues, $aTestedValues, "Values should be the same and ordered the same way"); + } + + public function LoadValuesProvider(): array + { + return [ + "CSV list, trimmed values, already ordered" => [ + "a,b,c", + [ + "a" => "a", + "b" => "b", + "c" => "c", + ], + ], + "CSV list, values to trim, already ordered" => [ + "a, b ,c ", + [ + "a" => "a", + "b" => "b", + "c" => "c", + ], + ], + "Array, already ordered" => [ + ["a", "b", "c"], + [ + 0 => "a", + 1 => "b", + 2 => "c", + ], + ], + "Backed-Enum" => [ + ABCEnum::class, + [ + 0 => "a", + 1 => "b", + 2 => "c", + ], + true, // Is the input value a backed enum? + ], + "Invalid int value" => [ + 123, + [], + ] + ]; + } +} \ No newline at end of file