From 05d5fbc93adcfc83009bbbc22194528ac7d0b81f Mon Sep 17 00:00:00 2001 From: Curtis Conard Date: Wed, 10 Jan 2024 23:05:46 -0500 Subject: [PATCH] Add recipient exclusions for notifications --- CHANGELOG.md | 2 + .../update_10.0.x_to_10.1.0/notifications.php | 3 + install/mysql/glpi-empty.sql | 4 +- src/NotificationTarget.php | 125 +++++++++++++++++- .../setup/notification/recipients.html.twig | 6 + 5 files changed, 133 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f596da74b32a..01af324199a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ The present file will list all changes made to the project; according to the - Public reminders in central view now include public reminders regardless of who created them. - Project description field is now a rich text field. - Entity, profile, debug mode flag, and language are restored after ending impersonation. +- Notifications can now specify exclusions for recipients. ### Deprecated - Survey URL tags `TICKETCATEGORY_ID` and `TICKETCATEGORY_NAME` are deprecated and replaced by `ITILCATEGORY_ID` and `ITILCATEGORY_NAME` respectively. @@ -66,6 +67,7 @@ The present file will list all changes made to the project; according to the #### Added - `phpCAS` library is now bundled in GLPI, to prevent version compatibility issues. - `Glpi\DBAL\QueryFunction` class with multiple static methods for building SQL query function strings in an abstract way. +- `is_exclusion` column added to `glpi_notificationtargets` table. #### Changes - `chartist` library has been replaced by `echarts`. diff --git a/install/migrations/update_10.0.x_to_10.1.0/notifications.php b/install/migrations/update_10.0.x_to_10.1.0/notifications.php index d3c1c31fcb21..0d5bc6c9be2a 100644 --- a/install/migrations/update_10.0.x_to_10.1.0/notifications.php +++ b/install/migrations/update_10.0.x_to_10.1.0/notifications.php @@ -317,3 +317,6 @@ "item_trigger" ); /** /Add handling of source item of attached documents in notification */ + +$migration->addField('glpi_notificationtargets', 'is_exclusion', 'boolean'); +$migration->addKey('glpi_notificationtargets', ['is_exclusion'], 'is_exclusion'); diff --git a/install/mysql/glpi-empty.sql b/install/mysql/glpi-empty.sql index 52ddf31902e3..2f3d1d8873f2 100644 --- a/install/mysql/glpi-empty.sql +++ b/install/mysql/glpi-empty.sql @@ -5011,9 +5011,11 @@ CREATE TABLE `glpi_notificationtargets` ( `items_id` int unsigned NOT NULL DEFAULT '0', `type` int NOT NULL DEFAULT '0', `notifications_id` int unsigned NOT NULL DEFAULT '0', + `is_exclusion` tinyint NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `items` (`type`,`items_id`), - KEY `notifications_id` (`notifications_id`) + KEY `notifications_id` (`notifications_id`), + KEY `is_exclusion` (`is_exclusion`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; diff --git a/src/NotificationTarget.php b/src/NotificationTarget.php index ff834887d1b7..7f592147fa1f 100644 --- a/src/NotificationTarget.php +++ b/src/NotificationTarget.php @@ -438,10 +438,21 @@ public function showForNotification(Notification $notification) $canedit = $notification->can($notifications_id, UPDATE); $values = []; + $allowed_exclusion_types = [ + Notification::PROFILE_TYPE, + Notification::GROUP_TYPE, + ]; + $all_exclusion_targets = []; foreach ($this->notification_targets as $key => $val) { [$type, $id] = explode('_', $key); - $values[$key] = $this->notification_targets_labels[$type][$id]; + $label = $this->notification_targets_labels[$type][$id]; + if (in_array((int) $type, $allowed_exclusion_types, true)) { + $all_exclusion_targets[$key] = $label; + } else { + $values[$key] = $label; + } } + $targets = getAllDataFromTable( self::getTable(), [ @@ -449,16 +460,24 @@ public function showForNotification(Notification $notification) ] ); $actives = []; + $exclusions = []; if (count($targets)) { foreach ($targets as $data) { - $actives[$data['type'] . '_' . $data['items_id']] = $data['type'] . '_' . $data['items_id']; + $target_key = $data['type'] . '_' . $data['items_id']; + if ($data['is_exclusion']) { + $exclusions[$target_key] = $target_key; + } else { + $actives[$target_key] = $target_key; + } } } TemplateRenderer::getInstance()->display('pages/setup/notification/recipients.html.twig', [ 'item' => $this, 'notification' => $notification, 'all_targets' => $values, + 'all_exclusion_targets' => $all_exclusion_targets, 'active_targets' => $actives, + 'excluded_targets' => $exclusions, 'params' => [ 'canedit' => $canedit ] @@ -512,7 +531,26 @@ public static function updateTargets($input) } } - // Drop others + if (isset($input['_exclusions']) && count($input['_exclusions'])) { + $input['_exclusions'] = array_unique($input['_exclusions']); + // Remove exclusions already set in _targets + $input['_exclusions'] = array_diff($input['_exclusions'], $input['_targets'] ?? []); + foreach ($input['_exclusions'] as $val) { + // Add if not set + if (!isset($actives[$val])) { + list($type, $items_id) = explode("_", $val); + $tmp = []; + $tmp['items_id'] = $items_id; + $tmp['type'] = $type; + $tmp['notifications_id'] = $input['notifications_id']; + $tmp['is_exclusion'] = 1; + $target->add($tmp); + } + unset($actives[$val]); + } + } + + // Drop others if (count($actives)) { foreach ($actives as $val) { list($type, $items_id) = explode("_", $val); @@ -1350,9 +1388,82 @@ public function addDataForTemplate($event, $options = []) final public function getTargets() { - return $this->target; + return $this->removeExcludedTargets($this->target); } + private function removeExcludedTargets(array $target_list) + { + /** @var \DBmysql $DB */ + global $DB; + $exclusions = iterator_to_array($DB->request([ + 'SELECT' => ['type', 'items_id'], + 'FROM' => self::getTable(), + 'WHERE' => [ + 'is_exclusion' => 1, + 'notifications_id' => $this->data['notifications_id'] + ] + ])); + if (empty($exclusions)) { + // No exclusion, no need to filter + return $target_list; + } + $user_ids = []; + foreach ($target_list as $target) { + if (isset($target['users_id'])) { + $user_ids[] = $target['users_id']; + } + } + if (empty($user_ids)) { + // Cannot filter targets without a user id + return $target_list; + } + + // Criteria to get any user IDs that are excluded + $criteria = [ + 'SELECT' => [User::getTableField('id')], + 'FROM' => User::getTable(), + 'INNER JOIN' => [ + Profile_User::getTable() => [ + 'ON' => [ + Profile_User::getTable() => 'users_id', + User::getTable() => 'id' + ] + ], + Group_User::getTable() => [ + 'ON' => [ + Group_User::getTable() => 'users_id', + User::getTable() => 'id' + ] + ] + ], + 'WHERE' => [ + 'OR' => [ + [ + Profile_User::getTableField('profiles_id') => array_column($exclusions, 'items_id'), + Profile_User::getTableField('users_id') => $user_ids + ], + [ + Group_User::getTableField('groups_id') => array_column($exclusions, 'items_id'), + Group_User::getTableField('users_id') => $user_ids + ] + ], + ] + ]; + + $excluded_user_ids = []; + $it = $DB->request($criteria); + foreach ($it as $data) { + $excluded_user_ids[] = $data['id']; + } + + foreach ($target_list as $key => $target) { + if (isset($target['users_id']) && in_array($target['users_id'], $excluded_user_ids, true)) { + unset($target_list[$key]); + } + } + + return $target_list; + } public function getEntity() { @@ -1570,7 +1681,8 @@ public static function countForGroup(Group $group) Notification::SUPERVISOR_GROUP_TYPE, Notification::GROUP_TYPE ], - 'items_id' => $group->getID() + 'items_id' => $group->getID(), + 'is_exclusion' => 0, ] + getEntitiesRestrictCriteria(Notification::getTable(), '', '', true) ])->current(); return $count['cpt']; @@ -1611,7 +1723,8 @@ public static function showForGroup(Group $group) Notification::SUPERVISOR_GROUP_TYPE, Notification::GROUP_TYPE ], - 'items_id' => $group->getID() + 'items_id' => $group->getID(), + 'is_exclusion' => 0, ] + getEntitiesRestrictCriteria(Notification::getTable(), '', '', true) ]); diff --git a/templates/pages/setup/notification/recipients.html.twig b/templates/pages/setup/notification/recipients.html.twig index 55a2c996efe0..f895403d2fe7 100644 --- a/templates/pages/setup/notification/recipients.html.twig +++ b/templates/pages/setup/notification/recipients.html.twig @@ -51,7 +51,13 @@ }) }} {{ fields.nullField() }} + {{ fields.dropdownArrayField('_exclusions', null, all_exclusion_targets, _n('Exclusion', 'Exclusions', get_plural_number()), { + multiple: true, + readonly: not params['canedit'], + values: excluded_targets + }) }} {{ fields.nullField() }} + {{ fields.nullField() }} {{ fields.htmlField('', inputs.submit('update', _x('button', 'Update'), 1), '') }}