Files
nextcloud-workflow-deck-aut…/lib/Notification/Notifier.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

153 lines
5.1 KiB
PHP

<?php
declare(strict_types=1);
namespace OCA\WorkflowDeckAutomation\Notification;
use OCA\WorkflowDeckAutomation\AppInfo\Application;
use OCA\WorkflowDeckAutomation\Service\NotificationMailer;
use OCA\WorkflowDeckAutomation\Service\NotificationPusher;
use OCA\WorkflowDeckAutomation\Service\TriggerDate;
use OCP\IURLGenerator;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
use OCP\Notification\UnknownNotificationException;
/**
* Turns the values NotificationPusher stored into text. German only, like
* the rest of the app — the language Nextcloud asks for is ignored.
*
* Deliberately asks Deck nothing: the stored titles are the ones from the
* moment the card moved, and a Deck lookup here would run as whoever
* happens to fetch notifications (see CLAUDE.md on Deck's frozen uid).
*/
class Notifier implements INotifier {
public function __construct(
private IURLGenerator $urlGenerator,
) {
}
public function getID(): string {
return Application::APP_ID;
}
public function getName(): string {
return 'Deck Workflow-Automatisierung';
}
public function prepare(INotification $notification, string $languageCode): INotification {
if ($notification->getApp() !== Application::APP_ID) {
throw new UnknownNotificationException();
}
$notification->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath(Application::APP_ID, 'app-dark.svg')));
$params = $notification->getSubjectParameters();
switch ($notification->getSubject()) {
case NotificationPusher::SUBJECT_CARD_MOVED:
$this->prepareCardMoved($notification, $params);
return $notification;
case NotificationPusher::SUBJECT_WORKFLOW_DISABLED:
$this->prepareWorkflowDisabled($notification, $params);
return $notification;
default:
throw new UnknownNotificationException();
}
}
private function prepareCardMoved(INotification $notification, array $params): void {
$cardId = $notification->getObjectId();
$boardId = (string)($params['boardId'] ?? '');
$boardTitle = (string)($params['boardTitle'] ?? '');
$targetStackTitle = (string)($params['targetStackTitle'] ?? '');
$cardUrl = $this->urlGenerator->getAbsoluteURL('/apps/deck/board/' . $boardId . '/card/' . $cardId);
// Every title is user input and goes in as a rich object, never into
// the template itself — a title containing "{…}" would otherwise be
// read as a placeholder.
$objects = [
'deck-card' => [
'type' => 'deck-card',
'id' => $cardId,
'name' => (string)($params['cardTitle'] ?? ''),
'boardname' => $boardTitle,
'stackname' => $targetStackTitle,
'link' => $cardUrl,
],
'deck-board' => [
'type' => 'deck-board',
'id' => $boardId,
'name' => $boardTitle,
'link' => $this->urlGenerator->getAbsoluteURL('/apps/deck/board/' . $boardId),
],
'source-stack' => [
'type' => 'highlight',
'id' => (string)($params['sourceStackId'] ?? ''),
'name' => (string)($params['sourceStackTitle'] ?? ''),
],
'target-stack' => [
'type' => 'highlight',
'id' => (string)($params['targetStackId'] ?? ''),
'name' => $targetStackTitle,
],
];
$notification->setLink($cardUrl);
self::setText(
$notification,
'{deck-card} wurde nach {target-stack} verschoben',
sprintf(
'Die Karte auf {deck-board} %s und wurde automatisch von {source-stack} nach {target-stack} verschoben.',
// Missing on notifications from before the start date existed,
// which were all about due dates.
TriggerDate::describeReason((string)($params['triggerDate'] ?? TriggerDate::DUE)),
),
$objects,
);
}
private function prepareWorkflowDisabled(INotification $notification, array $params): void {
$settingsUrl = $this->urlGenerator->getAbsoluteURL('/settings/user/' . Application::APP_ID);
$objects = [
'workflow' => [
'type' => 'highlight',
'id' => $notification->getObjectId(),
'name' => (string)($params['workflowTitle'] ?? ''),
'link' => $settingsUrl,
],
];
$notification->setLink($settingsUrl);
self::setText(
$notification,
'Workflow {workflow} wurde deaktiviert',
sprintf(
'Er wurde automatisch ausgeschaltet, weil %s, und verschiebt keine Karten mehr. Lösche ihn oder stelle ein vorhandenes Board und vorhandene Stapel ein, um ihn wieder zu aktivieren.',
NotificationMailer::describeBrokenTarget((string)($params['brokenTarget'] ?? '')),
),
$objects,
);
}
/**
* Sets the rich texts and their plain fallback (used by clients without
* rich text support), with titles in German quotes there.
*
* @param array<string, array{name: string}> $objects
*/
private static function setText(INotification $notification, string $subject, string $message, array $objects): void {
$plain = [];
foreach ($objects as $key => $object) {
$plain['{' . $key . '}'] = '„' . $object['name'] . '“';
}
// strtr() replaces in a single pass, so a title that itself contains
// a placeholder is left as it is.
$notification->setParsedSubject(strtr($subject, $plain))
->setRichSubject($subject, $objects)
->setParsedMessage(strtr($message, $plain))
->setRichMessage($message, $objects);
}
}