Files
nextcloud-workflow-deck-aut…/lib/Service/NotificationPolicy.php
T
Patrick Niebeling 8268f1068d
Build package / xml-lint (push) Successful in 3m32s
Build package / unit-tests (push) Successful in 4m49s
Build package / php-lint (8.5) (push) Successful in 4m52s
Build package / php-lint (8.4) (push) Successful in 4m56s
Build package / package (push) Successful in 2m28s
Let workflows move cards by start date instead of due date
Each workflow now has a trigger date (due or start): the runner compares
either the card's due date or its start date against now, read straight
from Deck's Card entity. Mails and Nextcloud notifications say whether
the card was overdue or reached its start date. Cards without the chosen
date stay put.

Deck only has a start date since 1.18 and Entity getters are magic, so
DeckIntegrationService::supportsStartdate() checks the property; without
it the form rejects start-date workflows and the runner skips them
without disabling them.

WorkflowRunner::isOverdue() becomes hasPassed(), as it now covers both
dates. Existing workflows keep the due date (migration Version1005).
Bumps to 35.4.0 so the migration runs.
2026-10-01 13:19:32 +02:00

95 lines
3.4 KiB
PHP

<?php
declare(strict_types=1);
namespace OCA\WorkflowDeckAutomation\Service;
/**
* Who hears about a moved card, and how — pure logic, no Nextcloud or Deck
* dependencies, so it is unit-testable like WorkflowRunner::hasPassed().
*/
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];
/** One mail per recipient and run. */
public const CHANNEL_MAIL = 'mail';
/** One Nextcloud notification (bell, push to the mobile apps) per card. */
public const CHANNEL_NOTIFICATION = 'notification';
public const CHANNEL_BOTH = 'both';
public const CHANNELS = [self::CHANNEL_MAIL, self::CHANNEL_NOTIFICATION, self::CHANNEL_BOTH];
/**
* 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);
}
public static function isValidChannel(string $channel): bool {
return in_array($channel, self::CHANNELS, true);
}
/**
* An unknown channel counts as `mail`, the column default — mail was the
* only channel before the column existed.
*/
public static function sendsMail(string $channel): bool {
return $channel !== self::CHANNEL_NOTIFICATION;
}
public static function sendsNotification(string $channel): bool {
return $channel === self::CHANNEL_NOTIFICATION || $channel === self::CHANNEL_BOTH;
}
/**
* 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;
}
}