Skip to content

Commit

Permalink
Add recipient exclusions for notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
cconard96 committed Jan 11, 2024
1 parent 988efde commit 05d5fbc
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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`.
Expand Down
3 changes: 3 additions & 0 deletions install/migrations/update_10.0.x_to_10.1.0/notifications.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
4 changes: 3 additions & 1 deletion install/mysql/glpi-empty.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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;


Expand Down
125 changes: 119 additions & 6 deletions src/NotificationTarget.php
Original file line number Diff line number Diff line change
Expand Up @@ -438,27 +438,46 @@ 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(),
[
'notifications_id' => $notifications_id
]
);
$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
]
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -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'];
Expand Down Expand Up @@ -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)
]);

Expand Down
6 changes: 6 additions & 0 deletions templates/pages/setup/notification/recipients.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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), '') }}
</div>
</div>
Expand Down

0 comments on commit 05d5fbc

Please sign in to comment.