Sabre VObject v5.0.0

This commit is contained in:
the-djmaze 2026-10-21 15:18:17 +02:00
parent aad651f2a1
commit e66f325aa3
54 changed files with 598 additions and 644 deletions

View file

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

View file

@ -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) {

View file

@ -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 {

View file

@ -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;
}
/**

View file

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

View file

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

View file

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

View file

@ -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' => '*',

View file

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

View file

@ -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' => '*',

View file

@ -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' => '*',

View file

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

View file

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

View file

@ -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[] = [

View file

@ -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) {

View file

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

View file

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

View file

@ -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.

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -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;
}
/**

View file

@ -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']);

View file

@ -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.',

View file

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

View file

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

View file

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

View file

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

View file

@ -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,

View file

@ -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,
];
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {

View file

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

View file

@ -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',

View file

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

View file

@ -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().

View file

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

View file

@ -96,8 +96,8 @@ class ICalendar implements SplitterInterface
}
return $object;
} else {
return null;
}
return null;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -14,5 +14,5 @@ class Version
/**
* Full version number.
*/
public const VERSION = '4.5.2';
public const VERSION = '5.0.0';
}

View file

@ -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',

View file

@ -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',

View file

@ -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',