mirror of
https://github.com/nextcloud/server.git
synced 2026-06-06 15:23:17 -04:00
Merge pull request #45547 from nextcloud/feature/recurrence-invitations2
feature: Improved Recurrence Invitations Messages
This commit is contained in:
commit
31d051ae74
8 changed files with 3510 additions and 231 deletions
|
|
@ -59,6 +59,9 @@ return array(
|
|||
'OCA\\DAV\\CalDAV\\CalendarProvider' => $baseDir . '/../lib/CalDAV/CalendarProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\CalendarRoot' => $baseDir . '/../lib/CalDAV/CalendarRoot.php',
|
||||
'OCA\\DAV\\CalDAV\\EventComparisonService' => $baseDir . '/../lib/CalDAV/EventComparisonService.php',
|
||||
'OCA\\DAV\\CalDAV\\EventReader' => $baseDir . '/../lib/CalDAV/EventReader.php',
|
||||
'OCA\\DAV\\CalDAV\\EventReaderRDate' => $baseDir . '/../lib/CalDAV/EventReaderRDate.php',
|
||||
'OCA\\DAV\\CalDAV\\EventReaderRRule' => $baseDir . '/../lib/CalDAV/EventReaderRRule.php',
|
||||
'OCA\\DAV\\CalDAV\\FreeBusy\\FreeBusyGenerator' => $baseDir . '/../lib/CalDAV/FreeBusy/FreeBusyGenerator.php',
|
||||
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => $baseDir . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\IRestorable' => $baseDir . '/../lib/CalDAV/IRestorable.php',
|
||||
|
|
|
|||
|
|
@ -74,6 +74,9 @@ class ComposerStaticInitDAV
|
|||
'OCA\\DAV\\CalDAV\\CalendarProvider' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarProvider.php',
|
||||
'OCA\\DAV\\CalDAV\\CalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarRoot.php',
|
||||
'OCA\\DAV\\CalDAV\\EventComparisonService' => __DIR__ . '/..' . '/../lib/CalDAV/EventComparisonService.php',
|
||||
'OCA\\DAV\\CalDAV\\EventReader' => __DIR__ . '/..' . '/../lib/CalDAV/EventReader.php',
|
||||
'OCA\\DAV\\CalDAV\\EventReaderRDate' => __DIR__ . '/..' . '/../lib/CalDAV/EventReaderRDate.php',
|
||||
'OCA\\DAV\\CalDAV\\EventReaderRRule' => __DIR__ . '/..' . '/../lib/CalDAV/EventReaderRRule.php',
|
||||
'OCA\\DAV\\CalDAV\\FreeBusy\\FreeBusyGenerator' => __DIR__ . '/..' . '/../lib/CalDAV/FreeBusy/FreeBusyGenerator.php',
|
||||
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\IRestorable' => __DIR__ . '/..' . '/../lib/CalDAV/IRestorable.php',
|
||||
|
|
|
|||
758
apps/dav/lib/CalDAV/EventReader.php
Normal file
758
apps/dav/lib/CalDAV/EventReader.php
Normal file
|
|
@ -0,0 +1,758 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
use InvalidArgumentException;
|
||||
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
use Sabre\VObject\Component\VEvent;
|
||||
use Sabre\VObject\Reader;
|
||||
|
||||
class EventReader {
|
||||
|
||||
protected VEvent $baseEvent;
|
||||
protected DateTimeInterface $baseEventStartDate;
|
||||
protected DateTimeZone $baseEventStartTimeZone;
|
||||
protected DateTimeInterface $baseEventEndDate;
|
||||
protected DateTimeZone $baseEventEndTimeZone;
|
||||
protected bool $baseEventStartDateFloating = false;
|
||||
protected bool $baseEventEndDateFloating = false;
|
||||
protected int $baseEventDuration;
|
||||
|
||||
protected ?EventReaderRRule $rruleIterator = null;
|
||||
protected ?EventReaderRDate $rdateIterator = null;
|
||||
protected ?EventReaderRRule $eruleIterator = null;
|
||||
protected ?EventReaderRDate $edateIterator = null;
|
||||
|
||||
protected array $recurrenceModified;
|
||||
protected ?DateTimeInterface $recurrenceCurrentDate;
|
||||
|
||||
protected array $dayNamesMap = [
|
||||
'MO' => 'Monday', 'TU' => 'Tuesday', 'WE' => 'Wednesday', 'TH' => 'Thursday', 'FR' => 'Friday', 'SA' => 'Saturday', 'SU' => 'Sunday'
|
||||
];
|
||||
protected array $monthNamesMap = [
|
||||
1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April', 5 => 'May', 6 => 'June',
|
||||
7 => 'July', 8 => 'August', 9 => 'September', 10 => 'October', 11 => 'November', 12 => 'December'
|
||||
];
|
||||
protected array $relativePositionNamesMap = [
|
||||
1 => 'First', 2 => 'Second', 3 => 'Third', 4 => 'Fourth', 5 => 'Fifty',
|
||||
-1 => 'Last', -2 => 'Second Last', -3 => 'Third Last', -4 => 'Fourth Last', -5 => 'Fifty Last'
|
||||
];
|
||||
|
||||
/**
|
||||
* Initilizes the Event Reader
|
||||
*
|
||||
* There is several ways to set up the iterator.
|
||||
*
|
||||
* 1. You can pass a VCALENDAR component (as object or string) and a UID.
|
||||
* 2. You can pass an array of VEVENTs (all UIDS should match).
|
||||
* 3. You can pass a single VEVENT component (as object or string).
|
||||
*
|
||||
* Only the second method is recommended. The other 1 and 3 will be removed
|
||||
* at some point in the future.
|
||||
*
|
||||
* The $uid parameter is only required for the first method.
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @param VCalendar|VEvent|Array|String $input
|
||||
* @param string|null $uid
|
||||
* @param DateTimeZone|null $timeZone reference timezone for floating dates and times
|
||||
*/
|
||||
public function __construct(VCalendar|VEvent|array|string $input, ?string $uid = null, ?DateTimeZone $timeZone = null) {
|
||||
|
||||
// evaluate if the input is a string and convert it to and vobject if required
|
||||
if (is_string($input)) {
|
||||
$input = Reader::read($input);
|
||||
}
|
||||
// evaluate if input is a single event vobject and convert it to a collection
|
||||
if ($input instanceof VEvent) {
|
||||
$events = [$input];
|
||||
}
|
||||
// evaluate if input is a calendar vobject
|
||||
elseif ($input instanceof VCalendar) {
|
||||
// Calendar + UID mode.
|
||||
if ($uid === null) {
|
||||
throw new InvalidArgumentException('The UID argument is required when a VCALENDAR object is used');
|
||||
}
|
||||
// extract events from calendar
|
||||
$events = $input->getByUID($uid);
|
||||
// evaluate if any event where found
|
||||
if (count($events) === 0) {
|
||||
throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: '.$uid);
|
||||
}
|
||||
// extract calendar timezone
|
||||
if (isset($input->VTIMEZONE) && isset($input->VTIMEZONE->TZID)) {
|
||||
$calendarTimeZone = new DateTimeZone($input->VTIMEZONE->TZID->getValue());
|
||||
}
|
||||
}
|
||||
// evaluate if input is a collection of event vobjects
|
||||
elseif (is_array($input)) {
|
||||
$events = $input;
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid input data type');
|
||||
}
|
||||
// find base event instance and remove it from events collection
|
||||
foreach ($events as $key => $vevent) {
|
||||
if (!isset($vevent->{'RECURRENCE-ID'})) {
|
||||
$this->baseEvent = $vevent;
|
||||
unset($events[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// No base event was found. CalDAV does allow cases where only
|
||||
// overridden instances are stored.
|
||||
//
|
||||
// In this particular case, we're just going to grab the first
|
||||
// event and use that instead. This may not always give the
|
||||
// desired result.
|
||||
if (!isset($this->baseEvent) && count($events) > 0) {
|
||||
$this->baseEvent = array_shift($events);
|
||||
}
|
||||
|
||||
// determain the event starting time zone
|
||||
// we require this to align all other dates times
|
||||
// evaluate if timezone paramater was used (treat this as a override)
|
||||
if ($timeZone !== null) {
|
||||
$this->baseEventStartTimeZone = $timeZone;
|
||||
}
|
||||
// evaluate if event start date has a timezone parameter
|
||||
elseif (isset($this->baseEvent->DTSTART->parameters['TZID'])) {
|
||||
$this->baseEventStartTimeZone = new DateTimeZone($this->baseEvent->DTSTART->parameters['TZID']->getValue());
|
||||
}
|
||||
// evaluate if event parent calendar has a time zone
|
||||
elseif (isset($calendarTimeZone)) {
|
||||
$this->baseEventStartTimeZone = clone $calendarTimeZone;
|
||||
}
|
||||
// otherwise, as a last resort use the UTC timezone
|
||||
else {
|
||||
$this->baseEventStartTimeZone = new DateTimeZone('UTC');
|
||||
}
|
||||
|
||||
// determain the event end time zone
|
||||
// we require this to align all other dates and times
|
||||
// evaluate if timezone paramater was used (treat this as a override)
|
||||
if ($timeZone !== null) {
|
||||
$this->baseEventEndTimeZone = $timeZone;
|
||||
}
|
||||
// evaluate if event end date has a timezone parameter
|
||||
elseif (isset($this->baseEvent->DTEND->parameters['TZID'])) {
|
||||
$this->baseEventEndTimeZone = new DateTimeZone($this->baseEvent->DTEND->parameters['TZID']->getValue());
|
||||
}
|
||||
// evaluate if event parent calendar has a time zone
|
||||
elseif (isset($calendarTimeZone)) {
|
||||
$this->baseEventEndTimeZone = clone $calendarTimeZone;
|
||||
}
|
||||
// otherwise, as a last resort use the start date time zone
|
||||
else {
|
||||
$this->baseEventEndTimeZone = clone $this->baseEventStartTimeZone;
|
||||
}
|
||||
// extract start date and time
|
||||
$this->baseEventStartDate = $this->baseEvent->DTSTART->getDateTime($this->baseEventStartTimeZone);
|
||||
$this->baseEventStartDateFloating = $this->baseEvent->DTSTART->isFloating();
|
||||
// determine event end date and duration
|
||||
// evaluate if end date exists
|
||||
// extract end date and calculate duration
|
||||
if (isset($this->baseEvent->DTEND)) {
|
||||
$this->baseEventEndDate = $this->baseEvent->DTEND->getDateTime($this->baseEventEndTimeZone);
|
||||
$this->baseEventEndDateFloating = $this->baseEvent->DTEND->isFloating();
|
||||
$this->baseEventDuration =
|
||||
$this->baseEvent->DTEND->getDateTime($this->baseEventEndTimeZone)->getTimeStamp() -
|
||||
$this->baseEventStartDate->getTimeStamp();
|
||||
}
|
||||
// evaluate if duration exists
|
||||
// extract duration and calculate end date
|
||||
elseif (isset($this->baseEvent->DURATION)) {
|
||||
$this->baseEventDuration = $this->baseEvent->DURATION->getDateInterval();
|
||||
$this->baseEventEndDate = ((clone $this->baseEventStartDate)->add($this->baseEventDuration));
|
||||
}
|
||||
// evaluate if start date is floating
|
||||
// set duration to 24 hours and calculate the end date
|
||||
// according to the rfc any event without a end date or duration is a complete day
|
||||
elseif ($this->baseEventStartDateFloating == true) {
|
||||
$this->baseEventDuration = 86400;
|
||||
$this->baseEventEndDate = ((clone $this->baseEventStartDate)->add($this->baseEventDuration));
|
||||
}
|
||||
// otherwise, set duration to zero this should never happen
|
||||
else {
|
||||
$this->baseEventDuration = 0;
|
||||
$this->baseEventEndDate = $this->baseEventStartDate;
|
||||
}
|
||||
// evaluate if RRULE exist and construct iterator
|
||||
if (isset($this->baseEvent->RRULE)) {
|
||||
$this->rruleIterator = new EventReaderRRule(
|
||||
$this->baseEvent->RRULE->getParts(),
|
||||
$this->baseEventStartDate
|
||||
);
|
||||
}
|
||||
// evaluate if RDATE exist and construct iterator
|
||||
if (isset($this->baseEvent->RDATE)) {
|
||||
$this->rdateIterator = new EventReaderRDate(
|
||||
$this->baseEvent->RDATE->getValue(),
|
||||
$this->baseEventStartDate
|
||||
);
|
||||
}
|
||||
// evaluate if EXRULE exist and construct iterator
|
||||
if (isset($this->baseEvent->EXRULE)) {
|
||||
$this->eruleIterator = new EventReaderRRule(
|
||||
$this->baseEvent->EXRULE->getParts(),
|
||||
$this->baseEventStartDate
|
||||
);
|
||||
}
|
||||
// evaluate if EXDATE exist and construct iterator
|
||||
if (isset($this->baseEvent->EXDATE)) {
|
||||
$this->edateIterator = new EventReaderRDate(
|
||||
$this->baseEvent->EXDATE->getValue(),
|
||||
$this->baseEventStartDate
|
||||
);
|
||||
}
|
||||
// construct collection of modified events with recurrence id as hash
|
||||
foreach ($events as $vevent) {
|
||||
$this->recurrenceModified[$vevent->{'RECURRENCE-ID'}->getDateTime($this->baseEventStartTimeZone)->getTimeStamp()] = $vevent;
|
||||
}
|
||||
|
||||
$this->recurrenceCurrentDate = clone $this->baseEventStartDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve date and time of event start
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
public function startDateTime(): DateTime {
|
||||
return DateTime::createFromInterface($this->baseEventStartDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve time zone of event start
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return DateTimeZone
|
||||
*/
|
||||
public function startTimeZone(): DateTimeZone {
|
||||
return $this->baseEventStartTimeZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve date and time of event end
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
public function endDateTime(): DateTime {
|
||||
return DateTime::createFromInterface($this->baseEventEndDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve time zone of event end
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return DateTimeZone
|
||||
*/
|
||||
public function endTimeZone(): DateTimeZone {
|
||||
return $this->baseEventEndTimeZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* is this an all day event
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function entireDay(): bool {
|
||||
return $this->baseEventStartDateFloating;
|
||||
}
|
||||
|
||||
/**
|
||||
* is this a recurring event
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function recurs(): bool {
|
||||
return ($this->rruleIterator !== null || $this->rdateIterator !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence pattern
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return string|null R - Relative or A - Absolute
|
||||
*/
|
||||
public function recurringPattern(): string | null {
|
||||
if ($this->rruleIterator === null && $this->rdateIterator === null) {
|
||||
return null;
|
||||
}
|
||||
if ($this->rruleIterator?->isRelative()) {
|
||||
return 'R';
|
||||
}
|
||||
return 'A';
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence precision
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return string|null daily, weekly, monthly, yearly, fixed
|
||||
*/
|
||||
public function recurringPrecision(): string | null {
|
||||
if ($this->rruleIterator !== null) {
|
||||
return $this->rruleIterator->precision();
|
||||
}
|
||||
if ($this->rdateIterator !== null) {
|
||||
return 'fixed';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence interval
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function recurringInterval(): int | null {
|
||||
return $this->rruleIterator?->interval();
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence conclusion
|
||||
*
|
||||
* returns true if RRULE with UNTIL or COUNT (calculated) is used
|
||||
* returns true RDATE is used
|
||||
* returns false if RRULE or RDATE are absent, or RRRULE is infinite
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function recurringConcludes(): bool {
|
||||
|
||||
// retrieve rrule conclusions
|
||||
if ($this->rruleIterator?->concludesOn() !== null ||
|
||||
$this->rruleIterator?->concludesAfter() !== null) {
|
||||
return true;
|
||||
}
|
||||
// retrieve rdate conclusions
|
||||
if ($this->rdateIterator?->concludesAfter() !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence conclusion iterations
|
||||
*
|
||||
* returns the COUNT value if RRULE is used
|
||||
* returns the collection count if RDATE is used
|
||||
* returns combined count of RRULE COUNT and RDATE if both are used
|
||||
* returns null if RRULE and RDATE are absent
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function recurringConcludesAfter(): int | null {
|
||||
|
||||
// construct count place holder
|
||||
$count = 0;
|
||||
// retrieve and add RRULE iterations count
|
||||
$count += (int) $this->rruleIterator?->concludesAfter();
|
||||
// retrieve and add RDATE iterations count
|
||||
$count += (int) $this->rdateIterator?->concludesAfter();
|
||||
// return count
|
||||
return !empty($count) ? $count : null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence conclusion date
|
||||
*
|
||||
* returns the last date of UNTIL or COUNT (calculated) if RRULE is used
|
||||
* returns the last date in the collection if RDATE is used
|
||||
* returns the highest date if both RRULE and RDATE are used
|
||||
* returns null if RRULE and RDATE are absent or RRULE is infinite
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return DateTime|null
|
||||
*/
|
||||
public function recurringConcludesOn(): DateTime | null {
|
||||
|
||||
if ($this->rruleIterator !== null) {
|
||||
// retrieve rrule conclusion date
|
||||
$rrule = $this->rruleIterator->concludes();
|
||||
// evaluate if rrule conclusion is null
|
||||
// if this is null that means the recurrence is infinate
|
||||
if ($rrule === null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// retrieve rdate conclusion date
|
||||
if ($this->rdateIterator !== null) {
|
||||
$rdate = $this->rdateIterator->concludes();
|
||||
}
|
||||
// evaluate if both rrule and rdate have date
|
||||
if (isset($rdate) && isset($rrule)) {
|
||||
// return the highest date
|
||||
return (($rdate > $rrule) ? $rdate : $rrule);
|
||||
} elseif (isset($rrule)) {
|
||||
return $rrule;
|
||||
} elseif (isset($rdate)) {
|
||||
return $rdate;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence days of the week
|
||||
*
|
||||
* returns collection of RRULE BYDAY day(s) ['MO','WE','FR']
|
||||
* returns blank collection if RRULE is absent, RDATE presents or absents has no affect
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function recurringDaysOfWeek(): array {
|
||||
// evaluate if RRULE exists and return day(s) of the week
|
||||
return $this->rruleIterator !== null ? $this->rruleIterator->daysOfWeek() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence days of the week (named)
|
||||
*
|
||||
* returns collection of RRULE BYDAY day(s) ['Monday','Wednesday','Friday']
|
||||
* returns blank collection if RRULE is absent, RDATE presents or absents has no affect
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function recurringDaysOfWeekNamed(): array {
|
||||
// evaluate if RRULE exists and extract day(s) of the week
|
||||
$days = $this->rruleIterator !== null ? $this->rruleIterator->daysOfWeek() : [];
|
||||
// convert numberic month to month name
|
||||
foreach ($days as $key => $value) {
|
||||
$days[$key] = $this->dayNamesMap[$value];
|
||||
}
|
||||
// return names collection
|
||||
return $days;
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence days of the month
|
||||
*
|
||||
* returns collection of RRULE BYMONTHDAY day(s) [7, 15, 31]
|
||||
* returns blank collection if RRULE is absent, RDATE presents or absents has no affect
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function recurringDaysOfMonth(): array {
|
||||
// evaluate if RRULE exists and return day(s) of the month
|
||||
return $this->rruleIterator !== null ? $this->rruleIterator->daysOfMonth() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence days of the year
|
||||
*
|
||||
* returns collection of RRULE BYYEARDAY day(s) [57, 205, 365]
|
||||
* returns blank collection if RRULE is absent, RDATE presents or absents has no affect
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function recurringDaysOfYear(): array {
|
||||
// evaluate if RRULE exists and return day(s) of the year
|
||||
return $this->rruleIterator !== null ? $this->rruleIterator->daysOfYear() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence weeks of the month
|
||||
*
|
||||
* returns collection of RRULE SETPOS weeks(s) [1, 3, -1]
|
||||
* returns blank collection if RRULE is absent or SETPOS is absent, RDATE presents or absents has no affect
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function recurringWeeksOfMonth(): array {
|
||||
// evaluate if RRULE exists and RRULE is relative return relative position(s)
|
||||
return $this->rruleIterator?->isRelative() ? $this->rruleIterator->relativePosition() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence weeks of the month (named)
|
||||
*
|
||||
* returns collection of RRULE SETPOS weeks(s) [1, 3, -1]
|
||||
* returns blank collection if RRULE is absent or SETPOS is absent, RDATE presents or absents has no affect
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function recurringWeeksOfMonthNamed(): array {
|
||||
// evaluate if RRULE exists and extract relative position(s)
|
||||
$positions = $this->rruleIterator?->isRelative() ? $this->rruleIterator->relativePosition() : [];
|
||||
// convert numberic relative position to relative label
|
||||
foreach ($positions as $key => $value) {
|
||||
$positions[$key] = $this->relativePositionNamesMap[$value];
|
||||
}
|
||||
// return positions collection
|
||||
return $positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence weeks of the year
|
||||
*
|
||||
* returns collection of RRULE BYWEEKNO weeks(s) [12, 32, 52]
|
||||
* returns blank collection if RRULE is absent, RDATE presents or absents has no affect
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function recurringWeeksOfYear(): array {
|
||||
// evaluate if RRULE exists and return weeks(s) of the year
|
||||
return $this->rruleIterator !== null ? $this->rruleIterator->weeksOfYear() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence months of the year
|
||||
*
|
||||
* returns collection of RRULE BYMONTH month(s) [3, 7, 12]
|
||||
* returns blank collection if RRULE is absent, RDATE presents or absents has no affect
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function recurringMonthsOfYear(): array {
|
||||
// evaluate if RRULE exists and return month(s) of the year
|
||||
return $this->rruleIterator !== null ? $this->rruleIterator->monthsOfYear() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence months of the year (named)
|
||||
*
|
||||
* returns collection of RRULE BYMONTH month(s) [3, 7, 12]
|
||||
* returns blank collection if RRULE is absent, RDATE presents or absents has no affect
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function recurringMonthsOfYearNamed(): array {
|
||||
// evaluate if RRULE exists and extract month(s) of the year
|
||||
$months = $this->rruleIterator !== null ? $this->rruleIterator->monthsOfYear() : [];
|
||||
// convert numberic month to month name
|
||||
foreach ($months as $key => $value) {
|
||||
$months[$key] = $this->monthNamesMap[$value];
|
||||
}
|
||||
// return months collection
|
||||
return $months;
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence relative positions
|
||||
*
|
||||
* returns collection of RRULE SETPOS value(s) [1, 5, -3]
|
||||
* returns blank collection if RRULE is absent, RDATE presents or absents has no affect
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function recurringRelativePosition(): array {
|
||||
// evaluate if RRULE exists and return relative position(s)
|
||||
return $this->rruleIterator !== null ? $this->rruleIterator->relativePosition() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence relative positions (named)
|
||||
*
|
||||
* returns collection of RRULE SETPOS [1, 3, -1]
|
||||
* returns blank collection if RRULE is absent or SETPOS is absent, RDATE presents or absents has no affect
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function recurringRelativePositionNamed(): array {
|
||||
// evaluate if RRULE exists and extract relative position(s)
|
||||
$positions = $this->rruleIterator?->isRelative() ? $this->rruleIterator->relativePosition() : [];
|
||||
// convert numberic relative position to relative label
|
||||
foreach ($positions as $key => $value) {
|
||||
$positions[$key] = $this->relativePositionNamesMap[$value];
|
||||
}
|
||||
// return positions collection
|
||||
return $positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence date
|
||||
*
|
||||
* returns date of currently selected recurrence
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
public function recurrenceDate(): DateTime | null {
|
||||
if ($this->recurrenceCurrentDate !== null) {
|
||||
return DateTime::createFromInterface($this->recurrenceCurrentDate);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence rewind
|
||||
*
|
||||
* sets the current recurrence to the first recurrence in the collection
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function recurrenceRewind(): void {
|
||||
// rewind and increment rrule
|
||||
if ($this->rruleIterator !== null) {
|
||||
$this->rruleIterator->rewind();
|
||||
}
|
||||
// rewind and increment rdate
|
||||
if ($this->rdateIterator !== null) {
|
||||
$this->rdateIterator->rewind();
|
||||
}
|
||||
// rewind and increment exrule
|
||||
if ($this->eruleIterator !== null) {
|
||||
$this->eruleIterator->rewind();
|
||||
}
|
||||
// rewind and increment exdate
|
||||
if ($this->edateIterator !== null) {
|
||||
$this->edateIterator->rewind();
|
||||
}
|
||||
// set current date to event start date
|
||||
$this->recurrenceCurrentDate = clone $this->baseEventStartDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence advance
|
||||
*
|
||||
* sets the current recurrence to the next recurrence in the collection
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function recurrenceAdvance(): void {
|
||||
// place holders
|
||||
$nextOccurrenceDate = null;
|
||||
$nextExceptionDate = null;
|
||||
$rruleDate = null;
|
||||
$rdateDate = null;
|
||||
$eruleDate = null;
|
||||
$edateDate = null;
|
||||
// evaludate if rrule is set and advance one interation past current date
|
||||
if ($this->rruleIterator !== null) {
|
||||
// forward rrule to the next future date
|
||||
while ($this->rruleIterator->valid() && $this->rruleIterator->current() <= $this->recurrenceCurrentDate) {
|
||||
$this->rruleIterator->next();
|
||||
}
|
||||
$rruleDate = $this->rruleIterator->current();
|
||||
}
|
||||
// evaludate if rdate is set and advance one interation past current date
|
||||
if ($this->rdateIterator !== null) {
|
||||
// forward rdate to the next future date
|
||||
while ($this->rdateIterator->valid() && $this->rdateIterator->current() <= $this->recurrenceCurrentDate) {
|
||||
$this->rdateIterator->next();
|
||||
}
|
||||
$rdateDate = $this->rdateIterator->current();
|
||||
}
|
||||
if ($rruleDate !== null && $rdateDate !== null) {
|
||||
$nextOccurrenceDate = ($rruleDate <= $rdateDate) ? $rruleDate : $rdateDate;
|
||||
} elseif ($rruleDate !== null) {
|
||||
$nextOccurrenceDate = $rruleDate;
|
||||
} elseif ($rdateDate !== null) {
|
||||
$nextOccurrenceDate = $rdateDate;
|
||||
}
|
||||
|
||||
// evaludate if exrule is set and advance one interation past current date
|
||||
if ($this->eruleIterator !== null) {
|
||||
// forward exrule to the next future date
|
||||
while ($this->eruleIterator->valid() && $this->eruleIterator->current() <= $this->recurrenceCurrentDate) {
|
||||
$this->eruleIterator->next();
|
||||
}
|
||||
$eruleDate = $this->eruleIterator->current();
|
||||
}
|
||||
// evaludate if exdate is set and advance one interation past current date
|
||||
if ($this->edateIterator !== null) {
|
||||
// forward exdate to the next future date
|
||||
while ($this->edateIterator->valid() && $this->edateIterator->current() <= $this->recurrenceCurrentDate) {
|
||||
$this->edateIterator->next();
|
||||
}
|
||||
$edateDate = $this->edateIterator->current();
|
||||
}
|
||||
// evaludate if exrule and exdate are set and set nextExDate to the first next date
|
||||
if ($eruleDate !== null && $edateDate !== null) {
|
||||
$nextExceptionDate = ($eruleDate <= $edateDate) ? $eruleDate : $edateDate;
|
||||
} elseif ($eruleDate !== null) {
|
||||
$nextExceptionDate = $eruleDate;
|
||||
} elseif ($edateDate !== null) {
|
||||
$nextExceptionDate = $edateDate;
|
||||
}
|
||||
// if the next date is part of exrule or exdate find another date
|
||||
if ($nextOccurrenceDate !== null && $nextExceptionDate !== null && $nextOccurrenceDate == $nextExceptionDate) {
|
||||
$this->recurrenceCurrentDate = $nextOccurrenceDate;
|
||||
$this->recurrenceAdvance();
|
||||
} else {
|
||||
$this->recurrenceCurrentDate = $nextOccurrenceDate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* event recurrence advance
|
||||
*
|
||||
* sets the current recurrence to the next recurrence in the collection after the specific date
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @param DateTimeInterface $dt date and time to advance
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function recurrenceAdvanceTo(DateTimeInterface $dt): void {
|
||||
while ($this->recurrenceCurrentDate !== null && $this->recurrenceCurrentDate < $dt) {
|
||||
$this->recurrenceAdvance();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
35
apps/dav/lib/CalDAV/EventReaderRDate.php
Normal file
35
apps/dav/lib/CalDAV/EventReaderRDate.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV;
|
||||
|
||||
use DateTime;
|
||||
|
||||
class EventReaderRDate extends \Sabre\VObject\Recur\RDateIterator {
|
||||
|
||||
public function concludes(): DateTime | null {
|
||||
return $this->concludesOn();
|
||||
}
|
||||
|
||||
public function concludesAfter(): int | null {
|
||||
return !empty($this->dates) ? count($this->dates) : null;
|
||||
}
|
||||
|
||||
public function concludesOn(): DateTime | null {
|
||||
if (count($this->dates) > 0) {
|
||||
return new DateTime(
|
||||
$this->dates[array_key_last($this->dates)],
|
||||
$this->startDate->getTimezone()
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
87
apps/dav/lib/CalDAV/EventReaderRRule.php
Normal file
87
apps/dav/lib/CalDAV/EventReaderRRule.php
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\CalDAV;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
|
||||
class EventReaderRRule extends \Sabre\VObject\Recur\RRuleIterator {
|
||||
|
||||
public function precision(): string {
|
||||
return $this->frequency;
|
||||
}
|
||||
|
||||
public function interval(): int {
|
||||
return $this->interval;
|
||||
}
|
||||
|
||||
public function concludes(): DateTime | null {
|
||||
// evaluate if until value is a date
|
||||
if ($this->until instanceof DateTimeInterface) {
|
||||
return DateTime::createFromInterface($this->until);
|
||||
}
|
||||
// evaluate if count value is higher than 0
|
||||
if ($this->count > 0) {
|
||||
// temporarily store current recurrence date and counter
|
||||
$currentReccuranceDate = $this->currentDate;
|
||||
$currentCounter = $this->counter;
|
||||
// iterate over occurrences until last one (subtract 2 from count for start and end occurrence)
|
||||
while ($this->counter <= ($this->count - 2)) {
|
||||
$this->next();
|
||||
}
|
||||
// temporarly store last reccurance date
|
||||
$lastReccuranceDate = $this->currentDate;
|
||||
// restore current recurrence date and counter
|
||||
$this->currentDate = $currentReccuranceDate;
|
||||
$this->counter = $currentCounter;
|
||||
// return last recurrence date
|
||||
return DateTime::createFromInterface($lastReccuranceDate);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function concludesAfter(): int | null {
|
||||
return !empty($this->count) ? $this->count : null;
|
||||
}
|
||||
|
||||
public function concludesOn(): DateTime | null {
|
||||
return isset($this->until) ? DateTime::createFromInterface($this->until) : null;
|
||||
}
|
||||
|
||||
public function daysOfWeek(): array {
|
||||
return $this->byDay;
|
||||
}
|
||||
|
||||
public function daysOfMonth(): array {
|
||||
return $this->byMonthDay;
|
||||
}
|
||||
|
||||
public function daysOfYear(): array {
|
||||
return $this->byYearDay;
|
||||
}
|
||||
|
||||
public function weeksOfYear(): array {
|
||||
return $this->byWeekNo;
|
||||
}
|
||||
|
||||
public function monthsOfYear(): array {
|
||||
return $this->byMonth;
|
||||
}
|
||||
|
||||
public function isRelative(): bool {
|
||||
return isset($this->bySetPos);
|
||||
}
|
||||
|
||||
public function relativePosition(): array {
|
||||
return $this->bySetPos;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,6 +9,8 @@ declare(strict_types=1);
|
|||
namespace OCA\DAV\CalDAV\Schedule;
|
||||
|
||||
use OC\URLGenerator;
|
||||
use OCA\DAV\CalDAV\EventReader;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IL10N;
|
||||
|
|
@ -31,6 +33,7 @@ class IMipService {
|
|||
private ISecureRandom $random;
|
||||
private L10NFactory $l10nFactory;
|
||||
private IL10N $l10n;
|
||||
private ITimeFactory $timeFactory;
|
||||
|
||||
/** @var string[] */
|
||||
private const STRING_DIFF = [
|
||||
|
|
@ -44,7 +47,8 @@ class IMipService {
|
|||
IConfig $config,
|
||||
IDBConnection $db,
|
||||
ISecureRandom $random,
|
||||
L10NFactory $l10nFactory) {
|
||||
L10NFactory $l10nFactory,
|
||||
ITimeFactory $timeFactory) {
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->config = $config;
|
||||
$this->db = $db;
|
||||
|
|
@ -52,6 +56,7 @@ class IMipService {
|
|||
$this->l10nFactory = $l10nFactory;
|
||||
$default = $this->l10nFactory->findGenericLanguage();
|
||||
$this->l10n = $this->l10nFactory->get('dav', $default);
|
||||
$this->timeFactory = $timeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -130,9 +135,13 @@ class IMipService {
|
|||
* @return array
|
||||
*/
|
||||
public function buildBodyData(VEvent $vEvent, ?VEvent $oldVEvent): array {
|
||||
|
||||
// construct event reader
|
||||
$eventReaderCurrent = new EventReader($vEvent);
|
||||
$eventReaderPrevious = !empty($oldVEvent) ? new EventReader($oldVEvent) : null;
|
||||
$defaultVal = '';
|
||||
$data = [];
|
||||
$data['meeting_when'] = $this->generateWhenString($vEvent);
|
||||
$data['meeting_when'] = $this->generateWhenString($eventReaderCurrent);
|
||||
|
||||
foreach(self::STRING_DIFF as $key => $property) {
|
||||
$data[$key] = self::readPropertyWithDefault($vEvent, $property, $defaultVal);
|
||||
|
|
@ -145,7 +154,7 @@ class IMipService {
|
|||
}
|
||||
|
||||
if(!empty($oldVEvent)) {
|
||||
$oldMeetingWhen = $this->generateWhenString($oldVEvent);
|
||||
$oldMeetingWhen = $this->generateWhenString($eventReaderPrevious);
|
||||
$data['meeting_title_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'SUMMARY', $data['meeting_title']);
|
||||
$data['meeting_description_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'DESCRIPTION', $data['meeting_description']);
|
||||
$data['meeting_location_html'] = $this->generateLinkifiedDiffString($vEvent, $oldVEvent, 'LOCATION', $data['meeting_location']);
|
||||
|
|
@ -153,107 +162,334 @@ class IMipService {
|
|||
$oldUrl = self::readPropertyWithDefault($oldVEvent, 'URL', $defaultVal);
|
||||
$data['meeting_url_html'] = !empty($oldUrl) && $oldUrl !== $data['meeting_url'] ? sprintf('<a href="%1$s">%1$s</a>', $oldUrl) : $data['meeting_url'];
|
||||
|
||||
$data['meeting_when_html'] =
|
||||
($oldMeetingWhen !== $data['meeting_when'] && $oldMeetingWhen !== null)
|
||||
? sprintf("<span style='text-decoration: line-through'>%s</span><br />%s", $oldMeetingWhen, $data['meeting_when'])
|
||||
: $data['meeting_when'];
|
||||
$data['meeting_when_html'] = $oldMeetingWhen !== $data['meeting_when'] ? sprintf("<span style='text-decoration: line-through'>%s</span><br />%s", $oldMeetingWhen, $data['meeting_when']) : $data['meeting_when'];
|
||||
}
|
||||
// generate occuring next string
|
||||
if ($eventReaderCurrent->recurs()) {
|
||||
$data['meeting_occurring'] = $this->generateOccurringString($eventReaderCurrent);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IL10N $this->l10n
|
||||
* @param VEvent $vevent
|
||||
* @return false|int|string
|
||||
* genarates a when string based on if a event has an recurrence or not
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @param EventReader $er
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateWhenString(VEvent $vevent) {
|
||||
/** @var Property\ICalendar\DateTime $dtstart */
|
||||
$dtstart = $vevent->DTSTART;
|
||||
if (isset($vevent->DTEND)) {
|
||||
/** @var Property\ICalendar\DateTime $dtend */
|
||||
$dtend = $vevent->DTEND;
|
||||
} elseif (isset($vevent->DURATION)) {
|
||||
$isFloating = $dtstart->isFloating();
|
||||
$dtend = clone $dtstart;
|
||||
$endDateTime = $dtend->getDateTime();
|
||||
$endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue()));
|
||||
$dtend->setDateTime($endDateTime, $isFloating);
|
||||
} elseif (!$dtstart->hasTime()) {
|
||||
$isFloating = $dtstart->isFloating();
|
||||
$dtend = clone $dtstart;
|
||||
$endDateTime = $dtend->getDateTime();
|
||||
$endDateTime = $endDateTime->modify('+1 day');
|
||||
$dtend->setDateTime($endDateTime, $isFloating);
|
||||
public function generateWhenString(EventReader $er): string {
|
||||
return match ($er->recurs()) {
|
||||
true => $this->generateWhenStringRecurring($er),
|
||||
false => $this->generateWhenStringSingular($er)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* genarates a when string for a non recurring event
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @param EventReader $er
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateWhenStringSingular(EventReader $er): string {
|
||||
// calculate time differnce from now to start of event
|
||||
$occuring = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate()));
|
||||
// extract start date
|
||||
$startDate = $this->l10n->l('date', $er->startDateTime(), ['width' => 'full']);
|
||||
// time of the day
|
||||
if (!$er->entireDay()) {
|
||||
$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
|
||||
$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
|
||||
$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
|
||||
}
|
||||
// generate localized when string
|
||||
return match ([($occuring[0] > 1), !empty($endTime)]) {
|
||||
[false, false] => $this->l10n->t('In a %1$s on %2$s for the entire day', [$occuring[1], $startDate]),
|
||||
[false, true] => $this->l10n->t('In a %1$s on %2$s between %3$s - %4$s', [$occuring[1], $startDate, $startTime, $endTime]),
|
||||
[true, false] => $this->l10n->t('In %1$s %2$s on %3$s for the entire day', [$occuring[0], $occuring[1], $startDate]),
|
||||
[true, true] => $this->l10n->t('In %1$s %2$s on %3$s between %4$s - %5$s', [$occuring[0], $occuring[1], $startDate, $startTime, $endTime]),
|
||||
default => $this->l10n->t('Could not generate when statement')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* genarates a when string based on recurrance precision/frequency
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @param EventReader $er
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateWhenStringRecurring(EventReader $er): string {
|
||||
return match ($er->recurringPrecision()) {
|
||||
'daily' => $this->generateWhenStringRecurringDaily($er),
|
||||
'weekly' => $this->generateWhenStringRecurringWeekly($er),
|
||||
'monthly' => $this->generateWhenStringRecurringMonthly($er),
|
||||
'yearly' => $this->generateWhenStringRecurringYearly($er),
|
||||
'fixed' => $this->generateWhenStringRecurringFixed($er),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* genarates a when string for a daily precision/frequency
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @param EventReader $er
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateWhenStringRecurringDaily(EventReader $er): string {
|
||||
|
||||
// initialize
|
||||
$interval = (int) $er->recurringInterval();
|
||||
$startTime = '';
|
||||
$endTime = '';
|
||||
$conclusion = '';
|
||||
// time of the day
|
||||
if (!$er->entireDay()) {
|
||||
$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
|
||||
$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
|
||||
$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
|
||||
}
|
||||
// conclusion
|
||||
if ($er->recurringConcludes()) {
|
||||
$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
|
||||
}
|
||||
// generate localized when string
|
||||
return match ([($interval > 1), !empty($startTime), !empty($conclusion)]) {
|
||||
[false, false, false] => $this->l10n->t('Every Day for the entire day'),
|
||||
[false, false, true] => $this->l10n->t('Every Day for the entire day until %1$s', [$conclusion]),
|
||||
[false, true, false] => $this->l10n->t('Every Day between %1$s - %2$s', [$startTime, $endTime]),
|
||||
[false, true, true] => $this->l10n->t('Every Day between %1$s - %2$s until %3$s', [$startTime, $endTime, $conclusion]),
|
||||
[true, false, false] => $this->l10n->t('Every %1$d Days for the entire day', [$interval]),
|
||||
[true, false, true] => $this->l10n->t('Every %1$d Days for the entire day until %2$s', [$interval, $conclusion]),
|
||||
[true, true, false] => $this->l10n->t('Every %1$d Days between %2$s - %3$s', [$interval, $startTime, $endTime]),
|
||||
[true, true, true] => $this->l10n->t('Every %1$d Days between %2$s - %3$s until %4$s', [$interval, $startTime, $endTime, $conclusion]),
|
||||
default => $this->l10n->t('Could not generate event recurrence statement')
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* genarates a when string for a weekly precision/frequency
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @param EventReader $er
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateWhenStringRecurringWeekly(EventReader $er): string {
|
||||
|
||||
// initialize
|
||||
$interval = (int) $er->recurringInterval();
|
||||
$startTime = '';
|
||||
$endTime = '';
|
||||
$conclusion = '';
|
||||
// days of the week
|
||||
$days = implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
|
||||
// time of the day
|
||||
if (!$er->entireDay()) {
|
||||
$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
|
||||
$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
|
||||
$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
|
||||
}
|
||||
// conclusion
|
||||
if ($er->recurringConcludes()) {
|
||||
$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
|
||||
}
|
||||
// generate localized when string
|
||||
return match ([($interval > 1), !empty($startTime), !empty($conclusion)]) {
|
||||
[false, false, false] => $this->l10n->t('Every Week on %1$s for the entire day', [$days]),
|
||||
[false, false, true] => $this->l10n->t('Every Week on %1$s for the entire day until %2$s', [$days, $conclusion]),
|
||||
[false, true, false] => $this->l10n->t('Every Week on %1$s between %2$s - %3$s', [$days, $startTime, $endTime]),
|
||||
[false, true, true] => $this->l10n->t('Every Week on %1$s between %2$s - %3$s until %4$s', [$days, $startTime, $endTime, $conclusion]),
|
||||
[true, false, false] => $this->l10n->t('Every %1$d Weeks on %2$s for the entire day', [$interval, $days]),
|
||||
[true, false, true] => $this->l10n->t('Every %1$d Weeks on %2$s for the entire day until %3$s', [$interval, $days, $conclusion]),
|
||||
[true, true, false] => $this->l10n->t('Every %1$d Weeks on %2$s between %3$s - %4$s', [$interval, $days, $startTime, $endTime]),
|
||||
[true, true, true] => $this->l10n->t('Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s', [$interval, $days, $startTime, $endTime, $conclusion]),
|
||||
default => $this->l10n->t('Could not generate event recurrence statement')
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* genarates a when string for a monthly precision/frequency
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @param EventReader $er
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateWhenStringRecurringMonthly(EventReader $er): string {
|
||||
|
||||
// initialize
|
||||
$interval = (int) $er->recurringInterval();
|
||||
$startTime = '';
|
||||
$endTime = '';
|
||||
$conclusion = '';
|
||||
// days of month
|
||||
if ($er->recurringPattern() === 'R') {
|
||||
$days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' ' .
|
||||
implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
|
||||
} else {
|
||||
$dtend = clone $dtstart;
|
||||
$days = implode(', ', $er->recurringDaysOfMonth());
|
||||
}
|
||||
|
||||
/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
|
||||
/** @var \DateTimeImmutable $dtstartDt */
|
||||
$dtstartDt = $dtstart->getDateTime();
|
||||
|
||||
/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */
|
||||
/** @var \DateTimeImmutable $dtendDt */
|
||||
$dtendDt = $dtend->getDateTime();
|
||||
|
||||
$diff = $dtstartDt->diff($dtendDt);
|
||||
|
||||
$dtstartDt = new \DateTime($dtstartDt->format(\DateTimeInterface::ATOM));
|
||||
$dtendDt = new \DateTime($dtendDt->format(\DateTimeInterface::ATOM));
|
||||
|
||||
if ($dtstart instanceof Property\ICalendar\Date) {
|
||||
// One day event
|
||||
if ($diff->days === 1) {
|
||||
return $this->l10n->l('date', $dtstartDt, ['width' => 'medium']);
|
||||
}
|
||||
|
||||
// DTEND is exclusive, so if the ics data says 2020-01-01 to 2020-01-05,
|
||||
// the email should show 2020-01-01 to 2020-01-04.
|
||||
$dtendDt->modify('-1 day');
|
||||
|
||||
//event that spans over multiple days
|
||||
$localeStart = $this->l10n->l('date', $dtstartDt, ['width' => 'medium']);
|
||||
$localeEnd = $this->l10n->l('date', $dtendDt, ['width' => 'medium']);
|
||||
|
||||
return $localeStart . ' - ' . $localeEnd;
|
||||
// time of the day
|
||||
if (!$er->entireDay()) {
|
||||
$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
|
||||
$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
|
||||
$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
|
||||
}
|
||||
|
||||
/** @var Property\ICalendar\DateTime $dtstart */
|
||||
/** @var Property\ICalendar\DateTime $dtend */
|
||||
$isFloating = $dtstart->isFloating();
|
||||
$startTimezone = $endTimezone = null;
|
||||
if (!$isFloating) {
|
||||
$prop = $dtstart->offsetGet('TZID');
|
||||
if ($prop instanceof Parameter) {
|
||||
$startTimezone = $prop->getValue();
|
||||
}
|
||||
|
||||
$prop = $dtend->offsetGet('TZID');
|
||||
if ($prop instanceof Parameter) {
|
||||
$endTimezone = $prop->getValue();
|
||||
}
|
||||
// conclusion
|
||||
if ($er->recurringConcludes()) {
|
||||
$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
|
||||
}
|
||||
// generate localized when string
|
||||
return match ([($interval > 1), !empty($startTime), !empty($conclusion)]) {
|
||||
[false, false, false] => $this->l10n->t('Every Month on the %1$s for the entire day', [$days]),
|
||||
[false, false, true] => $this->l10n->t('Every Month on the %1$s for the entire day until %2$s', [$days, $conclusion]),
|
||||
[false, true, false] => $this->l10n->t('Every Month on the %1$s between %2$s - %3$s', [$days, $startTime, $endTime]),
|
||||
[false, true, true] => $this->l10n->t('Every Month on the %1$s between %2$s - %3$s until %4$s', [$days, $startTime, $endTime, $conclusion]),
|
||||
[true, false, false] => $this->l10n->t('Every %1$d Months on the %2$s for the entire day', [$interval, $days]),
|
||||
[true, false, true] => $this->l10n->t('Every %1$d Months on the %2$s for the entire day until %3$s', [$interval, $days, $conclusion]),
|
||||
[true, true, false] => $this->l10n->t('Every %1$d Months on the %2$s between %3$s - %4$s', [$interval, $days, $startTime, $endTime]),
|
||||
[true, true, true] => $this->l10n->t('Every %1$d Months on the %2$s between %3$s - %4$s until %5$s', [$interval, $days, $startTime, $endTime, $conclusion]),
|
||||
default => $this->l10n->t('Could not generate event recurrence statement')
|
||||
};
|
||||
}
|
||||
|
||||
$localeStart = $this->l10n->l('weekdayName', $dtstartDt, ['width' => 'abbreviated']) . ', ' .
|
||||
$this->l10n->l('datetime', $dtstartDt, ['width' => 'medium|short']);
|
||||
|
||||
// always show full date with timezone if timezones are different
|
||||
if ($startTimezone !== $endTimezone) {
|
||||
$localeEnd = $this->l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
|
||||
|
||||
return $localeStart . ' (' . $startTimezone . ') - ' .
|
||||
$localeEnd . ' (' . $endTimezone . ')';
|
||||
}
|
||||
|
||||
// show only end time if date is the same
|
||||
if ($dtstartDt->format('Y-m-d') === $dtendDt->format('Y-m-d')) {
|
||||
$localeEnd = $this->l10n->l('time', $dtendDt, ['width' => 'short']);
|
||||
/**
|
||||
* genarates a when string for a yearly precision/frequency
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @param EventReader $er
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateWhenStringRecurringYearly(EventReader $er): string {
|
||||
|
||||
// initialize
|
||||
$interval = (int) $er->recurringInterval();
|
||||
$startTime = '';
|
||||
$endTime = '';
|
||||
$conclusion = '';
|
||||
// months of year
|
||||
$months = implode(', ', array_map(function ($value) { return $this->localizeMonthName($value); }, $er->recurringMonthsOfYearNamed()));
|
||||
// days of month
|
||||
if ($er->recurringPattern() === 'R') {
|
||||
$days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' ' .
|
||||
implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
|
||||
} else {
|
||||
$localeEnd = $this->l10n->l('weekdayName', $dtendDt, ['width' => 'abbreviated']) . ', ' .
|
||||
$this->l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
|
||||
$days = $er->startDateTime()->format('jS');
|
||||
}
|
||||
// time of the day
|
||||
if (!$er->entireDay()) {
|
||||
$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
|
||||
$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
|
||||
$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
|
||||
}
|
||||
// conclusion
|
||||
if ($er->recurringConcludes()) {
|
||||
$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
|
||||
}
|
||||
// generate localized when string
|
||||
return match ([($interval > 1), !empty($startTime), !empty($conclusion)]) {
|
||||
[false, false, false] => $this->l10n->t('Every Year in %1$s on the %2$s for the entire day', [$months, $days]),
|
||||
[false, false, true] => $this->l10n->t('Every Year in %1$s on the %2$s for the entire day until %3$s', [$months, $days, $conclusion]),
|
||||
[false, true, false] => $this->l10n->t('Every Year in %1$s on the %2$s between %3$s - %4$s', [$months, $days, $startTime, $endTime]),
|
||||
[false, true, true] => $this->l10n->t('Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s', [$months, $days, $startTime, $endTime, $conclusion]),
|
||||
[true, false, false] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s for the entire day', [$interval, $months, $days]),
|
||||
[true, false, true] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s for the entire day until %4$s', [$interval, $months, $days, $conclusion]),
|
||||
[true, true, false] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s between %4$s - %5$s', [$interval, $months, $days, $startTime, $endTime]),
|
||||
[true, true, true] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s', [$interval, $months, $days, $startTime, $endTime, $conclusion]),
|
||||
default => $this->l10n->t('Could not generate event recurrence statement')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* genarates a when string for a fixed precision/frequency
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @param EventReader $er
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateWhenStringRecurringFixed(EventReader $er): string {
|
||||
// initialize
|
||||
$startTime = '';
|
||||
$endTime = '';
|
||||
$conclusion = '';
|
||||
// time of the day
|
||||
if (!$er->entireDay()) {
|
||||
$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
|
||||
$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
|
||||
$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
|
||||
}
|
||||
// conclusion
|
||||
$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
|
||||
// generate localized when string
|
||||
return match (!empty($startTime)) {
|
||||
false => $this->l10n->t('On specific dates for the entire day until %1$s', [$conclusion]),
|
||||
true => $this->l10n->t('On specific dates between %1$s - %2$s until %3$s', [$startTime, $endTime, $conclusion]),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* genarates a occurring next string for a recurring event
|
||||
*
|
||||
* @since 30.0.0
|
||||
*
|
||||
* @param EventReader $er
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateOccurringString(EventReader $er): string {
|
||||
|
||||
// reset to initial occurance
|
||||
$er->recurrenceRewind();
|
||||
// forward to current date
|
||||
$er->recurrenceAdvanceTo($this->timeFactory->getDateTime());
|
||||
// calculate time differnce from now to start of next event occurance and minimize it
|
||||
$occuranceIn = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate()));
|
||||
// store next occurance value
|
||||
$occurance = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
|
||||
// forward one occurance
|
||||
$er->recurrenceAdvance();
|
||||
// evaluate if occurance is valid
|
||||
if ($er->recurrenceDate() !== null) {
|
||||
// store following occurance value
|
||||
$occurance2 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
|
||||
// forward one occurance
|
||||
$er->recurrenceAdvance();
|
||||
// evaluate if occurance is valid
|
||||
if ($er->recurrenceDate()) {
|
||||
// store following occurance value
|
||||
$occurance3 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
|
||||
}
|
||||
}
|
||||
// generate occurance string
|
||||
return match ([($occuranceIn[0] > 1), !empty($occurance2), !empty($occurance3)]) {
|
||||
[false, false, false] => $this->l10n->t('In a %1$s on %2$s', [$occuranceIn[1], $occurance]),
|
||||
[false, true, false] => $this->l10n->t('In a %1$s on %2$s then on %3$s', [$occuranceIn[1], $occurance, $occurance2]),
|
||||
[false, true, true] => $this->l10n->t('In a %1$s on %2$s then on %3$s and %4$s', [$occuranceIn[1], $occurance, $occurance2, $occurance3]),
|
||||
[true, false, false] => $this->l10n->t('In %1$s %2$s on %3$s', [$occuranceIn[0], $occuranceIn[1], $occurance]),
|
||||
[true, true, false] => $this->l10n->t('In %1$s %2$s on %3$s then on %4$s', [$occuranceIn[0], $occuranceIn[1], $occurance, $occurance2]),
|
||||
[true, true, true] => $this->l10n->t('In %1$s %2$s on %3$s then on %4$s and %5$s', [$occuranceIn[0], $occuranceIn[1], $occurance, $occurance2, $occurance3]),
|
||||
default => $this->l10n->t('Could not generate next recurrence statement')
|
||||
};
|
||||
|
||||
return $localeStart . ' - ' . $localeEnd . ' (' . $startTimezone . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -261,12 +497,13 @@ class IMipService {
|
|||
* @return array
|
||||
*/
|
||||
public function buildCancelledBodyData(VEvent $vEvent): array {
|
||||
// construct event reader
|
||||
$eventReaderCurrent = new EventReader($vEvent);
|
||||
$defaultVal = '';
|
||||
$strikethrough = "<span style='text-decoration: line-through'>%s</span>";
|
||||
|
||||
$newMeetingWhen = $this->generateWhenString($vEvent);
|
||||
$newMeetingWhen = $this->generateWhenString($eventReaderCurrent);
|
||||
$newSummary = isset($vEvent->SUMMARY) && (string)$vEvent->SUMMARY !== '' ? (string)$vEvent->SUMMARY : $this->l10n->t('Untitled event');
|
||||
;
|
||||
$newDescription = isset($vEvent->DESCRIPTION) && (string)$vEvent->DESCRIPTION !== '' ? (string)$vEvent->DESCRIPTION : $defaultVal;
|
||||
$newUrl = isset($vEvent->URL) && (string)$vEvent->URL !== '' ? sprintf('<a href="%1$s">%1$s</a>', $vEvent->URL) : $defaultVal;
|
||||
$newLocation = isset($vEvent->LOCATION) && (string)$vEvent->LOCATION !== '' ? (string)$vEvent->LOCATION : $defaultVal;
|
||||
|
|
@ -522,7 +759,7 @@ class IMipService {
|
|||
$data['meeting_title_html'] ?? $data['meeting_title'], $this->l10n->t('Title:'),
|
||||
$this->getAbsoluteImagePath('caldav/title.png'), $data['meeting_title'], '', IMipPlugin::IMIP_INDENT);
|
||||
if ($data['meeting_when'] !== '') {
|
||||
$template->addBodyListItem($data['meeting_when_html'] ?? $data['meeting_when'], $this->l10n->t('Date and time:'),
|
||||
$template->addBodyListItem($data['meeting_when_html'] ?? $data['meeting_when'], $this->l10n->t('When:'),
|
||||
$this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_when'], '', IMipPlugin::IMIP_INDENT);
|
||||
}
|
||||
if ($data['meeting_location'] !== '') {
|
||||
|
|
@ -533,6 +770,10 @@ class IMipService {
|
|||
$template->addBodyListItem($data['meeting_url_html'] ?? $data['meeting_url'], $this->l10n->t('Link:'),
|
||||
$this->getAbsoluteImagePath('caldav/link.png'), $data['meeting_url'], '', IMipPlugin::IMIP_INDENT);
|
||||
}
|
||||
if (isset($data['meeting_occurring'])) {
|
||||
$template->addBodyListItem($data['meeting_occurring_html'] ?? $data['meeting_occurring'], $this->l10n->t('Occurring:'),
|
||||
$this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_occurring'], '', IMipPlugin::IMIP_INDENT);
|
||||
}
|
||||
|
||||
$this->addAttendees($template, $vevent);
|
||||
|
||||
|
|
@ -643,10 +884,104 @@ class IMipService {
|
|||
return false;
|
||||
}
|
||||
$type = $cuType->getValue() ?? 'INDIVIDUAL';
|
||||
if (\in_array(strtoupper($type), ['RESOURCE', 'ROOM'], true)) {
|
||||
if (\in_array(strtoupper($type), ['RESOURCE', 'ROOM', 'UNKNOWN'], true)) {
|
||||
// Don't send emails to things
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function minimizeInterval(\DateInterval $dateInterval): array {
|
||||
// evaluate if time interval is in the past
|
||||
if ($dateInterval->invert == 1) {
|
||||
return [1, 'the past'];
|
||||
}
|
||||
// evaluate interval parts and return smallest time period
|
||||
if ($dateInterval->y > 0) {
|
||||
$interval = $dateInterval->y;
|
||||
$scale = ($dateInterval->y > 1) ? 'years' : 'year';
|
||||
} elseif ($dateInterval->m > 0) {
|
||||
$interval = $dateInterval->m;
|
||||
$scale = ($dateInterval->m > 1) ? 'months' : 'month';
|
||||
} elseif ($dateInterval->d >= 7) {
|
||||
$interval = (int)($dateInterval->d / 7);
|
||||
$scale = ((int)($dateInterval->d / 7) > 1) ? 'weeks' : 'week';
|
||||
} elseif ($dateInterval->d > 0) {
|
||||
$interval = $dateInterval->d;
|
||||
$scale = ($dateInterval->d > 1) ? 'days' : 'day';
|
||||
} elseif ($dateInterval->h > 0) {
|
||||
$interval = $dateInterval->h;
|
||||
$scale = ($dateInterval->h > 1) ? 'hours' : 'hour';
|
||||
} else {
|
||||
$interval = $dateInterval->i;
|
||||
$scale = 'minutes';
|
||||
}
|
||||
|
||||
return [$interval, $scale];
|
||||
}
|
||||
|
||||
/**
|
||||
* Localizes week day names to another language
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function localizeDayName(string $value): string {
|
||||
return match ($value) {
|
||||
'Monday' => $this->l10n->t('Monday'),
|
||||
'Tuesday' => $this->l10n->t('Tuesday'),
|
||||
'Wednesday' => $this->l10n->t('Wednesday'),
|
||||
'Thursday' => $this->l10n->t('Thursday'),
|
||||
'Friday' => $this->l10n->t('Friday'),
|
||||
'Saturday' => $this->l10n->t('Saturday'),
|
||||
'Sunday' => $this->l10n->t('Sunday'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Localizes month names to another language
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function localizeMonthName(string $value): string {
|
||||
return match ($value) {
|
||||
'January' => $this->l10n->t('January'),
|
||||
'February' => $this->l10n->t('February'),
|
||||
'March' => $this->l10n->t('March'),
|
||||
'April' => $this->l10n->t('April'),
|
||||
'May' => $this->l10n->t('May'),
|
||||
'June' => $this->l10n->t('June'),
|
||||
'July' => $this->l10n->t('July'),
|
||||
'August' => $this->l10n->t('August'),
|
||||
'September' => $this->l10n->t('September'),
|
||||
'October' => $this->l10n->t('October'),
|
||||
'November' => $this->l10n->t('November'),
|
||||
'December' => $this->l10n->t('December'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Localizes relative position names to another language
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function localizeRelativePositionName(string $value): string {
|
||||
return match ($value) {
|
||||
'First' => $this->l10n->t('First'),
|
||||
'Second' => $this->l10n->t('Second'),
|
||||
'Third' => $this->l10n->t('Third'),
|
||||
'Fourth' => $this->l10n->t('Fourth'),
|
||||
'Fifty' => $this->l10n->t('Fifty'),
|
||||
'Last' => $this->l10n->t('Last'),
|
||||
'Second Last' => $this->l10n->t('Second Last'),
|
||||
'Third Last' => $this->l10n->t('Third Last'),
|
||||
'Fourth Last' => $this->l10n->t('Fourth Last'),
|
||||
'Fifty Last' => $this->l10n->t('Fifty Last'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1025
apps/dav/tests/unit/CalDAV/EventReaderTest.php
Normal file
1025
apps/dav/tests/unit/CalDAV/EventReaderTest.php
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue