Files
nextcloud-workflow-deck-aut…/lib/Service/NotificationPolicy.php
T
Patrick Niebeling 578b235f08
Build package / unit-tests (push) Failing after 20s
Build package / xml-lint (push) Successful in 46s
Build package / php-lint (8.4) (push) Successful in 1m30s
Build package / php-lint (8.5) (push) Successful in 1m36s
Build package / package (push) Skipped
Notify board members about moved cards according to their Deck setting
Replaces the yes/no notify_email flag with notify_mode:
- off:   no mail
- owner: only the board owner, as before (migrated from notify_email=1)
- board: everyone with access to the board, filtered by their own Deck
         "Notifications" setting for it (board:<id>:notify-due: off /
         assigned / all), mirroring Deck's NotificationHelper rules

Recipients are resolved via PermissionService::findUsers() (owner, ACL,
groups and circles expanded), AssignmentMapper::isUserAssigned() and
IConfig, none of which reads Deck's frozen $userId. If they cannot be
resolved, no mail is sent rather than one to the wrong people.

Moved cards are collected over the whole run and each recipient gets a
single digest mail afterwards, in their own language.

Bumps to 35.2.0 so the migrations run.
2026-09-30 10:31:45 +02:00

71 lines
2.5 KiB
PHP

<?php
declare(strict_types=1);
namespace OCA\WorkflowDeckAutomation\Service;
/**
* Who gets a mail about a moved card — pure logic, no Nextcloud or Deck
* dependencies, so it is unit-testable like WorkflowRunner::isOverdue().
*/
class NotificationPolicy {
/** No mail at all. */
public const MODE_OFF = 'off';
/** Only the workflow's owner (the board owner), regardless of Deck settings. */
public const MODE_OWNER = 'owner';
/** Everyone with access to the board, filtered by their own Deck setting. */
public const MODE_BOARD = 'board';
public const MODES = [self::MODE_OFF, self::MODE_OWNER, self::MODE_BOARD];
/**
* Values of Deck's per-user, per-board preference
* `deck` / `board:<boardId>:notify-due` (Deck's
* ConfigService::SETTING_BOARD_NOTIFICATION_DUE_*), inlined so a renamed
* constant cannot fatal this app. It is the board's "Notifications" menu
* in Deck — the due-date reminder setting, which is exactly what fits a
* card being moved because it fell due.
*/
public const DECK_OFF = 'off';
public const DECK_ASSIGNED = 'assigned';
public const DECK_ALL = 'all';
/** What Deck assumes for a user who never touched the setting. */
public const DECK_DEFAULT = self::DECK_ASSIGNED;
public static function isValidMode(string $mode): bool {
return in_array($mode, self::MODES, true);
}
/**
* Mirrors Deck's own decision in NotificationHelper::sendCardDuedate():
*
* - `off` never notifies — not even the owner of an unshared board;
* - `all` always notifies;
* - the owner of an unshared board is notified for every card, since on a
* board nobody else can see "assigned" would otherwise mean nothing;
* - `assigned` notifies users assigned to the card, directly or through a
* group or circle.
*
* An unknown value is treated as Deck's default. Deck rejects anything
* else when storing the setting, so that only happens if the preference
* was edited by hand.
*
* @param bool|callable(): bool $isAssigned a callable is only invoked when
* the answer actually depends on it — it costs a query per user
*/
public static function shouldNotify(string $deckSetting, bool $isOwnerOfUnsharedBoard, bool|callable $isAssigned): bool {
if (!in_array($deckSetting, [self::DECK_OFF, self::DECK_ASSIGNED, self::DECK_ALL], true)) {
$deckSetting = self::DECK_DEFAULT;
}
if ($deckSetting === self::DECK_OFF) {
return false;
}
if ($deckSetting === self::DECK_ALL || $isOwnerOfUnsharedBoard) {
return true;
}
return is_callable($isAssigned) ? (bool)$isAssigned() : $isAssigned;
}
}