Update libs
This commit is contained in:
parent
3d1ca79209
commit
b95de25ee8
2 changed files with 230 additions and 204 deletions
|
@ -32,7 +32,7 @@ class Event
|
|||
/**
|
||||
* https://www.kanzaki.com/docs/ical/duration.html
|
||||
*
|
||||
* @var string
|
||||
* @var string|null
|
||||
*/
|
||||
public $duration;
|
||||
|
||||
|
@ -81,14 +81,14 @@ class Event
|
|||
/**
|
||||
* https://www.kanzaki.com/docs/ical/description.html
|
||||
*
|
||||
* @var string
|
||||
* @var string|null
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* https://www.kanzaki.com/docs/ical/location.html
|
||||
*
|
||||
* @var string
|
||||
* @var string|null
|
||||
*/
|
||||
public $location;
|
||||
|
||||
|
@ -132,7 +132,7 @@ class Event
|
|||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private $additionalProperties = [];
|
||||
public $additionalProperties = array();
|
||||
|
||||
/**
|
||||
* Creates the Event object
|
||||
|
@ -250,10 +250,15 @@ class Event
|
|||
*/
|
||||
protected static function snakeCase($input, $glue = '_', $separator = '-')
|
||||
{
|
||||
$input = preg_split('/(?<=[a-z])(?=[A-Z])/x', $input);
|
||||
$input = implode($glue, $input);
|
||||
$input = str_replace($separator, $glue, $input);
|
||||
$inputSplit = preg_split('/(?<=[a-z])(?=[A-Z])/x', $input);
|
||||
|
||||
return strtolower($input);
|
||||
if ($inputSplit === false) {
|
||||
return $input;
|
||||
}
|
||||
|
||||
$inputSplit = implode($glue, $inputSplit);
|
||||
$inputSplit = str_replace($separator, $glue, $inputSplit);
|
||||
|
||||
return strtolower($inputSplit);
|
||||
}
|
||||
}
|
355
ICal/ICal.php
355
ICal/ICal.php
|
@ -517,7 +517,7 @@ class ICal
|
|||
|
||||
// Fallback to use the system default time zone
|
||||
if (!isset($this->defaultTimeZone) || !$this->isValidTimeZoneId($this->defaultTimeZone)) {
|
||||
$this->defaultTimeZone = date_default_timezone_get();
|
||||
$this->defaultTimeZone = $this->getDefaultTimeZone(true);
|
||||
}
|
||||
|
||||
// Ideally you would use `PHP_INT_MIN` from PHP 7
|
||||
|
@ -553,7 +553,7 @@ class ICal
|
|||
{
|
||||
$string = str_replace(array("\r\n", "\n\r", "\r"), "\n", $string);
|
||||
|
||||
if (empty($this->cal)) {
|
||||
if ($this->cal === array()) {
|
||||
$lines = explode("\n", $string);
|
||||
|
||||
$this->initLines($lines);
|
||||
|
@ -572,7 +572,7 @@ class ICal
|
|||
*/
|
||||
public function initFile($file)
|
||||
{
|
||||
if (empty($this->cal)) {
|
||||
if ($this->cal === array()) {
|
||||
$lines = $this->fileOrUrl($file);
|
||||
|
||||
$this->initLines($lines);
|
||||
|
@ -640,15 +640,16 @@ class ICal
|
|||
}
|
||||
|
||||
if (!$this->disableCharacterReplacement) {
|
||||
$line = $this->cleanData($line);
|
||||
$line = str_replace(array(
|
||||
' ',
|
||||
"\t",
|
||||
"\xc2\xa0", // Non-breaking space
|
||||
), ' ', $line);
|
||||
|
||||
$line = $this->cleanCharacters($line);
|
||||
}
|
||||
|
||||
$add = $this->keyValueFromString($line);
|
||||
|
||||
if ($add === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$keyword = $add[0];
|
||||
$values = $add[1]; // May be an array containing multiple values
|
||||
|
||||
|
@ -679,8 +680,8 @@ class ICal
|
|||
|
||||
break;
|
||||
|
||||
// https://www.kanzaki.com/docs/ical/vevent.html
|
||||
case 'BEGIN:VEVENT':
|
||||
// https://www.kanzaki.com/docs/ical/vevent.html
|
||||
if (!is_array($value)) {
|
||||
$this->eventCount++;
|
||||
}
|
||||
|
@ -689,8 +690,8 @@ class ICal
|
|||
|
||||
break;
|
||||
|
||||
// https://www.kanzaki.com/docs/ical/vfreebusy.html
|
||||
case 'BEGIN:VFREEBUSY':
|
||||
// https://www.kanzaki.com/docs/ical/vfreebusy.html
|
||||
if (!is_array($value)) {
|
||||
$this->freeBusyIndex++;
|
||||
}
|
||||
|
@ -754,7 +755,7 @@ class ICal
|
|||
$this->processRecurrences();
|
||||
|
||||
// Apply changes to altered recurrence instances
|
||||
if (!empty($this->alteredRecurrenceInstances)) {
|
||||
if ($this->alteredRecurrenceInstances !== array()) {
|
||||
$events = $this->cal['VEVENT'];
|
||||
|
||||
foreach ($this->alteredRecurrenceInstances as $alteredRecurrenceInstance) {
|
||||
|
@ -787,7 +788,7 @@ class ICal
|
|||
{
|
||||
$events = $this->cal['VEVENT'];
|
||||
|
||||
if (!empty($events)) {
|
||||
if ($events !== array()) {
|
||||
$lastIndex = count($events) - 1;
|
||||
$lastEvent = $events[$lastIndex];
|
||||
|
||||
|
@ -810,7 +811,7 @@ class ICal
|
|||
{
|
||||
$events = (isset($this->cal['VEVENT'])) ? $this->cal['VEVENT'] : array();
|
||||
|
||||
if (!empty($events)) {
|
||||
if ($events !== array()) {
|
||||
foreach ($events as $key => $anEvent) {
|
||||
if ($anEvent === null) {
|
||||
unset($events[$key]);
|
||||
|
@ -869,9 +870,10 @@ class ICal
|
|||
{
|
||||
$string = implode(PHP_EOL, $lines);
|
||||
$string = str_ireplace(' ', ' ', $string);
|
||||
$string = preg_replace('/' . PHP_EOL . '[ \t]/', '', $string);
|
||||
|
||||
$lines = explode(PHP_EOL, $string);
|
||||
$cleanedString = preg_replace('/' . PHP_EOL . '[ \t]/', '', $string);
|
||||
|
||||
$lines = explode(PHP_EOL, $cleanedString ?: $string);
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
@ -912,7 +914,6 @@ class ICal
|
|||
$this->cal[$key1][$key2][$key3][$keyword] .= ',' . $value;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'VEVENT':
|
||||
|
@ -948,11 +949,10 @@ class ICal
|
|||
}
|
||||
}
|
||||
|
||||
if ($this->cal[$key1][$key2][$keyword] !== $value) {
|
||||
if (!is_array($value) && $this->cal[$key1][$key2][$keyword] !== $value) {
|
||||
$this->cal[$key1][$key2][$keyword] .= ',' . $value;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'VFREEBUSY':
|
||||
|
@ -975,7 +975,6 @@ class ICal
|
|||
} else {
|
||||
$this->cal[$key1][$key2][$key3][] = $value;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'VTODO':
|
||||
|
@ -989,14 +988,16 @@ class ICal
|
|||
break;
|
||||
}
|
||||
|
||||
if (is_string($keyword)) {
|
||||
$this->lastKeyword = $keyword;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key value pair from an iCal string
|
||||
*
|
||||
* @param string $text
|
||||
* @return array|boolean
|
||||
* @return array
|
||||
*/
|
||||
public function keyValueFromString($text)
|
||||
{
|
||||
|
@ -1011,6 +1012,7 @@ class ICal
|
|||
if ($i === 0) {
|
||||
$object[0] = $splitLine[$i];
|
||||
$i++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1052,7 +1054,7 @@ class ICal
|
|||
}
|
||||
|
||||
// Object construction
|
||||
if ($paramObj !== []) {
|
||||
if ($paramObj !== array()) {
|
||||
$object[1][0] = $valueObj;
|
||||
$object[1][1] = $paramObj;
|
||||
} else {
|
||||
|
@ -1103,11 +1105,29 @@ class ICal
|
|||
return $words;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default time zone if set.
|
||||
* Falls back to the system default if not set.
|
||||
*
|
||||
* @param boolean $forceReturnSystemDefault
|
||||
* @return string
|
||||
*/
|
||||
private function getDefaultTimeZone($forceReturnSystemDefault = false)
|
||||
{
|
||||
$systemDefault = date_default_timezone_get();
|
||||
|
||||
if ($forceReturnSystemDefault) {
|
||||
return $systemDefault;
|
||||
}
|
||||
|
||||
return $this->defaultTimeZone ?: $systemDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a `DateTime` object from an iCal date time format
|
||||
*
|
||||
* @param string $icalDate
|
||||
* @return \DateTime
|
||||
* @return \DateTime|false
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function iCalDateToDateTime($icalDate)
|
||||
|
@ -1131,7 +1151,7 @@ class ICal
|
|||
|
||||
preg_match($pattern, $icalDate, $date);
|
||||
|
||||
if (empty($date)) {
|
||||
if ($date === array()) {
|
||||
throw new \Exception('Invalid iCal date format.');
|
||||
}
|
||||
|
||||
|
@ -1141,10 +1161,10 @@ class ICal
|
|||
|
||||
if ($date[4] === 'Z') {
|
||||
$dateTimeZone = new \DateTimeZone(self::TIME_ZONE_UTC);
|
||||
} elseif (!empty($date[1])) {
|
||||
} elseif (isset($date[1]) && $date[1] !== '') {
|
||||
$dateTimeZone = $this->timeZoneStringToDateTimeZone($date[1]);
|
||||
} else {
|
||||
$dateTimeZone = new \DateTimeZone($this->defaultTimeZone);
|
||||
$dateTimeZone = new \DateTimeZone($this->getDefaultTimeZone());
|
||||
}
|
||||
|
||||
// The exclamation mark at the start of the format string indicates that if a
|
||||
|
@ -1152,7 +1172,7 @@ class ICal
|
|||
// set to 00:00:00. Without it, the time would be set to the current system time.
|
||||
$dateFormat = '!Ymd';
|
||||
$dateBasic = $date[2];
|
||||
if (!empty($date[3])) {
|
||||
if (isset($date[3]) && $date[3] !== '') {
|
||||
$dateBasic .= "T{$date[3]}";
|
||||
$dateFormat .= '\THis';
|
||||
}
|
||||
|
@ -1168,7 +1188,15 @@ class ICal
|
|||
*/
|
||||
public function iCalDateToUnixTimestamp($icalDate)
|
||||
{
|
||||
return $this->iCalDateToDateTime($icalDate)->getTimestamp();
|
||||
$iCalDateToDateTime = $this->iCalDateToDateTime($icalDate);
|
||||
|
||||
if ($iCalDateToDateTime === false) {
|
||||
trigger_error("ICal::iCalDateToUnixTimestamp: Invalid date passed ({$icalDate})", E_USER_NOTICE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $iCalDateToDateTime->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1177,7 +1205,7 @@ class ICal
|
|||
* @param array $event
|
||||
* @param string $key
|
||||
* @param string|null $format
|
||||
* @return string|boolean|\DateTime
|
||||
* @return string|integer|boolean|\DateTime
|
||||
*/
|
||||
public function iCalDateWithTimeZone(array $event, $key, $format = self::DATE_TIME_FORMAT)
|
||||
{
|
||||
|
@ -1188,14 +1216,24 @@ class ICal
|
|||
$dateArray = $event["{$key}_array"];
|
||||
|
||||
if ($key === 'DURATION') {
|
||||
$dateTime = $this->parseDuration($event['DTSTART'], $dateArray[2], null);
|
||||
$dateTime = $this->parseDuration($event['DTSTART'], $dateArray[2]);
|
||||
|
||||
if ($dateTime instanceof \DateTime === false) {
|
||||
trigger_error("ICal::iCalDateWithTimeZone: Invalid date passed ({$event['DTSTART']})", E_USER_NOTICE);
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// When constructing from a Unix Timestamp, no time zone needs passing.
|
||||
$dateTime = new \DateTime("@{$dateArray[2]}");
|
||||
}
|
||||
|
||||
$calendarTimeZone = $this->calendarTimeZone();
|
||||
|
||||
if (!is_null($calendarTimeZone)) {
|
||||
// Set the time zone we wish to use when running `$dateTime->format`.
|
||||
$dateTime->setTimezone(new \DateTimeZone($this->calendarTimeZone()));
|
||||
$dateTime->setTimezone(new \DateTimeZone($calendarTimeZone));
|
||||
}
|
||||
|
||||
if (is_null($format)) {
|
||||
return $dateTime;
|
||||
|
@ -1216,7 +1254,7 @@ class ICal
|
|||
$checks = null;
|
||||
$events = (isset($this->cal['VEVENT'])) ? $this->cal['VEVENT'] : array();
|
||||
|
||||
if (!empty($events)) {
|
||||
if ($events !== array()) {
|
||||
foreach ($events as $key => $anEvent) {
|
||||
foreach (array('DTSTART', 'DTEND', 'RECURRENCE-ID') as $type) {
|
||||
if (isset($anEvent[$type])) {
|
||||
|
@ -1286,7 +1324,7 @@ class ICal
|
|||
$events = (isset($this->cal['VEVENT'])) ? $this->cal['VEVENT'] : array();
|
||||
|
||||
// If there are no events, then we have nothing to process.
|
||||
if (empty($events)) {
|
||||
if ($events === array()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1304,6 +1342,12 @@ class ICal
|
|||
// Create new initial starting point.
|
||||
$initialEventDate = $this->icalDateToDateTime($anEvent['DTSTART_array'][3]);
|
||||
|
||||
if ($initialEventDate === false) {
|
||||
trigger_error("ICal::processRecurrences: Invalid date passed ({$anEvent['DTSTART_array'][3]})", E_USER_NOTICE);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Separate the RRULE stanzas, and explode the values that are lists.
|
||||
$rrules = array();
|
||||
foreach (array_filter(explode(';', $anEvent['RRULE'])) as $s) {
|
||||
|
@ -1315,9 +1359,14 @@ class ICal
|
|||
}
|
||||
}
|
||||
|
||||
// Get frequency
|
||||
$frequency = $rrules['FREQ'];
|
||||
|
||||
if (!is_string($frequency)) {
|
||||
trigger_error('ICal::processRecurrences: Invalid frequency passed', E_USER_NOTICE);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reject RRULE if BYDAY stanza is invalid:
|
||||
// > The BYDAY rule part MUST NOT be specified with a numeric value
|
||||
// > when the FREQ rule part is not set to MONTHLY or YEARLY.
|
||||
|
@ -1329,21 +1378,20 @@ class ICal
|
|||
return $carry && substr($weekday, -2) === $weekday;
|
||||
};
|
||||
if (!in_array($frequency, array('MONTHLY', 'YEARLY'))) {
|
||||
if (!array_reduce($rrules['BYDAY'], $checkByDays, true)) {
|
||||
error_log("ICal::ProcessRecurrences: A {$frequency} RRULE may not contain BYDAY values with numeric prefixes");
|
||||
if (is_array($rrules['BYDAY']) && !array_reduce($rrules['BYDAY'], $checkByDays, true)) {
|
||||
trigger_error("ICal::processRecurrences: A {$frequency} RRULE may not contain BYDAY values with numeric prefixes", E_USER_NOTICE);
|
||||
|
||||
continue;
|
||||
}
|
||||
} elseif ($frequency === 'YEARLY' && !empty($rrules['BYWEEKNO'])) {
|
||||
if (!array_reduce($rrules['BYDAY'], $checkByDays, true)) {
|
||||
error_log('ICal::ProcessRecurrences: A YEARLY RRULE with a BYWEEKNO part may not contain BYDAY values with numeric prefixes');
|
||||
} elseif ($frequency === 'YEARLY' && (isset($rrules['BYWEEKNO']) && ($rrules['BYWEEKNO'] !== '' && $rrules['BYWEEKNO'] !== array()))) {
|
||||
if (is_array($rrules['BYDAY']) && !array_reduce($rrules['BYDAY'], $checkByDays, true)) {
|
||||
trigger_error('ICal::processRecurrences: A YEARLY RRULE with a BYWEEKNO part may not contain BYDAY values with numeric prefixes', E_USER_NOTICE);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get Interval
|
||||
$interval = (empty($rrules['INTERVAL'])) ? 1 : (int) $rrules['INTERVAL'];
|
||||
|
||||
// Throw an error if this isn't an integer.
|
||||
|
@ -1380,22 +1428,45 @@ class ICal
|
|||
*/
|
||||
$count = 1;
|
||||
$countLimit = (isset($rrules['COUNT'])) ? intval($rrules['COUNT']) : PHP_INT_MAX;
|
||||
$until = date_create()->modify("{$this->defaultSpan} years")->setTime(23, 59, 59)->getTimestamp();
|
||||
$now = date_create();
|
||||
|
||||
if (isset($rrules['UNTIL'])) {
|
||||
$until = min($until, $this->iCalDateToUnixTimestamp($rrules['UNTIL']));
|
||||
$until = $now === false
|
||||
? 0
|
||||
: $now->modify("{$this->defaultSpan} years")->setTime(23, 59, 59)->getTimestamp();
|
||||
|
||||
$untilWhile = $until;
|
||||
|
||||
if (isset($rrules['UNTIL']) && is_string($rrules['UNTIL'])) {
|
||||
$untilDT = $this->iCalDateToDateTime($rrules['UNTIL']);
|
||||
$until = min($until, ($untilDT === false) ? $until : $untilDT->getTimestamp());
|
||||
|
||||
// There are certain edge cases where we need to go a little beyond the UNTIL to
|
||||
// ensure we get all events. Consider:
|
||||
//
|
||||
// DTSTART:20200103
|
||||
// RRULE:FREQ=MONTHLY;BYDAY=-5FR;UNTIL=20200502
|
||||
//
|
||||
// In this case the last occurrence should be 1st May, however when we transition
|
||||
// from April to May:
|
||||
//
|
||||
// $until ~= 2nd May
|
||||
// $frequencyRecurringDateTime ~= 3rd May
|
||||
//
|
||||
// And as the latter comes after the former, the while loop ends before any dates
|
||||
// in May have the chance to be considered.
|
||||
$untilWhile = min($untilWhile, ($untilDT === false) ? $untilWhile : $untilDT->modify("+1 {$this->frequencyConversion[$frequency]}")->getTimestamp());
|
||||
}
|
||||
|
||||
$eventRecurrences = array();
|
||||
|
||||
$frequencyRecurringDateTime = clone $initialEventDate;
|
||||
while ($frequencyRecurringDateTime->getTimestamp() <= $until && $count < $countLimit) {
|
||||
while ($frequencyRecurringDateTime->getTimestamp() <= $untilWhile && $count < $countLimit) {
|
||||
$candidateDateTimes = array();
|
||||
|
||||
// phpcs:ignore Squiz.ControlStructures.SwitchDeclaration.MissingDefault
|
||||
switch ($frequency) {
|
||||
case 'DAILY':
|
||||
if (!empty($rrules['BYMONTHDAY'])) {
|
||||
if (isset($rrules['BYMONTHDAY']) && (is_array($rrules['BYMONTHDAY']) && $rrules['BYMONTHDAY'] !== array())) {
|
||||
if (!isset($monthDays)) {
|
||||
// This variable is unset when we change months (see below)
|
||||
$monthDays = $this->getDaysOfMonthMatchingByMonthDayRRule($rrules['BYMONTHDAY'], $frequencyRecurringDateTime);
|
||||
|
@ -1414,7 +1485,7 @@ class ICal
|
|||
$initialDayOfWeek = $frequencyRecurringDateTime->format('N');
|
||||
$matchingDays = array($initialDayOfWeek);
|
||||
|
||||
if (!empty($rrules['BYDAY'])) {
|
||||
if (isset($rrules['BYDAY']) && (is_array($rrules['BYDAY']) && $rrules['BYDAY'] !== array())) {
|
||||
// setISODate() below uses the ISO-8601 specification of weeks: start on
|
||||
// a Monday, end on a Sunday. However, RRULEs (or the caller of the
|
||||
// parser) may state an alternate WeeKSTart.
|
||||
|
@ -1459,18 +1530,17 @@ class ICal
|
|||
$candidateDateTimes[] = $clonedDateTime->setISODate(
|
||||
(int) $frequencyRecurringDateTime->format('o'),
|
||||
(int) $frequencyRecurringDateTime->format('W'),
|
||||
$day
|
||||
(int) $day
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'MONTHLY':
|
||||
$matchingDays = array();
|
||||
|
||||
if (!empty($rrules['BYMONTHDAY'])) {
|
||||
if (isset($rrules['BYMONTHDAY']) && (is_array($rrules['BYMONTHDAY']) && $rrules['BYMONTHDAY'] !== array())) {
|
||||
$matchingDays = $this->getDaysOfMonthMatchingByMonthDayRRule($rrules['BYMONTHDAY'], $frequencyRecurringDateTime);
|
||||
if (!empty($rrules['BYDAY'])) {
|
||||
if (isset($rrules['BYDAY']) && (is_array($rrules['BYDAY']) && $rrules['BYDAY'] !== array())) {
|
||||
$matchingDays = array_filter(
|
||||
$this->getDaysOfMonthMatchingByDayRRule($rrules['BYDAY'], $frequencyRecurringDateTime),
|
||||
function ($monthDay) use ($matchingDays) {
|
||||
|
@ -1478,13 +1548,13 @@ class ICal
|
|||
}
|
||||
);
|
||||
}
|
||||
} elseif (!empty($rrules['BYDAY'])) {
|
||||
} elseif (isset($rrules['BYDAY']) && (is_array($rrules['BYDAY']) && $rrules['BYDAY'] !== array())) {
|
||||
$matchingDays = $this->getDaysOfMonthMatchingByDayRRule($rrules['BYDAY'], $frequencyRecurringDateTime);
|
||||
} else {
|
||||
$matchingDays[] = $frequencyRecurringDateTime->format('d');
|
||||
}
|
||||
|
||||
if (!empty($rrules['BYSETPOS'])) {
|
||||
if (isset($rrules['BYSETPOS']) && (is_array($rrules['BYSETPOS']) && $rrules['BYSETPOS'] !== array())) {
|
||||
$matchingDays = $this->filterValuesUsingBySetPosRRule($rrules['BYSETPOS'], $matchingDays);
|
||||
}
|
||||
|
||||
|
@ -1501,27 +1571,26 @@ class ICal
|
|||
$day
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'YEARLY':
|
||||
$matchingDays = array();
|
||||
|
||||
if (!empty($rrules['BYMONTH'])) {
|
||||
if (isset($rrules['BYMONTH']) && (is_array($rrules['BYMONTH']) && $rrules['BYMONTH'] !== array())) {
|
||||
$bymonthRecurringDatetime = clone $frequencyRecurringDateTime;
|
||||
foreach ($rrules['BYMONTH'] as $byMonth) {
|
||||
$bymonthRecurringDatetime->setDate(
|
||||
(int) $frequencyRecurringDateTime->format('Y'),
|
||||
$byMonth,
|
||||
(int) $byMonth,
|
||||
(int) $frequencyRecurringDateTime->format('d')
|
||||
);
|
||||
|
||||
// Determine the days of the month affected
|
||||
// (The interaction between BYMONTHDAY and BYDAY is resolved later.)
|
||||
$monthDays = array();
|
||||
if (!empty($rrules['BYMONTHDAY'])) {
|
||||
if (isset($rrules['BYMONTHDAY']) && (is_array($rrules['BYMONTHDAY']) && $rrules['BYMONTHDAY'] !== array())) {
|
||||
$monthDays = $this->getDaysOfMonthMatchingByMonthDayRRule($rrules['BYMONTHDAY'], $bymonthRecurringDatetime);
|
||||
} elseif (!empty($rrules['BYDAY'])) {
|
||||
} elseif (isset($rrules['BYDAY']) && (is_array($rrules['BYDAY']) && $rrules['BYDAY'] !== array())) {
|
||||
$monthDays = $this->getDaysOfMonthMatchingByDayRRule($rrules['BYDAY'], $bymonthRecurringDatetime);
|
||||
} else {
|
||||
$monthDays[] = $bymonthRecurringDatetime->format('d');
|
||||
|
@ -1536,34 +1605,34 @@ class ICal
|
|||
)->format('z') + 1;
|
||||
}
|
||||
}
|
||||
} elseif (!empty($rrules['BYWEEKNO'])) {
|
||||
} elseif (isset($rrules['BYWEEKNO']) && (is_array($rrules['BYWEEKNO']) && $rrules['BYWEEKNO'] !== array())) {
|
||||
$matchingDays = $this->getDaysOfYearMatchingByWeekNoRRule($rrules['BYWEEKNO'], $frequencyRecurringDateTime);
|
||||
} elseif (!empty($rrules['BYYEARDAY'])) {
|
||||
} elseif (isset($rrules['BYYEARDAY']) && (is_array($rrules['BYYEARDAY']) && $rrules['BYYEARDAY'] !== array())) {
|
||||
$matchingDays = $this->getDaysOfYearMatchingByYearDayRRule($rrules['BYYEARDAY'], $frequencyRecurringDateTime);
|
||||
} elseif (!empty($rrules['BYMONTHDAY'])) {
|
||||
} elseif (isset($rrules['BYMONTHDAY']) && (is_array($rrules['BYMONTHDAY']) && $rrules['BYMONTHDAY'] !== array())) {
|
||||
$matchingDays = $this->getDaysOfYearMatchingByMonthDayRRule($rrules['BYMONTHDAY'], $frequencyRecurringDateTime);
|
||||
}
|
||||
|
||||
if (!empty($rrules['BYDAY'])) {
|
||||
if (!empty($rrules['BYYEARDAY']) || !empty($rrules['BYMONTHDAY']) || !empty($rrules['BYWEEKNO'])) {
|
||||
if (isset($rrules['BYDAY']) && (is_array($rrules['BYDAY']) && $rrules['BYDAY'] !== array())) {
|
||||
if (isset($rrules['BYYEARDAY']) && ($rrules['BYYEARDAY'] !== '' && $rrules['BYYEARDAY'] !== array()) || isset($rrules['BYMONTHDAY']) && ($rrules['BYMONTHDAY'] !== '' && $rrules['BYMONTHDAY'] !== array()) || isset($rrules['BYWEEKNO']) && ($rrules['BYWEEKNO'] !== '' && $rrules['BYWEEKNO'] !== array())) {
|
||||
$matchingDays = array_filter(
|
||||
$this->getDaysOfYearMatchingByDayRRule($rrules['BYDAY'], $frequencyRecurringDateTime),
|
||||
function ($yearDay) use ($matchingDays) {
|
||||
return in_array($yearDay, $matchingDays);
|
||||
}
|
||||
);
|
||||
} elseif ($matchingDays === []) {
|
||||
} elseif ($matchingDays === array()) {
|
||||
$matchingDays = $this->getDaysOfYearMatchingByDayRRule($rrules['BYDAY'], $frequencyRecurringDateTime);
|
||||
}
|
||||
}
|
||||
|
||||
if ($matchingDays === []) {
|
||||
if ($matchingDays === array()) {
|
||||
$matchingDays = array($frequencyRecurringDateTime->format('z') + 1);
|
||||
} else {
|
||||
sort($matchingDays);
|
||||
}
|
||||
|
||||
if (!empty($rrules['BYSETPOS'])) {
|
||||
if (isset($rrules['BYSETPOS']) && (is_array($rrules['BYSETPOS']) && $rrules['BYSETPOS'] !== array())) {
|
||||
$matchingDays = $this->filterValuesUsingBySetPosRRule($rrules['BYSETPOS'], $matchingDays);
|
||||
}
|
||||
|
||||
|
@ -1575,7 +1644,6 @@ class ICal
|
|||
$day
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1753,6 +1821,7 @@ class ICal
|
|||
protected function getDaysOfMonthMatchingByDayRRule(array $byDays, $initialDateTime)
|
||||
{
|
||||
$matchingDays = array();
|
||||
$currentMonth = $initialDateTime->format('n');
|
||||
|
||||
foreach ($byDays as $weekday) {
|
||||
$bydayDateTime = clone $initialDateTime;
|
||||
|
@ -1771,10 +1840,14 @@ class ICal
|
|||
|
||||
if ($ordwk < 0) { // -ve {ordwk}
|
||||
$bydayDateTime->modify((++$ordwk) . ' week');
|
||||
if ($bydayDateTime->format('n') === $currentMonth) {
|
||||
$matchingDays[] = $bydayDateTime->format('j');
|
||||
}
|
||||
} elseif ($ordwk > 0) { // +ve {ordwk}
|
||||
$bydayDateTime->modify((--$ordwk) . ' week');
|
||||
if ($bydayDateTime->format('n') === $currentMonth) {
|
||||
$matchingDays[] = $bydayDateTime->format('j');
|
||||
}
|
||||
} else { // No {ordwk}
|
||||
while ($bydayDateTime->format('n') === $initialDateTime->format('n')) {
|
||||
$matchingDays[] = $bydayDateTime->format('j');
|
||||
|
@ -1924,7 +1997,8 @@ class ICal
|
|||
{
|
||||
// `\DateTime::format('L')` returns 1 if leap year, 0 if not.
|
||||
$isLeapYear = $initialDateTime->format('L');
|
||||
$firstDayOfTheYear = date_create("first day of January {$initialDateTime->format('Y')}")->format('D');
|
||||
$initialYear = date_create("first day of January {$initialDateTime->format('Y')}");
|
||||
$firstDayOfTheYear = ($initialYear === false) ? null : $initialYear->format('D');
|
||||
$weeksInThisYear = ($firstDayOfTheYear === 'Thu' || $isLeapYear && $firstDayOfTheYear === 'Wed') ? 53 : 52;
|
||||
|
||||
$matchingWeeks = $this->resolveIndicesOfRange($byWeekNums, $weeksInThisYear);
|
||||
|
@ -2039,7 +2113,7 @@ class ICal
|
|||
{
|
||||
$events = (isset($this->cal['VEVENT'])) ? $this->cal['VEVENT'] : array();
|
||||
|
||||
if (!empty($events)) {
|
||||
if ($events !== array()) {
|
||||
foreach ($events as $key => $anEvent) {
|
||||
if (is_null($anEvent) || !$this->isValidDate($anEvent['DTSTART'])) {
|
||||
unset($events[$key]);
|
||||
|
@ -2077,11 +2151,9 @@ class ICal
|
|||
|
||||
$events = array();
|
||||
|
||||
if (!empty($array)) {
|
||||
foreach ($array as $event) {
|
||||
$events[] = new Event($event);
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
@ -2154,7 +2226,7 @@ class ICal
|
|||
*/
|
||||
public function hasEvents()
|
||||
{
|
||||
return ($this->events() !== []) ?: false;
|
||||
return ($this->events() !== array()) ?: false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2185,7 +2257,7 @@ class ICal
|
|||
// Sort events before processing range
|
||||
$events = $this->sortEventsWithOrder($this->events());
|
||||
|
||||
if (empty($events)) {
|
||||
if ($events === array()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
|
@ -2193,27 +2265,28 @@ class ICal
|
|||
|
||||
if (!is_null($rangeStart)) {
|
||||
try {
|
||||
$rangeStart = new \DateTime($rangeStart, new \DateTimeZone($this->defaultTimeZone));
|
||||
$rangeStart = new \DateTime($rangeStart, new \DateTimeZone($this->getDefaultTimeZone()));
|
||||
} catch (\Exception $exception) {
|
||||
error_log("ICal::eventsFromRange: Invalid date passed ({$rangeStart})");
|
||||
$rangeStart = false;
|
||||
}
|
||||
} else {
|
||||
$rangeStart = new \DateTime('now', new \DateTimeZone($this->defaultTimeZone));
|
||||
$rangeStart = new \DateTime('now', new \DateTimeZone($this->getDefaultTimeZone()));
|
||||
}
|
||||
|
||||
if (!is_null($rangeEnd)) {
|
||||
try {
|
||||
$rangeEnd = new \DateTime($rangeEnd, new \DateTimeZone($this->defaultTimeZone));
|
||||
$rangeEnd = new \DateTime($rangeEnd, new \DateTimeZone($this->getDefaultTimeZone()));
|
||||
} catch (\Exception $exception) {
|
||||
error_log("ICal::eventsFromRange: Invalid date passed ({$rangeEnd})");
|
||||
$rangeEnd = false;
|
||||
}
|
||||
} else {
|
||||
$rangeEnd = new \DateTime('now', new \DateTimeZone($this->defaultTimeZone));
|
||||
$rangeEnd = new \DateTime('now', new \DateTimeZone($this->getDefaultTimeZone()));
|
||||
$rangeEnd->modify('+20 years');
|
||||
}
|
||||
|
||||
if ($rangeEnd !== false && $rangeStart !== false) {
|
||||
// If start and end are identical and are dates with no times...
|
||||
if ($rangeEnd->format('His') == 0 && $rangeStart->getTimestamp() === $rangeEnd->getTimestamp()) {
|
||||
$rangeEnd->modify('+1 day');
|
||||
|
@ -2221,6 +2294,7 @@ class ICal
|
|||
|
||||
$rangeStart = $rangeStart->getTimestamp();
|
||||
$rangeEnd = $rangeEnd->getTimestamp();
|
||||
}
|
||||
|
||||
foreach ($events as $anEvent) {
|
||||
$eventStart = $anEvent->dtstart_array[2];
|
||||
|
@ -2228,7 +2302,8 @@ class ICal
|
|||
|
||||
if (
|
||||
($eventStart >= $rangeStart && $eventStart < $rangeEnd) // Event start date contained in the range
|
||||
|| ($eventEnd !== null
|
||||
|| (
|
||||
$eventEnd !== null
|
||||
&& (
|
||||
($eventEnd > $rangeStart && $eventEnd <= $rangeEnd) // Event end date contained in the range
|
||||
|| ($eventStart < $rangeStart && $eventEnd > $rangeEnd) // Event starts before and finishes after range
|
||||
|
@ -2239,27 +2314,26 @@ class ICal
|
|||
}
|
||||
}
|
||||
|
||||
if (empty($extendedEvents)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $extendedEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sorted array of the events following a given string,
|
||||
* or `false` if no events exist in the range.
|
||||
* Returns a sorted array of the events following a given string
|
||||
*
|
||||
* @param string $interval
|
||||
* @return array
|
||||
*/
|
||||
public function eventsFromInterval($interval)
|
||||
{
|
||||
$rangeStart = new \DateTime('now', new \DateTimeZone($this->defaultTimeZone));
|
||||
$rangeEnd = new \DateTime('now', new \DateTimeZone($this->defaultTimeZone));
|
||||
$timeZone = $this->getDefaultTimeZone();
|
||||
$rangeStart = new \DateTime('now', new \DateTimeZone($timeZone));
|
||||
$rangeEnd = new \DateTime('now', new \DateTimeZone($timeZone));
|
||||
|
||||
$dateInterval = \DateInterval::createFromDateString($interval);
|
||||
|
||||
if ($dateInterval instanceof \DateInterval) {
|
||||
$rangeEnd->add($dateInterval);
|
||||
}
|
||||
|
||||
return $this->eventsFromRange($rangeStart->format('Y-m-d'), $rangeEnd->format('Y-m-d'));
|
||||
}
|
||||
|
@ -2358,12 +2432,16 @@ class ICal
|
|||
*
|
||||
* @param string $date
|
||||
* @param \DateInterval $duration
|
||||
* @param string|null $format
|
||||
* @return integer|\DateTime
|
||||
* @return \DateTime|false
|
||||
*/
|
||||
protected function parseDuration($date, $duration, $format = self::UNIX_FORMAT)
|
||||
protected function parseDuration($date, $duration)
|
||||
{
|
||||
$dateTime = date_create($date);
|
||||
|
||||
if ($dateTime === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dateTime->modify("{$duration->y} year");
|
||||
$dateTime->modify("{$duration->m} month");
|
||||
$dateTime->modify("{$duration->d} day");
|
||||
|
@ -2371,22 +2449,14 @@ class ICal
|
|||
$dateTime->modify("{$duration->i} minute");
|
||||
$dateTime->modify("{$duration->s} second");
|
||||
|
||||
if (is_null($format)) {
|
||||
$output = $dateTime;
|
||||
} elseif ($format === self::UNIX_FORMAT) {
|
||||
$output = $dateTime->getTimestamp();
|
||||
} else {
|
||||
$output = $dateTime->format($format);
|
||||
}
|
||||
|
||||
return $output;
|
||||
return $dateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unprintable ASCII and UTF-8 characters
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
protected function removeUnprintableChars($data)
|
||||
{
|
||||
|
@ -2419,53 +2489,6 @@ class ICal
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace all occurrences of the search string with the replacement string.
|
||||
* Multibyte safe.
|
||||
*
|
||||
* @param string|array $search
|
||||
* @param string|array $replace
|
||||
* @param string|array $subject
|
||||
* @param string $encoding
|
||||
* @param integer $count
|
||||
* @return array|string
|
||||
*/
|
||||
protected static function mb_str_replace($search, $replace, $subject, $encoding = null, &$count = 0) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
{
|
||||
if (is_array($subject)) {
|
||||
// Call `mb_str_replace()` for each subject in the array, recursively
|
||||
foreach ($subject as $key => $value) {
|
||||
$subject[$key] = self::mb_str_replace($search, $replace, $value, $encoding, $count);
|
||||
}
|
||||
} else {
|
||||
// Normalize $search and $replace so they are both arrays of the same length
|
||||
$searches = is_array($search) ? array_values($search) : array($search);
|
||||
$replacements = is_array($replace) ? array_values($replace) : array($replace);
|
||||
$replacements = array_pad($replacements, count($searches), '');
|
||||
|
||||
foreach ($searches as $key => $search) {
|
||||
if (is_null($encoding)) {
|
||||
$encoding = mb_detect_encoding($search, 'UTF-8', true);
|
||||
}
|
||||
|
||||
$replace = $replacements[$key];
|
||||
$searchLen = mb_strlen($search, $encoding);
|
||||
|
||||
$sb = array();
|
||||
while (($offset = mb_strpos($subject, $search, 0, $encoding)) !== false) {
|
||||
$sb[] = mb_substr($subject, 0, $offset, $encoding);
|
||||
$subject = mb_substr($subject, $offset + $searchLen, null, $encoding);
|
||||
++$count;
|
||||
}
|
||||
|
||||
$sb[] = $subject;
|
||||
$subject = implode($replace, $sb);
|
||||
}
|
||||
}
|
||||
|
||||
return $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Places double-quotes around texts that have characters not permitted
|
||||
* in parameter-texts, but are permitted in quoted-texts.
|
||||
|
@ -2483,16 +2506,17 @@ class ICal
|
|||
}
|
||||
|
||||
/**
|
||||
* Replaces curly quotes and other special characters
|
||||
* with their standard equivalents
|
||||
* Replace curly quotes and other special characters with their standard equivalents
|
||||
* @see https://utf8-chartable.de/unicode-utf8-table.pl?start=8211&utf8=string-literal
|
||||
*
|
||||
* @param string $data
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
protected function cleanData($data)
|
||||
protected function cleanCharacters($input)
|
||||
{
|
||||
$replacementChars = array(
|
||||
"\t" => ' ',
|
||||
return strtr(
|
||||
$input,
|
||||
array(
|
||||
"\xe2\x80\x98" => "'", // ‘
|
||||
"\xe2\x80\x99" => "'", // ’
|
||||
"\xe2\x80\x9a" => "'", // ‚
|
||||
|
@ -2504,18 +2528,15 @@ class ICal
|
|||
"\xe2\x80\x93" => '-', // –
|
||||
"\xe2\x80\x94" => '--', // —
|
||||
"\xe2\x80\xa6" => '...', // …
|
||||
"\xc2\xa0" => ' ', // Non-breaking space
|
||||
$this->mb_chr(145) => "'", // ‘
|
||||
$this->mb_chr(146) => "'", // ’
|
||||
$this->mb_chr(147) => '"', // “
|
||||
$this->mb_chr(148) => '"', // ”
|
||||
$this->mb_chr(150) => '-', // –
|
||||
$this->mb_chr(151) => '--', // —
|
||||
$this->mb_chr(133) => '...', // …
|
||||
)
|
||||
);
|
||||
// Replace UTF-8 characters
|
||||
$cleanedData = strtr($data, $replacementChars);
|
||||
|
||||
// Replace Windows-1252 equivalents
|
||||
$charsToReplace = array_map(function ($code) {
|
||||
return $this->mb_chr($code);
|
||||
}, array(133, 145, 146, 147, 148, 150, 151, 194));
|
||||
$cleanedData = $this->mb_str_replace($charsToReplace, $replacementChars, $cleanedData);
|
||||
|
||||
return $cleanedData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2534,7 +2555,7 @@ class ICal
|
|||
}
|
||||
|
||||
$output = array();
|
||||
$currentTimeZone = new \DateTimeZone($this->defaultTimeZone);
|
||||
$currentTimeZone = new \DateTimeZone($this->getDefaultTimeZone());
|
||||
|
||||
foreach ($exdates as $subArray) {
|
||||
end($subArray);
|
||||
|
@ -2554,7 +2575,7 @@ class ICal
|
|||
|
||||
if ($key === $finalKey) {
|
||||
// Reset to default
|
||||
$currentTimeZone = new \DateTimeZone($this->defaultTimeZone);
|
||||
$currentTimeZone = new \DateTimeZone($this->getDefaultTimeZone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2609,8 +2630,8 @@ class ICal
|
|||
$options['http'] = array();
|
||||
$options['http']['header'] = array();
|
||||
|
||||
if (!empty($this->httpBasicAuth) || !empty($this->httpUserAgent) || !empty($this->httpAcceptLanguage)) {
|
||||
if (!empty($this->httpBasicAuth)) {
|
||||
if ($this->httpBasicAuth === array() || !empty($this->httpUserAgent) || !empty($this->httpAcceptLanguage)) {
|
||||
if ($this->httpBasicAuth !== array()) {
|
||||
$username = $this->httpBasicAuth['username'];
|
||||
$password = $this->httpBasicAuth['password'];
|
||||
$basicAuth = base64_encode("{$username}:{$password}");
|
||||
|
@ -2678,6 +2699,6 @@ class ICal
|
|||
return new \DateTimeZone(self::$windowsTimeZonesMap[$timeZoneString]);
|
||||
}
|
||||
|
||||
return new \DateTimeZone($this->defaultTimeZone);
|
||||
return new \DateTimeZone($this->getDefaultTimeZone());
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue