From 781359a5827f8a6fa477cbc23b0c8ef5e94bac23 Mon Sep 17 00:00:00 2001 From: brad2014 Date: Tue, 8 Oct 2019 00:23:26 -0700 Subject: iMIP email improvements (take 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR is a replacement for PR #17195. It is intended to be simpler to review and approve, with fewer changes, some disabled by default. It addresses issues #12391 and #13555, with the following changes: - The plainText of iMIP emails has been upgraded as described in issue #12391. The HTML design style has not been changed. - Some of the HTML and plainText content has been rearranged (simplified header language, moving the event title to from text body to the first item in the bullet list, spelling corrections, moving the description to the end of the list), per issue #12391. - The interface for EMailTemplate has been extended: addBodyListItem now takes an optional `plainIndent` parameter. Existing callers see no change. Where new calls set the new parameter >0, the list item label (metaInfo) is put in column 1, and the value is indented into column 2 (properly accounting for multiple lines, if any). - An optional dav config setting has been added, `invitation_list_attendees`. It defaults to 'no', leaving emails unchanged. If set by the site admin to 'yes', then iMIP emails include, for the organizer and each attendee, their name, email, and a ✔︎ if they have accepted the invitation. - Minor refactoring. Notes: - The labels for organizers and attendees list items are new, and require translation/localization. - Dav config settings are documented in the code, but not in the Administrator's Guide. Signed-off-by: brad2014 --- apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 178 ++++++++++++++++++++-------- 1 file changed, 127 insertions(+), 51 deletions(-) (limited to 'apps/dav/lib') diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 6358a3a0293..011314b41d6 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -108,6 +108,7 @@ class IMipPlugin extends SabreIMipPlugin { public const METHOD_REQUEST = 'request'; public const METHOD_REPLY = 'reply'; public const METHOD_CANCEL = 'cancel'; + public const IMIP_INDENT = 15; // Enough for the length of all body bullet items, in all languages /** * @param IConfig $config @@ -204,26 +205,6 @@ class IMipPlugin extends SabreIMipPlugin { $meetingTitle = $vevent->SUMMARY; $meetingDescription = $vevent->DESCRIPTION; - $start = $vevent->DTSTART; - if (isset($vevent->DTEND)) { - $end = $vevent->DTEND; - } elseif (isset($vevent->DURATION)) { - $isFloating = $vevent->DTSTART->isFloating(); - $end = clone $vevent->DTSTART; - $endDateTime = $end->getDateTime(); - $endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue())); - $end->setDateTime($endDateTime, $isFloating); - } elseif (!$vevent->DTSTART->hasTime()) { - $isFloating = $vevent->DTSTART->isFloating(); - $end = clone $vevent->DTSTART; - $endDateTime = $end->getDateTime(); - $endDateTime = $endDateTime->modify('+1 day'); - $end->setDateTime($endDateTime, $isFloating); - } else { - $end = clone $vevent->DTSTART; - } - - $meetingWhen = $this->generateWhenString($l10n, $start, $end); $meetingUrl = $vevent->URL; $meetingLocation = $vevent->LOCATION; @@ -263,8 +244,7 @@ class IMipPlugin extends SabreIMipPlugin { $this->addSubjectAndHeading($template, $l10n, $method, $summary, $meetingAttendeeName, $meetingInviteeName); - $this->addBulletList($template, $l10n, $meetingWhen, $meetingLocation, - $meetingDescription, $meetingUrl); + $this->addBulletList($template, $l10n, $vevent); // Only add response buttons to invitation requests: Fix Issue #11230 @@ -370,7 +350,6 @@ class IMipPlugin extends SabreIMipPlugin { return $lastOccurrence; } - /** * @param Message $iTipMessage * @return null|Property @@ -420,10 +399,29 @@ class IMipPlugin extends SabreIMipPlugin { /** * @param IL10N $l10n - * @param Property $dtstart - * @param Property $dtend + * @param VEvent $vevent */ - private function generateWhenString(IL10N $l10n, Property $dtstart, Property $dtend) { + private function generateWhenString(IL10N $l10n, VEvent $vevent) { + + $dtstart = $vevent->DTSTART; + if (isset($vevent->DTEND)) { + $dtend = $vevent->DTEND; + } elseif (isset($vevent->DURATION)) { + $isFloating = $vevent->DTSTART->isFloating(); + $dtend = clone $vevent->DTSTART; + $endDateTime = $end->getDateTime(); + $endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue())); + $dtend->setDateTime($endDateTime, $isFloating); + } elseif (!$vevent->DTSTART->hasTime()) { + $isFloating = $vevent->DTSTART->isFloating(); + $dtend = clone $vevent->DTSTART; + $endDateTime = $end->getDateTime(); + $endDateTime = $endDateTime->modify('+1 day'); + $dtend->setDateTime($endDateTime, $isFloating); + } else { + $dtend = clone $vevent->DTSTART; + } + $isAllDay = $dtstart instanceof Property\ICalendar\Date; /** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */ @@ -507,49 +505,127 @@ class IMipPlugin extends SabreIMipPlugin { * @param IL10N $l10n * @param string $method * @param string $summary - * @param string $attendeeName - * @param string $inviteeName */ private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n, - $method, $summary, $attendeeName, $inviteeName) { + $method, $summary) { if ($method === self::METHOD_CANCEL) { - $template->setSubject('Cancelled: ' . $summary); - $template->addHeading($l10n->t('Invitation canceled'), $l10n->t('Hello %s,', [$attendeeName])); - $template->addBodyText($l10n->t('The meeting »%1$s« with %2$s was canceled.', [$summary, $inviteeName])); + $template->setSubject('Canceled: ' . $summary); + $template->addHeading($l10n->t('Invitation canceled')); } elseif ($method === self::METHOD_REPLY) { $template->setSubject('Re: ' . $summary); - $template->addHeading($l10n->t('Invitation updated'), $l10n->t('Hello %s,', [$attendeeName])); - $template->addBodyText($l10n->t('The meeting »%1$s« with %2$s was updated.', [$summary, $inviteeName])); + $template->addHeading($l10n->t('Invitation updated')); } else { $template->setSubject('Invitation: ' . $summary); - $template->addHeading($l10n->t('%1$s invited you to »%2$s«', [$inviteeName, $summary]), $l10n->t('Hello %s,', [$attendeeName])); + $template->addHeading($l10n->t('Invitation')); } } /** * @param IEMailTemplate $template * @param IL10N $l10n - * @param string $time - * @param string $location - * @param string $description - * @param string $url + * @param VEVENT $vevent */ - private function addBulletList(IEMailTemplate $template, IL10N $l10n, $time, $location, $description, $url) { - $template->addBodyListItem($time, $l10n->t('When:'), - $this->getAbsoluteImagePath('filetypes/text-calendar.svg')); + private function addBulletList(IEMailTemplate $template, IL10N $l10n, $vevent) { - if ($location) { - $template->addBodyListItem($location, $l10n->t('Where:'), - $this->getAbsoluteImagePath('filetypes/location.svg')); + if ($vevent->SUMMARY) { + $template->addBodyListItem($vevent->SUMMARY, $l10n->t('Title:'), + $this->getAbsoluteImagePath('filetypes/text.svg'),'','',self::IMIP_INDENT); } - if ($description) { - $template->addBodyListItem((string)$description, $l10n->t('Description:'), - $this->getAbsoluteImagePath('filetypes/text.svg')); + $meetingWhen = $this->generateWhenString($l10n, $vevent); + if ($meetingWhen) { + $template->addBodyListItem($meetingWhen, $l10n->t('Time:'), + $this->getAbsoluteImagePath('filetypes/text-calendar.svg'),'','',self::IMIP_INDENT); } - if ($url) { - $template->addBodyListItem((string)$url, $l10n->t('Link:'), - $this->getAbsoluteImagePath('filetypes/link.svg')); + if ($vevent->LOCATION) { + $template->addBodyListItem($vevent->LOCATION, $l10n->t('Location:'), + $this->getAbsoluteImagePath('filetypes/location.svg'),'','',self::IMIP_INDENT); } + if ($vevent->URL) { + $template->addBodyListItem(sprintf('%s', + htmlspecialchars($vevent->URL), + htmlspecialchars($vevent->URL)), + $l10n->t('Link:'), + $this->getAbsoluteImagePath('filetypes/link.svg'), + $vevent->URL,'',self::IMIP_INDENT); + } + + $this->addAttendees($template, $l10n, $vevent); + + /* Put description last, like an email body, since it can be arbitrarily long */ + if ($vevent->DESCRIPTION) { + $template->addBodyListItem($vevent->DESCRIPTION, $l10n->t('Description:'), + $this->getAbsoluteImagePath('filetypes/text.svg'),'','',self::IMIP_INDENT); + } + } + + /** + * addAttendees: add organizer and attendee names/emails to iMip mail. + * + * Enable with DAV setting: invitation_list_attendees (default: no) + * + * The default is 'no', which matches old behavior, and is privacy preserving. + * + * To enable including attendees in invitation emails: + * % php occ config:app:set dav invitation_list_attendees --value yes + * + * @param IEMailTemplate $template + * @param IL10N $l10n + * @param Message $iTipMessage + * @param int $lastOccurrence + * @author brad2014 on github.com + */ + + private function addAttendees(IEMailTemplate $template, IL10N $l10n, VEvent $vevent) { + if ($this->config->getAppValue('dav', 'invitation_list_attendees', 'no') === 'no') { + return; + } + + if (isset($vevent->ORGANIZER)) { + $organizer = $vevent->ORGANIZER; + $organizerURI = $organizer->getNormalizedValue(); + list($scheme,$organizerEmail) = explode(':',$organizerURI,2); # strip off scheme mailto: + $organizerName = isset($organizer['CN']) ? $organizer['CN'] : null; + $organizerHTML = sprintf('%s', + htmlspecialchars($organizerURI), + htmlspecialchars($organizerName ?: $organizerEmail)); + $organizerText = sprintf('%s <%s>', $organizerName, $organizerEmail); + if (isset($organizer['PARTSTAT']) + && strcasecmp($organizer['PARTSTAT'], 'ACCEPTED') === 0) { + $organizerHTML .= ' ✔︎'; + $organizerText .= ' ✔︎'; + } + $template->addBodyListItem($organizerHTML, $l10n->t('Organizer:'), + $this->getAbsoluteImagePath('filetypes/text-vcard.svg'), + $organizerText,'',self::IMIP_INDENT); + } + + $attendees = $vevent->select('ATTENDEE'); + if (count($attendees) === 0) { + return; + } + + $attendeesHTML = []; + $attendeesText = []; + foreach ($attendees as $attendee) { + $attendeeURI = $attendee->getNormalizedValue(); + list($scheme,$attendeeEmail) = explode(':',$attendeeURI,2); # strip off scheme mailto: + $attendeeName = isset($attendee['CN']) ? $attendee['CN'] : null; + $attendeeHTML = sprintf('%s', + htmlspecialchars($attendeeURI), + htmlspecialchars($attendeeName ?: $attendeeEmail)); + $attendeeText = sprintf('%s <%s>', $attendeeName, $attendeeEmail); + if (isset($attendee['PARTSTAT']) + && strcasecmp($attendee['PARTSTAT'], 'ACCEPTED') === 0) { + $attendeeHTML .= ' ✔︎'; + $attendeeText .= ' ✔︎'; + } + array_push($attendeesHTML, $attendeeHTML); + array_push($attendeesText, $attendeeText); + } + + $template->addBodyListItem(implode('
',$attendeesHTML), $l10n->t('Attendees:'), + $this->getAbsoluteImagePath('filetypes/text-vcard.svg'), + implode("\n",$attendeesText),'',self::IMIP_INDENT); } /** -- cgit v1.2.3 From b0687b1182e0c2da8873b2224d299f4c933f29db Mon Sep 17 00:00:00 2001 From: brad2014 Date: Thu, 10 Oct 2019 14:35:28 -0700 Subject: Make icon set for CalDav event presentation, use in iMIP email. iMIP e-mails were, as a hack, using filetypes icons for caldav elements (titles, locations). This commit creates a folder of caldav element icons. To start, they are used in iMIP emails, but eventually should be used by any app that wants to have icon labels for caldav elements. Signed-off-by: brad2014 --- apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 14 +++++++------- core/img/caldav/attendees.svg | 1 + core/img/caldav/description.svg | 1 + core/img/caldav/link.svg | 1 + core/img/caldav/location.svg | 1 + core/img/caldav/organizer.svg | 1 + core/img/caldav/time.svg | 1 + core/img/caldav/title.svg | 1 + 8 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 core/img/caldav/attendees.svg create mode 100644 core/img/caldav/description.svg create mode 100644 core/img/caldav/link.svg create mode 100644 core/img/caldav/location.svg create mode 100644 core/img/caldav/organizer.svg create mode 100644 core/img/caldav/time.svg create mode 100644 core/img/caldav/title.svg (limited to 'apps/dav/lib') diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 011314b41d6..6a9b64ff971 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -529,23 +529,23 @@ class IMipPlugin extends SabreIMipPlugin { if ($vevent->SUMMARY) { $template->addBodyListItem($vevent->SUMMARY, $l10n->t('Title:'), - $this->getAbsoluteImagePath('filetypes/text.svg'),'','',self::IMIP_INDENT); + $this->getAbsoluteImagePath('caldav/title.svg'),'','',self::IMIP_INDENT); } $meetingWhen = $this->generateWhenString($l10n, $vevent); if ($meetingWhen) { $template->addBodyListItem($meetingWhen, $l10n->t('Time:'), - $this->getAbsoluteImagePath('filetypes/text-calendar.svg'),'','',self::IMIP_INDENT); + $this->getAbsoluteImagePath('caldav/time.svg'),'','',self::IMIP_INDENT); } if ($vevent->LOCATION) { $template->addBodyListItem($vevent->LOCATION, $l10n->t('Location:'), - $this->getAbsoluteImagePath('filetypes/location.svg'),'','',self::IMIP_INDENT); + $this->getAbsoluteImagePath('caldav/location.svg'),'','',self::IMIP_INDENT); } if ($vevent->URL) { $template->addBodyListItem(sprintf('%s', htmlspecialchars($vevent->URL), htmlspecialchars($vevent->URL)), $l10n->t('Link:'), - $this->getAbsoluteImagePath('filetypes/link.svg'), + $this->getAbsoluteImagePath('caldav/link.svg'), $vevent->URL,'',self::IMIP_INDENT); } @@ -554,7 +554,7 @@ class IMipPlugin extends SabreIMipPlugin { /* Put description last, like an email body, since it can be arbitrarily long */ if ($vevent->DESCRIPTION) { $template->addBodyListItem($vevent->DESCRIPTION, $l10n->t('Description:'), - $this->getAbsoluteImagePath('filetypes/text.svg'),'','',self::IMIP_INDENT); + $this->getAbsoluteImagePath('caldav/description.svg'),'','',self::IMIP_INDENT); } } @@ -595,7 +595,7 @@ class IMipPlugin extends SabreIMipPlugin { $organizerText .= ' ✔︎'; } $template->addBodyListItem($organizerHTML, $l10n->t('Organizer:'), - $this->getAbsoluteImagePath('filetypes/text-vcard.svg'), + $this->getAbsoluteImagePath('caldav/organizer.svg'), $organizerText,'',self::IMIP_INDENT); } @@ -624,7 +624,7 @@ class IMipPlugin extends SabreIMipPlugin { } $template->addBodyListItem(implode('
',$attendeesHTML), $l10n->t('Attendees:'), - $this->getAbsoluteImagePath('filetypes/text-vcard.svg'), + $this->getAbsoluteImagePath('caldav/attendees.svg'), implode("\n",$attendeesText),'',self::IMIP_INDENT); } diff --git a/core/img/caldav/attendees.svg b/core/img/caldav/attendees.svg new file mode 100644 index 00000000000..86c3d4a4132 --- /dev/null +++ b/core/img/caldav/attendees.svg @@ -0,0 +1 @@ + diff --git a/core/img/caldav/description.svg b/core/img/caldav/description.svg new file mode 100644 index 00000000000..57c2b1f5725 --- /dev/null +++ b/core/img/caldav/description.svg @@ -0,0 +1 @@ + diff --git a/core/img/caldav/link.svg b/core/img/caldav/link.svg new file mode 100644 index 00000000000..7bfbe1eb2de --- /dev/null +++ b/core/img/caldav/link.svg @@ -0,0 +1 @@ + diff --git a/core/img/caldav/location.svg b/core/img/caldav/location.svg new file mode 100644 index 00000000000..5e63f7563cd --- /dev/null +++ b/core/img/caldav/location.svg @@ -0,0 +1 @@ + diff --git a/core/img/caldav/organizer.svg b/core/img/caldav/organizer.svg new file mode 100644 index 00000000000..7b75d9e29a6 --- /dev/null +++ b/core/img/caldav/organizer.svg @@ -0,0 +1 @@ + diff --git a/core/img/caldav/time.svg b/core/img/caldav/time.svg new file mode 100644 index 00000000000..2fdfde67960 --- /dev/null +++ b/core/img/caldav/time.svg @@ -0,0 +1 @@ + diff --git a/core/img/caldav/title.svg b/core/img/caldav/title.svg new file mode 100644 index 00000000000..57d674b9f2c --- /dev/null +++ b/core/img/caldav/title.svg @@ -0,0 +1 @@ + -- cgit v1.2.3 From 442af8c5d57797bf255db036c8c04ab48f36c3a1 Mon Sep 17 00:00:00 2001 From: Brad Rubenstein Date: Wed, 2 Sep 2020 17:16:24 +0200 Subject: Minor cleanup: php-cs-fixer, tests, interface consistency IMipPlugin.php Removed blank lines to make php-cs-fixer happy. Minor cleanup: bugs found by Psalm static checker IEMailTemplate: The public interface to addBodyListItem also needs to include the new plainIndent parameter. IMipPlugin: Fixes an undefined variable for events that do not have DTEND. Also use explicit string conversion for parameters and properties in several places. The new email template adds an additional blank line before "button" links in plain text, so the tests were fixed to include that additional blank line. Signed-off-by: Brad Rubenstein --- apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 32 ++++++++++++---------- .../tests/unit/CalDAV/Schedule/IMipPluginTest.php | 12 ++++---- .../tests/Mailer/NewUserMailHelperTest.php | 2 ++ lib/private/Mail/EMailTemplate.php | 12 ++++---- lib/public/Mail/IEMailTemplate.php | 3 +- .../new-account-email-custom-text-alternative.txt | 1 + tests/data/emails/new-account-email-custom.txt | 1 + tests/data/emails/new-account-email.txt | 1 + 8 files changed, 38 insertions(+), 26 deletions(-) (limited to 'apps/dav/lib') diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 6a9b64ff971..56517ab28c1 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -3,7 +3,6 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2017, Georg Ehrke * - * @author brad2014 * @author Brad Rubenstein * @author Christoph Wurst * @author Georg Ehrke @@ -242,8 +241,7 @@ class IMipPlugin extends SabreIMipPlugin { $summary = ((string) $summary !== '') ? (string) $summary : $l10n->t('Untitled event'); - $this->addSubjectAndHeading($template, $l10n, $method, $summary, - $meetingAttendeeName, $meetingInviteeName); + $this->addSubjectAndHeading($template, $l10n, $method, $summary); $this->addBulletList($template, $l10n, $vevent); @@ -402,20 +400,19 @@ class IMipPlugin extends SabreIMipPlugin { * @param VEvent $vevent */ private function generateWhenString(IL10N $l10n, VEvent $vevent) { - $dtstart = $vevent->DTSTART; if (isset($vevent->DTEND)) { $dtend = $vevent->DTEND; } elseif (isset($vevent->DURATION)) { $isFloating = $vevent->DTSTART->isFloating(); $dtend = clone $vevent->DTSTART; - $endDateTime = $end->getDateTime(); + $endDateTime = $dtend->getDateTime(); $endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue())); $dtend->setDateTime($endDateTime, $isFloating); } elseif (!$vevent->DTSTART->hasTime()) { $isFloating = $vevent->DTSTART->isFloating(); $dtend = clone $vevent->DTSTART; - $endDateTime = $end->getDateTime(); + $endDateTime = $dtend->getDateTime(); $endDateTime = $endDateTime->modify('+1 day'); $dtend->setDateTime($endDateTime, $isFloating); } else { @@ -526,7 +523,6 @@ class IMipPlugin extends SabreIMipPlugin { * @param VEVENT $vevent */ private function addBulletList(IEMailTemplate $template, IL10N $l10n, $vevent) { - if ($vevent->SUMMARY) { $template->addBodyListItem($vevent->SUMMARY, $l10n->t('Title:'), $this->getAbsoluteImagePath('caldav/title.svg'),'','',self::IMIP_INDENT); @@ -541,19 +537,20 @@ class IMipPlugin extends SabreIMipPlugin { $this->getAbsoluteImagePath('caldav/location.svg'),'','',self::IMIP_INDENT); } if ($vevent->URL) { + $url = $vevent->URL->getValue(); $template->addBodyListItem(sprintf('%s', - htmlspecialchars($vevent->URL), - htmlspecialchars($vevent->URL)), + htmlspecialchars($url), + htmlspecialchars($url)), $l10n->t('Link:'), $this->getAbsoluteImagePath('caldav/link.svg'), - $vevent->URL,'',self::IMIP_INDENT); + $url,'',self::IMIP_INDENT); } $this->addAttendees($template, $l10n, $vevent); /* Put description last, like an email body, since it can be arbitrarily long */ if ($vevent->DESCRIPTION) { - $template->addBodyListItem($vevent->DESCRIPTION, $l10n->t('Description:'), + $template->addBodyListItem($vevent->DESCRIPTION->getValue(), $l10n->t('Description:'), $this->getAbsoluteImagePath('caldav/description.svg'),'','',self::IMIP_INDENT); } } @@ -581,18 +578,23 @@ class IMipPlugin extends SabreIMipPlugin { } if (isset($vevent->ORGANIZER)) { + /** @var Property\ICalendar\CalAddress $organizer */ $organizer = $vevent->ORGANIZER; $organizerURI = $organizer->getNormalizedValue(); list($scheme,$organizerEmail) = explode(':',$organizerURI,2); # strip off scheme mailto: + /** @var string|null $organizerName */ $organizerName = isset($organizer['CN']) ? $organizer['CN'] : null; $organizerHTML = sprintf('%s', htmlspecialchars($organizerURI), htmlspecialchars($organizerName ?: $organizerEmail)); $organizerText = sprintf('%s <%s>', $organizerName, $organizerEmail); - if (isset($organizer['PARTSTAT']) - && strcasecmp($organizer['PARTSTAT'], 'ACCEPTED') === 0) { - $organizerHTML .= ' ✔︎'; - $organizerText .= ' ✔︎'; + if (isset($organizer['PARTSTAT'])) { + /** @var Parameter $partstat */ + $partstat = $organizer['PARTSTAT']; + if (strcasecmp($partstat->getValue(), 'ACCEPTED') === 0) { + $organizerHTML .= ' ✔︎'; + $organizerText .= ' ✔︎'; + } } $template->addBodyListItem($organizerHTML, $l10n->t('Organizer:'), $this->getAbsoluteImagePath('caldav/organizer.svg'), diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php index 8faa54f534a..a31fdfdc5f7 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php @@ -136,6 +136,7 @@ class IMipPluginTest extends TestCase { public function testDelivery() { $this->config + ->expects($this->at(1)) ->method('getAppValue') ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn('yes'); @@ -148,6 +149,7 @@ class IMipPluginTest extends TestCase { public function testFailedDelivery() { $this->config + ->expects($this->at(1)) ->method('getAppValue') ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn('yes'); @@ -163,6 +165,7 @@ class IMipPluginTest extends TestCase { public function testDeliveryWithNoCommonName() { $this->config + ->expects($this->at(1)) ->method('getAppValue') ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn('yes'); @@ -188,9 +191,8 @@ class IMipPluginTest extends TestCase { */ public function testNoMessageSendForPastEvents(array $veventParams, bool $expectsMail) { $this->config - ->method('getAppValue') - ->with('dav', 'invitation_link_recipients', 'yes') - ->willReturn('yes'); + ->method('getAppValue') + ->willReturn('yes'); $message = $this->_testMessage($veventParams); @@ -228,6 +230,7 @@ class IMipPluginTest extends TestCase { $this->_expectSend($recipient, true, $has_buttons); $this->config + ->expects($this->at(1)) ->method('getAppValue') ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn($config_setting); @@ -252,14 +255,13 @@ class IMipPluginTest extends TestCase { public function testMessageSendWhenEventWithoutName() { $this->config ->method('getAppValue') - ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn('yes'); $message = $this->_testMessage(['SUMMARY' => '']); $this->_expectSend('frodo@hobb.it', true, true,'Invitation: Untitled event'); $this->emailTemplate->expects($this->once()) ->method('addHeading') - ->with('Mr. Wizard invited you to »Untitled event«'); + ->with('Invitation'); $this->plugin->schedule($message); $this->assertEquals('1.1', $message->getScheduleStatus()); } diff --git a/apps/settings/tests/Mailer/NewUserMailHelperTest.php b/apps/settings/tests/Mailer/NewUserMailHelperTest.php index fdb5da3bb54..7507c8a9dac 100644 --- a/apps/settings/tests/Mailer/NewUserMailHelperTest.php +++ b/apps/settings/tests/Mailer/NewUserMailHelperTest.php @@ -599,6 +599,7 @@ Welcome to your TestCloud account, you can add, protect, and share your data. Your username is: john + Go to TestCloud: https://example.com/ Install Client: https://nextcloud.com/install/#install-clients @@ -817,6 +818,7 @@ Welcome aboard John Doe Welcome to your TestCloud account, you can add, protect, and share your data. + Go to TestCloud: https://example.com/ Install Client: https://nextcloud.com/install/#install-clients diff --git a/lib/private/Mail/EMailTemplate.php b/lib/private/Mail/EMailTemplate.php index 054378c2afa..e3768ae6cde 100644 --- a/lib/private/Mail/EMailTemplate.php +++ b/lib/private/Mail/EMailTemplate.php @@ -447,21 +447,21 @@ EOF; * @param string $metaInfo Note: When $plainMetaInfo falls back to this, HTML is automatically escaped in the HTML email * @param string $icon Absolute path, must be 16*16 pixels * @param string|bool $plainText Text that is used in the plain text email - * if empty the $text is used, if false none will be used + * if empty or true the $text is used, if false none will be used * @param string|bool $plainMetaInfo Meta info that is used in the plain text email - * if empty the $metaInfo is used, if false none will be used + * if empty or true the $metaInfo is used, if false none will be used * @param integer plainIndent If > 0, Indent plainText by this amount. * @since 12.0.0 */ public function addBodyListItem(string $text, string $metaInfo = '', string $icon = '', $plainText = '', $plainMetaInfo = '', $plainIndent = 0) { $this->ensureBodyListOpened(); - if ($plainText === '') { + if ($plainText === '' || $plainText === true) { $plainText = $text; $text = htmlspecialchars($text); $text = str_replace("\n", "
", $text); // convert newlines to HTML breaks } - if ($plainMetaInfo === '') { + if ($plainMetaInfo === '' || $plainMetaInfo === true) { $plainMetaInfo = $metaInfo; $metaInfo = htmlspecialchars($metaInfo); } @@ -494,8 +494,10 @@ EOF; * "plainIndent". Multilines after the first are indented plainIndent+1 * (to account for space after label). Fixes: #12391 */ + /** @var string $label */ + $label = ($plainMetaInfo !== false)? $plainMetaInfo : ''; $this->plainBody .= sprintf("%${plainIndent}s %s\n", - $plainMetaInfo, + $label, str_replace("\n", "\n" . str_repeat(' ', $plainIndent+1), $plainText)); } } diff --git a/lib/public/Mail/IEMailTemplate.php b/lib/public/Mail/IEMailTemplate.php index 70046d5c508..5f4e235a7ee 100644 --- a/lib/public/Mail/IEMailTemplate.php +++ b/lib/public/Mail/IEMailTemplate.php @@ -106,9 +106,10 @@ interface IEMailTemplate { * if empty the $text is used, if false none will be used * @param string|bool $plainMetaInfo Meta info that is used in the plain text email * if empty the $metaInfo is used, if false none will be used + * @param integer plainIndent If > 0, Indent plainText by this amount. * @since 12.0.0 */ - public function addBodyListItem(string $text, string $metaInfo = '', string $icon = '', $plainText = '', $plainMetaInfo = ''); + public function addBodyListItem(string $text, string $metaInfo = '', string $icon = '', $plainText = '', $plainMetaInfo = '', $plainIndent = 0); /** * Adds a button group of two buttons to the body of the email diff --git a/tests/data/emails/new-account-email-custom-text-alternative.txt b/tests/data/emails/new-account-email-custom-text-alternative.txt index f65744b20d9..03cb99c1d76 100644 --- a/tests/data/emails/new-account-email-custom-text-alternative.txt +++ b/tests/data/emails/new-account-email-custom-text-alternative.txt @@ -4,6 +4,7 @@ Welcome to your Nextcloud account, you can add, protect, and share your data. - Your username is: abc + Set your password - text: https://example.org/resetPassword/123 Install Client - text: https://nextcloud.com/install/#install-clients diff --git a/tests/data/emails/new-account-email-custom.txt b/tests/data/emails/new-account-email-custom.txt index 57c5202a744..c075c49d649 100644 --- a/tests/data/emails/new-account-email-custom.txt +++ b/tests/data/emails/new-account-email-custom.txt @@ -4,6 +4,7 @@ Welcome to your Nextcloud account, you can add, protect, and share your data. Your username is: abc + Set your password: https://example.org/resetPassword/123 Install Client: https://nextcloud.com/install/#install-clients diff --git a/tests/data/emails/new-account-email.txt b/tests/data/emails/new-account-email.txt index 89524134183..b246482af13 100644 --- a/tests/data/emails/new-account-email.txt +++ b/tests/data/emails/new-account-email.txt @@ -4,6 +4,7 @@ Welcome to your Nextcloud account, you can add, protect, and share your data. Your username is: abc + Set your password: https://example.org/resetPassword/123 Install Client: https://nextcloud.com/install/#install-clients -- cgit v1.2.3