diff --git a/appinfo/info.xml b/appinfo/info.xml
index f7e79e6..0632728 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -24,7 +24,7 @@ This app lets every user configure Deck automation workflows for the boards they
A background job periodically evaluates all enabled workflows and moves cards whose due date has passed, using Deck's own internal PHP classes only (no HTTP/OCS calls).
]]>
- 35.2.1
+ 35.2.2
agpl
Patrick Niebeling
WorkflowDeckAutomation
diff --git a/lib/Service/NotificationMailer.php b/lib/Service/NotificationMailer.php
index 572f279..ea3e790 100644
--- a/lib/Service/NotificationMailer.php
+++ b/lib/Service/NotificationMailer.php
@@ -98,21 +98,34 @@ class NotificationMailer {
* @param array{board: string, sourceStack: string, targetStack: string} $targets
*/
private function addCard(IEMailTemplate $template, Workflow $workflow, Card $card, array $targets): void {
- // Single-argument addBodyText() runs htmlspecialchars() itself, so
- // these card-, workflow-, board- and stack-titles (user input) are
- // safe as-is. The card description and the workflow's name are
- // deliberately left out — the mail only says what moved where.
- $template->addBodyText(sprintf(
- '„%1$s“ (Board „%2$s“) ist überfällig und wurde von „%3$s“ nach „%4$s“ verschoben.',
+ // The card description and the workflow's name are deliberately left
+ // out — the mail only says what moved where. The card title itself is
+ // the link to the card.
+ $sentence = '„%1$s“ (Board „%2$s“) ist überfällig und wurde von „%3$s“ nach „%4$s“ verschoben.';
+ $url = $this->urlGenerator->getAbsoluteURL('/apps/deck/board/' . $workflow->getBoardId() . '/card/' . $card->getId());
+
+ // With both parts passed, addBodyText() escapes nothing: every title
+ // is user input and has to be escaped here for the HTML part.
+ $html = sprintf(
+ $sentence,
+ '' . self::escape($card->getTitle()) . '',
+ self::escape($targets['board']),
+ self::escape($targets['sourceStack']),
+ self::escape($targets['targetStack']),
+ );
+ $plain = sprintf(
+ $sentence,
$card->getTitle(),
$targets['board'],
$targets['sourceStack'],
$targets['targetStack'],
- ));
- $template->addBodyButton(
- 'Karte öffnen',
- $this->urlGenerator->getAbsoluteURL('/apps/deck/board/' . $workflow->getBoardId() . '/card/' . $card->getId()),
- );
+ ) . PHP_EOL . $url;
+
+ $template->addBodyText($html, $plain);
+ }
+
+ private static function escape(string $text): string {
+ return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}
/**
diff --git a/package.json b/package.json
index 7d6ecf6..b1d9db5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "workflow_deck_automation",
- "version": "35.2.1",
+ "version": "35.2.2",
"private": true,
"type": "module",
"description": "Automates moving overdue Deck cards between stacks, configurable per user.",