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
$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;
$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
return $lastOccurrence;
}
-
/**
* @param Message $iTipMessage
* @return null|Property
/**
* @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 */
* @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('<a href="%s">%s</a>',
+ 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('<a href="%s">%s</a>',
+ 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('<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('filetypes/text-vcard.svg'),
+ implode("\n",$attendeesText),'',self::IMIP_INDENT);
}
/**
* 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) {
$this->ensureBodyListOpened();
if ($plainText === '') {
$plainText = $text;
$text = htmlspecialchars($text);
+ $text = str_replace("\n", "<br/>", $text); // convert newlines to HTML breaks
}
if ($plainMetaInfo === '') {
$plainMetaInfo = $metaInfo;
}
$this->htmlBody .= vsprintf($this->listItem, [$icon, $htmlText]);
if ($plainText !== false) {
- $this->plainBody .= ' * ' . $plainText;
- if ($plainMetaInfo !== false) {
- $this->plainBody .= ' (' . $plainMetaInfo . ')';
+ if ($plainIndent === 0) {
+ /*
+ * 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
+ */
+ $this->plainBody .= sprintf("%${plainIndent}s %s\n",
+ $plainMetaInfo,
+ str_replace("\n", "\n" . str_repeat(' ', $plainIndent+1), $plainText));
}
- $this->plainBody .= PHP_EOL;
}
}
$textColor = $this->themingDefaults->getTextColorPrimary();
$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;
}