Update libs

This commit is contained in:
Patrick Schwarz 2024-01-12 00:49:45 +01:00
parent 3d1ca79209
commit b95de25ee8
2 changed files with 230 additions and 204 deletions

View file

@ -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);
}
}

View file

@ -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(
'&nbsp;',
"\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('&nbsp;', ' ', $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());
}
}