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

This commit is contained in:
John Molakvoæ 2020-09-04 19:53:02 +02:00 committed by GitHub
commit 593d64d935
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 183 additions and 70 deletions

View File

@ -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 {
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 {
$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 {
$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 {
return $lastOccurrence; return $lastOccurrence;
} }
/** /**
* @param Message $iTipMessage * @param Message $iTipMessage
* @return null|Property * @return null|Property
@ -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 {
* @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 IEMailTemplate $template
* @param IL10N $l10n * @param IL10N $l10n
* @param string $time * @param VEVENT $vevent
* @param string $location
* @param string $description
* @param string $url
*/ */
private function addBulletList(IEMailTemplate $template, IL10N $l10n, $time, $location, $description, $url) { private function addBulletList(IEMailTemplate $template, IL10N $l10n, $vevent) {
$template->addBodyListItem($time, $l10n->t('When:'), if ($vevent->SUMMARY) {
$this->getAbsoluteImagePath('filetypes/text-calendar.svg')); $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);
}
if ($location) { $this->addAttendees($template, $l10n, $vevent);
$template->addBodyListItem($location, $l10n->t('Where:'),
$this->getAbsoluteImagePath('filetypes/location.svg')); /* 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);
} }
if ($description) { }
$template->addBodyListItem((string)$description, $l10n->t('Description:'),
$this->getAbsoluteImagePath('filetypes/text.svg')); /**
* 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 ($url) {
$template->addBodyListItem((string)$url, $l10n->t('Link:'), if (isset($vevent->ORGANIZER)) {
$this->getAbsoluteImagePath('filetypes/link.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);
} }
$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('<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);
} }
/** /**

View File

@ -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 {
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 {
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 {
*/ */
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 {
$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 {
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());
} }

View File

@ -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
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

View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 853 B

View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 295 B

1
core/img/caldav/link.svg Normal file
View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 218 B

View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 490 B

1
core/img/caldav/time.svg Normal file
View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 514 B

View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 146 B

View File

@ -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;
} }
$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;
$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;
} }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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