Include the card description in the notification mail
Build package / php-lint (8.2) (push) Successful in 1m46s
Build package / php-lint (8.3) (push) Successful in 47s
Build package / php-lint (8.4) (push) Successful in 38s
Build package / xml-lint (push) Successful in 17s
Build package / unit-tests (push) Successful in 45s
Build package / package (push) Successful in 1m4s

The mail named the card and the workflow but not what the card is about,
which meant opening Deck to know whether the move mattered.

The description is escaped explicitly: addBodyText() only calls
htmlspecialchars() when it has to derive the plain-text part itself, and we
pass both parts to get line breaks in the HTML body -- so escaping user
input is on us. Long descriptions are cut at 2000 characters.

Deck stores the description as Markdown; it is sent unrendered rather than
pulling in a parser for one mail.
This commit is contained in:
Patrick Niebeling
2026-08-13 15:01:53 +02:00
parent 01fdd51b5a
commit 68d88e952b
+37
View File
@@ -9,12 +9,19 @@ use OCA\WorkflowDeckAutomation\Db\Workflow;
use OCP\IL10N; use OCP\IL10N;
use OCP\IURLGenerator; use OCP\IURLGenerator;
use OCP\IUser; use OCP\IUser;
use OCP\Mail\IEMailTemplate;
use OCP\Mail\IMailer; use OCP\Mail\IMailer;
use OCP\Util; use OCP\Util;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Throwable; use Throwable;
class NotificationMailer { class NotificationMailer {
/**
* Descriptions are unbounded in Deck; keep the mail readable and well
* clear of any MTA size limits.
*/
private const MAX_DESCRIPTION_LENGTH = 2000;
public function __construct( public function __construct(
private IMailer $mailer, private IMailer $mailer,
private IURLGenerator $urlGenerator, private IURLGenerator $urlGenerator,
@@ -48,6 +55,7 @@ class NotificationMailer {
'The card "%1$s" was moved because it is overdue (workflow "%2$s").', 'The card "%1$s" was moved because it is overdue (workflow "%2$s").',
[$card->getTitle(), $workflow->getTitle()], [$card->getTitle(), $workflow->getTitle()],
)); ));
$this->addCardDescription($template, $card);
$template->addBodyButton($this->l10n->t('Open card'), $cardLink); $template->addBodyButton($this->l10n->t('Open card'), $cardLink);
$template->addFooter(); $template->addFooter();
@@ -69,4 +77,33 @@ class NotificationMailer {
]); ]);
} }
} }
/**
* Appends the card's description, if it has one.
*
* Deck stores the description as Markdown. It is sent as-is rather than
* rendered: pulling a Markdown parser in just for the mail would be a
* lot of machinery, and unrendered Markdown still reads fine.
*/
private function addCardDescription(IEMailTemplate $template, Card $card): void {
$description = trim((string)$card->getDescription());
if ($description === '') {
return;
}
if (mb_strlen($description) > self::MAX_DESCRIPTION_LENGTH) {
$description = mb_substr($description, 0, self::MAX_DESCRIPTION_LENGTH) . ' […]';
}
$template->addBodyText($this->l10n->t('Card content:'));
// addBodyText() only runs htmlspecialchars() when it has to build the
// plain-text part itself. Passing both parts means escaping is on us
// -- and it has to be, because a card description is user input that
// would otherwise land unescaped in the HTML mail.
$template->addBodyText(
nl2br(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), false),
$description,
);
}
} }