mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-10-24 22:25:09 +03:00
Sabre VObject v5.0.0
This commit is contained in:
parent
aad651f2a1
commit
e66f325aa3
54 changed files with 598 additions and 644 deletions
|
|
@ -95,7 +95,7 @@ class BirthdayCalendarGenerator
|
|||
// We've seen clients (ez-vcard) putting "BDAY:" properties
|
||||
// without a value into vCards. If we come across those, we'll
|
||||
// skip them.
|
||||
if (empty($object->BDAY->getValue())) {
|
||||
if ('' === $object->BDAY->getValue()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ class BirthdayCalendarGenerator
|
|||
// Skip if we can't parse the BDAY value.
|
||||
try {
|
||||
$dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
|
||||
} catch (InvalidDataException $e) {
|
||||
} catch (InvalidDataException) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class Cli
|
|||
// @codeCoverageIgnoreEnd
|
||||
|
||||
try {
|
||||
list($options, $positional) = $this->parseArguments($argv);
|
||||
[$options, $positional] = $this->parseArguments($argv);
|
||||
|
||||
if (isset($options['q'])) {
|
||||
$this->quiet = true;
|
||||
|
|
@ -110,27 +110,10 @@ class Cli
|
|||
|
||||
return 0;
|
||||
case 'format':
|
||||
switch ($value) {
|
||||
// jcard/jcal documents
|
||||
case 'jcard':
|
||||
case 'jcal':
|
||||
// specific document versions
|
||||
case 'vcard21':
|
||||
case 'vcard30':
|
||||
case 'vcard40':
|
||||
case 'icalendar20':
|
||||
// specific formats
|
||||
case 'json':
|
||||
case 'mimedir':
|
||||
// icalendar/vcard
|
||||
case 'icalendar':
|
||||
case 'vcard':
|
||||
$this->format = $value;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unknown format: '.$value);
|
||||
}
|
||||
$this->format = match ($value) {
|
||||
'jcard', 'jcal', 'vcard21', 'vcard30', 'vcard40', 'icalendar20', 'json', 'mimedir', 'icalendar', 'vcard' => $value,
|
||||
default => throw new \InvalidArgumentException('Unknown format: '.$value),
|
||||
};
|
||||
break;
|
||||
case 'pretty':
|
||||
$this->pretty = true;
|
||||
|
|
@ -139,28 +122,11 @@ class Cli
|
|||
$this->forgiving = true;
|
||||
break;
|
||||
case 'inputformat':
|
||||
switch ($value) {
|
||||
// json formats
|
||||
case 'jcard':
|
||||
case 'jcal':
|
||||
case 'json':
|
||||
$this->inputFormat = 'json';
|
||||
break;
|
||||
|
||||
// mimedir formats
|
||||
case 'mimedir':
|
||||
case 'icalendar':
|
||||
case 'vcard':
|
||||
case 'vcard21':
|
||||
case 'vcard30':
|
||||
case 'vcard40':
|
||||
case 'icalendar20':
|
||||
$this->inputFormat = 'mimedir';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unknown format: '.$value);
|
||||
}
|
||||
$this->inputFormat = match ($value) {
|
||||
'jcard', 'jcal', 'json' => 'json',
|
||||
'mimedir', 'icalendar', 'vcard', 'vcard21', 'vcard30', 'vcard40', 'icalendar20' => 'mimedir',
|
||||
default => throw new \InvalidArgumentException('Unknown format: '.$value),
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unknown option: '.$name);
|
||||
|
|
@ -181,7 +147,7 @@ class Cli
|
|||
throw new \InvalidArgumentException('Too many arguments');
|
||||
}
|
||||
|
||||
if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) {
|
||||
if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'], true)) {
|
||||
throw new \InvalidArgumentException('Unknown command: '.$positional[0]);
|
||||
}
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
|
|
@ -201,14 +167,14 @@ class Cli
|
|||
}
|
||||
|
||||
if (null === $this->inputFormat) {
|
||||
if ('.json' === substr($this->inputPath, -5)) {
|
||||
if (str_ends_with($this->inputPath, '.json')) {
|
||||
$this->inputFormat = 'json';
|
||||
} else {
|
||||
$this->inputFormat = 'mimedir';
|
||||
}
|
||||
}
|
||||
if (null === $this->format) {
|
||||
if ('.json' === substr($this->outputPath, -5)) {
|
||||
if (str_ends_with($this->outputPath, '.json')) {
|
||||
$this->format = 'json';
|
||||
} else {
|
||||
$this->format = 'mimedir';
|
||||
|
|
@ -219,12 +185,13 @@ class Cli
|
|||
|
||||
try {
|
||||
while ($input = $this->readInput()) {
|
||||
// @phpstan-ignore method.dynamicName
|
||||
$returnCode = $this->$command($input);
|
||||
if (0 !== $returnCode) {
|
||||
$realCode = $returnCode;
|
||||
}
|
||||
}
|
||||
} catch (EofException $e) {
|
||||
} catch (EofException) {
|
||||
// end of file
|
||||
} catch (\Exception $e) {
|
||||
$this->log('Error: '.$e->getMessage(), 'red');
|
||||
|
|
@ -408,7 +375,7 @@ HELP
|
|||
}
|
||||
fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
|
||||
} else {
|
||||
fwrite($this->stdout, $vObj->serialize());
|
||||
fwrite($this->stdout, (string) $vObj->serialize());
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -538,7 +505,7 @@ HELP
|
|||
$this->cWrite('red', ':');
|
||||
|
||||
if ($property instanceof Property\Binary) {
|
||||
$this->cWrite('default', 'embedded binary stripped. ('.strlen($property->getValue()).' bytes)');
|
||||
$this->cWrite('default', 'embedded binary stripped. ('.strlen((string) $property->getValue()).' bytes)');
|
||||
} else {
|
||||
$parts = $property->getParts();
|
||||
$first1 = true;
|
||||
|
|
@ -593,17 +560,17 @@ HELP
|
|||
|
||||
$v = $argv[$ii];
|
||||
|
||||
if ('--' === substr($v, 0, 2)) {
|
||||
if (str_starts_with((string) $v, '--')) {
|
||||
// This is a long-form option.
|
||||
$optionName = substr($v, 2);
|
||||
$optionName = substr((string) $v, 2);
|
||||
$optionValue = true;
|
||||
if (strpos($optionName, '=')) {
|
||||
list($optionName, $optionValue) = explode('=', $optionName);
|
||||
[$optionName, $optionValue] = explode('=', $optionName);
|
||||
}
|
||||
$options[$optionName] = $optionValue;
|
||||
} elseif ('-' === substr($v, 0, 1) && strlen($v) > 1) {
|
||||
} elseif (str_starts_with((string) $v, '-') && strlen((string) $v) > 1) {
|
||||
// This is a short-form option.
|
||||
foreach (str_split(substr($v, 1)) as $option) {
|
||||
foreach (str_split(substr((string) $v, 1)) as $option) {
|
||||
$options[$option] = true;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -628,6 +595,9 @@ HELP
|
|||
if (!$this->parser) {
|
||||
if ('-' !== $this->inputPath) {
|
||||
$this->stdin = fopen($this->inputPath, 'r');
|
||||
if (false === $this->stdin) {
|
||||
throw new InvalidDataException('Cannot open "'.$this->inputPath);
|
||||
}
|
||||
}
|
||||
|
||||
if ('mimedir' === $this->inputFormat) {
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ class Component extends Node
|
|||
// If there's no dot in the name, it's an exact property name,
|
||||
// we can just wipe out all those properties.
|
||||
//
|
||||
if (false === strpos($item, '.')) {
|
||||
if (!str_contains($item, '.')) {
|
||||
unset($this->children[strtoupper($item)]);
|
||||
|
||||
return;
|
||||
|
|
@ -210,8 +210,8 @@ class Component extends Node
|
|||
{
|
||||
$group = null;
|
||||
$name = strtoupper($name);
|
||||
if (false !== strpos($name, '.')) {
|
||||
list($group, $name) = explode('.', $name, 2);
|
||||
if (str_contains($name, '.')) {
|
||||
[$group, $name] = explode('.', $name, 2);
|
||||
}
|
||||
if ('' === $name) {
|
||||
$name = null;
|
||||
|
|
@ -222,16 +222,14 @@ class Component extends Node
|
|||
|
||||
if (is_null($group)) {
|
||||
return $result;
|
||||
} else {
|
||||
// If we have a group filter as well, we need to narrow it down
|
||||
// more.
|
||||
return array_filter(
|
||||
$result,
|
||||
function ($child) use ($group) {
|
||||
return $child instanceof Property && (null !== $child->group ? strtoupper($child->group) : '') === $group;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// If we have a group filter as well, we need to narrow it down
|
||||
// more.
|
||||
return array_filter(
|
||||
$result,
|
||||
fn ($child) => $child instanceof Property && (null !== $child->group ? strtoupper($child->group) : '') === $group
|
||||
);
|
||||
}
|
||||
|
||||
// If we got to this point, it means there was no 'name' specified for
|
||||
|
|
@ -368,7 +366,7 @@ class Component extends Node
|
|||
|
||||
$writer->startElement(strtolower($this->name));
|
||||
|
||||
if (!empty($properties)) {
|
||||
if ([] !== $properties) {
|
||||
$writer->startElement('properties');
|
||||
|
||||
foreach ($properties as $property) {
|
||||
|
|
@ -378,7 +376,7 @@ class Component extends Node
|
|||
$writer->endElement();
|
||||
}
|
||||
|
||||
if (!empty($components)) {
|
||||
if ([] !== $components) {
|
||||
$writer->startElement('components');
|
||||
|
||||
foreach ($components as $component) {
|
||||
|
|
@ -422,13 +420,12 @@ class Component extends Node
|
|||
$matches = $this->select($name);
|
||||
if (0 === count($matches)) {
|
||||
return null;
|
||||
} else {
|
||||
$firstMatch = current($matches);
|
||||
/* @var $firstMatch Property */
|
||||
$firstMatch->setIterator(new ElementList(array_values($matches)));
|
||||
|
||||
return $firstMatch;
|
||||
}
|
||||
$firstMatch = current($matches);
|
||||
/* @var $firstMatch Property */
|
||||
$firstMatch->setIterator(new ElementList(array_values($matches)));
|
||||
|
||||
return $firstMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -542,7 +539,7 @@ class Component extends Node
|
|||
$messages = [];
|
||||
|
||||
foreach ($this->children() as $child) {
|
||||
$name = strtoupper($child->name);
|
||||
$name = strtoupper((string) $child->name);
|
||||
if (!isset($propertyCounters[$name])) {
|
||||
$propertyCounters[$name] = 1;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ class VAlarm extends VObject\Component
|
|||
public function getEffectiveTriggerTime(): \DateTimeImmutable
|
||||
{
|
||||
$trigger = $this->TRIGGER;
|
||||
if (!isset($trigger['VALUE']) || ($trigger['VALUE'] && 'DURATION' === strtoupper($trigger['VALUE']))) {
|
||||
if (!isset($trigger['VALUE']) || ($trigger['VALUE'] && 'DURATION' === strtoupper((string) $trigger['VALUE']))) {
|
||||
$triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
|
||||
$related = (isset($trigger['RELATED']) && 'END' == strtoupper($trigger['RELATED'])) ? 'END' : 'START';
|
||||
$related = (isset($trigger['RELATED']) && 'END' === strtoupper($trigger['RELATED'])) ? 'END' : 'START';
|
||||
|
||||
/** @var VEvent|VTodo $parentComponent */
|
||||
$parentComponent = $this->parent;
|
||||
|
|
@ -96,9 +96,9 @@ class VAlarm extends VObject\Component
|
|||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return $start <= $effectiveTrigger && $end > $effectiveTrigger;
|
||||
}
|
||||
|
||||
return $start <= $effectiveTrigger && $end > $effectiveTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class VAvailability extends VObject\Component
|
|||
*/
|
||||
public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end): bool
|
||||
{
|
||||
list($effectiveStart, $effectiveEnd) = $this->getEffectiveStartEnd();
|
||||
[$effectiveStart, $effectiveEnd] = $this->getEffectiveStartEnd();
|
||||
|
||||
return
|
||||
(is_null($effectiveStart) || $start < $effectiveEnd)
|
||||
|
|
|
|||
|
|
@ -145,6 +145,10 @@ class VCalendar extends VObject\Document
|
|||
|
||||
// Additions from draft-daboo-calendar-availability-05
|
||||
'BUSYTYPE' => Property\Text::class,
|
||||
|
||||
// RFC 7986 properties
|
||||
'NAME' => Property\FlatText::class,
|
||||
'COLOR' => Property\FlatText::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -164,7 +168,7 @@ class VCalendar extends VObject\Document
|
|||
*
|
||||
* @param string|null $componentName filter by component name
|
||||
*
|
||||
* @return VObject\Component[]
|
||||
* @return Component[]
|
||||
*/
|
||||
public function getBaseComponents(?string $componentName = null): array
|
||||
{
|
||||
|
|
@ -327,7 +331,7 @@ class VCalendar extends VObject\Document
|
|||
foreach ($recurringEvents as $events) {
|
||||
try {
|
||||
$it = new EventIterator($events, null, $timeZone);
|
||||
} catch (NoInstancesException $e) {
|
||||
} catch (NoInstancesException) {
|
||||
// This event is recurring, but it doesn't have a single
|
||||
// instance. We are skipping this event from the output
|
||||
// entirely.
|
||||
|
|
@ -379,6 +383,18 @@ class VCalendar extends VObject\Document
|
|||
|
||||
'CALSCALE' => '?',
|
||||
'METHOD' => '?',
|
||||
|
||||
// RFC 7986 calendar properties
|
||||
'UID' => '?',
|
||||
'LAST-MODIFIED' => '?',
|
||||
'URL' => '?',
|
||||
'REFRESH-INTERVAL' => '?',
|
||||
'SOURCE' => '?',
|
||||
'COLOR' => '?',
|
||||
'NAME' => '*',
|
||||
'DESCRIPTION' => '*',
|
||||
'CATEGORIES' => '*',
|
||||
'IMAGE' => '*',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -424,7 +440,7 @@ class VCalendar extends VObject\Document
|
|||
if ($child instanceof Component) {
|
||||
++$componentsFound;
|
||||
|
||||
if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'])) {
|
||||
if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'], true)) {
|
||||
continue;
|
||||
}
|
||||
$componentTypes[] = $child->name;
|
||||
|
|
|
|||
|
|
@ -132,6 +132,13 @@ class VCard extends VObject\Document
|
|||
'ORG-DIRECTORY' => VObject\Property\FlatText::class,
|
||||
|
||||
'X-CRYPTO' => VObject\Property\XCrypto::class,
|
||||
|
||||
// rfc9554 properties
|
||||
'CREATED' => VObject\Property\VCard\TimeStamp::class,
|
||||
'GRAMGENDER' => VObject\Property\FlatText::class,
|
||||
'LANGUAGE' => VObject\Property\VCard\LanguageTag::class,
|
||||
'PRONOUNS' => VObject\Property\FlatText::class,
|
||||
'SOCIALPROFILE' => VObject\Property\Uri::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -326,6 +333,11 @@ class VCard extends VObject\Document
|
|||
'CALURI' => '*',
|
||||
'CATEGORIES' => '*',
|
||||
'CLIENTPIDMAP' => '*',
|
||||
'CREATED' => '?',
|
||||
'GRAMGENDER' => '*',
|
||||
'LANGUAGE' => '?',
|
||||
'PRONOUNS' => '*',
|
||||
'SOCIALPROFILE' => '*',
|
||||
'EMAIL' => '*',
|
||||
'FBURL' => '*',
|
||||
'IMPP' => '*',
|
||||
|
|
@ -411,6 +423,34 @@ class VCard extends VObject\Document
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a property with a specific TYPE value (ADR, TEL, or EMAIL).
|
||||
*
|
||||
* This function will return null if the exact property list does not exist.
|
||||
*
|
||||
* For example to get the property of `TEL;TYPE=HOME,CELL`
|
||||
* you would call `getByTypes('TEL', ['HOME', 'CELL'])`
|
||||
*
|
||||
* @param string[] $types
|
||||
*
|
||||
* @return \ArrayAccess|array|null
|
||||
*/
|
||||
public function getByTypes(string $propertyName, array $types)
|
||||
{
|
||||
$types = array_map(strtolower(...), $types);
|
||||
foreach ($this->select($propertyName) as $field) {
|
||||
if (isset($field['TYPE'])) {
|
||||
$parts = array_map(strtolower(...), $field['TYPE']->getParts());
|
||||
|
||||
if (!array_diff($types, $parts) && !array_diff($parts, $types)) {
|
||||
return $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should return a list of default property values.
|
||||
*/
|
||||
|
|
@ -453,11 +493,9 @@ class VCard extends VObject\Document
|
|||
$propertiesByGroup = [];
|
||||
|
||||
foreach ($this->children() as $property) {
|
||||
$group = $property->group;
|
||||
$group = $property->group ?? '';
|
||||
|
||||
if (!isset($propertiesByGroup[$group])) {
|
||||
$propertiesByGroup[$group] = [];
|
||||
}
|
||||
$propertiesByGroup[$group] ??= [];
|
||||
|
||||
$propertiesByGroup[$group][] = $property;
|
||||
}
|
||||
|
|
@ -465,9 +503,9 @@ class VCard extends VObject\Document
|
|||
$writer->startElement(strtolower($this->name));
|
||||
|
||||
foreach ($propertiesByGroup as $group => $properties) {
|
||||
if (!empty($group)) {
|
||||
if ('' !== $group) {
|
||||
$writer->startElement('group');
|
||||
$writer->writeAttribute('name', strtolower($group));
|
||||
$writer->writeAttribute('name', strtolower((string) $group));
|
||||
}
|
||||
|
||||
foreach ($properties as $property) {
|
||||
|
|
@ -487,7 +525,7 @@ class VCard extends VObject\Document
|
|||
}
|
||||
}
|
||||
|
||||
if (!empty($group)) {
|
||||
if ('' !== $group) {
|
||||
$writer->endElement();
|
||||
}
|
||||
}
|
||||
|
|
@ -503,7 +541,7 @@ class VCard extends VObject\Document
|
|||
$className = parent::getClassNameForPropertyName($propertyName);
|
||||
|
||||
// In vCard 4, BINARY no longer exists, and we need URI instead.
|
||||
if (VObject\Property\Binary::class == $className && self::VCARD40 === $this->getDocumentType()) {
|
||||
if (VObject\Property\Binary::class === $className && self::VCARD40 === $this->getDocumentType()) {
|
||||
return VObject\Property\Uri::class;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,10 +41,10 @@ class VEvent extends VObject\Component
|
|||
*/
|
||||
public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end): bool
|
||||
{
|
||||
if ($this->RRULE) {
|
||||
if ($this->RRULE || $this->RDATE) {
|
||||
try {
|
||||
$it = new EventIterator($this, null, $start->getTimezone());
|
||||
} catch (NoInstancesException $e) {
|
||||
} catch (NoInstancesException) {
|
||||
// If we've caught this exception, there are no instances
|
||||
// for the event that fall into the specified time-range.
|
||||
return false;
|
||||
|
|
@ -117,6 +117,7 @@ class VEvent extends VObject\Component
|
|||
'DTSTART' => $hasMethod ? '?' : '1',
|
||||
'CLASS' => '?',
|
||||
'CREATED' => '?',
|
||||
'COLOR' => '?',
|
||||
'DESCRIPTION' => '?',
|
||||
'GEO' => '?',
|
||||
'LAST-MODIFIED' => '?',
|
||||
|
|
@ -137,7 +138,9 @@ class VEvent extends VObject\Component
|
|||
'ATTENDEE' => '*',
|
||||
'CATEGORIES' => '*',
|
||||
'COMMENT' => '*',
|
||||
'CONFERENCE' => '*',
|
||||
'CONTACT' => '*',
|
||||
'IMAGE' => '*',
|
||||
'EXDATE' => '*',
|
||||
'REQUEST-STATUS' => '*',
|
||||
'RELATED-TO' => '*',
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class VFreeBusy extends VObject\Component
|
|||
// Every period is formatted as [start]/[end]. The start is an
|
||||
// absolute UTC time, the end may be an absolute UTC time, or
|
||||
// duration (relative) value.
|
||||
list($busyStart, $busyEnd) = explode('/', $period);
|
||||
[$busyStart, $busyEnd] = explode('/', $period);
|
||||
|
||||
$busyStart = VObject\DateTimeParser::parse($busyStart);
|
||||
$busyEnd = VObject\DateTimeParser::parse($busyEnd);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ class VJournal extends VObject\Component
|
|||
|
||||
'CLASS' => '?',
|
||||
'CREATED' => '?',
|
||||
'COLOR' => '?',
|
||||
'DTSTART' => '?',
|
||||
'LAST-MODIFIED' => '?',
|
||||
'ORGANIZER' => '?',
|
||||
|
|
@ -78,6 +79,7 @@ class VJournal extends VObject\Component
|
|||
'CATEGORIES' => '*',
|
||||
'COMMENT' => '*',
|
||||
'CONTACT' => '*',
|
||||
'IMAGE' => '*',
|
||||
'DESCRIPTION' => '*',
|
||||
'EXDATE' => '*',
|
||||
'RELATED-TO' => '*',
|
||||
|
|
|
|||
|
|
@ -54,9 +54,9 @@ class VTodo extends VObject\Component
|
|||
return
|
||||
($start < $due || $start <= $dtstart)
|
||||
&& ($end > $dtstart || $end >= $due);
|
||||
} else {
|
||||
return $start <= $dtstart && $end > $dtstart;
|
||||
}
|
||||
|
||||
return $start <= $dtstart && $end > $dtstart;
|
||||
}
|
||||
if ($due) {
|
||||
return $start < $due && $end >= $due;
|
||||
|
|
@ -98,6 +98,7 @@ class VTodo extends VObject\Component
|
|||
'CLASS' => '?',
|
||||
'COMPLETED' => '?',
|
||||
'CREATED' => '?',
|
||||
'COLOR' => '?',
|
||||
'DESCRIPTION' => '?',
|
||||
'DTSTART' => '?',
|
||||
'GEO' => '?',
|
||||
|
|
@ -120,7 +121,9 @@ class VTodo extends VObject\Component
|
|||
'ATTENDEE' => '*',
|
||||
'CATEGORIES' => '*',
|
||||
'COMMENT' => '*',
|
||||
'CONFERENCE' => '*',
|
||||
'CONTACT' => '*',
|
||||
'IMAGE' => '*',
|
||||
'EXDATE' => '*',
|
||||
'REQUEST-STATUS' => '*',
|
||||
'RELATED-TO' => '*',
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class DateTimeParser
|
|||
|
||||
try {
|
||||
$date = new \DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3].' '.$matches[4].':'.$matches[5].':'.$matches[6], $tz);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Exception) {
|
||||
throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: '.$dt);
|
||||
}
|
||||
|
||||
|
|
@ -63,13 +63,11 @@ class DateTimeParser
|
|||
throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date);
|
||||
}
|
||||
|
||||
if (is_null($tz)) {
|
||||
$tz = new \DateTimeZone('UTC');
|
||||
}
|
||||
$tz ??= new \DateTimeZone('UTC');
|
||||
|
||||
try {
|
||||
$date = new \DateTimeImmutable($matches[1].'-'.$matches[2].'-'.$matches[3], $tz);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Exception) {
|
||||
throw new InvalidDataException('The supplied iCalendar date value is incorrect: '.$date);
|
||||
}
|
||||
|
||||
|
|
@ -205,9 +203,9 @@ class DateTimeParser
|
|||
return self::parseDuration($date);
|
||||
} elseif (8 === strlen($date)) {
|
||||
return self::parseDate($date, $referenceTz);
|
||||
} else {
|
||||
return self::parseDateTime($date, $referenceTz);
|
||||
}
|
||||
|
||||
return self::parseDateTime($date, $referenceTz);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -329,7 +327,7 @@ class DateTimeParser
|
|||
|
||||
$result = [];
|
||||
foreach ($parts as $part) {
|
||||
if (empty($matches[$part])) {
|
||||
if (!array_key_exists($part, $matches) || '' === $matches[$part]) {
|
||||
$result[$part] = null;
|
||||
} elseif ('-' === $matches[$part] || '--' === $matches[$part]) {
|
||||
$result[$part] = null;
|
||||
|
|
@ -424,7 +422,7 @@ class DateTimeParser
|
|||
|
||||
$result = [];
|
||||
foreach ($parts as $part) {
|
||||
if (empty($matches[$part])) {
|
||||
if (!array_key_exists($part, $matches) || '' === $matches[$part]) {
|
||||
$result[$part] = null;
|
||||
} elseif ('-' === $matches[$part]) {
|
||||
$result[$part] = null;
|
||||
|
|
@ -541,7 +539,7 @@ class DateTimeParser
|
|||
$parts['year0'] = &$parts['year'];
|
||||
|
||||
foreach ($parts as $part => &$value) {
|
||||
if (!empty($matches[$part])) {
|
||||
if (array_key_exists($part, $matches) && '' !== $matches[$part]) {
|
||||
$value = $matches[$part];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,10 +118,10 @@ abstract class Document extends Component
|
|||
public function create(string $name)
|
||||
{
|
||||
if (isset(static::$componentMap[strtoupper($name)])) {
|
||||
return call_user_func_array([$this, 'createComponent'], func_get_args());
|
||||
} else {
|
||||
return call_user_func_array([$this, 'createProperty'], func_get_args());
|
||||
return $this->createComponent(...func_get_args());
|
||||
}
|
||||
|
||||
return $this->createProperty(...func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -146,9 +146,7 @@ abstract class Document extends Component
|
|||
if (isset(static::$componentMap[$name])) {
|
||||
$class = static::$componentMap[$name];
|
||||
}
|
||||
if (is_null($children)) {
|
||||
$children = [];
|
||||
}
|
||||
$children ??= [];
|
||||
|
||||
return new $class($this, $name, $children, $defaults);
|
||||
}
|
||||
|
|
@ -180,26 +178,29 @@ abstract class Document extends Component
|
|||
|
||||
$class = null;
|
||||
|
||||
// If a VALUE parameter is supplied, we have to use that
|
||||
// According to https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.20
|
||||
// If the property's value is the default value type, then this
|
||||
// parameter need not be specified. However, if the property's
|
||||
// default value type is overridden by some other allowable value
|
||||
// type, then this parameter MUST be specified.
|
||||
if (!$valueType) {
|
||||
$valueType = $parameters['VALUE'] ?? null;
|
||||
}
|
||||
|
||||
if ($valueType) {
|
||||
// The valueType argument comes first to figure out the correct
|
||||
// class.
|
||||
$class = $this->getClassNameForPropertyValue($valueType);
|
||||
}
|
||||
|
||||
if (is_null($class)) {
|
||||
// If a VALUE parameter is supplied, we should use that.
|
||||
if (isset($parameters['VALUE'])) {
|
||||
$class = $this->getClassNameForPropertyValue($parameters['VALUE']);
|
||||
if (is_null($class)) {
|
||||
throw new InvalidDataException('Unsupported VALUE parameter for '.$name.' property. You supplied "'.$parameters['VALUE'].'"');
|
||||
}
|
||||
} else {
|
||||
$class = $this->getClassNameForPropertyName($name);
|
||||
}
|
||||
}
|
||||
if (is_null($parameters)) {
|
||||
$parameters = [];
|
||||
}
|
||||
// If the value parameter is not set or set to something we do not recognize
|
||||
// we do not attempt to interpret or parse the datass value as specified in
|
||||
// https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.20
|
||||
// So when we so far did not get a class-name, we use the default for the property
|
||||
$class ??= $this->getClassNameForPropertyName($name);
|
||||
|
||||
$parameters ??= [];
|
||||
|
||||
return new $class($this, $name, $value, $parameters, $group, $lineIndex, $lineString);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,25 +11,19 @@ namespace Sabre\VObject;
|
|||
*/
|
||||
class FreeBusyData
|
||||
{
|
||||
/**
|
||||
* Start timestamp.
|
||||
*/
|
||||
protected int $start;
|
||||
|
||||
/**
|
||||
* End timestamp.
|
||||
*/
|
||||
protected int $end;
|
||||
|
||||
/**
|
||||
* A list of free-busy times.
|
||||
*/
|
||||
protected array $data;
|
||||
|
||||
public function __construct(int $start, int $end)
|
||||
public function __construct(/**
|
||||
* Start timestamp.
|
||||
*/
|
||||
protected int $start, /**
|
||||
* End timestamp.
|
||||
*/
|
||||
protected int $end)
|
||||
{
|
||||
$this->start = $start;
|
||||
$this->end = $end;
|
||||
$this->data = [];
|
||||
|
||||
$this->data[] = [
|
||||
|
|
|
|||
|
|
@ -79,9 +79,7 @@ class FreeBusyGenerator
|
|||
if ($objects) {
|
||||
$this->setObjects($objects);
|
||||
}
|
||||
if (is_null($timeZone)) {
|
||||
$timeZone = new \DateTimeZone('UTC');
|
||||
}
|
||||
$timeZone ??= new \DateTimeZone('UTC');
|
||||
$this->setTimeZone($timeZone);
|
||||
}
|
||||
|
||||
|
|
@ -217,7 +215,7 @@ class FreeBusyGenerator
|
|||
$new = [];
|
||||
|
||||
foreach ($old as $vavail) {
|
||||
list($compStart, $compEnd) = $vavail->getEffectiveStartEnd();
|
||||
[$compStart, $compEnd] = $vavail->getEffectiveStartEnd();
|
||||
|
||||
// We don't care about date-times that are earlier or later than the
|
||||
// start and end of the freebusy report, so this gets normalized
|
||||
|
|
@ -237,7 +235,7 @@ class FreeBusyGenerator
|
|||
// Going through our existing list of components to see if there's
|
||||
// a higher priority component that already fully covers this one.
|
||||
foreach ($new as $higherVavail) {
|
||||
list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd();
|
||||
[$higherStart, $higherEnd] = $higherVavail->getEffectiveStartEnd();
|
||||
if (
|
||||
(is_null($higherStart) || $higherStart < $compStart)
|
||||
&& (is_null($higherEnd) || $higherEnd > $compEnd)
|
||||
|
|
@ -259,7 +257,7 @@ class FreeBusyGenerator
|
|||
// priority components to override the lower ones.
|
||||
foreach (array_reverse($new) as $vavail) {
|
||||
$busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE';
|
||||
list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd();
|
||||
[$vavailStart, $vavailEnd] = $vavail->getEffectiveStartEnd();
|
||||
|
||||
// Making the component size no larger than the requested free-busy
|
||||
// report range.
|
||||
|
|
@ -281,7 +279,7 @@ class FreeBusyGenerator
|
|||
// Looping over the AVAILABLE components.
|
||||
if (isset($vavail->AVAILABLE)) {
|
||||
foreach ($vavail->AVAILABLE as $available) {
|
||||
list($availStart, $availEnd) = $available->getEffectiveStartEnd();
|
||||
[$availStart, $availEnd] = $available->getEffectiveStartEnd();
|
||||
$fbData->add(
|
||||
$availStart->getTimeStamp(),
|
||||
$availEnd->getTimeStamp(),
|
||||
|
|
@ -362,7 +360,7 @@ class FreeBusyGenerator
|
|||
if ($component->RRULE) {
|
||||
try {
|
||||
$iterator = new EventIterator($object, (string) $component->UID, $this->timeZone);
|
||||
} catch (NoInstancesException $e) {
|
||||
} catch (NoInstancesException) {
|
||||
// This event is recurring, but it doesn't have a single
|
||||
// instance. We are skipping this event from the output
|
||||
// entirely.
|
||||
|
|
@ -437,12 +435,12 @@ class FreeBusyGenerator
|
|||
continue;
|
||||
}
|
||||
|
||||
$values = explode(',', $freebusy);
|
||||
$values = explode(',', (string) $freebusy);
|
||||
foreach ($values as $value) {
|
||||
list($startTime, $endTime) = explode('/', $value);
|
||||
[$startTime, $endTime] = explode('/', $value);
|
||||
$startTime = DateTimeParser::parseDateTime($startTime);
|
||||
|
||||
if ('P' === substr($endTime, 0, 1) || '-P' === substr($endTime, 0, 2)) {
|
||||
if (str_starts_with($endTime, 'P') || str_starts_with($endTime, '-P')) {
|
||||
$duration = DateTimeParser::parseDuration($endTime);
|
||||
$endTime = clone $startTime;
|
||||
$endTime = $endTime->add($duration);
|
||||
|
|
@ -507,7 +505,7 @@ class FreeBusyGenerator
|
|||
$vfreebusy->add($dtstamp);
|
||||
|
||||
foreach ($fbData->getData() as $busyTime) {
|
||||
$busyType = strtoupper($busyTime['type']);
|
||||
$busyType = strtoupper((string) $busyTime['type']);
|
||||
|
||||
// Ignoring all the FREE parts, because those are already assumed.
|
||||
if ('FREE' === $busyType) {
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ class Broker
|
|||
|
||||
$eventInfo = $oldEventInfo;
|
||||
|
||||
if (in_array($eventInfo['organizer'], $userHref)) {
|
||||
if (in_array($eventInfo['organizer'], $userHref, true)) {
|
||||
// This is an organizer deleting the event.
|
||||
$eventInfo['attendees'] = [];
|
||||
// Increasing the sequence, but only if the organizer deleted
|
||||
|
|
@ -237,7 +237,7 @@ class Broker
|
|||
} else {
|
||||
// This is an attendee deleting the event.
|
||||
foreach ($eventInfo['attendees'] as $key => $attendee) {
|
||||
if (in_array($attendee['href'], $userHref)) {
|
||||
if (in_array($attendee['href'], $userHref, true)) {
|
||||
$eventInfo['attendees'][$key]['instances'] = ['master' => ['id' => 'master', 'partstat' => 'DECLINED'],
|
||||
];
|
||||
}
|
||||
|
|
@ -246,16 +246,29 @@ class Broker
|
|||
$baseCalendar = $oldCalendar;
|
||||
}
|
||||
|
||||
if (in_array($eventInfo['organizer'], $userHref)) {
|
||||
// Check if the user is the organizer
|
||||
if (in_array($eventInfo['organizer'], $userHref, true)) {
|
||||
return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo);
|
||||
} elseif ($oldCalendar) {
|
||||
// We need to figure out if the user is an attendee, but we're only
|
||||
// doing so if there's an oldCalendar, because we only want to
|
||||
// process updates, not creation of new events.
|
||||
foreach ($eventInfo['attendees'] as $attendee) {
|
||||
if (in_array($attendee['href'], $userHref)) {
|
||||
}
|
||||
|
||||
// Check if the user is an attendee
|
||||
foreach ($eventInfo['attendees'] as $attendee) {
|
||||
if (in_array($attendee['href'], $userHref, true)) {
|
||||
// If this is a event update, we always generate a reply
|
||||
if ($oldCalendar) {
|
||||
return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
|
||||
}
|
||||
|
||||
// If this is a new event, we only generate a reply if the participation status is set
|
||||
foreach ($attendee['instances'] as $instance) {
|
||||
if (isset($instance['partstat']) && 'NEEDS-ACTION' !== $instance['partstat']) {
|
||||
// Attendee has responded (ACCEPTED/DECLINED/TENTATIVE) - generate REPLY
|
||||
return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
|
||||
}
|
||||
}
|
||||
|
||||
// User is attendee but no response to process
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +354,7 @@ class Broker
|
|||
$instances[$recurId] = $attendee['PARTSTAT']->getValue();
|
||||
if (isset($vevent->{'REQUEST-STATUS'})) {
|
||||
$requestStatus = $vevent->{'REQUEST-STATUS'}->getValue();
|
||||
list($requestStatus) = explode(';', $requestStatus);
|
||||
[$requestStatus] = explode(';', $requestStatus);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -479,7 +492,6 @@ class Broker
|
|||
}
|
||||
|
||||
$messages = [];
|
||||
|
||||
foreach ($attendees as $attendee) {
|
||||
// An organizer can also be an attendee. We should not generate any
|
||||
// messages for those.
|
||||
|
|
@ -554,7 +566,7 @@ class Broker
|
|||
|
||||
$message->significantChange =
|
||||
'REQUEST' === $attendee['forceSend']
|
||||
|| count($oldAttendeeInstances) != count($newAttendeeInstances)
|
||||
|| count($oldAttendeeInstances) !== count($newAttendeeInstances)
|
||||
|| count(array_diff($oldAttendeeInstances, $newAttendeeInstances)) > 0
|
||||
|| $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];
|
||||
|
||||
|
|
@ -564,9 +576,9 @@ class Broker
|
|||
// We need to find a list of events that the attendee
|
||||
// is not a part of to add to the list of exceptions.
|
||||
$exceptions = [];
|
||||
foreach ($eventInfo['instances'] as $instanceId => $vevent) {
|
||||
if (!isset($attendee['newInstances'][$instanceId])) {
|
||||
$exceptions[] = $instanceId;
|
||||
foreach ($eventInfo['instances'] as $eventInstanceId => $vevent) {
|
||||
if (!isset($attendee['newInstances'][$eventInstanceId])) {
|
||||
$exceptions[] = $eventInstanceId;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -580,6 +592,9 @@ class Broker
|
|||
));
|
||||
} else {
|
||||
$currentEvent->EXDATE = $exceptions;
|
||||
if ($currentEvent->DTSTART['TZID']) {
|
||||
$currentEvent->EXDATE['TZID'] = clone $currentEvent->DTSTART['TZID'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -588,15 +603,13 @@ class Broker
|
|||
unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
|
||||
unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);
|
||||
|
||||
foreach ($currentEvent->ATTENDEE as $attendee) {
|
||||
unset($attendee['SCHEDULE-FORCE-SEND']);
|
||||
unset($attendee['SCHEDULE-STATUS']);
|
||||
foreach ($currentEvent->ATTENDEE as $currentEventAttendee) {
|
||||
unset($currentEventAttendee['SCHEDULE-FORCE-SEND']);
|
||||
unset($currentEventAttendee['SCHEDULE-STATUS']);
|
||||
|
||||
// We're adding PARTSTAT=NEEDS-ACTION to ensure that
|
||||
// iOS shows an "Inbox Item"
|
||||
if (!isset($attendee['PARTSTAT'])) {
|
||||
$attendee['PARTSTAT'] = 'NEEDS-ACTION';
|
||||
}
|
||||
$currentEventAttendee['PARTSTAT'] ??= 'NEEDS-ACTION';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -633,7 +646,12 @@ class Broker
|
|||
return [];
|
||||
}
|
||||
|
||||
$oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ?
|
||||
$oldInstances =
|
||||
is_array($oldEventInfo['attendees'])
|
||||
&& array_key_exists($attendee, $oldEventInfo['attendees'])
|
||||
&& is_array($oldEventInfo['attendees'][$attendee])
|
||||
&& array_key_exists('instances', $oldEventInfo['attendees'][$attendee])
|
||||
&& is_array($oldEventInfo['attendees'][$attendee]['instances']) ?
|
||||
$oldEventInfo['attendees'][$attendee]['instances'] :
|
||||
[];
|
||||
|
||||
|
|
@ -664,7 +682,7 @@ class Broker
|
|||
// We only need to do that though, if the master event is not declined.
|
||||
if (isset($instances['master']) && 'DECLINED' !== $instances['master']['newstatus']) {
|
||||
foreach ($eventInfo['exdate'] as $exDate) {
|
||||
if (!in_array($exDate, $oldEventInfo['exdate'])) {
|
||||
if (!in_array($exDate, $oldEventInfo['exdate'] ?? [], true)) {
|
||||
if (isset($instances[$exDate])) {
|
||||
$instances[$exDate]['newstatus'] = 'DECLINED';
|
||||
} else {
|
||||
|
|
@ -707,7 +725,7 @@ class Broker
|
|||
$hasReply = false;
|
||||
|
||||
foreach ($instances as $instance) {
|
||||
if ($instance['oldstatus'] == $instance['newstatus'] && 'REPLY' !== $eventInfo['organizerForceSend']) {
|
||||
if ($instance['oldstatus'] === $instance['newstatus'] && 'REPLY' !== $eventInfo['organizerForceSend']) {
|
||||
// Skip
|
||||
continue;
|
||||
}
|
||||
|
|
@ -739,7 +757,7 @@ class Broker
|
|||
// EXDATE
|
||||
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
|
||||
// Treat is as a DATE field
|
||||
if (strlen($instance['id']) <= 8) {
|
||||
if (strlen((string) $instance['id']) <= 8) {
|
||||
$event->add('DTSTART', $dt, ['VALUE' => 'DATE']);
|
||||
} else {
|
||||
$event->add('DTSTART', $dt);
|
||||
|
|
@ -751,7 +769,7 @@ class Broker
|
|||
if ('master' !== $instance['id']) {
|
||||
$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
|
||||
// Treat is as a DATE field
|
||||
if (strlen($instance['id']) <= 8) {
|
||||
if (strlen((string) $instance['id']) <= 8) {
|
||||
$event->add('RECURRENCE-ID', $dt, ['VALUE' => 'DATE']);
|
||||
} else {
|
||||
$event->add('RECURRENCE-ID', $dt);
|
||||
|
|
@ -774,9 +792,9 @@ class Broker
|
|||
$message->message = $icalMsg;
|
||||
|
||||
return [$message];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -843,7 +861,7 @@ class Broker
|
|||
$organizer = $vevent->ORGANIZER->getNormalizedValue();
|
||||
$organizerName = $vevent->ORGANIZER['CN'] ?? null;
|
||||
} else {
|
||||
if (strtoupper($organizer) !== strtoupper($vevent->ORGANIZER->getNormalizedValue())) {
|
||||
if (strtoupper($organizer) !== strtoupper((string) $vevent->ORGANIZER->getNormalizedValue())) {
|
||||
throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.');
|
||||
}
|
||||
}
|
||||
|
|
@ -869,7 +887,7 @@ class Broker
|
|||
foreach ($vevent->select('RRULE') as $rr) {
|
||||
foreach ($rr->getParts() as $key => $val) {
|
||||
// ignore default values (https://github.com/sabre-io/vobject/issues/126)
|
||||
if ('INTERVAL' === $key && 1 == $val) {
|
||||
if ('INTERVAL' === $key && '1' === (string) $val) {
|
||||
continue;
|
||||
}
|
||||
if (is_array($val)) {
|
||||
|
|
@ -881,7 +899,7 @@ class Broker
|
|||
sort($rrule);
|
||||
}
|
||||
if (isset($vevent->STATUS)) {
|
||||
$status = strtoupper($vevent->STATUS->getValue());
|
||||
$status = strtoupper((string) $vevent->STATUS->getValue());
|
||||
}
|
||||
|
||||
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
|
||||
|
|
@ -892,11 +910,14 @@ class Broker
|
|||
$timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone();
|
||||
}
|
||||
}
|
||||
|
||||
$instances[$recurId] = $vevent;
|
||||
|
||||
if (isset($vevent->ATTENDEE)) {
|
||||
foreach ($vevent->ATTENDEE as $attendee) {
|
||||
if ($this->scheduleAgentServerRules
|
||||
&& isset($attendee['SCHEDULE-AGENT'])
|
||||
&& 'CLIENT' === strtoupper($attendee['SCHEDULE-AGENT']->getValue())
|
||||
&& 'CLIENT' === strtoupper((string) $attendee['SCHEDULE-AGENT']->getValue())
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -930,10 +951,10 @@ class Broker
|
|||
];
|
||||
}
|
||||
}
|
||||
$instances[$recurId] = $vevent;
|
||||
}
|
||||
|
||||
foreach ($this->significantChangeProperties as $prop) {
|
||||
// @phpstan-ignore property.dynamicName
|
||||
if (isset($vevent->$prop)) {
|
||||
$propertyValues = $vevent->select($prop);
|
||||
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@ class Message
|
|||
* Contains the ITip method, which is something like REQUEST, REPLY or
|
||||
* CANCEL.
|
||||
*/
|
||||
public ?string $method;
|
||||
public ?string $method = null;
|
||||
|
||||
/**
|
||||
* The current sequence number for the event.
|
||||
*/
|
||||
public ?int $sequence;
|
||||
public ?int $sequence = null;
|
||||
|
||||
/**
|
||||
* The senders' email address.
|
||||
|
|
@ -52,7 +52,7 @@ class Message
|
|||
* The name of the sender. This is often populated from a CN parameter from
|
||||
* either the ORGANIZER or ATTENDEE, depending on the message.
|
||||
*/
|
||||
public ?string $senderName;
|
||||
public ?string $senderName = null;
|
||||
|
||||
/**
|
||||
* The recipient's email address.
|
||||
|
|
@ -63,7 +63,7 @@ class Message
|
|||
* The name of the recipient. This is usually populated with the CN
|
||||
* parameter from the ATTENDEE or ORGANIZER property, if it's available.
|
||||
*/
|
||||
public ?string $recipientName;
|
||||
public ?string $recipientName = null;
|
||||
|
||||
/**
|
||||
* After the message has been delivered, this should contain a string such
|
||||
|
|
@ -107,10 +107,9 @@ class Message
|
|||
{
|
||||
if (!$this->scheduleStatus) {
|
||||
return false;
|
||||
} else {
|
||||
list($scheduleStatus) = explode(';', $this->scheduleStatus);
|
||||
|
||||
return $scheduleStatus;
|
||||
}
|
||||
[$scheduleStatus] = explode(';', $this->scheduleStatus);
|
||||
|
||||
return $scheduleStatus;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
|
|||
/**
|
||||
* Reference to the parent object, if this is not the top object.
|
||||
*/
|
||||
public ?Node $parent;
|
||||
public ?Node $parent = null;
|
||||
|
||||
/**
|
||||
* Iterator override.
|
||||
|
|
@ -52,7 +52,7 @@ abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable, \Js
|
|||
/**
|
||||
* The root document.
|
||||
*/
|
||||
protected ?Component $root;
|
||||
protected ?Component $root = null;
|
||||
|
||||
/**
|
||||
* Serializes the node into a mimedir format.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ trait PHPUnitAssertions
|
|||
$input = Reader::read($input);
|
||||
}
|
||||
if (!$input instanceof Component) {
|
||||
$this->fail('Input must be a string, stream or VObject component');
|
||||
self::fail('Input must be a string, stream or VObject component');
|
||||
}
|
||||
unset($input->PRODID);
|
||||
if ($input instanceof Component\VCalendar && 'GREGORIAN' === (string) $input->CALSCALE) {
|
||||
|
|
@ -51,23 +51,28 @@ trait PHPUnitAssertions
|
|||
return $input;
|
||||
};
|
||||
|
||||
$expected = $getObj($expected)->serialize();
|
||||
$actual = $getObj($actual)->serialize();
|
||||
/**
|
||||
* @var string $expectedSerialized
|
||||
*/
|
||||
$expectedSerialized = $getObj($expected)->serialize();
|
||||
$actualSerialized = $getObj($actual)->serialize();
|
||||
|
||||
// Finding wildcards in expected.
|
||||
preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expected, $matches, PREG_SET_ORDER);
|
||||
$result = preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expectedSerialized, $matches, PREG_SET_ORDER);
|
||||
|
||||
self::assertNotFalse($result);
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$actual = preg_replace(
|
||||
$actualSerialized = preg_replace(
|
||||
'|^'.preg_quote($match[1], '|').':(.*)\r$|m',
|
||||
$match[1].':**ANY**'."\r",
|
||||
$actual
|
||||
(string) $actualSerialized
|
||||
);
|
||||
}
|
||||
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$actual,
|
||||
self::assertEquals(
|
||||
$expectedSerialized,
|
||||
$actualSerialized,
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use Sabre\Xml;
|
|||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Parameter extends Node
|
||||
class Parameter extends Node implements \Stringable
|
||||
{
|
||||
/**
|
||||
* Parameter name.
|
||||
|
|
@ -74,83 +74,12 @@ class Parameter extends Node
|
|||
*/
|
||||
public static function guessParameterNameByValue(string $value): string
|
||||
{
|
||||
switch (strtoupper($value)) {
|
||||
// Encodings
|
||||
case '7-BIT':
|
||||
case 'QUOTED-PRINTABLE':
|
||||
case 'BASE64':
|
||||
$name = 'ENCODING';
|
||||
break;
|
||||
|
||||
// Common types
|
||||
case 'WORK':
|
||||
case 'HOME':
|
||||
case 'PREF':
|
||||
// Delivery Label Type
|
||||
case 'DOM':
|
||||
case 'INTL':
|
||||
case 'POSTAL':
|
||||
case 'PARCEL':
|
||||
// Telephone types
|
||||
case 'VOICE':
|
||||
case 'FAX':
|
||||
case 'MSG':
|
||||
case 'CELL':
|
||||
case 'PAGER':
|
||||
case 'BBS':
|
||||
case 'MODEM':
|
||||
case 'CAR':
|
||||
case 'ISDN':
|
||||
case 'VIDEO':
|
||||
// EMAIL types (lol)
|
||||
case 'AOL':
|
||||
case 'APPLELINK':
|
||||
case 'ATTMAIL':
|
||||
case 'CIS':
|
||||
case 'EWORLD':
|
||||
case 'INTERNET':
|
||||
case 'IBMMAIL':
|
||||
case 'MCIMAIL':
|
||||
case 'POWERSHARE':
|
||||
case 'PRODIGY':
|
||||
case 'TLX':
|
||||
case 'X400':
|
||||
// Photo / Logo format types
|
||||
case 'GIF':
|
||||
case 'CGM':
|
||||
case 'WMF':
|
||||
case 'BMP':
|
||||
case 'DIB':
|
||||
case 'PICT':
|
||||
case 'TIFF':
|
||||
case 'PDF':
|
||||
case 'PS':
|
||||
case 'JPEG':
|
||||
case 'MPEG':
|
||||
case 'MPEG2':
|
||||
case 'AVI':
|
||||
case 'QTIME':
|
||||
// Sound Digital Audio Type
|
||||
case 'WAVE':
|
||||
case 'PCM':
|
||||
case 'AIFF':
|
||||
// Key types
|
||||
case 'X509':
|
||||
case 'PGP':
|
||||
$name = 'TYPE';
|
||||
break;
|
||||
|
||||
// Value types
|
||||
case 'INLINE':
|
||||
case 'URL':
|
||||
case 'CONTENT-ID':
|
||||
case 'CID':
|
||||
$name = 'VALUE';
|
||||
break;
|
||||
|
||||
default:
|
||||
$name = '';
|
||||
}
|
||||
$name = match (strtoupper($value)) {
|
||||
'7-BIT', 'QUOTED-PRINTABLE', 'BASE64' => 'ENCODING',
|
||||
'WORK', 'HOME', 'PREF', 'DOM', 'INTL', 'POSTAL', 'PARCEL', 'VOICE', 'FAX', 'MSG', 'CELL', 'PAGER', 'BBS', 'MODEM', 'CAR', 'ISDN', 'VIDEO', 'AOL', 'APPLELINK', 'ATTMAIL', 'CIS', 'EWORLD', 'INTERNET', 'IBMMAIL', 'MCIMAIL', 'POWERSHARE', 'PRODIGY', 'TLX', 'X400', 'GIF', 'CGM', 'WMF', 'BMP', 'DIB', 'PICT', 'TIFF', 'PDF', 'PS', 'JPEG', 'MPEG', 'MPEG2', 'AVI', 'QTIME', 'WAVE', 'PCM', 'AIFF', 'X509', 'PGP' => 'TYPE',
|
||||
'INLINE', 'URL', 'CONTENT-ID', 'CID' => 'VALUE',
|
||||
default => '',
|
||||
};
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
|
@ -177,9 +106,9 @@ class Parameter extends Node
|
|||
{
|
||||
if (is_array($this->value)) {
|
||||
return implode(',', $this->value);
|
||||
} else {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -201,9 +130,9 @@ class Parameter extends Node
|
|||
return $this->value;
|
||||
} elseif (is_null($this->value)) {
|
||||
return [];
|
||||
} else {
|
||||
return [$this->value];
|
||||
}
|
||||
|
||||
return [$this->value];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -234,7 +163,8 @@ class Parameter extends Node
|
|||
{
|
||||
return in_array(
|
||||
strtolower($value),
|
||||
array_map('strtolower', (array) $this->value)
|
||||
array_map(strtolower(...), (array) $this->value),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -278,20 +208,19 @@ class Parameter extends Node
|
|||
// added + as well.
|
||||
if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) {
|
||||
return $out.$item;
|
||||
} else {
|
||||
// Enclosing in double-quotes, and using RFC6868 for encoding any
|
||||
// special characters
|
||||
$out .= '"'.strtr(
|
||||
$item,
|
||||
[
|
||||
'^' => '^^',
|
||||
"\n" => '^n',
|
||||
'"' => '^\'',
|
||||
]
|
||||
).'"';
|
||||
|
||||
return $out;
|
||||
}
|
||||
// Enclosing in double-quotes, and using RFC6868 for encoding any
|
||||
// special characters
|
||||
$out .= '"'.strtr(
|
||||
$item,
|
||||
[
|
||||
'^' => '^^',
|
||||
"\n" => '^n',
|
||||
'"' => '^\'',
|
||||
]
|
||||
).'"';
|
||||
|
||||
return $out;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@ class Json extends Parser
|
|||
/**
|
||||
* The input data.
|
||||
*/
|
||||
protected ?array $input;
|
||||
protected ?array $input = null;
|
||||
|
||||
/**
|
||||
* Root component.
|
||||
*/
|
||||
protected ?Document $root;
|
||||
protected ?Document $root = null;
|
||||
|
||||
/**
|
||||
* This method starts the parsing process.
|
||||
|
|
@ -61,16 +61,11 @@ class Json extends Parser
|
|||
$this->options = $options;
|
||||
}
|
||||
|
||||
switch ($this->input[0]) {
|
||||
case 'vcalendar':
|
||||
$this->root = new VCalendar([], false);
|
||||
break;
|
||||
case 'vcard':
|
||||
$this->root = new VCard([], false);
|
||||
break;
|
||||
default:
|
||||
throw new ParseException('The root component must either be a vcalendar, or a vcard');
|
||||
}
|
||||
$this->root = match ($this->input[0]) {
|
||||
'vcalendar' => new VCalendar([], false),
|
||||
'vcard' => new VCard([], false),
|
||||
default => throw new ParseException('The root component must either be a vcalendar, or a vcard'),
|
||||
};
|
||||
foreach ($this->input[1] as $prop) {
|
||||
$this->root->add($this->parseProperty($prop));
|
||||
}
|
||||
|
|
@ -97,17 +92,13 @@ class Json extends Parser
|
|||
$self = $this;
|
||||
|
||||
$properties = array_map(
|
||||
function ($jProp) use ($self) {
|
||||
return $self->parseProperty($jProp);
|
||||
},
|
||||
$self->parseProperty(...),
|
||||
$jComp[1]
|
||||
);
|
||||
|
||||
if (isset($jComp[2])) {
|
||||
$components = array_map(
|
||||
function ($jComp) use ($self) {
|
||||
return $self->parseComponent($jComp);
|
||||
},
|
||||
$self->parseComponent(...),
|
||||
$jComp[2]
|
||||
);
|
||||
} else {
|
||||
|
|
@ -134,7 +125,7 @@ class Json extends Parser
|
|||
$valueType,
|
||||
) = $jProp;
|
||||
|
||||
$propertyName = strtoupper($propertyName);
|
||||
$propertyName = strtoupper((string) $propertyName);
|
||||
|
||||
// This is the default class we would be using if we didn't know the
|
||||
// value type. We're using this value later in this function.
|
||||
|
|
@ -144,7 +135,7 @@ class Json extends Parser
|
|||
|
||||
$value = array_slice($jProp, 3);
|
||||
|
||||
$valueType = strtoupper($valueType);
|
||||
$valueType = strtoupper((string) $valueType);
|
||||
|
||||
if (isset($parameters['group'])) {
|
||||
$propertyName = $parameters['group'].'.'.$propertyName;
|
||||
|
|
@ -169,7 +160,7 @@ class Json extends Parser
|
|||
// If the value type we received (e.g.: TEXT) was not the default value
|
||||
// type for the given property (e.g.: BDAY), we need to add a VALUE=
|
||||
// parameter.
|
||||
if ($defaultPropertyClass !== get_class($prop)) {
|
||||
if ($defaultPropertyClass !== $prop::class) {
|
||||
$prop['VALUE'] = $valueType;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ use Sabre\VObject\Property;
|
|||
*/
|
||||
class MimeDir extends Parser
|
||||
{
|
||||
public const TOKEN_PROPNAME = 1;
|
||||
public const TOKEN_PROPVALUE = 2;
|
||||
public const TOKEN_PARAMNAME = 3;
|
||||
public const TOKEN_PARAMVALUE = 4;
|
||||
|
||||
/**
|
||||
* The input stream.
|
||||
*
|
||||
|
|
@ -36,7 +41,7 @@ class MimeDir extends Parser
|
|||
/**
|
||||
* Root component.
|
||||
*/
|
||||
protected ?Document $root;
|
||||
protected ?Document $root = null;
|
||||
|
||||
/**
|
||||
* By default, all input will be assumed to be UTF-8.
|
||||
|
|
@ -106,7 +111,7 @@ class MimeDir extends Parser
|
|||
*/
|
||||
public function setCharset(string $charset): void
|
||||
{
|
||||
if (!in_array($charset, self::$SUPPORTED_CHARSETS)) {
|
||||
if (!in_array($charset, self::$SUPPORTED_CHARSETS, true)) {
|
||||
throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: '.implode(', ', self::$SUPPORTED_CHARSETS).')');
|
||||
}
|
||||
$this->charset = $charset;
|
||||
|
|
@ -150,23 +155,18 @@ class MimeDir extends Parser
|
|||
|
||||
// BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF).
|
||||
// It's 0xEF 0xBB 0xBF in UTF-8 hex.
|
||||
if (3 <= strlen($line)
|
||||
if (3 <= strlen((string) $line)
|
||||
&& 0xEF === ord($line[0])
|
||||
&& 0xBB === ord($line[1])
|
||||
&& 0xBF === ord($line[2])) {
|
||||
$line = \substr($line, 3);
|
||||
$line = \substr((string) $line, 3);
|
||||
}
|
||||
|
||||
switch (strtoupper($line)) {
|
||||
case 'BEGIN:VCALENDAR':
|
||||
$class = VCalendar::$componentMap['VCALENDAR'];
|
||||
break;
|
||||
case 'BEGIN:VCARD':
|
||||
$class = VCard::$componentMap['VCARD'];
|
||||
break;
|
||||
default:
|
||||
throw new ParseException('This parser only supports VCARD and VCALENDAR files');
|
||||
}
|
||||
$class = match (strtoupper((string) $line)) {
|
||||
'BEGIN:VCALENDAR' => VCalendar::$componentMap['VCALENDAR'],
|
||||
'BEGIN:VCARD' => VCard::$componentMap['VCARD'],
|
||||
default => throw new ParseException('This parser only supports VCARD and VCALENDAR files'),
|
||||
};
|
||||
|
||||
$this->root = new $class([], false);
|
||||
|
||||
|
|
@ -174,10 +174,10 @@ class MimeDir extends Parser
|
|||
// Reading until we hit END:
|
||||
try {
|
||||
$line = $this->readLine();
|
||||
} catch (EofException $oEx) {
|
||||
} catch (EofException) {
|
||||
$line = 'END:'.$this->root->name;
|
||||
}
|
||||
if ('END:' === strtoupper(\substr($line, 0, 4))) {
|
||||
if ('END:' === strtoupper(\substr((string) $line, 0, 4))) {
|
||||
break;
|
||||
}
|
||||
$result = $this->parseLine($line);
|
||||
|
|
@ -186,7 +186,7 @@ class MimeDir extends Parser
|
|||
}
|
||||
}
|
||||
|
||||
$name = strtoupper(\substr($line, 4));
|
||||
$name = strtoupper(\substr((string) $line, 4));
|
||||
if ($name !== $this->root->name) {
|
||||
throw new ParseException('Invalid MimeDir file. expected: "END:'.$this->root->name.'" got: "END:'.$name.'"');
|
||||
}
|
||||
|
|
@ -215,7 +215,7 @@ class MimeDir extends Parser
|
|||
while (true) {
|
||||
// Reading until we hit END:
|
||||
$line = $this->readLine();
|
||||
if ('END:' === strtoupper(\substr($line, 0, 4))) {
|
||||
if ('END:' === strtoupper(\substr((string) $line, 0, 4))) {
|
||||
break;
|
||||
}
|
||||
$result = $this->parseLine($line);
|
||||
|
|
@ -224,22 +224,21 @@ class MimeDir extends Parser
|
|||
}
|
||||
}
|
||||
|
||||
$name = strtoupper(\substr($line, 4));
|
||||
$name = strtoupper(\substr((string) $line, 4));
|
||||
if ($name !== $component->name) {
|
||||
throw new ParseException('Invalid MimeDir file. expected: "END:'.$component->name.'" got: "END:'.$name.'"');
|
||||
}
|
||||
|
||||
return $component;
|
||||
} else {
|
||||
// Property reader
|
||||
$property = $this->readProperty($line);
|
||||
if (!$property) {
|
||||
// Ignored line
|
||||
return false;
|
||||
}
|
||||
|
||||
return $property;
|
||||
}
|
||||
// Property reader
|
||||
$property = $this->readProperty($line);
|
||||
if (!$property) {
|
||||
// Ignored line
|
||||
return false;
|
||||
}
|
||||
|
||||
return $property;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -360,6 +359,12 @@ class MimeDir extends Parser
|
|||
'value' => null,
|
||||
];
|
||||
|
||||
/*
|
||||
* Keep track on the last token we parsed in order to do
|
||||
* better error checking
|
||||
*/
|
||||
$lastToken = null;
|
||||
|
||||
$lastParam = null;
|
||||
|
||||
/*
|
||||
|
|
@ -385,10 +390,16 @@ class MimeDir extends Parser
|
|||
// option is set to ignore invalid lines, we ignore this line
|
||||
// This can happen when servers provide faulty data as iCloud
|
||||
// frequently does with X-APPLE-STRUCTURED-LOCATION
|
||||
$lastToken = self::TOKEN_PARAMVALUE;
|
||||
continue;
|
||||
}
|
||||
throw new ParseException('Invalid Mimedir file. Line starting at '.$this->startLine.' did not follow iCalendar/vCard conventions');
|
||||
}
|
||||
|
||||
if ('=' === $match[0][0] && self::TOKEN_PARAMNAME !== $lastToken) {
|
||||
throw new ParseException('Invalid Mimedir file. Line starting at '.$this->startLine.': Missing parameter name for parameter value "'.$match['paramValue'].'"');
|
||||
}
|
||||
|
||||
if (\is_null($property['parameters'][$lastParam])) {
|
||||
$property['parameters'][$lastParam] = $value;
|
||||
} elseif (is_array($property['parameters'][$lastParam])) {
|
||||
|
|
@ -402,21 +413,23 @@ class MimeDir extends Parser
|
|||
$value,
|
||||
];
|
||||
}
|
||||
$lastToken = self::TOKEN_PARAMVALUE;
|
||||
continue;
|
||||
}
|
||||
if (isset($match['paramName'])) {
|
||||
$lastParam = strtoupper($match['paramName']);
|
||||
if (!isset($property['parameters'][$lastParam])) {
|
||||
$property['parameters'][$lastParam] = null;
|
||||
}
|
||||
$property['parameters'][$lastParam] ??= null;
|
||||
$lastToken = self::TOKEN_PARAMNAME;
|
||||
continue;
|
||||
}
|
||||
if (isset($match['propValue'])) {
|
||||
$property['value'] = $match['propValue'];
|
||||
$lastToken = self::TOKEN_PROPVALUE;
|
||||
continue;
|
||||
}
|
||||
if (isset($match['name']) && $match['name']) {
|
||||
if (isset($match['name']) && 0 < strlen($match['name'])) {
|
||||
$property['name'] = strtoupper($match['name']);
|
||||
$lastToken = self::TOKEN_PROPNAME;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -425,10 +438,8 @@ class MimeDir extends Parser
|
|||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if (\is_null($property['value'])) {
|
||||
$property['value'] = '';
|
||||
}
|
||||
if (!$property['name']) {
|
||||
$property['value'] ??= '';
|
||||
if (!isset($property['name']) || 0 === strlen($property['name'])) {
|
||||
if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -671,7 +682,7 @@ class MimeDir extends Parser
|
|||
// missing a whitespace. So if 'forgiving' is turned on, we will take
|
||||
// those as well.
|
||||
if ($this->options & self::OPTION_FORGIVING) {
|
||||
while ('=' === \substr($value, -1) && $this->lineBuffer) {
|
||||
while (str_ends_with($value, '=') && $this->lineBuffer) {
|
||||
// Reading the line
|
||||
$this->readLine();
|
||||
// Grabbing the raw form
|
||||
|
|
|
|||
|
|
@ -30,11 +30,6 @@ abstract class Parser
|
|||
*/
|
||||
public const OPTION_IGNORE_INVALID_LINES = 2;
|
||||
|
||||
/**
|
||||
* Bitmask of parser options.
|
||||
*/
|
||||
protected int $options;
|
||||
|
||||
/**
|
||||
* Creates the parser.
|
||||
*
|
||||
|
|
@ -42,12 +37,11 @@ abstract class Parser
|
|||
*
|
||||
* @param int $options any parser options (OPTION constants)
|
||||
*/
|
||||
public function __construct($input = null, int $options = 0)
|
||||
public function __construct($input = null, protected int $options = 0)
|
||||
{
|
||||
if (!is_null($input)) {
|
||||
$this->setInput($input);
|
||||
}
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -28,17 +28,17 @@ class XML extends Parser
|
|||
/**
|
||||
* The input data.
|
||||
*/
|
||||
protected ?array $input;
|
||||
protected ?array $input = null;
|
||||
|
||||
/**
|
||||
* A pointer/reference to the input.
|
||||
*/
|
||||
private ?array $pointer;
|
||||
private ?array $pointer = null;
|
||||
|
||||
/**
|
||||
* Document, root component.
|
||||
*/
|
||||
protected ?Document $root;
|
||||
protected ?Document $root = null;
|
||||
|
||||
/**
|
||||
* Creates the parser.
|
||||
|
|
@ -112,7 +112,7 @@ class XML extends Parser
|
|||
*/
|
||||
protected function parseVCalendarComponents(Component $parentComponent): void
|
||||
{
|
||||
foreach ($this->pointer['value'] ?: [] as $children) {
|
||||
foreach ($this->pointer['value'] ?? [] as $children) {
|
||||
switch (static::getTagName($children['name'])) {
|
||||
case 'properties':
|
||||
$this->pointer = &$children['value'];
|
||||
|
|
@ -145,8 +145,8 @@ class XML extends Parser
|
|||
*/
|
||||
protected function parseProperties(Component $parentComponent, string $propertyNamePrefix = ''): void
|
||||
{
|
||||
foreach ($this->pointer ?: [] as $xmlProperty) {
|
||||
list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']);
|
||||
foreach ($this->pointer ?? [] as $xmlProperty) {
|
||||
[$namespace, $tagName] = SabreXml\Service::parseClarkNotation($xmlProperty['name']);
|
||||
|
||||
$propertyName = $tagName;
|
||||
$propertyValue = [];
|
||||
|
|
@ -322,7 +322,7 @@ class XML extends Parser
|
|||
*/
|
||||
protected function parseComponent(Component $parentComponent): void
|
||||
{
|
||||
$components = $this->pointer['value'] ?: [];
|
||||
$components = $this->pointer['value'] ?? [];
|
||||
|
||||
foreach ($components as $component) {
|
||||
$componentName = static::getTagName($component['name']);
|
||||
|
|
|
|||
|
|
@ -14,27 +14,8 @@ use Sabre\Xml;
|
|||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
abstract class Property extends Node
|
||||
abstract class Property extends Node implements \Stringable
|
||||
{
|
||||
/**
|
||||
* The root document.
|
||||
*/
|
||||
public ?Component $root;
|
||||
|
||||
/**
|
||||
* Property name.
|
||||
*
|
||||
* This will contain a string such as DTSTART, SUMMARY, FN.
|
||||
*/
|
||||
public ?string $name;
|
||||
|
||||
/**
|
||||
* Property group.
|
||||
*
|
||||
* This is only used in vcards
|
||||
*/
|
||||
public ?string $group;
|
||||
|
||||
/**
|
||||
* List of parameters.
|
||||
*/
|
||||
|
|
@ -75,13 +56,13 @@ abstract class Property extends Node
|
|||
* @param array $parameters List of parameters
|
||||
* @param string|null $group The vcard property group
|
||||
*/
|
||||
public function __construct(Component $root, ?string $name, $value = null, array $parameters = [], ?string $group = null, ?int $lineIndex = null, ?string $lineString = null)
|
||||
public function __construct(public ?Component $root, /**
|
||||
* Property name.
|
||||
*
|
||||
* This will contain a string such as DTSTART, SUMMARY, FN.
|
||||
*/
|
||||
public ?string $name, $value = null, array $parameters = [], public ?string $group = null, ?int $lineIndex = null, ?string $lineString = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->group = $group;
|
||||
|
||||
$this->root = $root;
|
||||
|
||||
foreach ($parameters as $k => $v) {
|
||||
$this->add($k, $v);
|
||||
}
|
||||
|
|
@ -123,7 +104,7 @@ abstract class Property extends Node
|
|||
public function getValue()
|
||||
{
|
||||
if (is_array($this->value)) {
|
||||
if (0 == count($this->value)) {
|
||||
if (0 === count($this->value)) {
|
||||
return null;
|
||||
} elseif (1 === count($this->value)) {
|
||||
return $this->value[0];
|
||||
|
|
@ -155,9 +136,9 @@ abstract class Property extends Node
|
|||
return [];
|
||||
} elseif (is_array($this->value)) {
|
||||
return $this->value;
|
||||
} else {
|
||||
return [$this->value];
|
||||
}
|
||||
|
||||
return [$this->value];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -242,7 +223,7 @@ abstract class Property extends Node
|
|||
);
|
||||
|
||||
// remove single space after last CRLF
|
||||
return \substr($str, 0, -1);
|
||||
return \substr((string) $str, 0, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -282,7 +263,7 @@ abstract class Property extends Node
|
|||
if ('VALUE' === $parameter->name) {
|
||||
continue;
|
||||
}
|
||||
$parameters[strtolower($parameter->name)] = $parameter->jsonSerialize();
|
||||
$parameters[strtolower((string) $parameter->name)] = $parameter->jsonSerialize();
|
||||
}
|
||||
// In jCard, we need to encode the property-group as a separate 'group'
|
||||
// parameter.
|
||||
|
|
@ -292,7 +273,7 @@ abstract class Property extends Node
|
|||
|
||||
return array_merge(
|
||||
[
|
||||
strtolower($this->name),
|
||||
strtolower((string) $this->name),
|
||||
(object) $parameters,
|
||||
strtolower($this->getValueType()),
|
||||
],
|
||||
|
|
@ -329,13 +310,13 @@ abstract class Property extends Node
|
|||
$parameters[] = $parameter;
|
||||
}
|
||||
|
||||
$writer->startElement(strtolower($this->name));
|
||||
$writer->startElement(strtolower((string) $this->name));
|
||||
|
||||
if (!empty($parameters)) {
|
||||
if ([] !== $parameters) {
|
||||
$writer->startElement('parameters');
|
||||
|
||||
foreach ($parameters as $parameter) {
|
||||
$writer->startElement(strtolower($parameter->name));
|
||||
$writer->startElement(strtolower((string) $parameter->name));
|
||||
$writer->write($parameter);
|
||||
$writer->endElement();
|
||||
}
|
||||
|
|
@ -520,7 +501,7 @@ abstract class Property extends Node
|
|||
}
|
||||
|
||||
// Checking if the property name does not contain any invalid bytes.
|
||||
if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) {
|
||||
if (!preg_match('/^([A-Z0-9-]+)$/', (string) $this->name)) {
|
||||
$warnings[] = [
|
||||
'level' => $options & self::REPAIR ? 1 : 3,
|
||||
'message' => 'The property name: '.$this->name.' contains invalid characters. Only A-Z, 0-9 and - are allowed',
|
||||
|
|
@ -572,7 +553,7 @@ abstract class Property extends Node
|
|||
}
|
||||
break;
|
||||
}
|
||||
if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) {
|
||||
if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding, true)) {
|
||||
$warnings[] = [
|
||||
'level' => 3,
|
||||
'message' => 'ENCODING='.strtoupper($encoding).' is not valid for this document type.',
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class Binary extends Property
|
|||
*/
|
||||
public function setRawMimeDirValue(string $val): void
|
||||
{
|
||||
$this->value = base64_decode($val);
|
||||
$this->value = base64_decode($val, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -62,7 +62,7 @@ class Binary extends Property
|
|||
*/
|
||||
public function getRawMimeDirValue(): string
|
||||
{
|
||||
return base64_encode($this->value);
|
||||
return base64_encode((string) $this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -83,7 +83,7 @@ class Binary extends Property
|
|||
*/
|
||||
public function getJsonValue(): array
|
||||
{
|
||||
return [base64_encode($this->getValue())];
|
||||
return [base64_encode((string) $this->getValue())];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -93,7 +93,7 @@ class Binary extends Property
|
|||
*/
|
||||
public function setJsonValue(array $value): void
|
||||
{
|
||||
$value = array_map('base64_decode', $value);
|
||||
$value = array_map(base64_decode(...), $value);
|
||||
parent::setJsonValue($value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,9 +56,7 @@ class Boolean extends Property
|
|||
public function setXmlValue(array $value): void
|
||||
{
|
||||
$value = array_map(
|
||||
function ($value) {
|
||||
return 'true' === $value;
|
||||
},
|
||||
fn ($value) => 'true' === $value,
|
||||
$value
|
||||
);
|
||||
parent::setXmlValue($value);
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class FloatValue extends Property
|
|||
*/
|
||||
public function getJsonValue(): array
|
||||
{
|
||||
$val = array_map('floatval', $this->getParts());
|
||||
$val = array_map(floatval(...), $this->getParts());
|
||||
|
||||
// Special-casing the GEO property.
|
||||
//
|
||||
|
|
@ -86,7 +86,7 @@ class FloatValue extends Property
|
|||
*/
|
||||
public function setXmlValue(array $value): void
|
||||
{
|
||||
$value = array_map('floatval', $value);
|
||||
$value = array_map(floatval(...), $value);
|
||||
parent::setXmlValue($value);
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ class FloatValue extends Property
|
|||
// See:
|
||||
// http://tools.ietf.org/html/rfc6321#section-3.4.1.2
|
||||
if ('GEO' === $this->name) {
|
||||
$value = array_map('floatval', $this->getParts());
|
||||
$value = array_map(floatval(...), $this->getParts());
|
||||
|
||||
$writer->writeElement('latitude', $value[0]);
|
||||
$writer->writeElement('longitude', $value[1]);
|
||||
|
|
|
|||
|
|
@ -43,10 +43,10 @@ class CalAddress extends Text
|
|||
public function getNormalizedValue(): string
|
||||
{
|
||||
$input = $this->getValue();
|
||||
if (!strpos($input, ':')) {
|
||||
if (!strpos((string) $input, ':')) {
|
||||
return $input;
|
||||
}
|
||||
list($schema, $everythingElse) = explode(':', $input, 2);
|
||||
[$schema, $everythingElse] = explode(':', (string) $input, 2);
|
||||
$schema = strtolower($schema);
|
||||
if ('mailto' === $schema) {
|
||||
$everythingElse = strtolower($everythingElse);
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ class DateTime extends Property
|
|||
!$this->hasTime()
|
||||
|| (
|
||||
!isset($this['TZID'])
|
||||
&& false === strpos($this->getValue(), 'Z')
|
||||
&& !str_contains((string) $this->getValue(), 'Z')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ class DateTime extends Property
|
|||
}
|
||||
if (is_null($tz)) {
|
||||
$tz = $d->getTimeZone();
|
||||
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00']);
|
||||
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00'], true);
|
||||
if (!$isUtc) {
|
||||
$this->offsetSet('TZID', $tz->getName());
|
||||
}
|
||||
|
|
@ -257,15 +257,15 @@ class DateTime extends Property
|
|||
$isFloating = $this->isFloating();
|
||||
|
||||
$tz = $dts[0]->getTimeZone();
|
||||
$isUtc = !$isFloating && in_array($tz->getName(), ['UTC', 'GMT', 'Z']);
|
||||
$isUtc = !$isFloating && in_array($tz->getName(), ['UTC', 'GMT', 'Z'], true);
|
||||
|
||||
return array_map(
|
||||
function (\DateTimeInterface $dt) use ($hasTime, $isUtc) {
|
||||
if ($hasTime) {
|
||||
return $dt->format('Y-m-d\\TH:i:s').($isUtc ? 'Z' : '');
|
||||
} else {
|
||||
return $dt->format('Y-m-d');
|
||||
}
|
||||
|
||||
return $dt->format('Y-m-d');
|
||||
},
|
||||
$dts
|
||||
);
|
||||
|
|
@ -286,9 +286,7 @@ class DateTime extends Property
|
|||
// those.
|
||||
$this->setValue(
|
||||
array_map(
|
||||
function ($item) {
|
||||
return strtr($item, [':' => '', '-' => '']);
|
||||
},
|
||||
fn ($item) => strtr($item, [':' => '', '-' => '']),
|
||||
$value
|
||||
)
|
||||
);
|
||||
|
|
@ -306,7 +304,7 @@ class DateTime extends Property
|
|||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
parent::offsetSet($offset, $value);
|
||||
if ('VALUE' !== strtoupper($offset)) {
|
||||
if ('VALUE' !== strtoupper((string) $offset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -347,7 +345,7 @@ class DateTime extends Property
|
|||
DateTimeParser::parseDateTime($value);
|
||||
break;
|
||||
}
|
||||
} catch (InvalidDataException $e) {
|
||||
} catch (InvalidDataException) {
|
||||
$messages[] = [
|
||||
'level' => 3,
|
||||
'message' => 'The supplied value ('.$value.') is not a correct '.$valueType,
|
||||
|
|
|
|||
|
|
@ -64,14 +64,17 @@ class Period extends Property
|
|||
public function setJsonValue(array $value): void
|
||||
{
|
||||
$value = array_map(
|
||||
function ($item) {
|
||||
return strtr(implode('/', $item), [':' => '', '-' => '']);
|
||||
},
|
||||
fn ($item) => strtr(implode('/', $item), [':' => '', '-' => '']),
|
||||
$value
|
||||
);
|
||||
parent::setJsonValue($value);
|
||||
}
|
||||
|
||||
public function appendUtc(string $strDate)
|
||||
{
|
||||
return !str_ends_with($strDate, 'Z') ? '' : 'Z';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value, in the format it should be encoded for json.
|
||||
*
|
||||
|
|
@ -83,21 +86,21 @@ class Period extends Property
|
|||
{
|
||||
$return = [];
|
||||
foreach ($this->getParts() as $item) {
|
||||
list($start, $end) = explode('/', $item, 2);
|
||||
[$start, $end] = explode('/', (string) $item, 2);
|
||||
|
||||
$start = DateTimeParser::parseDateTime($start);
|
||||
$startDt = DateTimeParser::parseDateTime($start)->format('Y-m-d\\TH:i:s').$this->appendUtc($start);
|
||||
|
||||
// This is a duration value.
|
||||
if ('P' === $end[0]) {
|
||||
$return[] = [
|
||||
$start->format('Y-m-d\\TH:i:s'),
|
||||
$startDt,
|
||||
$end,
|
||||
];
|
||||
} else {
|
||||
$end = DateTimeParser::parseDateTime($end);
|
||||
$endDt = DateTimeParser::parseDateTime($end)->format('Y-m-d\\TH:i:s').$this->appendUtc($end);
|
||||
$return[] = [
|
||||
$start->format('Y-m-d\\TH:i:s'),
|
||||
$end->format('Y-m-d\\TH:i:s'),
|
||||
$startDt,
|
||||
$endDt,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,14 +29,14 @@ class Recur extends Property
|
|||
/**
|
||||
* Reference to the parent object, if this is not the top object.
|
||||
*/
|
||||
public ?Node $parent;
|
||||
public ?Node $parent = null;
|
||||
|
||||
/**
|
||||
* Updates the current value.
|
||||
*
|
||||
* This may be either a single, or multiple strings in an array.
|
||||
*
|
||||
* @param string|array $value
|
||||
* @param string|array|object $value
|
||||
*/
|
||||
public function setValue($value): void
|
||||
{
|
||||
|
|
@ -52,17 +52,17 @@ class Recur extends Property
|
|||
$v = strtoupper($v);
|
||||
|
||||
// The value had multiple sub-values
|
||||
if (false !== strpos($v, ',')) {
|
||||
if (str_contains($v, ',')) {
|
||||
$v = explode(',', $v);
|
||||
}
|
||||
if (0 === strcmp($k, 'until')) {
|
||||
if (0 === strcmp((string) $k, 'until')) {
|
||||
$v = strtr($v, [':' => '', '-' => '']);
|
||||
}
|
||||
} elseif (is_array($v)) {
|
||||
$v = array_map('strtoupper', $v);
|
||||
$v = array_map(strtoupper(...), $v);
|
||||
}
|
||||
|
||||
$newVal[strtoupper($k)] = $v;
|
||||
$newVal[strtoupper((string) $k)] = $v;
|
||||
}
|
||||
$this->value = $newVal;
|
||||
} elseif (is_string($value)) {
|
||||
|
|
@ -151,13 +151,13 @@ class Recur extends Property
|
|||
{
|
||||
$values = [];
|
||||
foreach ($this->getParts() as $k => $v) {
|
||||
if (0 === strcmp($k, 'UNTIL')) {
|
||||
if (0 === strcmp((string) $k, 'UNTIL')) {
|
||||
$date = new DateTime($this->root, null, $v);
|
||||
$values[strtolower($k)] = $date->getJsonValue()[0];
|
||||
} elseif (0 === strcmp($k, 'COUNT')) {
|
||||
$values[strtolower($k)] = intval($v);
|
||||
$values[strtolower((string) $k)] = $date->getJsonValue()[0];
|
||||
} elseif (0 === strcmp((string) $k, 'COUNT')) {
|
||||
$values[strtolower((string) $k)] = intval($v);
|
||||
} else {
|
||||
$values[strtolower($k)] = $v;
|
||||
$values[strtolower((string) $k)] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ class Recur extends Property
|
|||
$newValue = [];
|
||||
foreach (explode(';', $value) as $part) {
|
||||
// Skipping empty parts.
|
||||
if (empty($part)) {
|
||||
if ('' === $part) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -196,10 +196,10 @@ class Recur extends Property
|
|||
throw new InvalidDataException('The supplied iCalendar RRULE part is incorrect: '.$part);
|
||||
}
|
||||
|
||||
list($partName, $partValue) = $parts;
|
||||
[$partName, $partValue] = $parts;
|
||||
|
||||
// The value itself had multiple values..
|
||||
if (false !== strpos($partValue, ',')) {
|
||||
if (str_contains($partValue, ',')) {
|
||||
$partValue = explode(',', $partValue);
|
||||
}
|
||||
$newValue[$partName] = $partValue;
|
||||
|
|
@ -243,7 +243,7 @@ class Recur extends Property
|
|||
if ($repair) {
|
||||
unset($values[$key]);
|
||||
}
|
||||
} elseif ('BYMONTH' == $key) {
|
||||
} elseif ('BYMONTH' === $key) {
|
||||
$byMonth = (array) $value;
|
||||
foreach ($byMonth as $i => $v) {
|
||||
if (!is_numeric($v) || (int) $v < 1 || (int) $v > 12) {
|
||||
|
|
@ -262,13 +262,13 @@ class Recur extends Property
|
|||
}
|
||||
}
|
||||
// if there is no valid entry left, remove the whole value
|
||||
if (is_array($value) && empty($values[$key])) {
|
||||
if (is_array($value) && ([] === $values[$key])) {
|
||||
unset($values[$key]);
|
||||
}
|
||||
} elseif ('BYWEEKNO' == $key) {
|
||||
} elseif ('BYWEEKNO' === $key) {
|
||||
$byWeekNo = (array) $value;
|
||||
foreach ($byWeekNo as $i => $v) {
|
||||
if (!is_numeric($v) || (int) $v < -53 || 0 == (int) $v || (int) $v > 53) {
|
||||
if (!is_numeric($v) || (int) $v < -53 || 0 === (int) $v || (int) $v > 53) {
|
||||
$warnings[] = [
|
||||
'level' => $repair ? 1 : 3,
|
||||
'message' => 'BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!',
|
||||
|
|
@ -284,13 +284,13 @@ class Recur extends Property
|
|||
}
|
||||
}
|
||||
// if there is no valid entry left, remove the whole value
|
||||
if (is_array($value) && empty($values[$key])) {
|
||||
if (is_array($value) && ([] === $values[$key])) {
|
||||
unset($values[$key]);
|
||||
}
|
||||
} elseif ('BYYEARDAY' == $key) {
|
||||
} elseif ('BYYEARDAY' === $key) {
|
||||
$byYearDay = (array) $value;
|
||||
foreach ($byYearDay as $i => $v) {
|
||||
if (!is_numeric($v) || (int) $v < -366 || 0 == (int) $v || (int) $v > 366) {
|
||||
if (!is_numeric($v) || (int) $v < -366 || 0 === (int) $v || (int) $v > 366) {
|
||||
$warnings[] = [
|
||||
'level' => $repair ? 1 : 3,
|
||||
'message' => 'BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!',
|
||||
|
|
@ -306,7 +306,7 @@ class Recur extends Property
|
|||
}
|
||||
}
|
||||
// if there is no valid entry left, remove the whole value
|
||||
if (is_array($value) && empty($values[$key])) {
|
||||
if (is_array($value) && ([] === $values[$key])) {
|
||||
unset($values[$key]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class IntegerValue extends Property
|
|||
*/
|
||||
public function setXmlValue(array $value): void
|
||||
{
|
||||
$value = array_map('intval', $value);
|
||||
$value = array_map(intval(...), $value);
|
||||
parent::setXmlValue($value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class Text extends Property
|
|||
// 2. structured value properties
|
||||
//
|
||||
// The former is always separated by a comma, the latter by semicolon.
|
||||
if (in_array($name, $this->structuredValues)) {
|
||||
if (in_array($name, $this->structuredValues, true)) {
|
||||
$this->delimiter = ';';
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ class Text extends Property
|
|||
// Structured text values should always be returned as a single
|
||||
// array-item. Multi-value text should be returned as multiple items in
|
||||
// the top-array.
|
||||
if (in_array($this->name, $this->structuredValues)) {
|
||||
if (in_array($this->name, $this->structuredValues, true)) {
|
||||
return [$this->getParts()];
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ class Text extends Property
|
|||
|
||||
// If the resulting value contains a \n, we must encode it as
|
||||
// quoted-printable.
|
||||
if (false !== \strpos($val, "\n")) {
|
||||
if (str_contains((string) $val, "\n")) {
|
||||
$str .= ';ENCODING=QUOTED-PRINTABLE:';
|
||||
$lastLine = $str;
|
||||
$out = '';
|
||||
|
|
@ -222,13 +222,13 @@ class Text extends Property
|
|||
// encode newlines for us. Specifically, the \r\n sequence must in
|
||||
// vcards be encoded as =0D=OA and we must insert soft-newlines
|
||||
// every 75 bytes.
|
||||
for ($ii = 0; $ii < \strlen($val); ++$ii) {
|
||||
for ($ii = 0; $ii < \strlen((string) $val); ++$ii) {
|
||||
$ord = \ord($val[$ii]);
|
||||
// These characters are encoded as themselves.
|
||||
if ($ord >= 32 && $ord <= 126) {
|
||||
$lastLine .= $val[$ii];
|
||||
} else {
|
||||
$lastLine .= '='.\strtoupper(\bin2hex($val[$ii]));
|
||||
$lastLine .= '='.\strtoupper(\bin2hex((string) $val[$ii]));
|
||||
}
|
||||
if (\strlen($lastLine) >= 75) {
|
||||
// Soft line break
|
||||
|
|
@ -241,22 +241,21 @@ class Text extends Property
|
|||
}
|
||||
|
||||
return $out;
|
||||
} else {
|
||||
$str .= ':'.$val;
|
||||
}
|
||||
$str .= ':'.$val;
|
||||
|
||||
$str = \preg_replace(
|
||||
'/(
|
||||
$str = \preg_replace(
|
||||
'/(
|
||||
(?:^.)? # 1 additional byte in first line because of missing single space (see next line)
|
||||
.{1,74} # max 75 bytes per line (1 byte is used for a single space added after every CRLF)
|
||||
(?![\x80-\xbf]) # prevent splitting multibyte characters
|
||||
)/x',
|
||||
"$1\r\n ",
|
||||
$str
|
||||
);
|
||||
"$1\r\n ",
|
||||
$str
|
||||
);
|
||||
|
||||
// remove single space after last CRLF
|
||||
return \substr($str, 0, -1);
|
||||
}
|
||||
// remove single space after last CRLF
|
||||
return \substr((string) $str, 0, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -271,7 +270,7 @@ class Text extends Property
|
|||
foreach ($items as $i => $item) {
|
||||
$writer->writeElement(
|
||||
$item,
|
||||
!empty($values[$i]) ? $values[$i] : null
|
||||
('' !== $values[$i] && [] !== $values[$i]) ? $values[$i] : null
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class Time extends Text
|
|||
$timeStr .= 'Z';
|
||||
} else {
|
||||
$timeStr .=
|
||||
preg_replace('/([0-9]{2})([0-9]{2})$/', '$1:$2', $parts['timezone']);
|
||||
preg_replace('/([0-9]{2})([0-9]{2})$/', '$1:$2', (string) $parts['timezone']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -118,9 +118,7 @@ class Time extends Text
|
|||
public function setXmlValue(array $value): void
|
||||
{
|
||||
$value = array_map(
|
||||
function ($value) {
|
||||
return str_replace(':', '', $value);
|
||||
},
|
||||
fn ($value) => str_replace(':', '', $value),
|
||||
$value
|
||||
);
|
||||
parent::setXmlValue($value);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class Uri extends Text
|
|||
public function parameters(): array
|
||||
{
|
||||
$parameters = parent::parameters();
|
||||
if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'])) {
|
||||
if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'], true)) {
|
||||
// If we are encoding a URI value, and this URI value has no
|
||||
// VALUE=URI parameter, we add it anyway.
|
||||
//
|
||||
|
|
@ -74,14 +74,10 @@ class Uri extends Text
|
|||
$matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
$newVal = '';
|
||||
foreach ($matches as $match) {
|
||||
switch ($match) {
|
||||
case '\:':
|
||||
$newVal .= ':';
|
||||
break;
|
||||
default:
|
||||
$newVal .= $match;
|
||||
break;
|
||||
}
|
||||
match ($match) {
|
||||
'\:' => $newVal .= ':',
|
||||
default => $newVal .= $match,
|
||||
};
|
||||
}
|
||||
$this->value = $newVal;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -42,9 +42,7 @@ class UtcOffset extends Text
|
|||
public function setJsonValue(array $value): void
|
||||
{
|
||||
$value = array_map(
|
||||
function ($value) {
|
||||
return str_replace(':', '', $value);
|
||||
},
|
||||
fn ($value) => str_replace(':', '', $value),
|
||||
$value
|
||||
);
|
||||
parent::setJsonValue($value);
|
||||
|
|
@ -58,10 +56,8 @@ class UtcOffset extends Text
|
|||
public function getJsonValue(): array
|
||||
{
|
||||
return array_map(
|
||||
function ($value) {
|
||||
return substr($value, 0, -2).':'.
|
||||
substr($value, -2);
|
||||
},
|
||||
fn ($value) => substr((string) $value, 0, -2).':'.
|
||||
substr((string) $value, -2),
|
||||
parent::getJsonValue()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class DateAndOrTime extends Property
|
|||
public function setDateTime(\DateTimeInterface $dt): void
|
||||
{
|
||||
$tz = $dt->getTimeZone();
|
||||
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z']);
|
||||
$isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z'], true);
|
||||
|
||||
if ($isUtc) {
|
||||
$value = $dt->format('Ymd\\THis\\Z');
|
||||
|
|
@ -232,14 +232,10 @@ class DateAndOrTime extends Property
|
|||
$value = '';
|
||||
|
||||
// $d = defined
|
||||
$d = function ($part) use ($parts): bool {
|
||||
return !is_null($parts[$part]);
|
||||
};
|
||||
$d = (fn ($part): bool => !is_null($parts[$part]));
|
||||
|
||||
// $r = read
|
||||
$r = function ($part) use ($parts) {
|
||||
return $parts[$part];
|
||||
};
|
||||
$r = (fn ($part) => $parts[$part]);
|
||||
|
||||
// From the Relax NG Schema.
|
||||
//
|
||||
|
|
@ -341,7 +337,7 @@ class DateAndOrTime extends Property
|
|||
|
||||
try {
|
||||
DateTimeParser::parseVCardDateTime($value);
|
||||
} catch (InvalidDataException $e) {
|
||||
} catch (InvalidDataException) {
|
||||
$messages[] = [
|
||||
'level' => 3,
|
||||
'message' => 'The supplied value ('.$value.') is not a correct DATE-AND-OR-TIME property',
|
||||
|
|
|
|||
|
|
@ -91,9 +91,7 @@ class EventIterator implements \Iterator
|
|||
*/
|
||||
public function __construct($input, ?string $uid = null, ?\DateTimeZone $timeZone = null)
|
||||
{
|
||||
if (is_null($timeZone)) {
|
||||
$timeZone = new \DateTimeZone('UTC');
|
||||
}
|
||||
$timeZone ??= new \DateTimeZone('UTC');
|
||||
$this->timeZone = $timeZone;
|
||||
|
||||
if (is_array($input)) {
|
||||
|
|
@ -164,8 +162,12 @@ class EventIterator implements \Iterator
|
|||
}
|
||||
|
||||
if (isset($this->masterEvent->RDATE)) {
|
||||
$rdateValues = [];
|
||||
foreach ($this->masterEvent->RDATE as $rdate) {
|
||||
$rdateValues = array_merge($rdateValues, $rdate->getParts());
|
||||
}
|
||||
$this->recurIterator = new RDateIterator(
|
||||
$this->masterEvent->RDATE->getParts(),
|
||||
$rdateValues,
|
||||
$this->startDate
|
||||
);
|
||||
} elseif (isset($this->masterEvent->RRULE)) {
|
||||
|
|
@ -228,11 +230,10 @@ class EventIterator implements \Iterator
|
|||
}
|
||||
if ($this->currentOverriddenEvent && $this->currentOverriddenEvent->DTEND) {
|
||||
return $this->currentOverriddenEvent->DTEND->getDateTime($this->timeZone);
|
||||
} else {
|
||||
$end = clone $this->currentDate;
|
||||
|
||||
return $end->modify('+'.$this->eventDuration.' seconds');
|
||||
}
|
||||
$end = clone $this->currentDate;
|
||||
|
||||
return $end->modify('+'.$this->eventDuration.' seconds');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -26,9 +26,13 @@ class RDateIterator implements \Iterator
|
|||
*
|
||||
* @param string|array $rrule
|
||||
*/
|
||||
public function __construct($rrule, \DateTimeInterface $start)
|
||||
public function __construct($rrule, /**
|
||||
* The reference start date/time for the rrule.
|
||||
*
|
||||
* All calculations are based on this initial date.
|
||||
*/
|
||||
protected \DateTimeInterface $startDate)
|
||||
{
|
||||
$this->startDate = $start;
|
||||
$this->parseRDate($rrule);
|
||||
$this->currentDate = clone $this->startDate;
|
||||
}
|
||||
|
|
@ -117,13 +121,6 @@ class RDateIterator implements \Iterator
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The reference start date/time for the rrule.
|
||||
*
|
||||
* All calculations are based on this initial date.
|
||||
*/
|
||||
protected \DateTimeInterface $startDate;
|
||||
|
||||
/**
|
||||
* The date of the current iteration. You can get this by calling
|
||||
* ->current().
|
||||
|
|
|
|||
|
|
@ -37,9 +37,13 @@ class RRuleIterator implements \Iterator
|
|||
*
|
||||
* @throws InvalidDataException
|
||||
*/
|
||||
public function __construct($rrule, \DateTimeInterface $start)
|
||||
public function __construct($rrule, /**
|
||||
* The reference start date/time for the rrule.
|
||||
*
|
||||
* All calculations are based on this initial date.
|
||||
*/
|
||||
protected \DateTimeInterface $startDate)
|
||||
{
|
||||
$this->startDate = $start;
|
||||
$this->parseRRule($rrule);
|
||||
$this->currentDate = clone $this->startDate;
|
||||
}
|
||||
|
|
@ -146,13 +150,6 @@ class RRuleIterator implements \Iterator
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The reference start date/time for the rrule.
|
||||
*
|
||||
* All calculations are based on this initial date.
|
||||
*/
|
||||
protected \DateTimeInterface $startDate;
|
||||
|
||||
/**
|
||||
* The date of the current iteration. You can get this by calling
|
||||
* ->current().
|
||||
|
|
@ -357,23 +354,23 @@ class RRuleIterator implements \Iterator
|
|||
}
|
||||
|
||||
$recurrenceHours = [];
|
||||
if (!empty($this->byHour)) {
|
||||
if ($this->byHour) {
|
||||
$recurrenceHours = $this->getHours();
|
||||
}
|
||||
|
||||
$recurrenceDays = [];
|
||||
if (!empty($this->byDay)) {
|
||||
if ($this->byDay) {
|
||||
$recurrenceDays = $this->getDays();
|
||||
}
|
||||
|
||||
$recurrenceMonths = [];
|
||||
if (!empty($this->byMonth)) {
|
||||
if ($this->byMonth) {
|
||||
$recurrenceMonths = $this->getMonths();
|
||||
}
|
||||
|
||||
do {
|
||||
if ($this->byHour) {
|
||||
if ('23' == $this->currentDate->format('G')) {
|
||||
if ('23' === $this->currentDate->format('G')) {
|
||||
// to obey the interval rule
|
||||
$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' days');
|
||||
}
|
||||
|
|
@ -398,9 +395,9 @@ class RRuleIterator implements \Iterator
|
|||
return;
|
||||
}
|
||||
} while (
|
||||
($this->byDay && !in_array($currentDay, $recurrenceDays))
|
||||
|| ($this->byHour && !in_array($currentHour, $recurrenceHours))
|
||||
|| ($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
|
||||
($this->byDay && !in_array((int) $currentDay, $recurrenceDays, true))
|
||||
|| ($this->byHour && !in_array($currentHour, $recurrenceHours, true))
|
||||
|| ($this->byMonth && !in_array($currentMonth, $recurrenceMonths, true))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -442,18 +439,18 @@ class RRuleIterator implements \Iterator
|
|||
$currentHour = (int) $this->currentDate->format('G');
|
||||
|
||||
// We need to roll over to the next week
|
||||
if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) {
|
||||
if ($currentDay === $firstDay && (!$this->byHour || 0 === $currentHour)) {
|
||||
$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' weeks');
|
||||
|
||||
// We need to go to the first day of this week, but only if we
|
||||
// are not already on this first day of this week.
|
||||
if ($this->currentDate->format('w') != $firstDay) {
|
||||
if ((int) $this->currentDate->format('w') !== $firstDay) {
|
||||
$this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]);
|
||||
}
|
||||
}
|
||||
|
||||
// We have a match
|
||||
} while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
|
||||
} while (($this->byDay && !in_array($currentDay, $recurrenceDays, true)) || ($this->byHour && !in_array((string) $currentHour, $recurrenceHours, true)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -476,14 +473,14 @@ class RRuleIterator implements \Iterator
|
|||
++$increase;
|
||||
$tempDate = clone $this->currentDate;
|
||||
$tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months '.$this->startTime());
|
||||
} while ($tempDate->format('j') != $currentDayOfMonth);
|
||||
} while ($tempDate->format('j') !== $currentDayOfMonth);
|
||||
$this->currentDate = $tempDate;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$occurrence = -1;
|
||||
$selectedOccurrence = -1;
|
||||
while (true) {
|
||||
$occurrences = $this->getMonthlyOccurrences();
|
||||
|
||||
|
|
@ -491,6 +488,7 @@ class RRuleIterator implements \Iterator
|
|||
// The first occurrence that's higher than the current
|
||||
// day of the month wins.
|
||||
if ($occurrence > $currentDayOfMonth) {
|
||||
$selectedOccurrence = $occurrence;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
|
@ -533,7 +531,7 @@ class RRuleIterator implements \Iterator
|
|||
$this->currentDate = $this->currentDate->setDate(
|
||||
(int) $this->currentDate->format('Y'),
|
||||
(int) $this->currentDate->format('n'),
|
||||
(int) $occurrence
|
||||
(int) $selectedOccurrence
|
||||
)->modify($this->startTime());
|
||||
}
|
||||
|
||||
|
|
@ -547,9 +545,9 @@ class RRuleIterator implements \Iterator
|
|||
$currentDayOfMonth = $this->currentDate->format('j');
|
||||
|
||||
// No sub-rules, so we just advance by year
|
||||
if (empty($this->byMonth)) {
|
||||
if (!$this->byMonth) {
|
||||
// Unless it was a leap day!
|
||||
if (2 == $currentMonth && 29 == $currentDayOfMonth) {
|
||||
if (2 === (int) $currentMonth && 29 === (int) $currentDayOfMonth) {
|
||||
$counter = 0;
|
||||
do {
|
||||
++$counter;
|
||||
|
|
@ -563,7 +561,7 @@ class RRuleIterator implements \Iterator
|
|||
// functions instead.
|
||||
$nextDate = clone $this->currentDate;
|
||||
$nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years');
|
||||
} while (2 != $nextDate->format('n'));
|
||||
} while (2 !== (int) $nextDate->format('n'));
|
||||
|
||||
$this->currentDate = $nextDate;
|
||||
|
||||
|
|
@ -634,7 +632,7 @@ class RRuleIterator implements \Iterator
|
|||
$date = $date->sub(new \DateInterval('P'.abs($byYearDay + 1).'D'));
|
||||
}
|
||||
|
||||
if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) {
|
||||
if ($date > $this->currentDate && in_array((int) $date->format('N'), $dayOffsets, true)) {
|
||||
$checkDates[] = $date;
|
||||
}
|
||||
}
|
||||
|
|
@ -665,7 +663,7 @@ class RRuleIterator implements \Iterator
|
|||
// If we got a byDay or getMonthDay filter, we must first expand
|
||||
// further.
|
||||
if ($this->byDay || $this->byMonthDay) {
|
||||
$occurrence = -1;
|
||||
$selectedOccurrence = -1;
|
||||
while (true) {
|
||||
$occurrences = $this->getMonthlyOccurrences();
|
||||
|
||||
|
|
@ -677,7 +675,8 @@ class RRuleIterator implements \Iterator
|
|||
if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
|
||||
// only consider byMonth matches,
|
||||
// otherwise, we don't follow RRule correctly
|
||||
if (in_array($currentMonth, $this->byMonth)) {
|
||||
if (in_array((string) $currentMonth, $this->byMonth, true)) {
|
||||
$selectedOccurrence = $occurrence;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
|
@ -693,12 +692,12 @@ class RRuleIterator implements \Iterator
|
|||
$currentYear += $this->interval;
|
||||
$currentMonth = 1;
|
||||
}
|
||||
} while (!in_array($currentMonth, $this->byMonth));
|
||||
} while (!in_array((string) $currentMonth, $this->byMonth, true));
|
||||
|
||||
$this->currentDate = $this->currentDate->setDate(
|
||||
(int) $currentYear,
|
||||
(int) $currentMonth,
|
||||
(int) $currentDayOfMonth
|
||||
$currentMonth,
|
||||
$currentDayOfMonth
|
||||
);
|
||||
|
||||
// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
|
||||
|
|
@ -714,28 +713,25 @@ class RRuleIterator implements \Iterator
|
|||
$this->currentDate = $this->currentDate->setDate(
|
||||
(int) $currentYear,
|
||||
(int) $currentMonth,
|
||||
(int) $occurrence
|
||||
)->modify($this->startTime());
|
||||
|
||||
return;
|
||||
} else {
|
||||
// These are the 'byMonth' rules, if there are no byDay or
|
||||
// byMonthDay sub-rules.
|
||||
do {
|
||||
++$currentMonth;
|
||||
if ($currentMonth > 12) {
|
||||
$currentYear += $this->interval;
|
||||
$currentMonth = 1;
|
||||
}
|
||||
} while (!in_array($currentMonth, $this->byMonth));
|
||||
$this->currentDate = $this->currentDate->setDate(
|
||||
(int) $currentYear,
|
||||
(int) $currentMonth,
|
||||
(int) $currentDayOfMonth
|
||||
(int) $selectedOccurrence
|
||||
)->modify($this->startTime());
|
||||
|
||||
return;
|
||||
}
|
||||
// These are the 'byMonth' rules, if there are no byDay or
|
||||
// byMonthDay sub-rules.
|
||||
do {
|
||||
++$currentMonth;
|
||||
if ($currentMonth > 12) {
|
||||
$currentYear += $this->interval;
|
||||
$currentMonth = 1;
|
||||
}
|
||||
} while (!in_array((string) $currentMonth, $this->byMonth, true));
|
||||
$this->currentDate = $this->currentDate->setDate(
|
||||
(int) $currentYear,
|
||||
$currentMonth,
|
||||
(int) $currentDayOfMonth
|
||||
)->modify($this->startTime());
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
|
@ -755,13 +751,14 @@ class RRuleIterator implements \Iterator
|
|||
}
|
||||
|
||||
foreach ($rrule as $key => $value) {
|
||||
$key = strtoupper($key);
|
||||
$key = strtoupper((string) $key);
|
||||
switch ($key) {
|
||||
case 'FREQ':
|
||||
$value = strtolower($value);
|
||||
$value = strtolower((string) $value);
|
||||
if (!in_array(
|
||||
$value,
|
||||
['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly']
|
||||
['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'],
|
||||
true
|
||||
)) {
|
||||
throw new InvalidDataException('Unknown value for FREQ='.strtoupper($value));
|
||||
}
|
||||
|
|
@ -809,7 +806,7 @@ class RRuleIterator implements \Iterator
|
|||
case 'BYDAY':
|
||||
$value = (array) $value;
|
||||
foreach ($value as $part) {
|
||||
if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
|
||||
if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $#xi', (string) $part)) {
|
||||
throw new InvalidDataException('Invalid part in BYDAY clause: '.$part);
|
||||
}
|
||||
}
|
||||
|
|
@ -823,7 +820,7 @@ class RRuleIterator implements \Iterator
|
|||
case 'BYYEARDAY':
|
||||
$this->byYearDay = (array) $value;
|
||||
foreach ($this->byYearDay as $byYearDay) {
|
||||
if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 == (int) $byYearDay || (int) $byYearDay > 366) {
|
||||
if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 === (int) $byYearDay || (int) $byYearDay > 366) {
|
||||
throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!');
|
||||
}
|
||||
}
|
||||
|
|
@ -832,7 +829,7 @@ class RRuleIterator implements \Iterator
|
|||
case 'BYWEEKNO':
|
||||
$this->byWeekNo = (array) $value;
|
||||
foreach ($this->byWeekNo as $byWeekNo) {
|
||||
if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 == (int) $byWeekNo || (int) $byWeekNo > 53) {
|
||||
if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 === (int) $byWeekNo || (int) $byWeekNo > 53) {
|
||||
throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!');
|
||||
}
|
||||
}
|
||||
|
|
@ -852,7 +849,7 @@ class RRuleIterator implements \Iterator
|
|||
break;
|
||||
|
||||
case 'WKST':
|
||||
$this->weekStart = strtoupper($value);
|
||||
$this->weekStart = strtoupper((string) $value);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -892,7 +889,7 @@ class RRuleIterator implements \Iterator
|
|||
// that point and add it to the results.
|
||||
if ($this->byDay) {
|
||||
foreach ($this->byDay as $day) {
|
||||
$dayName = $this->dayNames[$this->dayMap[substr($day, -2)]];
|
||||
$dayName = $this->dayNames[$this->dayMap[substr((string) $day, -2)]];
|
||||
|
||||
// Dayname will be something like 'wednesday'. Now we need to find
|
||||
// all wednesdays in this month.
|
||||
|
|
@ -913,8 +910,8 @@ class RRuleIterator implements \Iterator
|
|||
// So now we have 'all wednesdays' for month. It is however
|
||||
// possible that the user only really wanted the 1st, 2nd or last
|
||||
// wednesday.
|
||||
if (strlen($day) > 2) {
|
||||
$offset = (int) substr($day, 0, -2);
|
||||
if (strlen((string) $day) > 2) {
|
||||
$offset = (int) substr((string) $day, 0, -2);
|
||||
|
||||
if ($offset > 0) {
|
||||
// It is possible that the day does not exist, such as a
|
||||
|
|
@ -949,7 +946,7 @@ class RRuleIterator implements \Iterator
|
|||
$byMonthDayResults[] = $monthDay;
|
||||
} else {
|
||||
// Negative values
|
||||
$byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
|
||||
$byMonthDayResults[] = (int) $startDate->format('t') + 1 + $monthDay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1018,7 +1015,7 @@ class RRuleIterator implements \Iterator
|
|||
// The day may be preceded with a positive (+n) or
|
||||
// negative (-n) integer. However, this does not make
|
||||
// sense in 'weekly' so we ignore it here.
|
||||
$recurrenceDays[] = $this->dayMap[substr($byDay, -2)];
|
||||
$recurrenceDays[] = $this->dayMap[substr((string) $byDay, -2)];
|
||||
}
|
||||
|
||||
return $recurrenceDays;
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@ class ICalendar implements SplitterInterface
|
|||
}
|
||||
|
||||
return $object;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,13 +22,6 @@ use Sabre\VObject\Parser\MimeDir;
|
|||
*/
|
||||
class VCard implements SplitterInterface
|
||||
{
|
||||
/**
|
||||
* File handle.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* Persistent parser.
|
||||
*/
|
||||
|
|
@ -42,10 +35,12 @@ class VCard implements SplitterInterface
|
|||
* @param resource $input
|
||||
* @param int $options parser options, see the OPTIONS constants
|
||||
*/
|
||||
public function __construct($input, int $options = 0)
|
||||
public function __construct(/**
|
||||
* File handle.
|
||||
*/
|
||||
protected $input, int $options = 0)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->parser = new MimeDir($input, $options);
|
||||
$this->parser = new MimeDir($this->input, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -64,7 +59,7 @@ class VCard implements SplitterInterface
|
|||
if (!$object instanceof Component\VCard) {
|
||||
throw new VObject\ParseException('The supplied input contained non-VCARD data.');
|
||||
}
|
||||
} catch (VObject\EofException $e) {
|
||||
} catch (VObject\EofException) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,9 +41,7 @@ class TimeZoneUtil
|
|||
|
||||
private static function getInstance(): self
|
||||
{
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
self::$instance ??= new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,24 @@ class FindFromOffset implements TimezoneFinder
|
|||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if (0 === strncasecmp($tzid, 'UTC', 3)) {
|
||||
$offset = substr($tzid, 3);
|
||||
if (6 === strlen($offset) && ':' === $offset[3]) {
|
||||
$offset = substr($offset, 0, 3).substr($offset, 4);
|
||||
}
|
||||
|
||||
if (5 !== strlen($offset)
|
||||
|| ('+' !== $offset[0] && '-' !== $offset[0])
|
||||
|| 4 !== strspn(substr($offset, 1), '0123456789')
|
||||
|| (int) substr($offset, 1, 2) > 23
|
||||
|| (int) substr($offset, 3, 2) > 59
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new \DateTimeZone($offset);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,13 +40,13 @@ class FindFromTimezoneIdentifier implements TimezoneFinder
|
|||
|
||||
try {
|
||||
if (
|
||||
in_array($tzid, $tzIdentifiers)
|
||||
in_array($tzid, $tzIdentifiers, true)
|
||||
|| preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)
|
||||
|| in_array($tzid, $this->getIdentifiersBC())
|
||||
|| in_array($tzid, $this->getIdentifiersBC(), true)
|
||||
) {
|
||||
return new \DateTimeZone($tzid);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Exception) {
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -18,11 +18,18 @@ class FindFromTimezoneMap implements TimezoneFinder
|
|||
'/^\((UTC|GMT)(\+|\-)[\d]{2}\.[\d]{2}\) (.*)/',
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws void
|
||||
*/
|
||||
public function find(string $tzid, ?bool $failIfUncertain = false): ?\DateTimeZone
|
||||
{
|
||||
// Next, we check if the tzid is somewhere in our tzid map.
|
||||
if ($this->hasTzInMap($tzid)) {
|
||||
return new \DateTimeZone($this->getTzFromMap($tzid));
|
||||
try {
|
||||
return new \DateTimeZone($this->getTzFromMap($tzid));
|
||||
} catch (\Exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Some Microsoft products prefix the offset first, so let's strip that off
|
||||
|
|
@ -34,7 +41,11 @@ class FindFromTimezoneMap implements TimezoneFinder
|
|||
}
|
||||
$tzidAlternate = $matches[3];
|
||||
if ($this->hasTzInMap($tzidAlternate)) {
|
||||
return new \DateTimeZone($this->getTzFromMap($tzidAlternate));
|
||||
try {
|
||||
return new \DateTimeZone($this->getTzFromMap($tzidAlternate));
|
||||
} catch (\Exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class GuessFromLicEntry implements TimezoneGuesser
|
|||
// Libical generators may specify strings like
|
||||
// "SystemV/EST5EDT". For those we must remove the
|
||||
// SystemV part.
|
||||
if ('SystemV/' === substr($lic, 0, 8)) {
|
||||
if (str_starts_with($lic, 'SystemV/')) {
|
||||
$lic = substr($lic, 8);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ class GuessFromMsTzId implements TimezoneGuesser
|
|||
$cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();
|
||||
|
||||
// 2 can mean both Europe/Lisbon and Europe/Sarajevo.
|
||||
if (2 === $cdoId && false !== strpos((string) $vtimezone->TZID, 'Sarajevo')) {
|
||||
if (2 === $cdoId && str_contains((string) $vtimezone->TZID, 'Sarajevo')) {
|
||||
return new \DateTimeZone('Europe/Sarajevo');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,10 +38,10 @@ class VCardConverter
|
|||
return clone $input;
|
||||
}
|
||||
|
||||
if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40])) {
|
||||
if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40], true)) {
|
||||
throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data');
|
||||
}
|
||||
if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40])) {
|
||||
if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40], true)) {
|
||||
throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version');
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ class VCardConverter
|
|||
protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, int $targetVersion): void
|
||||
{
|
||||
// Skipping these, those are automatically added.
|
||||
if (in_array($property->name, ['VERSION', 'PRODID'])) {
|
||||
if (in_array($property->name, ['VERSION', 'PRODID'], true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ class VCardConverter
|
|||
);
|
||||
|
||||
if (Document::VCARD30 === $targetVersion) {
|
||||
if ($property instanceof Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) {
|
||||
if ($property instanceof Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'], true)) {
|
||||
/** @var Uri $newProperty */
|
||||
$newProperty = $this->convertUriToBinary($output, $newProperty);
|
||||
} elseif ($property instanceof Property\VCard\DateAndOrTime) {
|
||||
|
|
@ -111,7 +111,7 @@ class VCardConverter
|
|||
$newProperty['X-APPLE-OMIT-YEAR'] = '1604';
|
||||
}
|
||||
|
||||
if ('ANNIVERSARY' == $newProperty->name) {
|
||||
if ('ANNIVERSARY' === $newProperty->name) {
|
||||
// Microsoft non-standard anniversary
|
||||
$newProperty->name = 'X-ANNIVERSARY';
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ class VCardConverter
|
|||
$output->add('ITEM'.$x.'.X-ABLABEL', '_$!<Anniversary>!$_');
|
||||
}
|
||||
} elseif ('KIND' === $property->name) {
|
||||
switch (strtolower($property->getValue())) {
|
||||
switch (strtolower((string) $property->getValue())) {
|
||||
case 'org':
|
||||
// vCard 3.0 does not have an equivalent to KIND:ORG,
|
||||
// but apple has an extension that means the same
|
||||
|
|
@ -149,7 +149,7 @@ class VCardConverter
|
|||
}
|
||||
} elseif (Document::VCARD40 === $targetVersion) {
|
||||
// These properties were removed in vCard 4.0
|
||||
if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'])) {
|
||||
if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'], true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -171,12 +171,12 @@ class VCardConverter
|
|||
}
|
||||
switch ($property->name) {
|
||||
case 'X-ABSHOWAS':
|
||||
if ('COMPANY' === strtoupper($property->getValue())) {
|
||||
if ('COMPANY' === strtoupper((string) $property->getValue())) {
|
||||
$newProperty = $output->createProperty('KIND', 'ORG');
|
||||
}
|
||||
break;
|
||||
case 'X-ADDRESSBOOKSERVER-KIND':
|
||||
if ('GROUP' === strtoupper($property->getValue())) {
|
||||
if ('GROUP' === strtoupper((string) $property->getValue())) {
|
||||
$newProperty = $output->createProperty('KIND', 'GROUP');
|
||||
}
|
||||
break;
|
||||
|
|
@ -274,10 +274,11 @@ class VCardConverter
|
|||
$newTypes = [];
|
||||
foreach ($parameters['TYPE']->getParts() as $typePart) {
|
||||
if (in_array(
|
||||
strtoupper($typePart),
|
||||
['JPEG', 'PNG', 'GIF']
|
||||
strtoupper((string) $typePart),
|
||||
['JPEG', 'PNG', 'GIF'],
|
||||
true
|
||||
)) {
|
||||
$mimeType = 'image/'.strtolower($typePart);
|
||||
$mimeType = 'image/'.strtolower((string) $typePart);
|
||||
} else {
|
||||
$newTypes[] = $typePart;
|
||||
}
|
||||
|
|
@ -292,7 +293,7 @@ class VCardConverter
|
|||
}
|
||||
}
|
||||
|
||||
$newProperty->setValue('data:'.$mimeType.';base64,'.base64_encode($value));
|
||||
$newProperty->setValue('data:'.$mimeType.';base64,'.base64_encode((string) $value));
|
||||
|
||||
return $newProperty;
|
||||
}
|
||||
|
|
@ -313,7 +314,7 @@ class VCardConverter
|
|||
$value = $newProperty->getValue();
|
||||
|
||||
// Only converting data: uris
|
||||
if ('data:' !== substr($value, 0, 5)) {
|
||||
if (!str_starts_with((string) $value, 'data:')) {
|
||||
return $newProperty;
|
||||
}
|
||||
|
||||
|
|
@ -325,12 +326,12 @@ class VCardConverter
|
|||
'BINARY'
|
||||
);
|
||||
|
||||
$mimeType = substr($value, 5, strpos($value, ',') - 5);
|
||||
$mimeType = substr((string) $value, 5, strpos((string) $value, ',') - 5);
|
||||
if (strpos($mimeType, ';')) {
|
||||
$mimeType = substr($mimeType, 0, strpos($mimeType, ';'));
|
||||
$newProperty->setValue(base64_decode(substr($value, strpos($value, ',') + 1)));
|
||||
$newProperty->setValue(base64_decode(substr((string) $value, strpos((string) $value, ',') + 1), true));
|
||||
} else {
|
||||
$newProperty->setValue(substr($value, strpos($value, ',') + 1));
|
||||
$newProperty->setValue(substr((string) $value, strpos((string) $value, ',') + 1));
|
||||
}
|
||||
unset($value);
|
||||
|
||||
|
|
@ -367,7 +368,7 @@ class VCardConverter
|
|||
// that's now PREF=1.
|
||||
case 'TYPE':
|
||||
foreach ($param->getParts() as $paramPart) {
|
||||
if ('PREF' === strtoupper($paramPart)) {
|
||||
if ('PREF' === strtoupper((string) $paramPart)) {
|
||||
$newProperty->add('PREF', '1');
|
||||
} else {
|
||||
$newProperty->add($param->name, $paramPart);
|
||||
|
|
@ -402,7 +403,7 @@ class VCardConverter
|
|||
case 'ENCODING':
|
||||
// This value only existed in vCard 2.1, and should be
|
||||
// removed for anything else.
|
||||
if ('QUOTED-PRINTABLE' !== strtoupper($param->getValue())) {
|
||||
if ('QUOTED-PRINTABLE' !== strtoupper((string) $param->getValue())) {
|
||||
$newProperty->add($param->name, $param->getParts());
|
||||
}
|
||||
break;
|
||||
|
|
@ -413,7 +414,7 @@ class VCardConverter
|
|||
* Any other PREF numbers we'll drop.
|
||||
*/
|
||||
case 'PREF':
|
||||
if ('1' == $param->getValue()) {
|
||||
if ('1' === (string) $param->getValue()) {
|
||||
$newProperty->add('TYPE', 'PREF');
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ class Version
|
|||
/**
|
||||
* Full version number.
|
||||
*/
|
||||
public const VERSION = '4.5.2';
|
||||
public const VERSION = '5.0.0';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ return [
|
|||
'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
|
||||
'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London',
|
||||
'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
|
||||
'Amsterdam, Berlin, Bern, Rom, Stockholm, Wien' => 'Europe/Berlin',
|
||||
'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
|
||||
'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
|
||||
'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
|
||||
|
|
@ -39,12 +40,12 @@ return [
|
|||
'Kabul' => 'Asia/Kabul',
|
||||
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
||||
'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
|
||||
'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
|
||||
'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Kolkata',
|
||||
'Kathmandu, Nepal' => 'Asia/Kathmandu',
|
||||
'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
|
||||
'Astana, Dhaka' => 'Asia/Dhaka',
|
||||
'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
|
||||
'Rangoon' => 'Asia/Rangoon',
|
||||
'Rangoon' => 'Asia/Yangon',
|
||||
'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
|
||||
'Krasnoyarsk' => 'Asia/Krasnoyarsk',
|
||||
'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
|
||||
|
|
@ -71,7 +72,7 @@ return [
|
|||
'Mid-Atlantic' => 'America/Noronha',
|
||||
'Brasilia' => 'America/Sao_Paulo', // Best guess
|
||||
'Buenos Aires' => 'America/Argentina/Buenos_Aires',
|
||||
'Greenland' => 'America/Godthab',
|
||||
'Greenland' => 'America/Nuuk',
|
||||
'Newfoundland' => 'America/St_Johns',
|
||||
'Atlantic Time (Canada)' => 'America/Halifax',
|
||||
'Caracas, La Paz' => 'America/Caracas',
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ return [
|
|||
'Newfoundland' => 'America/St_Johns',
|
||||
'Argentina' => 'America/Argentina/Buenos_Aires',
|
||||
'E. South America' => 'America/Belem',
|
||||
'Greenland' => 'America/Godthab',
|
||||
'Greenland' => 'America/Nuuk',
|
||||
'Montevideo' => 'America/Montevideo',
|
||||
'SA Eastern' => 'America/Belem',
|
||||
// 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones.
|
||||
|
|
@ -71,12 +71,12 @@ return [
|
|||
'Ekaterinburg' => 'Asia/Yekaterinburg',
|
||||
'Pakistan' => 'Asia/Karachi',
|
||||
'West Asia' => 'Asia/Tashkent',
|
||||
'India' => 'Asia/Calcutta',
|
||||
'India' => 'Asia/Kolkata',
|
||||
'Sri Lanka' => 'Asia/Colombo',
|
||||
'Nepal' => 'Asia/Kathmandu',
|
||||
'Central Asia' => 'Asia/Dhaka',
|
||||
'N. Central Asia' => 'Asia/Almaty',
|
||||
'Myanmar' => 'Asia/Rangoon',
|
||||
'Myanmar' => 'Asia/Yangon',
|
||||
'North Asia' => 'Asia/Krasnoyarsk',
|
||||
'SE Asia' => 'Asia/Bangkok',
|
||||
'China' => 'Asia/Shanghai',
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ return [
|
|||
'Arab Standard Time' => 'Asia/Riyadh',
|
||||
'Arabian Standard Time' => 'Asia/Dubai',
|
||||
'Arabic Standard Time' => 'Asia/Baghdad',
|
||||
'Argentina Standard Time' => 'America/Buenos_Aires',
|
||||
'Argentina Standard Time' => 'America/Argentina/Buenos_Aires',
|
||||
'Astrakhan Standard Time' => 'Europe/Astrakhan',
|
||||
'Atlantic Standard Time' => 'America/Halifax',
|
||||
'Aus Central W. Standard Time' => 'Australia/Eucla',
|
||||
|
|
@ -55,16 +55,16 @@ return [
|
|||
'Eastern Standard Time (Mexico)' => 'America/Cancun',
|
||||
'Egypt Standard Time' => 'Africa/Cairo',
|
||||
'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg',
|
||||
'FLE Standard Time' => 'Europe/Kiev',
|
||||
'FLE Standard Time' => 'Europe/Kyiv',
|
||||
'Fiji Standard Time' => 'Pacific/Fiji',
|
||||
'GMT Standard Time' => 'Europe/London',
|
||||
'GTB Standard Time' => 'Europe/Bucharest',
|
||||
'Georgian Standard Time' => 'Asia/Tbilisi',
|
||||
'Greenland Standard Time' => 'America/Godthab',
|
||||
'Greenland Standard Time' => 'America/Nuuk',
|
||||
'Greenwich Standard Time' => 'Atlantic/Reykjavik',
|
||||
'Haiti Standard Time' => 'America/Port-au-Prince',
|
||||
'Hawaiian Standard Time' => 'Pacific/Honolulu',
|
||||
'India Standard Time' => 'Asia/Calcutta',
|
||||
'India Standard Time' => 'Asia/Kolkata',
|
||||
'Iran Standard Time' => 'Asia/Tehran',
|
||||
'Israel Standard Time' => 'Asia/Jerusalem',
|
||||
'Jordan Standard Time' => 'Asia/Amman',
|
||||
|
|
@ -82,10 +82,10 @@ return [
|
|||
'Morocco Standard Time' => 'Africa/Casablanca',
|
||||
'Mountain Standard Time' => 'America/Denver',
|
||||
'Mountain Standard Time (Mexico)' => 'America/Chihuahua',
|
||||
'Myanmar Standard Time' => 'Asia/Rangoon',
|
||||
'Myanmar Standard Time' => 'Asia/Yangon',
|
||||
'N. Central Asia Standard Time' => 'Asia/Novosibirsk',
|
||||
'Namibia Standard Time' => 'Africa/Windhoek',
|
||||
'Nepal Standard Time' => 'Asia/Katmandu',
|
||||
'Nepal Standard Time' => 'Asia/Kathmandu',
|
||||
'New Zealand Standard Time' => 'Pacific/Auckland',
|
||||
'Newfoundland Standard Time' => 'America/St_Johns',
|
||||
'Norfolk Standard Time' => 'Pacific/Norfolk',
|
||||
|
|
@ -127,7 +127,7 @@ return [
|
|||
'Transbaikal Standard Time' => 'Asia/Chita',
|
||||
'Turkey Standard Time' => 'Europe/Istanbul',
|
||||
'Turks And Caicos Standard Time' => 'America/Grand_Turk',
|
||||
'US Eastern Standard Time' => 'America/Indianapolis',
|
||||
'US Eastern Standard Time' => 'America/Indiana/Indianapolis',
|
||||
'US Mountain Standard Time' => 'America/Phoenix',
|
||||
'UTC' => 'Etc/GMT',
|
||||
'UTC+12' => 'Etc/GMT-12',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue