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