Skip to content
Snippets Groups Projects
Unverified Commit 593d64d9 authored by John Molakvoæ's avatar John Molakvoæ Committed by GitHub
Browse files

Merge pull request #17456 from brad2014/feature/brad2014/12391-improve-imip-mail-message-take-2

parents 5483d02b 442af8c5
No related branches found
No related tags found
No related merge requests found
Showing
with 183 additions and 70 deletions
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
* @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2016, ownCloud, Inc.
* @copyright Copyright (c) 2017, Georg Ehrke * @copyright Copyright (c) 2017, Georg Ehrke
* *
* @author brad2014 <brad2014@users.noreply.github.com>
* @author Brad Rubenstein <brad@wbr.tech> * @author Brad Rubenstein <brad@wbr.tech>
* @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Georg Ehrke <oc.list@georgehrke.com> * @author Georg Ehrke <oc.list@georgehrke.com>
...@@ -108,6 +107,7 @@ class IMipPlugin extends SabreIMipPlugin { ...@@ -108,6 +107,7 @@ class IMipPlugin extends SabreIMipPlugin {
public const METHOD_REQUEST = 'request'; public const METHOD_REQUEST = 'request';
public const METHOD_REPLY = 'reply'; public const METHOD_REPLY = 'reply';
public const METHOD_CANCEL = 'cancel'; 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 * @param IConfig $config
...@@ -204,26 +204,6 @@ class IMipPlugin extends SabreIMipPlugin { ...@@ -204,26 +204,6 @@ class IMipPlugin extends SabreIMipPlugin {
$meetingTitle = $vevent->SUMMARY; $meetingTitle = $vevent->SUMMARY;
$meetingDescription = $vevent->DESCRIPTION; $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; $meetingUrl = $vevent->URL;
$meetingLocation = $vevent->LOCATION; $meetingLocation = $vevent->LOCATION;
...@@ -261,10 +241,8 @@ class IMipPlugin extends SabreIMipPlugin { ...@@ -261,10 +241,8 @@ class IMipPlugin extends SabreIMipPlugin {
$summary = ((string) $summary !== '') ? (string) $summary : $l10n->t('Untitled event'); $summary = ((string) $summary !== '') ? (string) $summary : $l10n->t('Untitled event');
$this->addSubjectAndHeading($template, $l10n, $method, $summary, $this->addSubjectAndHeading($template, $l10n, $method, $summary);
$meetingAttendeeName, $meetingInviteeName); $this->addBulletList($template, $l10n, $vevent);
$this->addBulletList($template, $l10n, $meetingWhen, $meetingLocation,
$meetingDescription, $meetingUrl);
// Only add response buttons to invitation requests: Fix Issue #11230 // Only add response buttons to invitation requests: Fix Issue #11230
...@@ -370,7 +348,6 @@ class IMipPlugin extends SabreIMipPlugin { ...@@ -370,7 +348,6 @@ class IMipPlugin extends SabreIMipPlugin {
return $lastOccurrence; return $lastOccurrence;
} }
/** /**
* @param Message $iTipMessage * @param Message $iTipMessage
* @return null|Property * @return null|Property
...@@ -420,10 +397,28 @@ class IMipPlugin extends SabreIMipPlugin { ...@@ -420,10 +397,28 @@ class IMipPlugin extends SabreIMipPlugin {
/** /**
* @param IL10N $l10n * @param IL10N $l10n
* @param Property $dtstart * @param VEvent $vevent
* @param Property $dtend
*/ */
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 = $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 = $dtend->getDateTime();
$endDateTime = $endDateTime->modify('+1 day');
$dtend->setDateTime($endDateTime, $isFloating);
} else {
$dtend = clone $vevent->DTSTART;
}
$isAllDay = $dtstart instanceof Property\ICalendar\Date; $isAllDay = $dtstart instanceof Property\ICalendar\Date;
/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */ /** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
...@@ -507,49 +502,132 @@ class IMipPlugin extends SabreIMipPlugin { ...@@ -507,49 +502,132 @@ class IMipPlugin extends SabreIMipPlugin {
* @param IL10N $l10n * @param IL10N $l10n
* @param string $method * @param string $method
* @param string $summary * @param string $summary
* @param string $attendeeName
* @param string $inviteeName
*/ */
private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n, private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n,
$method, $summary, $attendeeName, $inviteeName) { $method, $summary) {
if ($method === self::METHOD_CANCEL) { if ($method === self::METHOD_CANCEL) {
$template->setSubject('Cancelled: ' . $summary); $template->setSubject('Canceled: ' . $summary);
$template->addHeading($l10n->t('Invitation canceled'), $l10n->t('Hello %s,', [$attendeeName])); $template->addHeading($l10n->t('Invitation canceled'));
$template->addBodyText($l10n->t('The meeting »%1$s« with %2$s was canceled.', [$summary, $inviteeName]));
} elseif ($method === self::METHOD_REPLY) { } elseif ($method === self::METHOD_REPLY) {
$template->setSubject('Re: ' . $summary); $template->setSubject('Re: ' . $summary);
$template->addHeading($l10n->t('Invitation updated'), $l10n->t('Hello %s,', [$attendeeName])); $template->addHeading($l10n->t('Invitation updated'));
$template->addBodyText($l10n->t('The meeting »%1$s« with %2$s was updated.', [$summary, $inviteeName]));
} else { } else {
$template->setSubject('Invitation: ' . $summary); $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 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);
}
$meetingWhen = $this->generateWhenString($l10n, $vevent);
if ($meetingWhen) {
$template->addBodyListItem($meetingWhen, $l10n->t('Time:'),
$this->getAbsoluteImagePath('caldav/time.svg'),'','',self::IMIP_INDENT);
}
if ($vevent->LOCATION) {
$template->addBodyListItem($vevent->LOCATION, $l10n->t('Location:'),
$this->getAbsoluteImagePath('caldav/location.svg'),'','',self::IMIP_INDENT);
}
if ($vevent->URL) {
$url = $vevent->URL->getValue();
$template->addBodyListItem(sprintf('<a href="%s">%s</a>',
htmlspecialchars($url),
htmlspecialchars($url)),
$l10n->t('Link:'),
$this->getAbsoluteImagePath('caldav/link.svg'),
$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->getValue(), $l10n->t('Description:'),
$this->getAbsoluteImagePath('caldav/description.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 IEMailTemplate $template
* @param IL10N $l10n * @param IL10N $l10n
* @param string $time * @param Message $iTipMessage
* @param string $location * @param int $lastOccurrence
* @param string $description * @author brad2014 on github.com
* @param string $url
*/ */
private function addBulletList(IEMailTemplate $template, IL10N $l10n, $time, $location, $description, $url) {
$template->addBodyListItem($time, $l10n->t('When:'),
$this->getAbsoluteImagePath('filetypes/text-calendar.svg'));
if ($location) { private function addAttendees(IEMailTemplate $template, IL10N $l10n, VEvent $vevent) {
$template->addBodyListItem($location, $l10n->t('Where:'), if ($this->config->getAppValue('dav', 'invitation_list_attendees', 'no') === 'no') {
$this->getAbsoluteImagePath('filetypes/location.svg')); return;
} }
if ($description) {
$template->addBodyListItem((string)$description, $l10n->t('Description:'), if (isset($vevent->ORGANIZER)) {
$this->getAbsoluteImagePath('filetypes/text.svg')); /** @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('<a href="%s">%s</a>',
htmlspecialchars($organizerURI),
htmlspecialchars($organizerName ?: $organizerEmail));
$organizerText = sprintf('%s <%s>', $organizerName, $organizerEmail);
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'),
$organizerText,'',self::IMIP_INDENT);
} }
if ($url) {
$template->addBodyListItem((string)$url, $l10n->t('Link:'), $attendees = $vevent->select('ATTENDEE');
$this->getAbsoluteImagePath('filetypes/link.svg')); 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('<a href="%s">%s</a>',
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('<br/>',$attendeesHTML), $l10n->t('Attendees:'),
$this->getAbsoluteImagePath('caldav/attendees.svg'),
implode("\n",$attendeesText),'',self::IMIP_INDENT);
} }
/** /**
......
...@@ -136,6 +136,7 @@ class IMipPluginTest extends TestCase { ...@@ -136,6 +136,7 @@ class IMipPluginTest extends TestCase {
public function testDelivery() { public function testDelivery() {
$this->config $this->config
->expects($this->at(1))
->method('getAppValue') ->method('getAppValue')
->with('dav', 'invitation_link_recipients', 'yes') ->with('dav', 'invitation_link_recipients', 'yes')
->willReturn('yes'); ->willReturn('yes');
...@@ -148,6 +149,7 @@ class IMipPluginTest extends TestCase { ...@@ -148,6 +149,7 @@ class IMipPluginTest extends TestCase {
public function testFailedDelivery() { public function testFailedDelivery() {
$this->config $this->config
->expects($this->at(1))
->method('getAppValue') ->method('getAppValue')
->with('dav', 'invitation_link_recipients', 'yes') ->with('dav', 'invitation_link_recipients', 'yes')
->willReturn('yes'); ->willReturn('yes');
...@@ -163,6 +165,7 @@ class IMipPluginTest extends TestCase { ...@@ -163,6 +165,7 @@ class IMipPluginTest extends TestCase {
public function testDeliveryWithNoCommonName() { public function testDeliveryWithNoCommonName() {
$this->config $this->config
->expects($this->at(1))
->method('getAppValue') ->method('getAppValue')
->with('dav', 'invitation_link_recipients', 'yes') ->with('dav', 'invitation_link_recipients', 'yes')
->willReturn('yes'); ->willReturn('yes');
...@@ -188,9 +191,8 @@ class IMipPluginTest extends TestCase { ...@@ -188,9 +191,8 @@ class IMipPluginTest extends TestCase {
*/ */
public function testNoMessageSendForPastEvents(array $veventParams, bool $expectsMail) { public function testNoMessageSendForPastEvents(array $veventParams, bool $expectsMail) {
$this->config $this->config
->method('getAppValue') ->method('getAppValue')
->with('dav', 'invitation_link_recipients', 'yes') ->willReturn('yes');
->willReturn('yes');
$message = $this->_testMessage($veventParams); $message = $this->_testMessage($veventParams);
...@@ -228,6 +230,7 @@ class IMipPluginTest extends TestCase { ...@@ -228,6 +230,7 @@ class IMipPluginTest extends TestCase {
$this->_expectSend($recipient, true, $has_buttons); $this->_expectSend($recipient, true, $has_buttons);
$this->config $this->config
->expects($this->at(1))
->method('getAppValue') ->method('getAppValue')
->with('dav', 'invitation_link_recipients', 'yes') ->with('dav', 'invitation_link_recipients', 'yes')
->willReturn($config_setting); ->willReturn($config_setting);
...@@ -252,14 +255,13 @@ class IMipPluginTest extends TestCase { ...@@ -252,14 +255,13 @@ class IMipPluginTest extends TestCase {
public function testMessageSendWhenEventWithoutName() { public function testMessageSendWhenEventWithoutName() {
$this->config $this->config
->method('getAppValue') ->method('getAppValue')
->with('dav', 'invitation_link_recipients', 'yes')
->willReturn('yes'); ->willReturn('yes');
$message = $this->_testMessage(['SUMMARY' => '']); $message = $this->_testMessage(['SUMMARY' => '']);
$this->_expectSend('frodo@hobb.it', true, true,'Invitation: Untitled event'); $this->_expectSend('frodo@hobb.it', true, true,'Invitation: Untitled event');
$this->emailTemplate->expects($this->once()) $this->emailTemplate->expects($this->once())
->method('addHeading') ->method('addHeading')
->with('Mr. Wizard invited you to »Untitled event«'); ->with('Invitation');
$this->plugin->schedule($message); $this->plugin->schedule($message);
$this->assertEquals('1.1', $message->getScheduleStatus()); $this->assertEquals('1.1', $message->getScheduleStatus());
} }
......
...@@ -601,6 +601,7 @@ Welcome to your TestCloud account, you can add, protect, and share your data. ...@@ -601,6 +601,7 @@ Welcome to your TestCloud account, you can add, protect, and share your data.
Your username is: john Your username is: john
Go to TestCloud: https://example.com/ Go to TestCloud: https://example.com/
Install Client: https://nextcloud.com/install/#install-clients Install Client: https://nextcloud.com/install/#install-clients
...@@ -819,6 +820,7 @@ Welcome aboard John Doe ...@@ -819,6 +820,7 @@ Welcome aboard John Doe
Welcome to your TestCloud account, you can add, protect, and share your data. Welcome to your TestCloud account, you can add, protect, and share your data.
Go to TestCloud: https://example.com/ Go to TestCloud: https://example.com/
Install Client: https://nextcloud.com/install/#install-clients Install Client: https://nextcloud.com/install/#install-clients
......
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 16 16" height="16" width="16" version="1.1"><path fill="#969696" d="m10 1c-1.75 0-3 1.43-3 2.8 0 1.4 0.1 2.4 0.8 3.5 0.2 0.29 0.5 0.35 0.7 0.6 0.135 0.5 0.24 1 0.1 1.5-0.28 0.1-0.525 0.22-0.8 0.33-0.085-0.15-0.23-0.2-0.47-0.4-0.73-0.44-1.56-0.75-2.33-1.04-0.1-0.37-0.1-0.65 0-1 0.156-0.166 0.37-0.27 0.5-0.43 0.46-0.6 0.5-1.654 0.5-2.37 0-1.06-0.954-1.9-2-1.9-1.17 0-2 1-2 1.9 0 0.93 0.034 1.64 0.5 2.37 0.13 0.2 0.367 0.26 0.5 0.43 0.1 0.33 0.1 0.654 0 1-0.85 0.3-1.6 0.64-2.34 1.04-0.57 0.4-0.52 0.205-0.66 1.53-0.11 1.06 2.335 1.13 4 1.13 0.06 0 0.11 0 0.17 0-0.054 0.274-0.1 0.63-0.17 1.3-0.16 1.59 3.5 1.7 6 1.7s6.16-0.1 6-1.7c-0.215-2-0.23-1.71-1-2.3-1.1-0.654-2.45-1.17-3.6-1.6-0.15-0.56-0.04-0.97 0.1-1.5 0.235-0.25 0.5-0.36 0.7-0.6 0.7-0.885 0.8-2.425 0.8-3.5 0-1.6-1.43-2.8-3-2.8z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" version="1.1" height="16"><path fill="#969696" d="m2.5 1c-0.28 0-0.5 0.22-0.5 0.5v13c0 0.28 0.22 0.5 0.5 0.5h11c0.28 0 0.5-0.22 0.5-0.5v-10.5l-3-3h-8.5zm1.5 2h6v1h-6v-1zm0 3h5v1h-5v-1zm0 3h8v1h-8v-1zm0 3h4v1h-4v-1z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 16 16" width="16" version="1.1" height="16"><path fill="#969696" d="m7.95 0.65c-4.1 0-7.4 3.3-7.4 7.4s3.3 7.4 7.4 7.4 7.4-3.3 7.4-7.4-3.3-7.4-7.4-7.4zm0.8 0.9c1.3 0 2.4 0.8 3.5 1.3l1.8 2.5-0.3 1.1 0.6 0.3v2.4c-0.2 0.7-0.6 1.3-0.9 2-0.2 0.1 0-0.8-0.1-1 0-0.6-0.5-0.6-0.9-0.2-0.4 0.3-1.4 0.3-1.5-0.4-0.3-0.8 0-1.7 0.3-2.5l-0.6-0.7 0.2-1.8-0.8-0.9 0.2-1-1-0.6c-0.2-0.2-0.6-0.2-0.7-0.4 0.1 0 0.2-0.1 0.2-0.1zm-2.6 0.1s0.1 0 0.1 0.1c0.4 0.2-0.1 0.4-0.2 0.6-0.5 0.3 0.3 0.7 0.5 1 0.4-0.1 0.8-0.7 1.4-0.5 0.7-0.2 0.6 0.6 1.1 1 0.1 0.2 0.9 0.8 0.4 0.6-0.5-0.4-1-0.4-1.3 0.1-0.8 0.5-0.3-0.9-0.7-1.2-0.6-0.7-0.4 0.5-0.4 0.9-0.4 0-1.1-0.3-1.5 0.2l0.4 0.6 0.5-0.7c0-0.3 0.1 0.2 0.3 0.3 0.1 0.2 0.8 0.7 0.3 0.9-0.8 0.4-1.4 1.1-2.1 1.7-0.2 0.5-0.7 0.4-1 0-0.7-0.4-0.7 0.7-0.6 1.1l0.6-0.4v1.1c-0.4 0.4-0.9-0.7-1.3-0.9v-1.6c0-0.4-0.1-0.9 0-1.3 0.8-0.9 1.7-1.9 2.2-3h0.8c0.6 0.2 0.3-0.7 0.5-0.6zm-1.2 8.2c0.1 0 0.2 0 0.3 0.1 0.8 0.1 1.4 0.7 2 1.1 0.5 0.5 1.6 0.3 1.7 1.2-0.2 0.9-1.1 1.4-1.8 1.7-0.2 0.1-0.4 0.2-0.6 0.2-0.7 0.2-1-0.6-1.2-1.1-0.3-0.7-1.1-1.2-1-2.1 0-0.4 0.2-1 0.6-1.1z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" version="1.1" height="16"><circle stroke-width="2" stroke="#969696" cy="6" cx="8" r="4" fill="none"/><path fill="#969696" d="m4 9h8l-4 6z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 16 16" height="16" width="16" version="1.1"><path fill="#969696" d="m5 3.8c0 1.4 0.1 2.4 0.8 3.5 0.2 0.286 0.5 0.35 0.7 0.6 0.135 0.5 0.24 0.98 0.1 1.5-1.275 0.45-2.49 1-3.6 1.6-0.85 0.6-0.785 0.31-1 2.3-0.16 1.59 3.5 1.7 6 1.7s6.163-0.1 6-1.7c-0.215-2-0.23-1.71-1-2.3-1.1-0.654-2.45-1.167-3.6-1.6-0.15-0.56-0.04-0.973 0.1-1.5 0.235-0.25 0.5-0.363 0.7-0.6 0.69-0.885 0.8-2.425 0.8-3.5 0-1.59-1.43-2.8-3-2.8-1.75 0-3 1.43-3 2.8z"/></svg>
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg" version="1.1" viewbox="0 0 16 16"><path fill="#969696" d="m4 1c-0.5 0-1 0.5-1 1v2c0 0.5 0.5 1 1 1s1-0.5 1-1v-2c0-0.5-0.5-1-1-1zm8 0c-0.5 0-1 0.5-1 1v2c0 0.5 0.5 1 1 1s1-0.5 1-1v-2c0-0.5-0.5-1-1-1zm-6.5 2v1c0 0.831-0.5 1.5-1.5 1.5s-1.5-0.5-1.5-1.5v-0.9375c-0.8841 0.227-1.5 1.0247-1.5 1.9375v8c0 1.108 0.892 2 2 2h10c1.108 0 2-0.892 2-2v-8c0-0.9128-0.61588-1.7105-1.5-1.9375v0.9375c0 0.831-0.5 1.5-1.5 1.5s-1.5-0.5-1.5-1.5v-1zm7.5 5v5h-10v-5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 16 16" width="16" height="16"><path fill="#969696" d="M2 2l12 6-12 6z"/></svg>
...@@ -448,19 +448,21 @@ EOF; ...@@ -448,19 +448,21 @@ EOF;
* @param string $metaInfo Note: When $plainMetaInfo falls back to this, HTML is automatically escaped in the HTML email * @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 $icon Absolute path, must be 16*16 pixels
* @param string|bool $plainText Text that is used in the plain text email * @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 * @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 * @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) {
$this->ensureBodyListOpened(); $this->ensureBodyListOpened();
if ($plainText === '') { if ($plainText === '' || $plainText === true) {
$plainText = $text; $plainText = $text;
$text = htmlspecialchars($text); $text = htmlspecialchars($text);
$text = str_replace("\n", "<br/>", $text); // convert newlines to HTML breaks
} }
if ($plainMetaInfo === '') { if ($plainMetaInfo === '' || $plainMetaInfo === true) {
$plainMetaInfo = $metaInfo; $plainMetaInfo = $metaInfo;
$metaInfo = htmlspecialchars($metaInfo); $metaInfo = htmlspecialchars($metaInfo);
} }
...@@ -476,11 +478,29 @@ EOF; ...@@ -476,11 +478,29 @@ EOF;
} }
$this->htmlBody .= vsprintf($this->listItem, [$icon, $htmlText]); $this->htmlBody .= vsprintf($this->listItem, [$icon, $htmlText]);
if ($plainText !== false) { if ($plainText !== false) {
$this->plainBody .= ' * ' . $plainText; if ($plainIndent === 0) {
if ($plainMetaInfo !== false) { /*
$this->plainBody .= ' (' . $plainMetaInfo . ')'; * If plainIndent is not set by caller, this is the old NC17 layout code.
*/
$this->plainBody .= ' * ' . $plainText;
if ($plainMetaInfo !== false) {
$this->plainBody .= ' (' . $plainMetaInfo . ')';
}
$this->plainBody .= PHP_EOL;
} else {
/*
* Caller can set plainIndent > 0 to format plainText in tabular fashion.
* with plainMetaInfo in column 1, and plainText in column 2.
* The plainMetaInfo label is right justified in a field of width
* "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",
$label,
str_replace("\n", "\n" . str_repeat(' ', $plainIndent+1), $plainText));
} }
$this->plainBody .= PHP_EOL;
} }
} }
...@@ -539,7 +559,7 @@ EOF; ...@@ -539,7 +559,7 @@ EOF;
$textColor = $this->themingDefaults->getTextColorPrimary(); $textColor = $this->themingDefaults->getTextColorPrimary();
$this->htmlBody .= vsprintf($this->buttonGroup, [$color, $color, $urlLeft, $color, $textColor, $textColor, $textLeft, $urlRight, $textRight]); $this->htmlBody .= vsprintf($this->buttonGroup, [$color, $color, $urlLeft, $color, $textColor, $textColor, $textLeft, $urlRight, $textRight]);
$this->plainBody .= $plainTextLeft . ': ' . $urlLeft . PHP_EOL; $this->plainBody .= PHP_EOL . $plainTextLeft . ': ' . $urlLeft . PHP_EOL;
$this->plainBody .= $plainTextRight . ': ' . $urlRight . PHP_EOL . PHP_EOL; $this->plainBody .= $plainTextRight . ': ' . $urlRight . PHP_EOL . PHP_EOL;
} }
......
...@@ -106,9 +106,10 @@ interface IEMailTemplate { ...@@ -106,9 +106,10 @@ interface IEMailTemplate {
* if empty the $text is used, if false none will be used * 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 * @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 the $metaInfo is used, if false none will be used
* @param integer plainIndent If > 0, Indent plainText by this amount.
* @since 12.0.0 * @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 * Adds a button group of two buttons to the body of the email
......
...@@ -4,6 +4,7 @@ Welcome to your Nextcloud account, you can add, protect, and share your data. - ...@@ -4,6 +4,7 @@ Welcome to your Nextcloud account, you can add, protect, and share your data. -
Your username is: abc Your username is: abc
Set your password - text: https://example.org/resetPassword/123 Set your password - text: https://example.org/resetPassword/123
Install Client - text: https://nextcloud.com/install/#install-clients Install Client - text: https://nextcloud.com/install/#install-clients
......
...@@ -4,6 +4,7 @@ Welcome to your Nextcloud account, you can add, protect, and share your data. ...@@ -4,6 +4,7 @@ Welcome to your Nextcloud account, you can add, protect, and share your data.
Your username is: abc Your username is: abc
Set your password: https://example.org/resetPassword/123 Set your password: https://example.org/resetPassword/123
Install Client: https://nextcloud.com/install/#install-clients Install Client: https://nextcloud.com/install/#install-clients
......
...@@ -4,6 +4,7 @@ Welcome to your Nextcloud account, you can add, protect, and share your data. ...@@ -4,6 +4,7 @@ Welcome to your Nextcloud account, you can add, protect, and share your data.
Your username is: abc Your username is: abc
Set your password: https://example.org/resetPassword/123 Set your password: https://example.org/resetPassword/123
Install Client: https://nextcloud.com/install/#install-clients Install Client: https://nextcloud.com/install/#install-clients
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment