Update ICal
This commit is contained in:
parent
3a59408fc5
commit
4d021eb32d
2 changed files with 240 additions and 153 deletions
|
@ -11,122 +11,129 @@ class Event
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/summary.html
|
* https://www.kanzaki.com/docs/ical/summary.html
|
||||||
*
|
*
|
||||||
* @var $summary
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $summary;
|
public $summary;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/dtstart.html
|
* https://www.kanzaki.com/docs/ical/dtstart.html
|
||||||
*
|
*
|
||||||
* @var $dtstart
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $dtstart;
|
public $dtstart;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/dtend.html
|
* https://www.kanzaki.com/docs/ical/dtend.html
|
||||||
*
|
*
|
||||||
* @var $dtend
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $dtend;
|
public $dtend;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/duration.html
|
* https://www.kanzaki.com/docs/ical/duration.html
|
||||||
*
|
*
|
||||||
* @var $duration
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $duration;
|
public $duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/dtstamp.html
|
* https://www.kanzaki.com/docs/ical/dtstamp.html
|
||||||
*
|
*
|
||||||
* @var $dtstamp
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $dtstamp;
|
public $dtstamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the event starts, represented as a timezone-adjusted string
|
* When the event starts, represented as a timezone-adjusted string
|
||||||
*
|
*
|
||||||
* @var $dtstart_tz
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $dtstart_tz;
|
public $dtstart_tz;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the event ends, represented as a timezone-adjusted string
|
* When the event ends, represented as a timezone-adjusted string
|
||||||
*
|
*
|
||||||
* @var $dtend_tz
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $dtend_tz;
|
public $dtend_tz;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/uid.html
|
* https://www.kanzaki.com/docs/ical/uid.html
|
||||||
*
|
*
|
||||||
* @var $uid
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $uid;
|
public $uid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/created.html
|
* https://www.kanzaki.com/docs/ical/created.html
|
||||||
*
|
*
|
||||||
* @var $created
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $created;
|
public $created;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/lastModified.html
|
* https://www.kanzaki.com/docs/ical/lastModified.html
|
||||||
*
|
*
|
||||||
* @var $lastmodified
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $lastmodified;
|
public $last_modified;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/description.html
|
* https://www.kanzaki.com/docs/ical/description.html
|
||||||
*
|
*
|
||||||
* @var $description
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $description;
|
public $description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/location.html
|
* https://www.kanzaki.com/docs/ical/location.html
|
||||||
*
|
*
|
||||||
* @var $location
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $location;
|
public $location;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/sequence.html
|
* https://www.kanzaki.com/docs/ical/sequence.html
|
||||||
*
|
*
|
||||||
* @var $sequence
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $sequence;
|
public $sequence;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/status.html
|
* https://www.kanzaki.com/docs/ical/status.html
|
||||||
*
|
*
|
||||||
* @var $status
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $status;
|
public $status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/transp.html
|
* https://www.kanzaki.com/docs/ical/transp.html
|
||||||
*
|
*
|
||||||
* @var $transp
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $transp;
|
public $transp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/organizer.html
|
* https://www.kanzaki.com/docs/ical/organizer.html
|
||||||
*
|
*
|
||||||
* @var $organizer
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $organizer;
|
public $organizer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.kanzaki.com/docs/ical/attendee.html
|
* https://www.kanzaki.com/docs/ical/attendee.html
|
||||||
*
|
*
|
||||||
* @var $attendee
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $attendee;
|
public $attendee;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage additional properties
|
||||||
|
*
|
||||||
|
* @var array<string, mixed>
|
||||||
|
*/
|
||||||
|
private $additionalProperties = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the Event object
|
* Creates the Event object
|
||||||
*
|
*
|
||||||
|
@ -137,10 +144,40 @@ class Event
|
||||||
{
|
{
|
||||||
foreach ($data as $key => $value) {
|
foreach ($data as $key => $value) {
|
||||||
$variable = self::snakeCase($key);
|
$variable = self::snakeCase($key);
|
||||||
$this->{$variable} = self::prepareData($value);
|
if (property_exists($this, $variable)) {
|
||||||
|
$this->{$variable} = $this->prepareData($value);
|
||||||
|
} else {
|
||||||
|
$this->additionalProperties[$variable] = $this->prepareData($value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic getter method
|
||||||
|
*
|
||||||
|
* @param string $additionalPropertyName
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __get($additionalPropertyName)
|
||||||
|
{
|
||||||
|
if (array_key_exists($additionalPropertyName, $this->additionalProperties)) {
|
||||||
|
return $this->additionalProperties[$additionalPropertyName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic isset method
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function __isset($name)
|
||||||
|
{
|
||||||
|
return is_null($this->$name) === false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the data for output
|
* Prepares the data for output
|
||||||
*
|
*
|
||||||
|
@ -151,8 +188,12 @@ class Event
|
||||||
{
|
{
|
||||||
if (is_string($value)) {
|
if (is_string($value)) {
|
||||||
return stripslashes(trim(str_replace('\n', "\n", $value)));
|
return stripslashes(trim(str_replace('\n', "\n", $value)));
|
||||||
} elseif (is_array($value)) {
|
}
|
||||||
return array_map('self::prepareData', $value);
|
|
||||||
|
if (is_array($value)) {
|
||||||
|
return array_map(function ($value) {
|
||||||
|
return $this->prepareData($value);
|
||||||
|
}, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
|
@ -177,7 +218,7 @@ class Event
|
||||||
'DTSTAMP' => $this->dtstamp,
|
'DTSTAMP' => $this->dtstamp,
|
||||||
'UID' => $this->uid,
|
'UID' => $this->uid,
|
||||||
'CREATED' => $this->created,
|
'CREATED' => $this->created,
|
||||||
'LAST-MODIFIED' => $this->lastmodified,
|
'LAST-MODIFIED' => $this->last_modified,
|
||||||
'DESCRIPTION' => $this->description,
|
'DESCRIPTION' => $this->description,
|
||||||
'LOCATION' => $this->location,
|
'LOCATION' => $this->location,
|
||||||
'SEQUENCE' => $this->sequence,
|
'SEQUENCE' => $this->sequence,
|
||||||
|
|
308
ICal/ICal.php
308
ICal/ICal.php
|
@ -4,11 +4,11 @@
|
||||||
* This PHP class will read an ICS (`.ics`, `.ical`, `.ifb`) file, parse it and return an
|
* This PHP class will read an ICS (`.ics`, `.ical`, `.ifb`) file, parse it and return an
|
||||||
* array of its contents.
|
* array of its contents.
|
||||||
*
|
*
|
||||||
* PHP 5 (≥ 5.3.9)
|
* PHP 5 (≥ 5.6.40)
|
||||||
*
|
*
|
||||||
* @author Jonathan Goode <https://github.com/u01jmg3>
|
* @author Jonathan Goode <https://github.com/u01jmg3>
|
||||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||||
* @version 2.2.2
|
* @version 3.2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace ICal;
|
namespace ICal;
|
||||||
|
@ -66,7 +66,7 @@ class ICal
|
||||||
/**
|
/**
|
||||||
* Enables customisation of the default time zone
|
* Enables customisation of the default time zone
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string|null
|
||||||
*/
|
*/
|
||||||
public $defaultTimeZone;
|
public $defaultTimeZone;
|
||||||
|
|
||||||
|
@ -94,14 +94,14 @@ class ICal
|
||||||
/**
|
/**
|
||||||
* With this being non-null the parser will ignore all events more than roughly this many days after now.
|
* With this being non-null the parser will ignore all events more than roughly this many days after now.
|
||||||
*
|
*
|
||||||
* @var integer
|
* @var integer|null
|
||||||
*/
|
*/
|
||||||
public $filterDaysBefore;
|
public $filterDaysBefore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* With this being non-null the parser will ignore all events more than roughly this many days before now.
|
* With this being non-null the parser will ignore all events more than roughly this many days before now.
|
||||||
*
|
*
|
||||||
* @var integer
|
* @var integer|null
|
||||||
*/
|
*/
|
||||||
public $filterDaysAfter;
|
public $filterDaysAfter;
|
||||||
|
|
||||||
|
@ -190,6 +190,13 @@ class ICal
|
||||||
*/
|
*/
|
||||||
protected $httpAcceptLanguage;
|
protected $httpAcceptLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the custom HTTP Protocol version
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $httpProtocolVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define which variables can be configured
|
* Define which variables can be configured
|
||||||
*
|
*
|
||||||
|
@ -202,6 +209,7 @@ class ICal
|
||||||
'disableCharacterReplacement',
|
'disableCharacterReplacement',
|
||||||
'filterDaysAfter',
|
'filterDaysAfter',
|
||||||
'filterDaysBefore',
|
'filterDaysBefore',
|
||||||
|
'httpUserAgent',
|
||||||
'skipRecurrence',
|
'skipRecurrence',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -497,7 +505,9 @@ class ICal
|
||||||
*/
|
*/
|
||||||
public function __construct($files = false, array $options = array())
|
public function __construct($files = false, array $options = array())
|
||||||
{
|
{
|
||||||
ini_set('auto_detect_line_endings', '1');
|
if (\PHP_VERSION_ID < 80100) {
|
||||||
|
ini_set('auto_detect_line_endings', '1');
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($options as $option => $value) {
|
foreach ($options as $option => $value) {
|
||||||
if (in_array($option, self::$configurableOptions)) {
|
if (in_array($option, self::$configurableOptions)) {
|
||||||
|
@ -581,9 +591,10 @@ class ICal
|
||||||
* @param string $password
|
* @param string $password
|
||||||
* @param string $userAgent
|
* @param string $userAgent
|
||||||
* @param string $acceptLanguage
|
* @param string $acceptLanguage
|
||||||
|
* @param string $httpProtocolVersion
|
||||||
* @return ICal
|
* @return ICal
|
||||||
*/
|
*/
|
||||||
public function initUrl($url, $username = null, $password = null, $userAgent = null, $acceptLanguage = null)
|
public function initUrl($url, $username = null, $password = null, $userAgent = null, $acceptLanguage = null, $httpProtocolVersion = null)
|
||||||
{
|
{
|
||||||
if (!is_null($username) && !is_null($password)) {
|
if (!is_null($username) && !is_null($password)) {
|
||||||
$this->httpBasicAuth['username'] = $username;
|
$this->httpBasicAuth['username'] = $username;
|
||||||
|
@ -598,6 +609,10 @@ class ICal
|
||||||
$this->httpAcceptLanguage = $acceptLanguage;
|
$this->httpAcceptLanguage = $acceptLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!is_null($httpProtocolVersion)) {
|
||||||
|
$this->httpProtocolVersion = $httpProtocolVersion;
|
||||||
|
}
|
||||||
|
|
||||||
$this->initFile($url);
|
$this->initFile($url);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -853,6 +868,7 @@ class ICal
|
||||||
protected function unfold(array $lines)
|
protected function unfold(array $lines)
|
||||||
{
|
{
|
||||||
$string = implode(PHP_EOL, $lines);
|
$string = implode(PHP_EOL, $lines);
|
||||||
|
$string = str_ireplace(' ', ' ', $string);
|
||||||
$string = preg_replace('/' . PHP_EOL . '[ \t]/', '', $string);
|
$string = preg_replace('/' . PHP_EOL . '[ \t]/', '', $string);
|
||||||
|
|
||||||
$lines = explode(PHP_EOL, $string);
|
$lines = explode(PHP_EOL, $string);
|
||||||
|
@ -865,12 +881,12 @@ class ICal
|
||||||
*
|
*
|
||||||
* @param string $component
|
* @param string $component
|
||||||
* @param string|boolean $keyword
|
* @param string|boolean $keyword
|
||||||
* @param string $value
|
* @param string|array $value
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function addCalendarComponentWithKeyAndValue($component, $keyword, $value)
|
protected function addCalendarComponentWithKeyAndValue($component, $keyword, $value)
|
||||||
{
|
{
|
||||||
if ($keyword == false) {
|
if ($keyword === false) {
|
||||||
$keyword = $this->lastKeyword;
|
$keyword = $this->lastKeyword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -896,6 +912,7 @@ class ICal
|
||||||
$this->cal[$key1][$key2][$key3][$keyword] .= ',' . $value;
|
$this->cal[$key1][$key2][$key3][$keyword] .= ',' . $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'VEVENT':
|
case 'VEVENT':
|
||||||
|
@ -935,6 +952,7 @@ class ICal
|
||||||
$this->cal[$key1][$key2][$keyword] .= ',' . $value;
|
$this->cal[$key1][$key2][$keyword] .= ',' . $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'VFREEBUSY':
|
case 'VFREEBUSY':
|
||||||
|
@ -957,6 +975,7 @@ class ICal
|
||||||
} else {
|
} else {
|
||||||
$this->cal[$key1][$key2][$key3][] = $value;
|
$this->cal[$key1][$key2][$key3][] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'VTODO':
|
case 'VTODO':
|
||||||
|
@ -979,95 +998,109 @@ class ICal
|
||||||
* @param string $text
|
* @param string $text
|
||||||
* @return array|boolean
|
* @return array|boolean
|
||||||
*/
|
*/
|
||||||
protected function keyValueFromString($text)
|
public function keyValueFromString($text)
|
||||||
{
|
{
|
||||||
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
|
$splitLine = $this->parseLine($text);
|
||||||
|
$object = array();
|
||||||
|
$paramObj = array();
|
||||||
|
$valueObj = '';
|
||||||
|
$i = 0;
|
||||||
|
|
||||||
$colon = strpos($text, ':');
|
while ($i < count($splitLine)) {
|
||||||
$quote = strpos($text, '"');
|
// The first token corresponds to the property name
|
||||||
if ($colon === false) {
|
if ($i === 0) {
|
||||||
$matches = array();
|
$object[0] = $splitLine[$i];
|
||||||
} elseif ($quote === false || $colon < $quote) {
|
$i++;
|
||||||
list($before, $after) = explode(':', $text, 2);
|
continue;
|
||||||
$matches = array($text, $before, $after);
|
}
|
||||||
} else {
|
|
||||||
list($before, $text) = explode('"', $text, 2);
|
|
||||||
$text = '"' . $text;
|
|
||||||
$matches = str_getcsv($text, ':');
|
|
||||||
$combinedValue = '';
|
|
||||||
|
|
||||||
foreach (array_keys($matches) as $key) {
|
// After each semicolon define the property parameters
|
||||||
if ($key === 0) {
|
if ($splitLine[$i] == ';') {
|
||||||
if (!empty($before)) {
|
$i++;
|
||||||
$matches[$key] = $before . '"' . $matches[$key] . '"';
|
$paramName = $splitLine[$i];
|
||||||
}
|
$i += 2;
|
||||||
|
$paramValue = array();
|
||||||
|
$multiValue = false;
|
||||||
|
// A parameter can have multiple values separated by a comma
|
||||||
|
while ($i + 1 < count($splitLine) && $splitLine[$i + 1] === ',') {
|
||||||
|
$paramValue[] = $splitLine[$i];
|
||||||
|
$i += 2;
|
||||||
|
$multiValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($multiValue) {
|
||||||
|
$paramValue[] = $splitLine[$i];
|
||||||
} else {
|
} else {
|
||||||
if ($key > 1) {
|
$paramValue = $splitLine[$i];
|
||||||
$combinedValue .= ':';
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$combinedValue .= $matches[$key];
|
// Create object with paramName => paramValue
|
||||||
|
$paramObj[$paramName] = $paramValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After a colon all tokens are concatenated (non-standard behaviour because the property can have multiple values
|
||||||
|
// according to RFC5545)
|
||||||
|
if ($splitLine[$i] === ':') {
|
||||||
|
$i++;
|
||||||
|
while ($i < count($splitLine)) {
|
||||||
|
$valueObj .= $splitLine[$i];
|
||||||
|
$i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$matches = array_slice($matches, 0, 2);
|
$i++;
|
||||||
$matches[1] = $combinedValue;
|
|
||||||
array_unshift($matches, $before . $text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($matches) === 0) {
|
// Object construction
|
||||||
return false;
|
if ($paramObj !== []) {
|
||||||
}
|
$object[1][0] = $valueObj;
|
||||||
|
$object[1][1] = $paramObj;
|
||||||
if (preg_match('/^([A-Z-]+)([;][\w\W]*)?$/', $matches[1])) {
|
|
||||||
$matches = array_splice($matches, 1, 2); // Remove first match and re-align ordering
|
|
||||||
|
|
||||||
// Process properties
|
|
||||||
if (preg_match('/([A-Z-]+)[;]([\w\W]*)/', $matches[0], $properties)) {
|
|
||||||
// Remove first match
|
|
||||||
array_shift($properties);
|
|
||||||
// Fix to ignore everything in keyword after a ; (e.g. Language, TZID, etc.)
|
|
||||||
$matches[0] = $properties[0];
|
|
||||||
array_shift($properties); // Repeat removing first match
|
|
||||||
|
|
||||||
$formatted = array();
|
|
||||||
foreach ($properties as $property) {
|
|
||||||
// Match semicolon separator outside of quoted substrings
|
|
||||||
preg_match_all('~[^' . PHP_EOL . '";]+(?:"[^"\\\]*(?:\\\.[^"\\\]*)*"[^' . PHP_EOL . '";]*)*~', $property, $attributes);
|
|
||||||
// Remove multi-dimensional array and use the first key
|
|
||||||
$attributes = (count($attributes) === 0) ? array($property) : reset($attributes);
|
|
||||||
|
|
||||||
if (is_array($attributes)) {
|
|
||||||
foreach ($attributes as $attribute) {
|
|
||||||
// Match equals sign separator outside of quoted substrings
|
|
||||||
preg_match_all(
|
|
||||||
'~[^' . PHP_EOL . '"=]+(?:"[^"\\\]*(?:\\\.[^"\\\]*)*"[^' . PHP_EOL . '"=]*)*~',
|
|
||||||
$attribute,
|
|
||||||
$values
|
|
||||||
);
|
|
||||||
// Remove multi-dimensional array and use the first key
|
|
||||||
$value = (count($values) === 0) ? null : reset($values);
|
|
||||||
|
|
||||||
if (is_array($value) && isset($value[1])) {
|
|
||||||
// Remove double quotes from beginning and end only
|
|
||||||
$formatted[$value[0]] = trim($value[1], '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign the keyword property information
|
|
||||||
$properties[0] = $formatted;
|
|
||||||
|
|
||||||
// Add match to beginning of array
|
|
||||||
array_unshift($properties, $matches[1]);
|
|
||||||
$matches[1] = $properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $matches;
|
|
||||||
} else {
|
} else {
|
||||||
return false; // Ignore this match
|
$object[1] = $valueObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a line from an iCal file into an array of tokens
|
||||||
|
*
|
||||||
|
* @param string $line
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function parseLine($line)
|
||||||
|
{
|
||||||
|
$words = array();
|
||||||
|
$word = '';
|
||||||
|
// The use of str_split is not a problem here even if the character set is in utf8
|
||||||
|
// Indeed we only compare the characters , ; : = " which are on a single byte
|
||||||
|
$arrayOfChar = str_split($line);
|
||||||
|
$inDoubleQuotes = false;
|
||||||
|
|
||||||
|
foreach ($arrayOfChar as $char) {
|
||||||
|
// Don't stop the word on ; , : = if it is enclosed in double quotes
|
||||||
|
if ($char === '"') {
|
||||||
|
if ($word !== '') {
|
||||||
|
$words[] = $word;
|
||||||
|
}
|
||||||
|
|
||||||
|
$word = '';
|
||||||
|
$inDoubleQuotes = !$inDoubleQuotes;
|
||||||
|
} elseif (!in_array($char, array(';', ':', ',', '=')) || $inDoubleQuotes) {
|
||||||
|
$word .= $char;
|
||||||
|
} else {
|
||||||
|
if ($word !== '') {
|
||||||
|
$words[] = $word;
|
||||||
|
}
|
||||||
|
|
||||||
|
$words[] = $char;
|
||||||
|
$word = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$words[] = $word;
|
||||||
|
|
||||||
|
return $words;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1141,10 +1174,10 @@ class ICal
|
||||||
/**
|
/**
|
||||||
* Returns a date adapted to the calendar time zone depending on the event `TZID`
|
* Returns a date adapted to the calendar time zone depending on the event `TZID`
|
||||||
*
|
*
|
||||||
* @param array $event
|
* @param array $event
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @param string $format
|
* @param string|null $format
|
||||||
* @return string|boolean
|
* @return string|boolean|\DateTime
|
||||||
*/
|
*/
|
||||||
public function iCalDateWithTimeZone(array $event, $key, $format = self::DATE_TIME_FORMAT)
|
public function iCalDateWithTimeZone(array $event, $key, $format = self::DATE_TIME_FORMAT)
|
||||||
{
|
{
|
||||||
|
@ -1224,7 +1257,7 @@ class ICal
|
||||||
$eventDtstartUnix = $this->iCalDateToUnixTimestamp($event['DTSTART_array'][3]);
|
$eventDtstartUnix = $this->iCalDateToUnixTimestamp($event['DTSTART_array'][3]);
|
||||||
|
|
||||||
// phpcs:ignore CustomPHPCS.ControlStructures.AssignmentInCondition
|
// phpcs:ignore CustomPHPCS.ControlStructures.AssignmentInCondition
|
||||||
if (($alteredEventKey = array_search($eventDtstartUnix, $this->alteredRecurrenceInstances[$event['UID']])) !== false) {
|
if (($alteredEventKey = array_search($eventDtstartUnix, $this->alteredRecurrenceInstances[$event['UID']], true)) !== false) {
|
||||||
$eventKeysToRemove[] = $alteredEventKey;
|
$eventKeysToRemove[] = $alteredEventKey;
|
||||||
|
|
||||||
$alteredEvent = array_replace_recursive($events[$key], $events[$alteredEventKey]);
|
$alteredEvent = array_replace_recursive($events[$key], $events[$alteredEventKey]);
|
||||||
|
@ -1273,7 +1306,7 @@ class ICal
|
||||||
|
|
||||||
// Separate the RRULE stanzas, and explode the values that are lists.
|
// Separate the RRULE stanzas, and explode the values that are lists.
|
||||||
$rrules = array();
|
$rrules = array();
|
||||||
foreach (explode(';', $anEvent['RRULE']) as $s) {
|
foreach (array_filter(explode(';', $anEvent['RRULE'])) as $s) {
|
||||||
list($k, $v) = explode('=', $s);
|
list($k, $v) = explode('=', $s);
|
||||||
if (in_array($k, array('BYSETPOS', 'BYDAY', 'BYMONTHDAY', 'BYMONTH', 'BYYEARDAY', 'BYWEEKNO'))) {
|
if (in_array($k, array('BYSETPOS', 'BYDAY', 'BYMONTHDAY', 'BYMONTH', 'BYYEARDAY', 'BYWEEKNO'))) {
|
||||||
$rrules[$k] = explode(',', $v);
|
$rrules[$k] = explode(',', $v);
|
||||||
|
@ -1311,7 +1344,7 @@ class ICal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Interval
|
// Get Interval
|
||||||
$interval = (empty($rrules['INTERVAL'])) ? 1 : $rrules['INTERVAL'];
|
$interval = (empty($rrules['INTERVAL'])) ? 1 : (int) $rrules['INTERVAL'];
|
||||||
|
|
||||||
// Throw an error if this isn't an integer.
|
// Throw an error if this isn't an integer.
|
||||||
if (!is_int($this->defaultSpan)) {
|
if (!is_int($this->defaultSpan)) {
|
||||||
|
@ -1323,7 +1356,7 @@ class ICal
|
||||||
|
|
||||||
// Determine if the initial date is also an EXDATE
|
// Determine if the initial date is also an EXDATE
|
||||||
$initialDateIsExdate = array_reduce($exdates, function ($carry, $exdate) use ($initialEventDate) {
|
$initialDateIsExdate = array_reduce($exdates, function ($carry, $exdate) use ($initialEventDate) {
|
||||||
return $carry || $exdate->getTimestamp() == $initialEventDate->getTimestamp();
|
return $carry || $exdate->getTimestamp() === $initialEventDate->getTimestamp();
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
if ($initialDateIsExdate) {
|
if ($initialDateIsExdate) {
|
||||||
|
@ -1346,7 +1379,7 @@ class ICal
|
||||||
* enddate = <icalDate> || <icalDateTime>
|
* enddate = <icalDate> || <icalDateTime>
|
||||||
*/
|
*/
|
||||||
$count = 1;
|
$count = 1;
|
||||||
$countLimit = (isset($rrules['COUNT'])) ? intval($rrules['COUNT']) : 0;
|
$countLimit = (isset($rrules['COUNT'])) ? intval($rrules['COUNT']) : PHP_INT_MAX;
|
||||||
$until = date_create()->modify("{$this->defaultSpan} years")->setTime(23, 59, 59)->getTimestamp();
|
$until = date_create()->modify("{$this->defaultSpan} years")->setTime(23, 59, 59)->getTimestamp();
|
||||||
|
|
||||||
if (isset($rrules['UNTIL'])) {
|
if (isset($rrules['UNTIL'])) {
|
||||||
|
@ -1356,7 +1389,7 @@ class ICal
|
||||||
$eventRecurrences = array();
|
$eventRecurrences = array();
|
||||||
|
|
||||||
$frequencyRecurringDateTime = clone $initialEventDate;
|
$frequencyRecurringDateTime = clone $initialEventDate;
|
||||||
while ($frequencyRecurringDateTime->getTimestamp() <= $until) {
|
while ($frequencyRecurringDateTime->getTimestamp() <= $until && $count < $countLimit) {
|
||||||
$candidateDateTimes = array();
|
$candidateDateTimes = array();
|
||||||
|
|
||||||
// phpcs:ignore Squiz.ControlStructures.SwitchDeclaration.MissingDefault
|
// phpcs:ignore Squiz.ControlStructures.SwitchDeclaration.MissingDefault
|
||||||
|
@ -1389,15 +1422,15 @@ class ICal
|
||||||
|
|
||||||
if (empty($rrules['WKST'])) {
|
if (empty($rrules['WKST'])) {
|
||||||
if ($this->defaultWeekStart !== self::ISO_8601_WEEK_START) {
|
if ($this->defaultWeekStart !== self::ISO_8601_WEEK_START) {
|
||||||
$wkstTransition = array_search($this->defaultWeekStart, array_keys($this->weekdays));
|
$wkstTransition = array_search($this->defaultWeekStart, array_keys($this->weekdays), true);
|
||||||
}
|
}
|
||||||
} elseif ($rrules['WKST'] !== self::ISO_8601_WEEK_START) {
|
} elseif ($rrules['WKST'] !== self::ISO_8601_WEEK_START) {
|
||||||
$wkstTransition = array_search($rrules['WKST'], array_keys($this->weekdays));
|
$wkstTransition = array_search($rrules['WKST'], array_keys($this->weekdays), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$matchingDays = array_map(
|
$matchingDays = array_map(
|
||||||
function ($weekday) use ($initialDayOfWeek, $wkstTransition, $interval) {
|
function ($weekday) use ($initialDayOfWeek, $wkstTransition, $interval) {
|
||||||
$day = array_search($weekday, array_keys($this->weekdays));
|
$day = array_search($weekday, array_keys($this->weekdays), true);
|
||||||
|
|
||||||
if ($day < $initialDayOfWeek) {
|
if ($day < $initialDayOfWeek) {
|
||||||
$day += 7;
|
$day += 7;
|
||||||
|
@ -1424,11 +1457,12 @@ class ICal
|
||||||
foreach ($matchingDays as $day) {
|
foreach ($matchingDays as $day) {
|
||||||
$clonedDateTime = clone $frequencyRecurringDateTime;
|
$clonedDateTime = clone $frequencyRecurringDateTime;
|
||||||
$candidateDateTimes[] = $clonedDateTime->setISODate(
|
$candidateDateTimes[] = $clonedDateTime->setISODate(
|
||||||
$frequencyRecurringDateTime->format('o'),
|
(int) $frequencyRecurringDateTime->format('o'),
|
||||||
$frequencyRecurringDateTime->format('W'),
|
(int) $frequencyRecurringDateTime->format('W'),
|
||||||
$day
|
$day
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'MONTHLY':
|
case 'MONTHLY':
|
||||||
|
@ -1446,6 +1480,8 @@ class ICal
|
||||||
}
|
}
|
||||||
} elseif (!empty($rrules['BYDAY'])) {
|
} elseif (!empty($rrules['BYDAY'])) {
|
||||||
$matchingDays = $this->getDaysOfMonthMatchingByDayRRule($rrules['BYDAY'], $frequencyRecurringDateTime);
|
$matchingDays = $this->getDaysOfMonthMatchingByDayRRule($rrules['BYDAY'], $frequencyRecurringDateTime);
|
||||||
|
} else {
|
||||||
|
$matchingDays[] = $frequencyRecurringDateTime->format('d');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($rrules['BYSETPOS'])) {
|
if (!empty($rrules['BYSETPOS'])) {
|
||||||
|
@ -1460,11 +1496,12 @@ class ICal
|
||||||
|
|
||||||
$clonedDateTime = clone $frequencyRecurringDateTime;
|
$clonedDateTime = clone $frequencyRecurringDateTime;
|
||||||
$candidateDateTimes[] = $clonedDateTime->setDate(
|
$candidateDateTimes[] = $clonedDateTime->setDate(
|
||||||
$frequencyRecurringDateTime->format('Y'),
|
(int) $frequencyRecurringDateTime->format('Y'),
|
||||||
$frequencyRecurringDateTime->format('m'),
|
(int) $frequencyRecurringDateTime->format('m'),
|
||||||
$day
|
$day
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'YEARLY':
|
case 'YEARLY':
|
||||||
|
@ -1474,9 +1511,9 @@ class ICal
|
||||||
$bymonthRecurringDatetime = clone $frequencyRecurringDateTime;
|
$bymonthRecurringDatetime = clone $frequencyRecurringDateTime;
|
||||||
foreach ($rrules['BYMONTH'] as $byMonth) {
|
foreach ($rrules['BYMONTH'] as $byMonth) {
|
||||||
$bymonthRecurringDatetime->setDate(
|
$bymonthRecurringDatetime->setDate(
|
||||||
$frequencyRecurringDateTime->format('Y'),
|
(int) $frequencyRecurringDateTime->format('Y'),
|
||||||
$byMonth,
|
$byMonth,
|
||||||
$frequencyRecurringDateTime->format('d')
|
(int) $frequencyRecurringDateTime->format('d')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Determine the days of the month affected
|
// Determine the days of the month affected
|
||||||
|
@ -1493,8 +1530,8 @@ class ICal
|
||||||
// And add each of them to the list of recurrences
|
// And add each of them to the list of recurrences
|
||||||
foreach ($monthDays as $day) {
|
foreach ($monthDays as $day) {
|
||||||
$matchingDays[] = $bymonthRecurringDatetime->setDate(
|
$matchingDays[] = $bymonthRecurringDatetime->setDate(
|
||||||
$frequencyRecurringDateTime->format('Y'),
|
(int) $frequencyRecurringDateTime->format('Y'),
|
||||||
$bymonthRecurringDatetime->format('m'),
|
(int) $bymonthRecurringDatetime->format('m'),
|
||||||
$day
|
$day
|
||||||
)->format('z') + 1;
|
)->format('z') + 1;
|
||||||
}
|
}
|
||||||
|
@ -1515,12 +1552,12 @@ class ICal
|
||||||
return in_array($yearDay, $matchingDays);
|
return in_array($yearDay, $matchingDays);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} elseif (count($matchingDays) === 0) {
|
} elseif ($matchingDays === []) {
|
||||||
$matchingDays = $this->getDaysOfYearMatchingByDayRRule($rrules['BYDAY'], $frequencyRecurringDateTime);
|
$matchingDays = $this->getDaysOfYearMatchingByDayRRule($rrules['BYDAY'], $frequencyRecurringDateTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($matchingDays) === 0) {
|
if ($matchingDays === []) {
|
||||||
$matchingDays = array($frequencyRecurringDateTime->format('z') + 1);
|
$matchingDays = array($frequencyRecurringDateTime->format('z') + 1);
|
||||||
} else {
|
} else {
|
||||||
sort($matchingDays);
|
sort($matchingDays);
|
||||||
|
@ -1533,11 +1570,12 @@ class ICal
|
||||||
foreach ($matchingDays as $day) {
|
foreach ($matchingDays as $day) {
|
||||||
$clonedDateTime = clone $frequencyRecurringDateTime;
|
$clonedDateTime = clone $frequencyRecurringDateTime;
|
||||||
$candidateDateTimes[] = $clonedDateTime->setDate(
|
$candidateDateTimes[] = $clonedDateTime->setDate(
|
||||||
$frequencyRecurringDateTime->format('Y'),
|
(int) $frequencyRecurringDateTime->format('Y'),
|
||||||
1,
|
1,
|
||||||
$day
|
$day
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1553,7 +1591,7 @@ class ICal
|
||||||
|
|
||||||
// Exclusions
|
// Exclusions
|
||||||
$isExcluded = array_filter($exdates, function ($exdate) use ($timestamp) {
|
$isExcluded = array_filter($exdates, function ($exdate) use ($timestamp) {
|
||||||
return $exdate->getTimestamp() == $timestamp;
|
return $exdate->getTimestamp() === $timestamp;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isset($this->alteredRecurrenceInstances[$anEvent['UID']])) {
|
if (isset($this->alteredRecurrenceInstances[$anEvent['UID']])) {
|
||||||
|
@ -1567,14 +1605,11 @@ class ICal
|
||||||
$this->eventCount++;
|
$this->eventCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count all evaluated candidates including excluded ones
|
// Count all evaluated candidates including excluded ones,
|
||||||
if (isset($rrules['COUNT'])) {
|
// and if RRULE[COUNT] (if set) is reached then break.
|
||||||
$count++;
|
$count++;
|
||||||
|
if ($count >= $countLimit) {
|
||||||
// If RRULE[COUNT] is reached then break
|
break 2;
|
||||||
if ($count >= $countLimit) {
|
|
||||||
break 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1771,7 +1806,7 @@ class ICal
|
||||||
*/
|
*/
|
||||||
protected function getDaysOfMonthMatchingByMonthDayRRule(array $byMonthDays, $initialDateTime)
|
protected function getDaysOfMonthMatchingByMonthDayRRule(array $byMonthDays, $initialDateTime)
|
||||||
{
|
{
|
||||||
return $this->resolveIndicesOfRange($byMonthDays, $initialDateTime->format('t'));
|
return $this->resolveIndicesOfRange($byMonthDays, (int) $initialDateTime->format('t'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1897,7 +1932,7 @@ class ICal
|
||||||
$byweekDateTime = clone $initialDateTime;
|
$byweekDateTime = clone $initialDateTime;
|
||||||
foreach ($matchingWeeks as $weekNum) {
|
foreach ($matchingWeeks as $weekNum) {
|
||||||
$dayNum = $byweekDateTime->setISODate(
|
$dayNum = $byweekDateTime->setISODate(
|
||||||
$initialDateTime->format('Y'),
|
(int) $initialDateTime->format('Y'),
|
||||||
$weekNum,
|
$weekNum,
|
||||||
1
|
1
|
||||||
)->format('z') + 1;
|
)->format('z') + 1;
|
||||||
|
@ -1932,7 +1967,7 @@ class ICal
|
||||||
$monthDateTime = clone $initialDateTime;
|
$monthDateTime = clone $initialDateTime;
|
||||||
for ($month = 1; $month < 13; $month++) {
|
for ($month = 1; $month < 13; $month++) {
|
||||||
$monthDateTime->setDate(
|
$monthDateTime->setDate(
|
||||||
$initialDateTime->format('Y'),
|
(int) $initialDateTime->format('Y'),
|
||||||
$month,
|
$month,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
@ -1940,8 +1975,8 @@ class ICal
|
||||||
$monthDays = $this->getDaysOfMonthMatchingByMonthDayRRule($byMonthDays, $monthDateTime);
|
$monthDays = $this->getDaysOfMonthMatchingByMonthDayRRule($byMonthDays, $monthDateTime);
|
||||||
foreach ($monthDays as $day) {
|
foreach ($monthDays as $day) {
|
||||||
$matchingDays[] = $monthDateTime->setDate(
|
$matchingDays[] = $monthDateTime->setDate(
|
||||||
$initialDateTime->format('Y'),
|
(int) $initialDateTime->format('Y'),
|
||||||
$monthDateTime->format('m'),
|
(int) $monthDateTime->format('m'),
|
||||||
$day
|
$day
|
||||||
)->format('z') + 1;
|
)->format('z') + 1;
|
||||||
}
|
}
|
||||||
|
@ -2075,7 +2110,7 @@ class ICal
|
||||||
* Returns the calendar time zone
|
* Returns the calendar time zone
|
||||||
*
|
*
|
||||||
* @param boolean $ignoreUtc
|
* @param boolean $ignoreUtc
|
||||||
* @return string
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
public function calendarTimeZone($ignoreUtc = false)
|
public function calendarTimeZone($ignoreUtc = false)
|
||||||
{
|
{
|
||||||
|
@ -2119,7 +2154,7 @@ class ICal
|
||||||
*/
|
*/
|
||||||
public function hasEvents()
|
public function hasEvents()
|
||||||
{
|
{
|
||||||
return (count($this->events()) > 0) ?: false;
|
return ($this->events() !== []) ?: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2321,9 +2356,9 @@ class ICal
|
||||||
/**
|
/**
|
||||||
* Parses a duration and applies it to a date
|
* Parses a duration and applies it to a date
|
||||||
*
|
*
|
||||||
* @param string $date
|
* @param string $date
|
||||||
* @param string $duration
|
* @param \DateInterval $duration
|
||||||
* @param string $format
|
* @param string|null $format
|
||||||
* @return integer|\DateTime
|
* @return integer|\DateTime
|
||||||
*/
|
*/
|
||||||
protected function parseDuration($date, $duration, $format = self::UNIX_FORMAT)
|
protected function parseDuration($date, $duration, $format = self::UNIX_FORMAT)
|
||||||
|
@ -2457,6 +2492,7 @@ class ICal
|
||||||
protected function cleanData($data)
|
protected function cleanData($data)
|
||||||
{
|
{
|
||||||
$replacementChars = array(
|
$replacementChars = array(
|
||||||
|
"\t" => ' ',
|
||||||
"\xe2\x80\x98" => "'", // ‘
|
"\xe2\x80\x98" => "'", // ‘
|
||||||
"\xe2\x80\x99" => "'", // ’
|
"\xe2\x80\x99" => "'", // ’
|
||||||
"\xe2\x80\x9a" => "'", // ‚
|
"\xe2\x80\x9a" => "'", // ‚
|
||||||
|
@ -2468,7 +2504,7 @@ class ICal
|
||||||
"\xe2\x80\x93" => '-', // –
|
"\xe2\x80\x93" => '-', // –
|
||||||
"\xe2\x80\x94" => '--', // —
|
"\xe2\x80\x94" => '--', // —
|
||||||
"\xe2\x80\xa6" => '...', // …
|
"\xe2\x80\xa6" => '...', // …
|
||||||
"\xc2\xa0" => ' ',
|
"\xc2\xa0" => ' ', // Non-breaking space
|
||||||
);
|
);
|
||||||
// Replace UTF-8 characters
|
// Replace UTF-8 characters
|
||||||
$cleanedData = strtr($data, $replacementChars);
|
$cleanedData = strtr($data, $replacementChars);
|
||||||
|
@ -2591,7 +2627,17 @@ class ICal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$options['http']['protocol_version'] = '1.1';
|
if (empty($this->httpUserAgent)) {
|
||||||
|
if (mb_stripos($filename, 'outlook.office365.com') !== false) {
|
||||||
|
$options['http']['header'][] = 'User-Agent: A User Agent';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->httpProtocolVersion)) {
|
||||||
|
$options['http']['protocol_version'] = $this->httpProtocolVersion;
|
||||||
|
} else {
|
||||||
|
$options['http']['protocol_version'] = '1.1';
|
||||||
|
}
|
||||||
|
|
||||||
$options['http']['header'][] = 'Connection: close';
|
$options['http']['header'][] = 'Connection: close';
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue