getEMailAddress(); if ($email === null || $email === '') { $this->logger->info('Skipping notification for workflow ' . $workflow->getId() . ': user ' . $user->getUID() . ' has no email address', [ 'app' => 'workflow_deck_automation', ]); return; } try { $cardLink = $this->urlGenerator->getAbsoluteURL( '/apps/deck/board/' . $workflow->getBoardId() . '/card/' . $card->getId(), ); $template = $this->mailer->createEMailTemplate('workflow_deck_automation.CardMoved', [ 'cardTitle' => $card->getTitle(), 'workflowTitle' => $workflow->getTitle(), ]); $template->setSubject($this->l10n->t('Deck card moved: %s', [$card->getTitle()])); $template->addHeader(); $template->addHeading($this->l10n->t('A card was moved automatically'), false); $template->addBodyText($this->l10n->t( 'The card "%1$s" was moved because it is overdue (workflow "%2$s").', [$card->getTitle(), $workflow->getTitle()], )); $this->addCardDescription($template, $card); $template->addBodyButton($this->l10n->t('Open card'), $cardLink); $template->addFooter(); $message = $this->mailer->createMessage(); $message->setTo([$email => $user->getDisplayName()]); $message->setFrom([Util::getDefaultEmailAddress('noreply') => $this->l10n->t('Deck Workflow Automation')]); $message->useTemplate($template); $failedRecipients = $this->mailer->send($message); if (!empty($failedRecipients)) { $this->logger->error('Notification mail for card ' . $card->getId() . ' failed for: ' . implode(', ', $failedRecipients), [ 'app' => 'workflow_deck_automation', ]); } } catch (Throwable $e) { $this->logger->error('Could not send notification mail for card ' . $card->getId() . ': ' . $e->getMessage(), [ 'app' => 'workflow_deck_automation', 'exception' => $e, ]); } } /** * 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, ); } }