Merge pull request #17456 from brad2014/feature/brad2014/12391-improve-imip-mail-message-take-2
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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. -
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|