From e5371c8b6aa7f1ba9083a3119ec1ccea63e6f5bc Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 20 May 2023 18:07:43 +0200 Subject: [PATCH 01/85] Add timezone Europe/Berlin 'Amsterdam, Berlin, Bern, Rom, Stockholm, Wien' => 'Europe/Berlin', --- lib/timezonedata/exchangezones.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/timezonedata/exchangezones.php b/lib/timezonedata/exchangezones.php index 89bddc27c..3e7eace13 100644 --- a/lib/timezonedata/exchangezones.php +++ b/lib/timezonedata/exchangezones.php @@ -17,6 +17,7 @@ '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', From 7acdf377d73b37c8b2dec1ac2f4c9d8dba91140e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 23:47:29 +0000 Subject: [PATCH 02/85] Bump actions/cache from 3 to 4 Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d665e841..2f5ba8c90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} # Use composer.json for key, if composer.lock is not committed. From 5c3a53299518bd7d199489dca5a97aa520a58491 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Fri, 2 Feb 2024 08:45:33 +0545 Subject: [PATCH 03/85] Apply cs-fixer changes --- bin/bench_freebusygenerator.php | 6 +- lib/Component.php | 3 +- lib/Component/VCalendar.php | 148 +++++++++++----------- lib/DateTimeParser.php | 4 +- lib/Document.php | 8 +- lib/FreeBusyGenerator.php | 4 +- lib/ITip/Broker.php | 8 +- lib/Property.php | 2 +- lib/Property/ICalendar/DateTime.php | 4 +- lib/Property/Text.php | 2 +- lib/Recur/EventIterator.php | 2 +- lib/Splitter/ICalendar.php | 2 +- lib/Splitter/VCard.php | 2 +- lib/TimeZoneUtil.php | 4 +- lib/VCardConverter.php | 12 +- tests/VObject/Component/VCalendarTest.php | 4 +- tests/VObject/Component/VCardTest.php | 4 +- tests/VObject/Component/VFreeBusyTest.php | 3 +- tests/VObject/ComponentTest.php | 12 +- tests/VObject/EmptyParameterTest.php | 2 +- tests/VObject/FreeBusyGeneratorTest.php | 2 +- tests/VObject/Recur/RRuleIteratorTest.php | 2 +- tests/VObject/VCardConverterTest.php | 2 +- 23 files changed, 119 insertions(+), 123 deletions(-) diff --git a/bin/bench_freebusygenerator.php b/bin/bench_freebusygenerator.php index 891335190..ae859f487 100644 --- a/bin/bench_freebusygenerator.php +++ b/bin/bench_freebusygenerator.php @@ -24,9 +24,9 @@ $bench->parse->stop(); $repeat = 100; -$start = new \DateTime('2000-01-01'); -$end = new \DateTime('2020-01-01'); -$timeZone = new \DateTimeZone('America/Toronto'); +$start = new DateTime('2000-01-01'); +$end = new DateTime('2020-01-01'); +$timeZone = new DateTimeZone('America/Toronto'); $bench->fb->start(); diff --git a/lib/Component.php b/lib/Component.php index da2c5ebd4..95bde3d40 100644 --- a/lib/Component.php +++ b/lib/Component.php @@ -2,7 +2,6 @@ namespace Sabre\VObject; -use Sabre\VObject; use Sabre\Xml; /** @@ -15,7 +14,7 @@ * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License * - * @property VObject\Property\FlatText UID + * @property Property\FlatText UID */ class Component extends Node { diff --git a/lib/Component/VCalendar.php b/lib/Component/VCalendar.php index b317e02c8..318492ce6 100644 --- a/lib/Component/VCalendar.php +++ b/lib/Component/VCalendar.php @@ -20,8 +20,8 @@ * * @property VEvent VEVENT * @property VJournal VJOURNAL - * @property VObject\Property\Text ORG - * @property VObject\Property\FlatText METHOD + * @property Property\Text ORG + * @property Property\FlatText METHOD */ class VCalendar extends VObject\Document { @@ -51,21 +51,21 @@ class VCalendar extends VObject\Document * List of value-types, and which classes they map to. */ public static array $valueMap = [ - 'BINARY' => VObject\Property\Binary::class, - 'BOOLEAN' => VObject\Property\Boolean::class, - 'CAL-ADDRESS' => VObject\Property\ICalendar\CalAddress::class, - 'DATE' => VObject\Property\ICalendar\Date::class, - 'DATE-TIME' => VObject\Property\ICalendar\DateTime::class, - 'DURATION' => VObject\Property\ICalendar\Duration::class, - 'FLOAT' => VObject\Property\FloatValue::class, - 'INTEGER' => VObject\Property\IntegerValue::class, - 'PERIOD' => VObject\Property\ICalendar\Period::class, - 'RECUR' => VObject\Property\ICalendar\Recur::class, - 'TEXT' => VObject\Property\Text::class, - 'TIME' => VObject\Property\Time::class, - 'UNKNOWN' => VObject\Property\Unknown::class, // jCard / jCal-only. - 'URI' => VObject\Property\Uri::class, - 'UTC-OFFSET' => VObject\Property\UtcOffset::class, + 'BINARY' => Property\Binary::class, + 'BOOLEAN' => Property\Boolean::class, + 'CAL-ADDRESS' => Property\ICalendar\CalAddress::class, + 'DATE' => Property\ICalendar\Date::class, + 'DATE-TIME' => Property\ICalendar\DateTime::class, + 'DURATION' => Property\ICalendar\Duration::class, + 'FLOAT' => Property\FloatValue::class, + 'INTEGER' => Property\IntegerValue::class, + 'PERIOD' => Property\ICalendar\Period::class, + 'RECUR' => Property\ICalendar\Recur::class, + 'TEXT' => Property\Text::class, + 'TIME' => Property\Time::class, + 'UNKNOWN' => Property\Unknown::class, // jCard / jCal-only. + 'URI' => Property\Uri::class, + 'UTC-OFFSET' => Property\UtcOffset::class, ]; /** @@ -73,78 +73,78 @@ class VCalendar extends VObject\Document */ public static array $propertyMap = [ // Calendar properties - 'CALSCALE' => VObject\Property\FlatText::class, - 'METHOD' => VObject\Property\FlatText::class, - 'PRODID' => VObject\Property\FlatText::class, - 'VERSION' => VObject\Property\FlatText::class, + 'CALSCALE' => Property\FlatText::class, + 'METHOD' => Property\FlatText::class, + 'PRODID' => Property\FlatText::class, + 'VERSION' => Property\FlatText::class, // Component properties - 'ATTACH' => VObject\Property\Uri::class, - 'CATEGORIES' => VObject\Property\Text::class, - 'CLASS' => VObject\Property\FlatText::class, - 'COMMENT' => VObject\Property\FlatText::class, - 'DESCRIPTION' => VObject\Property\FlatText::class, - 'GEO' => VObject\Property\FloatValue::class, - 'LOCATION' => VObject\Property\FlatText::class, - 'PERCENT-COMPLETE' => VObject\Property\IntegerValue::class, - 'PRIORITY' => VObject\Property\IntegerValue::class, - 'RESOURCES' => VObject\Property\Text::class, - 'STATUS' => VObject\Property\FlatText::class, - 'SUMMARY' => VObject\Property\FlatText::class, + 'ATTACH' => Property\Uri::class, + 'CATEGORIES' => Property\Text::class, + 'CLASS' => Property\FlatText::class, + 'COMMENT' => Property\FlatText::class, + 'DESCRIPTION' => Property\FlatText::class, + 'GEO' => Property\FloatValue::class, + 'LOCATION' => Property\FlatText::class, + 'PERCENT-COMPLETE' => Property\IntegerValue::class, + 'PRIORITY' => Property\IntegerValue::class, + 'RESOURCES' => Property\Text::class, + 'STATUS' => Property\FlatText::class, + 'SUMMARY' => Property\FlatText::class, // Date and Time Component Properties - 'COMPLETED' => VObject\Property\ICalendar\DateTime::class, - 'DTEND' => VObject\Property\ICalendar\DateTime::class, - 'DUE' => VObject\Property\ICalendar\DateTime::class, - 'DTSTART' => VObject\Property\ICalendar\DateTime::class, - 'DURATION' => VObject\Property\ICalendar\Duration::class, - 'FREEBUSY' => VObject\Property\ICalendar\Period::class, - 'TRANSP' => VObject\Property\FlatText::class, + 'COMPLETED' => Property\ICalendar\DateTime::class, + 'DTEND' => Property\ICalendar\DateTime::class, + 'DUE' => Property\ICalendar\DateTime::class, + 'DTSTART' => Property\ICalendar\DateTime::class, + 'DURATION' => Property\ICalendar\Duration::class, + 'FREEBUSY' => Property\ICalendar\Period::class, + 'TRANSP' => Property\FlatText::class, // Time Zone Component Properties - 'TZID' => VObject\Property\FlatText::class, - 'TZNAME' => VObject\Property\FlatText::class, - 'TZOFFSETFROM' => VObject\Property\UtcOffset::class, - 'TZOFFSETTO' => VObject\Property\UtcOffset::class, - 'TZURL' => VObject\Property\Uri::class, + 'TZID' => Property\FlatText::class, + 'TZNAME' => Property\FlatText::class, + 'TZOFFSETFROM' => Property\UtcOffset::class, + 'TZOFFSETTO' => Property\UtcOffset::class, + 'TZURL' => Property\Uri::class, // Relationship Component Properties - 'ATTENDEE' => VObject\Property\ICalendar\CalAddress::class, - 'CONTACT' => VObject\Property\FlatText::class, - 'ORGANIZER' => VObject\Property\ICalendar\CalAddress::class, - 'RECURRENCE-ID' => VObject\Property\ICalendar\DateTime::class, - 'RELATED-TO' => VObject\Property\FlatText::class, - 'URL' => VObject\Property\Uri::class, - 'UID' => VObject\Property\FlatText::class, + 'ATTENDEE' => Property\ICalendar\CalAddress::class, + 'CONTACT' => Property\FlatText::class, + 'ORGANIZER' => Property\ICalendar\CalAddress::class, + 'RECURRENCE-ID' => Property\ICalendar\DateTime::class, + 'RELATED-TO' => Property\FlatText::class, + 'URL' => Property\Uri::class, + 'UID' => Property\FlatText::class, // Recurrence Component Properties - 'EXDATE' => VObject\Property\ICalendar\DateTime::class, - 'RDATE' => VObject\Property\ICalendar\DateTime::class, - 'RRULE' => VObject\Property\ICalendar\Recur::class, - 'EXRULE' => VObject\Property\ICalendar\Recur::class, // Deprecated since rfc5545 + 'EXDATE' => Property\ICalendar\DateTime::class, + 'RDATE' => Property\ICalendar\DateTime::class, + 'RRULE' => Property\ICalendar\Recur::class, + 'EXRULE' => Property\ICalendar\Recur::class, // Deprecated since rfc5545 // Alarm Component Properties - 'ACTION' => VObject\Property\FlatText::class, - 'REPEAT' => VObject\Property\IntegerValue::class, - 'TRIGGER' => VObject\Property\ICalendar\Duration::class, + 'ACTION' => Property\FlatText::class, + 'REPEAT' => Property\IntegerValue::class, + 'TRIGGER' => Property\ICalendar\Duration::class, // Change Management Component Properties - 'CREATED' => VObject\Property\ICalendar\DateTime::class, - 'DTSTAMP' => VObject\Property\ICalendar\DateTime::class, - 'LAST-MODIFIED' => VObject\Property\ICalendar\DateTime::class, - 'SEQUENCE' => VObject\Property\IntegerValue::class, + 'CREATED' => Property\ICalendar\DateTime::class, + 'DTSTAMP' => Property\ICalendar\DateTime::class, + 'LAST-MODIFIED' => Property\ICalendar\DateTime::class, + 'SEQUENCE' => Property\IntegerValue::class, // Request Status - 'REQUEST-STATUS' => VObject\Property\Text::class, + 'REQUEST-STATUS' => Property\Text::class, // Additions from draft-daboo-valarm-extensions-04 - 'ALARM-AGENT' => VObject\Property\Text::class, - 'ACKNOWLEDGED' => VObject\Property\ICalendar\DateTime::class, - 'PROXIMITY' => VObject\Property\Text::class, - 'DEFAULT-ALARM' => VObject\Property\Boolean::class, + 'ALARM-AGENT' => Property\Text::class, + 'ACKNOWLEDGED' => Property\ICalendar\DateTime::class, + 'PROXIMITY' => Property\Text::class, + 'DEFAULT-ALARM' => Property\Boolean::class, // Additions from draft-daboo-calendar-availability-05 - 'BUSYTYPE' => VObject\Property\Text::class, + 'BUSYTYPE' => Property\Text::class, ]; /** @@ -166,10 +166,10 @@ public function getDocumentType(): int * * @return VObject\Component[] */ - public function getBaseComponents(string $componentName = null): array + public function getBaseComponents(?string $componentName = null): array { $isBaseComponent = function ($component): bool { - if (!$component instanceof VObject\Component) { + if (!$component instanceof Component) { return false; } if ('VTIMEZONE' === $component->name) { @@ -217,10 +217,10 @@ public function getBaseComponents(string $componentName = null): array * * @return VObject\Component|null */ - public function getBaseComponent(string $componentName = null): ?Component + public function getBaseComponent(?string $componentName = null): ?Component { $isBaseComponent = function ($component): bool { - if (!$component instanceof VObject\Component) { + if (!$component instanceof Component) { return false; } if ('VTIMEZONE' === $component->name) { @@ -275,7 +275,7 @@ public function getBaseComponent(string $componentName = null): ?Component * @throws InvalidDataException * @throws VObject\Recur\MaxInstancesExceededException */ - public function expand(\DateTimeInterface $start, \DateTimeInterface $end, \DateTimeZone $timeZone = null): VCalendar + public function expand(\DateTimeInterface $start, \DateTimeInterface $end, ?\DateTimeZone $timeZone = null): VCalendar { $newChildren = []; $recurringEvents = []; diff --git a/lib/DateTimeParser.php b/lib/DateTimeParser.php index 5ce9f207a..7e5749351 100644 --- a/lib/DateTimeParser.php +++ b/lib/DateTimeParser.php @@ -27,7 +27,7 @@ class DateTimeParser * * @throws InvalidDataException */ - public static function parseDateTime(string $dt, \DateTimeZone $tz = null): \DateTimeImmutable + public static function parseDateTime(string $dt, ?\DateTimeZone $tz = null): \DateTimeImmutable { // Format is YYYYMMDD + "T" + hhmmss $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/', $dt, $matches); @@ -54,7 +54,7 @@ public static function parseDateTime(string $dt, \DateTimeZone $tz = null): \Dat * * @throws InvalidDataException */ - public static function parseDate(string $date, \DateTimeZone $tz = null): \DateTimeImmutable + public static function parseDate(string $date, ?\DateTimeZone $tz = null): \DateTimeImmutable { // Format is YYYYMMDD $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/', $date, $matches); diff --git a/lib/Document.php b/lib/Document.php index b311987d7..6f87b5b97 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -2,8 +2,6 @@ namespace Sabre\VObject; -use Sabre\VObject; - /** * Document. * @@ -18,7 +16,7 @@ * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License * - * @property VObject\Property\FlatText VERSION + * @property Property\FlatText VERSION */ abstract class Document extends Component { @@ -140,7 +138,7 @@ public function create(string $name) * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To * ensure that this does not happen, set $defaults to false. */ - public function createComponent(string $name, array $children = null, bool $defaults = true): Component + public function createComponent(string $name, ?array $children = null, bool $defaults = true): Component { $name = strtoupper($name); $class = Component::class; @@ -169,7 +167,7 @@ public function createComponent(string $name, array $children = null, bool $defa * * @throws InvalidDataException */ - public function createProperty(string $name, $value = null, array $parameters = null, string $valueType = null): Property + public function createProperty(string $name, $value = null, ?array $parameters = null, ?string $valueType = null): Property { // If there's a . in the name, it means it's prefixed by a group name. if (false !== ($i = strpos($name, '.'))) { diff --git a/lib/FreeBusyGenerator.php b/lib/FreeBusyGenerator.php index 2f222e4e5..424826034 100644 --- a/lib/FreeBusyGenerator.php +++ b/lib/FreeBusyGenerator.php @@ -72,7 +72,7 @@ class FreeBusyGenerator * Check the setTimeRange and setObjects methods for details about the * arguments. */ - public function __construct(\DateTimeInterface $start = null, \DateTimeInterface $end = null, $objects = null, \DateTimeZone $timeZone = null) + public function __construct(?\DateTimeInterface $start = null, ?\DateTimeInterface $end = null, $objects = null, ?\DateTimeZone $timeZone = null) { $this->setTimeRange($start, $end); @@ -138,7 +138,7 @@ public function setObjects($objects): void * * @throws \Exception */ - public function setTimeRange(\DateTimeInterface $start = null, \DateTimeInterface $end = null): void + public function setTimeRange(?\DateTimeInterface $start = null, ?\DateTimeInterface $end = null): void { if (!$start) { $start = new \DateTimeImmutable(Settings::$minDate); diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index f792fd990..9cbc3f72b 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -113,7 +113,7 @@ class Broker * @throws MaxInstancesExceededException * @throws NoInstancesException */ - public function processMessage(Message $itipMessage, VCalendar $existingObject = null) + public function processMessage(Message $itipMessage, ?VCalendar $existingObject = null) { // We only support events at the moment. if ('VEVENT' !== $itipMessage->component) { @@ -268,7 +268,7 @@ public function parseEvent($calendar, $userHref, $oldCalendar = null): array * This is message from an organizer, and is either a new event * invite, or an update to an existing one. */ - protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null): ?VCalendar + protected function processMessageRequest(Message $itipMessage, ?VCalendar $existingObject = null): ?VCalendar { if (!$existingObject) { // This is a new invite, and we're just going to copy over @@ -296,7 +296,7 @@ protected function processMessageRequest(Message $itipMessage, VCalendar $existi * attendee got removed from an event, or an event got cancelled * altogether. */ - protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null): ?VCalendar + protected function processMessageCancel(Message $itipMessage, ?VCalendar $existingObject = null): ?VCalendar { if (!$existingObject) { // The event didn't exist in the first place, so we're just @@ -321,7 +321,7 @@ protected function processMessageCancel(Message $itipMessage, VCalendar $existin * @throws MaxInstancesExceededException * @throws NoInstancesException */ - protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null): ?VCalendar + protected function processMessageReply(Message $itipMessage, ?VCalendar $existingObject = null): ?VCalendar { // A reply can only be processed based on an existing object. // If the object is not available, the reply is ignored. diff --git a/lib/Property.php b/lib/Property.php index 53a252fb2..574099e65 100644 --- a/lib/Property.php +++ b/lib/Property.php @@ -61,7 +61,7 @@ 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) + public function __construct(Component $root, ?string $name, $value = null, array $parameters = [], ?string $group = null) { $this->name = $name; $this->group = $group; diff --git a/lib/Property/ICalendar/DateTime.php b/lib/Property/ICalendar/DateTime.php index 7ee907972..631a73b08 100644 --- a/lib/Property/ICalendar/DateTime.php +++ b/lib/Property/ICalendar/DateTime.php @@ -128,7 +128,7 @@ public function isFloating(): bool * * @throws InvalidDataException */ - public function getDateTime(\DateTimeZone $timeZone = null): ?\DateTimeImmutable + public function getDateTime(?\DateTimeZone $timeZone = null): ?\DateTimeImmutable { $dt = $this->getDateTimes($timeZone); if (!$dt) { @@ -149,7 +149,7 @@ public function getDateTime(\DateTimeZone $timeZone = null): ?\DateTimeImmutable * * @throws InvalidDataException */ - public function getDateTimes(\DateTimeZone $timeZone = null): array + public function getDateTimes(?\DateTimeZone $timeZone = null): array { // Does the property have a TZID? /** @var Property\FlatText $tzid */ diff --git a/lib/Property/Text.php b/lib/Property/Text.php index c606ad812..c59029059 100644 --- a/lib/Property/Text.php +++ b/lib/Property/Text.php @@ -64,7 +64,7 @@ class Text extends Property * @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) + public function __construct(Component $root, string $name, $value = null, array $parameters = [], ?string $group = null) { // There's two types of multi-valued text properties: // 1. multivalue properties. diff --git a/lib/Recur/EventIterator.php b/lib/Recur/EventIterator.php index d79cf3132..2c93bcf4e 100644 --- a/lib/Recur/EventIterator.php +++ b/lib/Recur/EventIterator.php @@ -89,7 +89,7 @@ class EventIterator implements \Iterator * @throws NoInstancesException * @throws InvalidDataException */ - public function __construct($input, string $uid = null, \DateTimeZone $timeZone = null) + public function __construct($input, ?string $uid = null, ?\DateTimeZone $timeZone = null) { if (is_null($timeZone)) { $timeZone = new \DateTimeZone('UTC'); diff --git a/lib/Splitter/ICalendar.php b/lib/Splitter/ICalendar.php index ccf0d1add..79f0bd443 100644 --- a/lib/Splitter/ICalendar.php +++ b/lib/Splitter/ICalendar.php @@ -46,7 +46,7 @@ public function __construct($input, int $options = 0) { $data = VObject\Reader::read($input, $options); - if (!$data instanceof VObject\Component\VCalendar) { + if (!$data instanceof VCalendar) { throw new VObject\ParseException('Supplied input could not be parsed as VCALENDAR.'); } diff --git a/lib/Splitter/VCard.php b/lib/Splitter/VCard.php index e63ae837d..40775a335 100644 --- a/lib/Splitter/VCard.php +++ b/lib/Splitter/VCard.php @@ -61,7 +61,7 @@ public function getNext(): ?Component try { $object = $this->parser->parse(); - if (!$object instanceof VObject\Component\VCard) { + if (!$object instanceof Component\VCard) { throw new VObject\ParseException('The supplied input contained non-VCARD data.'); } } catch (VObject\EofException $e) { diff --git a/lib/TimeZoneUtil.php b/lib/TimeZoneUtil.php index 8c2a374fb..2cd458578 100644 --- a/lib/TimeZoneUtil.php +++ b/lib/TimeZoneUtil.php @@ -72,7 +72,7 @@ private function addFinder(string $key, TimezoneFinder $finder): void * Alternatively, if $failIfUncertain is set to true, it will throw an * exception if we cannot accurately determine the timezone. */ - private function findTimeZone(string $tzid, Component $vcalendar = null, bool $failIfUncertain = false): \DateTimeZone + private function findTimeZone(string $tzid, ?Component $vcalendar = null, bool $failIfUncertain = false): \DateTimeZone { foreach ($this->timezoneFinders as $timezoneFinder) { $timezone = $timezoneFinder->find($tzid, $failIfUncertain); @@ -117,7 +117,7 @@ public static function addTimezoneFinder(string $key, TimezoneFinder $finder): v self::getInstance()->addFinder($key, $finder); } - public static function getTimeZone(string $tzid, Component $vcalendar = null, bool $failIfUncertain = false): \DateTimeZone + public static function getTimeZone(string $tzid, ?Component $vcalendar = null, bool $failIfUncertain = false): \DateTimeZone { return self::getInstance()->findTimeZone($tzid, $vcalendar, $failIfUncertain); } diff --git a/lib/VCardConverter.php b/lib/VCardConverter.php index 88d74602c..74901fc1b 100644 --- a/lib/VCardConverter.php +++ b/lib/VCardConverter.php @@ -93,8 +93,8 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp ); if (Document::VCARD30 === $targetVersion) { - if ($property instanceof Property\Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) { - /** @var Property\Uri $newProperty */ + if ($property instanceof Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) { + /** @var Uri $newProperty */ $newProperty = $this->convertUriToBinary($output, $newProperty); } elseif ($property instanceof Property\VCard\DateAndOrTime) { // In vCard 4, the birth year may be optional. This is not the @@ -153,8 +153,8 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp return; } - if ($property instanceof Property\Binary) { - /** @var Property\Binary $newProperty */ + if ($property instanceof Binary) { + /** @var Binary $newProperty */ $newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters); } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) { // If a property such as BDAY contained 'X-APPLE-OMIT-YEAR', @@ -256,7 +256,7 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp * * @throws InvalidDataException */ - protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters): Uri + protected function convertBinaryToUri(Component\VCard $output, Binary $newProperty, array &$parameters): Uri { $value = $newProperty->getValue(); /** @var Uri $newProperty */ @@ -308,7 +308,7 @@ protected function convertBinaryToUri(Component\VCard $output, Property\Binary $ * * @throws InvalidDataException */ - protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty): Property + protected function convertUriToBinary(Component\VCard $output, Uri $newProperty): Property { $value = $newProperty->getValue(); diff --git a/tests/VObject/Component/VCalendarTest.php b/tests/VObject/Component/VCalendarTest.php index 1893a9ac7..bde6a4fa5 100644 --- a/tests/VObject/Component/VCalendarTest.php +++ b/tests/VObject/Component/VCalendarTest.php @@ -756,7 +756,7 @@ public function testCalDAVMETHOD(): void ); } - public function assertValidate($ics, $options, $expectedLevel, string $expectedMessage = null): void + public function assertValidate($ics, $options, $expectedLevel, ?string $expectedMessage = null): void { $vcal = VObject\Reader::read($ics); $result = $vcal->validate($options); @@ -764,7 +764,7 @@ public function assertValidate($ics, $options, $expectedLevel, string $expectedM self::assertValidateResult($result, $expectedLevel, $expectedMessage); } - public function assertValidateResult($input, $expectedLevel, string $expectedMessage = null): void + public function assertValidateResult($input, $expectedLevel, ?string $expectedMessage = null): void { $messages = []; foreach ($input as $warning) { diff --git a/tests/VObject/Component/VCardTest.php b/tests/VObject/Component/VCardTest.php index b71b30856..3851d97d9 100644 --- a/tests/VObject/Component/VCardTest.php +++ b/tests/VObject/Component/VCardTest.php @@ -283,7 +283,7 @@ public function testVCard21NoCardDAV(): void ); } - public function assertValidate($vcf, $options, int $expectedLevel, string $expectedMessage = null): void + public function assertValidate($vcf, $options, int $expectedLevel, ?string $expectedMessage = null): void { $vcal = VObject\Reader::read($vcf); $result = $vcal->validate($options); @@ -291,7 +291,7 @@ public function assertValidate($vcf, $options, int $expectedLevel, string $expec self::assertValidateResult($result, $expectedLevel, $expectedMessage); } - public function assertValidateResult($input, int $expectedLevel, string $expectedMessage = null): void + public function assertValidateResult($input, int $expectedLevel, ?string $expectedMessage = null): void { $messages = []; foreach ($input as $warning) { diff --git a/tests/VObject/Component/VFreeBusyTest.php b/tests/VObject/Component/VFreeBusyTest.php index f3a521f95..f2f97ebe1 100644 --- a/tests/VObject/Component/VFreeBusyTest.php +++ b/tests/VObject/Component/VFreeBusyTest.php @@ -3,7 +3,6 @@ namespace Sabre\VObject\Component; use PHPUnit\Framework\TestCase; -use Sabre\VObject; use Sabre\VObject\Reader; class VFreeBusyTest extends TestCase @@ -23,7 +22,7 @@ public function testIsFree(): void END:VCALENDAR BLA; - $obj = VObject\Reader::read($input); + $obj = Reader::read($input); $vfb = $obj->VFREEBUSY; $tz = new \DateTimeZone('UTC'); diff --git a/tests/VObject/ComponentTest.php b/tests/VObject/ComponentTest.php index 4a247d906..c48480983 100644 --- a/tests/VObject/ComponentTest.php +++ b/tests/VObject/ComponentTest.php @@ -463,7 +463,7 @@ public function testRemoveNotFound(): void */ public function testValidateRules(array $componentList, int $errorCount): void { - $vcard = new Component\VCard(); + $vcard = new VCard(); $component = new FakeComponent($vcard, 'Hi', [], false); foreach ($componentList as $v) { @@ -475,7 +475,7 @@ public function testValidateRules(array $componentList, int $errorCount): void public function testValidateRepair(): void { - $vcard = new Component\VCard(); + $vcard = new VCard(); $component = new FakeComponent($vcard, 'Hi', [], false); $component->validate(Component::REPAIR); @@ -484,7 +484,7 @@ public function testValidateRepair(): void public function testValidateRepairShouldNotDeduplicatePropertiesWhenValuesDiffer(): void { - $vcard = new Component\VCard(); + $vcard = new VCard(); $component = new FakeComponent($vcard, 'WithDuplicateGIR', []); $component->add('BAZ', 'BAZ'); @@ -500,7 +500,7 @@ public function testValidateRepairShouldNotDeduplicatePropertiesWhenValuesDiffer public function testValidateRepairShouldNotDeduplicatePropertiesWhenParametersDiffer(): void { - $vcard = new Component\VCard(); + $vcard = new VCard(); $component = new FakeComponent($vcard, 'WithDuplicateGIR', []); $component->add('BAZ', 'BAZ'); @@ -516,7 +516,7 @@ public function testValidateRepairShouldNotDeduplicatePropertiesWhenParametersDi public function testValidateRepairShouldDeduplicatePropertiesWhenValuesAndParametersAreEqual(): void { - $vcard = new Component\VCard(); + $vcard = new VCard(); $component = new FakeComponent($vcard, 'WithDuplicateGIR', []); $component->add('BAZ', 'BAZ'); @@ -532,7 +532,7 @@ public function testValidateRepairShouldDeduplicatePropertiesWhenValuesAndParame public function testValidateRepairShouldDeduplicatePropertiesWhenValuesAreEqual(): void { - $vcard = new Component\VCard(); + $vcard = new VCard(); $component = new FakeComponent($vcard, 'WithDuplicateGIR', []); $component->add('BAZ', 'BAZ'); diff --git a/tests/VObject/EmptyParameterTest.php b/tests/VObject/EmptyParameterTest.php index ab197ab33..6739f104a 100644 --- a/tests/VObject/EmptyParameterTest.php +++ b/tests/VObject/EmptyParameterTest.php @@ -21,7 +21,7 @@ public function testRead(): void $vcard = Reader::read($input); self::assertInstanceOf(Component\VCard::class, $vcard); - $vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30); + $vcard = $vcard->convert(Document::VCARD30); $vcard = $vcard->serialize(); $converted = Reader::read($vcard); diff --git a/tests/VObject/FreeBusyGeneratorTest.php b/tests/VObject/FreeBusyGeneratorTest.php index 47283eafb..a4946cc10 100644 --- a/tests/VObject/FreeBusyGeneratorTest.php +++ b/tests/VObject/FreeBusyGeneratorTest.php @@ -46,7 +46,7 @@ public function testInvalidArg(): void * * @throws ParseException */ - public function assertFreeBusyReport(string $expected, $input, \DateTimeZone $timeZone = null, string $vavailability = null): void + public function assertFreeBusyReport(string $expected, $input, ?\DateTimeZone $timeZone = null, ?string $vavailability = null): void { $gen = new FreeBusyGenerator( new \DateTime('20110101T110000Z', new \DateTimeZone('UTC')), diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 2814df66d..519f51225 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -1072,7 +1072,7 @@ public function testIteratorFunctions(): void ); } - public function parse($rule, string $start, array $expected, string $fastForward = null, string $tz = 'UTC', bool $runTillTheEnd = false): void + public function parse($rule, string $start, array $expected, ?string $fastForward = null, string $tz = 'UTC', bool $runTillTheEnd = false): void { $dt = new \DateTime($start, new \DateTimeZone($tz)); $parser = new RRuleIterator($rule, $dt); diff --git a/tests/VObject/VCardConverterTest.php b/tests/VObject/VCardConverterTest.php index 9db0ad561..db778261d 100644 --- a/tests/VObject/VCardConverterTest.php +++ b/tests/VObject/VCardConverterTest.php @@ -554,7 +554,7 @@ public function testNoLabel(): void $vcard = Reader::read($input); - self::assertInstanceOf(Component\VCard::class, $vcard); + self::assertInstanceOf(VCard::class, $vcard); /** @var VCard $vcard */ $vcard = $vcard->convert(Document::VCARD40); $vcard = $vcard->serialize(); From 54b7952b196d94902ab20d1681c3907d7f204709 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Feb 2024 03:03:39 +0000 Subject: [PATCH 04/85] Bump codecov/codecov-action from 3 to 4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f5ba8c90..775b3389f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,5 +59,5 @@ jobs: run: vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover clover.xml - name: Code Coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 if: matrix.coverage != 'none' From 7f5ddc4c0fb10705377c5b33e9b943f13cc50855 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Sun, 11 Feb 2024 12:55:45 +0545 Subject: [PATCH 05/85] chore: use php-cs-fixer 3.49 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 67b8b26b0..bdc8591a1 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "^3.38", + "friendsofphp/php-cs-fixer": "^3.49", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", "phpstan/phpstan": "^1.10" From db652288d9fa2c546e5ad77eeed254ea0194df31 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 4 Mar 2024 11:39:59 +0545 Subject: [PATCH 06/85] chore: use php-cs-fixer 3.51 --- composer.json | 2 +- lib/Component/VCalendar.php | 2 -- lib/VCardConverter.php | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index bdc8591a1..5c8c96d36 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "^3.49", + "friendsofphp/php-cs-fixer": "^3.51", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", "phpstan/phpstan": "^1.10" diff --git a/lib/Component/VCalendar.php b/lib/Component/VCalendar.php index 318492ce6..7b6e66e88 100644 --- a/lib/Component/VCalendar.php +++ b/lib/Component/VCalendar.php @@ -214,8 +214,6 @@ public function getBaseComponents(?string $componentName = null): array * If there is no such component, null will be returned. * * @param string|null $componentName filter by component name - * - * @return VObject\Component|null */ public function getBaseComponent(?string $componentName = null): ?Component { diff --git a/lib/VCardConverter.php b/lib/VCardConverter.php index 74901fc1b..ff8fe3f81 100644 --- a/lib/VCardConverter.php +++ b/lib/VCardConverter.php @@ -304,7 +304,7 @@ protected function convertBinaryToUri(Component\VCard $output, Binary $newProper * be valid in vCard 3.0 as well, we should convert those to BINARY if * possible, to improve compatibility. * - * @return Property\Binary|Property\Uri|null + * @return Binary|Uri|null * * @throws InvalidDataException */ From 624a4f98a83574c2328c931e4a49a6a1ee8ebac7 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 18 Apr 2024 14:07:12 +0545 Subject: [PATCH 07/85] chore: apply cs-fixer 3.54.0 changes --- lib/Component/VCard.php | 2 +- lib/DateTimeParser.php | 12 +- lib/timezonedata/windowszones.php | 276 +++++++++--------- .../VObject/ITip/BrokerAttendeeReplyTest.php | 18 +- tests/VObject/JCardTest.php | 18 +- tests/VObject/Parser/JsonTest.php | 18 +- tests/VObject/PropertyTest.php | 2 +- tests/VObject/Recur/RRuleIteratorTest.php | 10 +- 8 files changed, 178 insertions(+), 178 deletions(-) diff --git a/lib/Component/VCard.php b/lib/Component/VCard.php index 0f2a5e266..6bbec5717 100644 --- a/lib/Component/VCard.php +++ b/lib/Component/VCard.php @@ -82,7 +82,7 @@ class VCard extends VObject\Document 'ROLE' => VObject\Property\FlatText::class, 'LOGO' => VObject\Property\Binary::class, // 'AGENT' => 'Sabre\\VObject\\Property\\', // Todo: is an embedded vCard. Probably rare, so - // not supported at the moment + // not supported at the moment 'ORG' => VObject\Property\Text::class, 'NOTE' => VObject\Property\FlatText::class, 'REV' => VObject\Property\VCard\TimeStamp::class, diff --git a/lib/DateTimeParser.php b/lib/DateTimeParser.php index 7e5749351..a8130b8bc 100644 --- a/lib/DateTimeParser.php +++ b/lib/DateTimeParser.php @@ -98,12 +98,12 @@ public static function parseDuration(string $duration): \DateInterval } $parts = [ - 'week', - 'day', - 'hour', - 'minute', - 'second', - ]; + 'week', + 'day', + 'hour', + 'minute', + 'second', + ]; foreach ($parts as $part) { $matches[$part] = isset($matches[$part]) && $matches[$part] ? (int) $matches[$part] : 0; diff --git a/lib/timezonedata/windowszones.php b/lib/timezonedata/windowszones.php index 2049a95c1..335007983 100644 --- a/lib/timezonedata/windowszones.php +++ b/lib/timezonedata/windowszones.php @@ -11,142 +11,142 @@ */ return [ - 'AUS Central Standard Time' => 'Australia/Darwin', - 'AUS Eastern Standard Time' => 'Australia/Sydney', - 'Afghanistan Standard Time' => 'Asia/Kabul', - 'Alaskan Standard Time' => 'America/Anchorage', - 'Aleutian Standard Time' => 'America/Adak', - 'Altai Standard Time' => 'Asia/Barnaul', - 'Arab Standard Time' => 'Asia/Riyadh', - 'Arabian Standard Time' => 'Asia/Dubai', - 'Arabic Standard Time' => 'Asia/Baghdad', - 'Argentina Standard Time' => 'America/Buenos_Aires', - 'Astrakhan Standard Time' => 'Europe/Astrakhan', - 'Atlantic Standard Time' => 'America/Halifax', - 'Aus Central W. Standard Time' => 'Australia/Eucla', - 'Azerbaijan Standard Time' => 'Asia/Baku', - 'Azores Standard Time' => 'Atlantic/Azores', - 'Bahia Standard Time' => 'America/Bahia', - 'Bangladesh Standard Time' => 'Asia/Dhaka', - 'Belarus Standard Time' => 'Europe/Minsk', - 'Bougainville Standard Time' => 'Pacific/Bougainville', - 'Canada Central Standard Time' => 'America/Regina', - 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', - 'Caucasus Standard Time' => 'Asia/Yerevan', - 'Cen. Australia Standard Time' => 'Australia/Adelaide', - 'Central America Standard Time' => 'America/Guatemala', - 'Central Asia Standard Time' => 'Asia/Almaty', - 'Central Brazilian Standard Time' => 'America/Cuiaba', - 'Central Europe Standard Time' => 'Europe/Budapest', - 'Central European Standard Time' => 'Europe/Warsaw', - 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', - 'Central Standard Time' => 'America/Chicago', - 'Central Standard Time (Mexico)' => 'America/Mexico_City', - 'Chatham Islands Standard Time' => 'Pacific/Chatham', - 'China Standard Time' => 'Asia/Shanghai', - 'Cuba Standard Time' => 'America/Havana', - 'Dateline Standard Time' => 'Etc/GMT+12', - 'E. Africa Standard Time' => 'Africa/Nairobi', - 'E. Australia Standard Time' => 'Australia/Brisbane', - 'E. Europe Standard Time' => 'Europe/Chisinau', - 'E. South America Standard Time' => 'America/Sao_Paulo', - 'Easter Island Standard Time' => 'Pacific/Easter', - 'Eastern Standard Time' => 'America/New_York', - 'Eastern Standard Time (Mexico)' => 'America/Cancun', - 'Egypt Standard Time' => 'Africa/Cairo', - 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', - 'FLE Standard Time' => 'Europe/Kiev', - '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', - 'Greenwich Standard Time' => 'Atlantic/Reykjavik', - 'Haiti Standard Time' => 'America/Port-au-Prince', - 'Hawaiian Standard Time' => 'Pacific/Honolulu', - 'India Standard Time' => 'Asia/Calcutta', - 'Iran Standard Time' => 'Asia/Tehran', - 'Israel Standard Time' => 'Asia/Jerusalem', - 'Jordan Standard Time' => 'Asia/Amman', - 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', - 'Korea Standard Time' => 'Asia/Seoul', - 'Libya Standard Time' => 'Africa/Tripoli', - 'Line Islands Standard Time' => 'Pacific/Kiritimati', - 'Lord Howe Standard Time' => 'Australia/Lord_Howe', - 'Magadan Standard Time' => 'Asia/Magadan', - 'Magallanes Standard Time' => 'America/Punta_Arenas', - 'Marquesas Standard Time' => 'Pacific/Marquesas', - 'Mauritius Standard Time' => 'Indian/Mauritius', - 'Middle East Standard Time' => 'Asia/Beirut', - 'Montevideo Standard Time' => 'America/Montevideo', - 'Morocco Standard Time' => 'Africa/Casablanca', - 'Mountain Standard Time' => 'America/Denver', - 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', - 'Myanmar Standard Time' => 'Asia/Rangoon', - 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', - 'Namibia Standard Time' => 'Africa/Windhoek', - 'Nepal Standard Time' => 'Asia/Katmandu', - 'New Zealand Standard Time' => 'Pacific/Auckland', - 'Newfoundland Standard Time' => 'America/St_Johns', - 'Norfolk Standard Time' => 'Pacific/Norfolk', - 'North Asia East Standard Time' => 'Asia/Irkutsk', - 'North Asia Standard Time' => 'Asia/Krasnoyarsk', - 'North Korea Standard Time' => 'Asia/Pyongyang', - 'Omsk Standard Time' => 'Asia/Omsk', - 'Pacific SA Standard Time' => 'America/Santiago', - 'Pacific Standard Time' => 'America/Los_Angeles', - 'Pacific Standard Time (Mexico)' => 'America/Tijuana', - 'Pakistan Standard Time' => 'Asia/Karachi', - 'Paraguay Standard Time' => 'America/Asuncion', - 'Qyzylorda Standard Time' => 'Asia/Qyzylorda', - 'Romance Standard Time' => 'Europe/Paris', - 'Russia Time Zone 10' => 'Asia/Srednekolymsk', - 'Russia Time Zone 11' => 'Asia/Kamchatka', - 'Russia Time Zone 3' => 'Europe/Samara', - 'Russian Standard Time' => 'Europe/Moscow', - 'SA Eastern Standard Time' => 'America/Cayenne', - 'SA Pacific Standard Time' => 'America/Bogota', - 'SA Western Standard Time' => 'America/La_Paz', - 'SE Asia Standard Time' => 'Asia/Bangkok', - 'Saint Pierre Standard Time' => 'America/Miquelon', - 'Sakhalin Standard Time' => 'Asia/Sakhalin', - 'Samoa Standard Time' => 'Pacific/Apia', - 'Sao Tome Standard Time' => 'Africa/Sao_Tome', - 'Saratov Standard Time' => 'Europe/Saratov', - 'Singapore Standard Time' => 'Asia/Singapore', - 'South Africa Standard Time' => 'Africa/Johannesburg', - 'Sri Lanka Standard Time' => 'Asia/Colombo', - 'Sudan Standard Time' => 'Africa/Khartoum', - 'Syria Standard Time' => 'Asia/Damascus', - 'Taipei Standard Time' => 'Asia/Taipei', - 'Tasmania Standard Time' => 'Australia/Hobart', - 'Tocantins Standard Time' => 'America/Araguaina', - 'Tokyo Standard Time' => 'Asia/Tokyo', - 'Tomsk Standard Time' => 'Asia/Tomsk', - 'Tonga Standard Time' => 'Pacific/Tongatapu', - '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 Mountain Standard Time' => 'America/Phoenix', - 'UTC' => 'Etc/GMT', - 'UTC+12' => 'Etc/GMT-12', - 'UTC+13' => 'Etc/GMT-13', - 'UTC-02' => 'Etc/GMT+2', - 'UTC-08' => 'Etc/GMT+8', - 'UTC-09' => 'Etc/GMT+9', - 'UTC-11' => 'Etc/GMT+11', - 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', - 'Venezuela Standard Time' => 'America/Caracas', - 'Vladivostok Standard Time' => 'Asia/Vladivostok', - 'Volgograd Standard Time' => 'Europe/Volgograd', - 'W. Australia Standard Time' => 'Australia/Perth', - 'W. Central Africa Standard Time' => 'Africa/Lagos', - 'W. Europe Standard Time' => 'Europe/Berlin', - 'W. Mongolia Standard Time' => 'Asia/Hovd', - 'West Asia Standard Time' => 'Asia/Tashkent', - 'West Bank Standard Time' => 'Asia/Hebron', - 'West Pacific Standard Time' => 'Pacific/Port_Moresby', - 'Yakutsk Standard Time' => 'Asia/Yakutsk', - 'Yukon Standard Time' => 'America/Whitehorse', + 'AUS Central Standard Time' => 'Australia/Darwin', + 'AUS Eastern Standard Time' => 'Australia/Sydney', + 'Afghanistan Standard Time' => 'Asia/Kabul', + 'Alaskan Standard Time' => 'America/Anchorage', + 'Aleutian Standard Time' => 'America/Adak', + 'Altai Standard Time' => 'Asia/Barnaul', + 'Arab Standard Time' => 'Asia/Riyadh', + 'Arabian Standard Time' => 'Asia/Dubai', + 'Arabic Standard Time' => 'Asia/Baghdad', + 'Argentina Standard Time' => 'America/Buenos_Aires', + 'Astrakhan Standard Time' => 'Europe/Astrakhan', + 'Atlantic Standard Time' => 'America/Halifax', + 'Aus Central W. Standard Time' => 'Australia/Eucla', + 'Azerbaijan Standard Time' => 'Asia/Baku', + 'Azores Standard Time' => 'Atlantic/Azores', + 'Bahia Standard Time' => 'America/Bahia', + 'Bangladesh Standard Time' => 'Asia/Dhaka', + 'Belarus Standard Time' => 'Europe/Minsk', + 'Bougainville Standard Time' => 'Pacific/Bougainville', + 'Canada Central Standard Time' => 'America/Regina', + 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', + 'Caucasus Standard Time' => 'Asia/Yerevan', + 'Cen. Australia Standard Time' => 'Australia/Adelaide', + 'Central America Standard Time' => 'America/Guatemala', + 'Central Asia Standard Time' => 'Asia/Almaty', + 'Central Brazilian Standard Time' => 'America/Cuiaba', + 'Central Europe Standard Time' => 'Europe/Budapest', + 'Central European Standard Time' => 'Europe/Warsaw', + 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', + 'Central Standard Time' => 'America/Chicago', + 'Central Standard Time (Mexico)' => 'America/Mexico_City', + 'Chatham Islands Standard Time' => 'Pacific/Chatham', + 'China Standard Time' => 'Asia/Shanghai', + 'Cuba Standard Time' => 'America/Havana', + 'Dateline Standard Time' => 'Etc/GMT+12', + 'E. Africa Standard Time' => 'Africa/Nairobi', + 'E. Australia Standard Time' => 'Australia/Brisbane', + 'E. Europe Standard Time' => 'Europe/Chisinau', + 'E. South America Standard Time' => 'America/Sao_Paulo', + 'Easter Island Standard Time' => 'Pacific/Easter', + 'Eastern Standard Time' => 'America/New_York', + 'Eastern Standard Time (Mexico)' => 'America/Cancun', + 'Egypt Standard Time' => 'Africa/Cairo', + 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', + 'FLE Standard Time' => 'Europe/Kiev', + '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', + 'Greenwich Standard Time' => 'Atlantic/Reykjavik', + 'Haiti Standard Time' => 'America/Port-au-Prince', + 'Hawaiian Standard Time' => 'Pacific/Honolulu', + 'India Standard Time' => 'Asia/Calcutta', + 'Iran Standard Time' => 'Asia/Tehran', + 'Israel Standard Time' => 'Asia/Jerusalem', + 'Jordan Standard Time' => 'Asia/Amman', + 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', + 'Korea Standard Time' => 'Asia/Seoul', + 'Libya Standard Time' => 'Africa/Tripoli', + 'Line Islands Standard Time' => 'Pacific/Kiritimati', + 'Lord Howe Standard Time' => 'Australia/Lord_Howe', + 'Magadan Standard Time' => 'Asia/Magadan', + 'Magallanes Standard Time' => 'America/Punta_Arenas', + 'Marquesas Standard Time' => 'Pacific/Marquesas', + 'Mauritius Standard Time' => 'Indian/Mauritius', + 'Middle East Standard Time' => 'Asia/Beirut', + 'Montevideo Standard Time' => 'America/Montevideo', + 'Morocco Standard Time' => 'Africa/Casablanca', + 'Mountain Standard Time' => 'America/Denver', + 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', + 'Myanmar Standard Time' => 'Asia/Rangoon', + 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', + 'Namibia Standard Time' => 'Africa/Windhoek', + 'Nepal Standard Time' => 'Asia/Katmandu', + 'New Zealand Standard Time' => 'Pacific/Auckland', + 'Newfoundland Standard Time' => 'America/St_Johns', + 'Norfolk Standard Time' => 'Pacific/Norfolk', + 'North Asia East Standard Time' => 'Asia/Irkutsk', + 'North Asia Standard Time' => 'Asia/Krasnoyarsk', + 'North Korea Standard Time' => 'Asia/Pyongyang', + 'Omsk Standard Time' => 'Asia/Omsk', + 'Pacific SA Standard Time' => 'America/Santiago', + 'Pacific Standard Time' => 'America/Los_Angeles', + 'Pacific Standard Time (Mexico)' => 'America/Tijuana', + 'Pakistan Standard Time' => 'Asia/Karachi', + 'Paraguay Standard Time' => 'America/Asuncion', + 'Qyzylorda Standard Time' => 'Asia/Qyzylorda', + 'Romance Standard Time' => 'Europe/Paris', + 'Russia Time Zone 10' => 'Asia/Srednekolymsk', + 'Russia Time Zone 11' => 'Asia/Kamchatka', + 'Russia Time Zone 3' => 'Europe/Samara', + 'Russian Standard Time' => 'Europe/Moscow', + 'SA Eastern Standard Time' => 'America/Cayenne', + 'SA Pacific Standard Time' => 'America/Bogota', + 'SA Western Standard Time' => 'America/La_Paz', + 'SE Asia Standard Time' => 'Asia/Bangkok', + 'Saint Pierre Standard Time' => 'America/Miquelon', + 'Sakhalin Standard Time' => 'Asia/Sakhalin', + 'Samoa Standard Time' => 'Pacific/Apia', + 'Sao Tome Standard Time' => 'Africa/Sao_Tome', + 'Saratov Standard Time' => 'Europe/Saratov', + 'Singapore Standard Time' => 'Asia/Singapore', + 'South Africa Standard Time' => 'Africa/Johannesburg', + 'Sri Lanka Standard Time' => 'Asia/Colombo', + 'Sudan Standard Time' => 'Africa/Khartoum', + 'Syria Standard Time' => 'Asia/Damascus', + 'Taipei Standard Time' => 'Asia/Taipei', + 'Tasmania Standard Time' => 'Australia/Hobart', + 'Tocantins Standard Time' => 'America/Araguaina', + 'Tokyo Standard Time' => 'Asia/Tokyo', + 'Tomsk Standard Time' => 'Asia/Tomsk', + 'Tonga Standard Time' => 'Pacific/Tongatapu', + '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 Mountain Standard Time' => 'America/Phoenix', + 'UTC' => 'Etc/GMT', + 'UTC+12' => 'Etc/GMT-12', + 'UTC+13' => 'Etc/GMT-13', + 'UTC-02' => 'Etc/GMT+2', + 'UTC-08' => 'Etc/GMT+8', + 'UTC-09' => 'Etc/GMT+9', + 'UTC-11' => 'Etc/GMT+11', + 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', + 'Venezuela Standard Time' => 'America/Caracas', + 'Vladivostok Standard Time' => 'Asia/Vladivostok', + 'Volgograd Standard Time' => 'Europe/Volgograd', + 'W. Australia Standard Time' => 'Australia/Perth', + 'W. Central Africa Standard Time' => 'Africa/Lagos', + 'W. Europe Standard Time' => 'Europe/Berlin', + 'W. Mongolia Standard Time' => 'Asia/Hovd', + 'West Asia Standard Time' => 'Asia/Tashkent', + 'West Bank Standard Time' => 'Asia/Hebron', + 'West Pacific Standard Time' => 'Pacific/Port_Moresby', + 'Yakutsk Standard Time' => 'Asia/Yakutsk', + 'Yukon Standard Time' => 'America/Whitehorse', ]; diff --git a/tests/VObject/ITip/BrokerAttendeeReplyTest.php b/tests/VObject/ITip/BrokerAttendeeReplyTest.php index 98942af41..6590356bb 100644 --- a/tests/VObject/ITip/BrokerAttendeeReplyTest.php +++ b/tests/VObject/ITip/BrokerAttendeeReplyTest.php @@ -404,15 +404,15 @@ public function testRecurringAllDay(): void $version = \Sabre\VObject\Version::VERSION; $expected = [ - [ - 'uid' => 'foobar', - 'method' => 'REPLY', - 'component' => 'VEVENT', - 'sender' => 'mailto:one@example.org', - 'senderName' => 'One', - 'recipient' => 'mailto:strunk@example.org', - 'recipientName' => 'Strunk', - 'message' => << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => << Date: Fri, 19 Apr 2024 15:19:29 +0545 Subject: [PATCH 08/85] chore: bump php-cs-fixer requirement to 3.54 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5c8c96d36..fc6911071 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "^3.51", + "friendsofphp/php-cs-fixer": "^3.54", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", "phpstan/phpstan": "^1.10" From a848f933365d7cacf39dc7361c191945dfa00c5f Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Tue, 23 Apr 2024 21:24:34 +0200 Subject: [PATCH 09/85] add `lineIndex` and `lineString` properties to Node --- lib/Document.php | 4 ++-- lib/Parser/MimeDir.php | 2 +- lib/Property.php | 24 +++++++++++++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/Document.php b/lib/Document.php index 6f87b5b97..02eaf13c7 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -167,7 +167,7 @@ public function createComponent(string $name, ?array $children = null, bool $def * * @throws InvalidDataException */ - public function createProperty(string $name, $value = null, ?array $parameters = null, ?string $valueType = null): Property + public function createProperty(string $name, $value = null, ?array $parameters = null, ?string $valueType = null, ?int $lineIndex = null, ?string $lineString = null): Property { // If there's a . in the name, it means it's prefixed by a group name. if (false !== ($i = strpos($name, '.'))) { @@ -201,7 +201,7 @@ public function createProperty(string $name, $value = null, ?array $parameters = $parameters = []; } - return new $class($this, $name, $value, $parameters, $group); + return new $class($this, $name, $value, $parameters, $group, $lineIndex, $lineString); } /** diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index bbef0ea6b..ddac9642b 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -445,7 +445,7 @@ protected function readProperty(string $line) } } - $propObj = $this->root->createProperty($property['name'], null, $namedParameters); + $propObj = $this->root->createProperty($property['name'], null, $namedParameters, null, $this->startLine, $line); foreach ($namelessParameters as $namelessParameter) { $propObj->add(null, $namelessParameter); diff --git a/lib/Property.php b/lib/Property.php index 574099e65..407a14565 100644 --- a/lib/Property.php +++ b/lib/Property.php @@ -51,6 +51,20 @@ abstract class Property extends Node */ public string $delimiter = ';'; + /** + * The line number in the original iCalendar / vCard file + * that corresponds with the current node + * if the node was read from a file + */ + public ?int $lineIndex; + + /** + * The line string from the original iCalendar / vCard file + * that corresponds with the current node + * if the node was read from a file + */ + public ?string $lineString; + /** * Creates the generic property. * @@ -61,7 +75,7 @@ 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) + public function __construct(Component $root, ?string $name, $value = null, array $parameters = [], ?string $group = null, ?int $lineIndex = null, ?string $lineString = null) { $this->name = $name; $this->group = $group; @@ -75,6 +89,14 @@ public function __construct(Component $root, ?string $name, $value = null, array if (!is_null($value)) { $this->setValue($value); } + + if (!is_null($lineIndex)) { + $this->lineIndex = $lineIndex; + } + + if (!is_null($lineString)) { + $this->lineString = $lineString; + } } /** From 800aac9bb2b0db500840a9cd2992787679070c14 Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Thu, 25 Apr 2024 23:26:41 +0200 Subject: [PATCH 10/85] creat Unit Test --- tests/VObject/Component/VCalendarTest.php | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/VObject/Component/VCalendarTest.php b/tests/VObject/Component/VCalendarTest.php index bde6a4fa5..ad19b5eb8 100644 --- a/tests/VObject/Component/VCalendarTest.php +++ b/tests/VObject/Component/VCalendarTest.php @@ -756,6 +756,49 @@ public function testCalDAVMETHOD(): void ); } + public function testNodeInValidationErrorHasLineIndexAndLineStringProps(): void + { + $defectiveInput = <<validate(); + $warningMessages = []; + foreach( $result as $error ) { + $warningMessages[] = $error['message']; + } + self::assertCount(2, $result, 'We expected exactly 2 validation messages, instead we got ' . count( $result ) . ' results:' . implode(', ', $warningMessages) ); + foreach( $result as $idx => $warning ) { + self::assertArrayHasKey( 'node', $warning, 'The validation errors should contain a node key' ); + self::assertInstanceOf( VObject\Property\ICalendar\DateTime::class, $warning[ 'node' ], 'We expected the defective node to be of type Sabre\VObject\Property\ICalendar\DateTime, instead we got type ' . gettype( $warning['node'] ) ); + self::assertObjectHasProperty( 'lineIndex', $warning['node'], 'We expected the defective node in the validation errors array to have a "lineIndex" property' ); + self::assertObjectHasProperty( 'lineString', $warning['node'], 'We expected the defective node in the validation errors array to have a "lineString" property' ); + switch( $idx ) { + case 0: + self::assertEquals( '10', $warning['node']->lineIndex, 'We expected the "lineIndex" property of the first defective node to be 10, instead it was ' . $warning['node']->lineIndex ); + self::assertEquals( 'CREATED:', $warning['node']->lineString, 'We expected the "lineString" property of the first defective node to be "CREATED:", instead it was ' . $warning['node']->lineString ); + break; + case 1: + self::assertEquals( '11', $warning['node']->lineIndex, 'We expected the "lineIndex" property of the second defective node to be 11, instead it was ' . $warning['node']->lineIndex ); + self::assertEquals( 'LAST-MODIFIED:', $warning['node']->lineString, 'We expected the "lineString" property of the second defective node to be "LAST-MODIFIED:", instead it was ' . $warning['node']->lineString ); + break; + } + } + } + public function assertValidate($ics, $options, $expectedLevel, ?string $expectedMessage = null): void { $vcal = VObject\Reader::read($ics); From 2a653373602572daee01b461b38a618addbc1b9a Mon Sep 17 00:00:00 2001 From: Gregor Harlan Date: Wed, 8 May 2024 10:37:50 +0200 Subject: [PATCH 11/85] ITip\Broker: handle timezones in replies to exception events Co-authored-by: Luc DUZAN --- lib/ITip/Broker.php | 6 +- tests/VObject/ITip/BrokerProcessReplyTest.php | 189 ++++++++++++++++++ 2 files changed, 192 insertions(+), 3 deletions(-) diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index 9cbc3f72b..07f53c598 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -333,7 +333,7 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin // Finding all the instances the attendee replied to. foreach ($itipMessage->message->VEVENT as $vevent) { - $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master'; + $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master'; $attendee = $vevent->ATTENDEE; $instances[$recurId] = $attendee['PARTSTAT']->getValue(); if (isset($vevent->{'REQUEST-STATUS'})) { @@ -346,7 +346,7 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin // all the instances where we have a reply for. $masterObject = null; foreach ($existingObject->VEVENT as $vevent) { - $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master'; + $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master'; if ('master' === $recurId) { $masterObject = $vevent; } @@ -393,7 +393,7 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin $newObject = $recurrenceIterator->getEventObject(); $recurrenceIterator->next(); - if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) { + if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() === $recurId) { $found = true; } --$iterations; diff --git a/tests/VObject/ITip/BrokerProcessReplyTest.php b/tests/VObject/ITip/BrokerProcessReplyTest.php index 064d5cb48..19b668818 100644 --- a/tests/VObject/ITip/BrokerProcessReplyTest.php +++ b/tests/VObject/ITip/BrokerProcessReplyTest.php @@ -253,6 +253,75 @@ public function testReplyPartyCrasher(): void $this->process($itip, $old, $expected); } + public function testReplyExistingExceptionRecurrenceIdInUTC(): void + { + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<process($itip, $old, $expected); + } + public function testReplyNewException(): void { // This is a reply to 1 instance of a recurring event. This should @@ -373,6 +442,126 @@ public function testReplyNewExceptionTz(): void $this->process($itip, $old, $expected); } + public function testReplyNewExceptionRecurrenceIdInDifferentTz(): void + { + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<process($itip, $old, $expected); + } + + public function testReplyNewExceptionRecurrenceIdInUTC(): void + { + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<process($itip, $old, $expected); + } + public function testReplyPartyCrashCreateException(): void { // IN this test there's a recurring event that has an exception. The From 620ee98489e91b701e7cd463a7c3bbf75816a52d Mon Sep 17 00:00:00 2001 From: Gregor Harlan Date: Wed, 8 May 2024 11:02:31 +0200 Subject: [PATCH 12/85] remove comment --- tests/VObject/ITip/BrokerProcessReplyTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/VObject/ITip/BrokerProcessReplyTest.php b/tests/VObject/ITip/BrokerProcessReplyTest.php index 19b668818..1718f76d2 100644 --- a/tests/VObject/ITip/BrokerProcessReplyTest.php +++ b/tests/VObject/ITip/BrokerProcessReplyTest.php @@ -255,8 +255,6 @@ public function testReplyPartyCrasher(): void public function testReplyExistingExceptionRecurrenceIdInUTC(): void { - // This is a reply to 1 instance of a recurring event. This should - // automatically create an exception. $itip = << Date: Wed, 8 May 2024 12:28:35 +0200 Subject: [PATCH 13/85] php-cs-fixer --- lib/Property.php | 4 ++-- tests/VObject/Component/VCalendarTest.php | 24 +++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/Property.php b/lib/Property.php index 407a14565..f4d0bdc0b 100644 --- a/lib/Property.php +++ b/lib/Property.php @@ -54,14 +54,14 @@ abstract class Property extends Node /** * The line number in the original iCalendar / vCard file * that corresponds with the current node - * if the node was read from a file + * if the node was read from a file. */ public ?int $lineIndex; /** * The line string from the original iCalendar / vCard file * that corresponds with the current node - * if the node was read from a file + * if the node was read from a file. */ public ?string $lineString; diff --git a/tests/VObject/Component/VCalendarTest.php b/tests/VObject/Component/VCalendarTest.php index ad19b5eb8..67dc2fe8d 100644 --- a/tests/VObject/Component/VCalendarTest.php +++ b/tests/VObject/Component/VCalendarTest.php @@ -777,23 +777,23 @@ public function testNodeInValidationErrorHasLineIndexAndLineStringProps(): void $vcal = VObject\Reader::read($defectiveInput); $result = $vcal->validate(); $warningMessages = []; - foreach( $result as $error ) { + foreach ($result as $error) { $warningMessages[] = $error['message']; } - self::assertCount(2, $result, 'We expected exactly 2 validation messages, instead we got ' . count( $result ) . ' results:' . implode(', ', $warningMessages) ); - foreach( $result as $idx => $warning ) { - self::assertArrayHasKey( 'node', $warning, 'The validation errors should contain a node key' ); - self::assertInstanceOf( VObject\Property\ICalendar\DateTime::class, $warning[ 'node' ], 'We expected the defective node to be of type Sabre\VObject\Property\ICalendar\DateTime, instead we got type ' . gettype( $warning['node'] ) ); - self::assertObjectHasProperty( 'lineIndex', $warning['node'], 'We expected the defective node in the validation errors array to have a "lineIndex" property' ); - self::assertObjectHasProperty( 'lineString', $warning['node'], 'We expected the defective node in the validation errors array to have a "lineString" property' ); - switch( $idx ) { + self::assertCount(2, $result, 'We expected exactly 2 validation messages, instead we got '.count($result).' results:'.implode(', ', $warningMessages)); + foreach ($result as $idx => $warning) { + self::assertArrayHasKey('node', $warning, 'The validation errors should contain a node key'); + self::assertInstanceOf(VObject\Property\ICalendar\DateTime::class, $warning['node'], 'We expected the defective node to be of type Sabre\VObject\Property\ICalendar\DateTime, instead we got type '.gettype($warning['node'])); + self::assertObjectHasProperty('lineIndex', $warning['node'], 'We expected the defective node in the validation errors array to have a "lineIndex" property'); + self::assertObjectHasProperty('lineString', $warning['node'], 'We expected the defective node in the validation errors array to have a "lineString" property'); + switch ($idx) { case 0: - self::assertEquals( '10', $warning['node']->lineIndex, 'We expected the "lineIndex" property of the first defective node to be 10, instead it was ' . $warning['node']->lineIndex ); - self::assertEquals( 'CREATED:', $warning['node']->lineString, 'We expected the "lineString" property of the first defective node to be "CREATED:", instead it was ' . $warning['node']->lineString ); + self::assertEquals('10', $warning['node']->lineIndex, 'We expected the "lineIndex" property of the first defective node to be 10, instead it was '.$warning['node']->lineIndex); + self::assertEquals('CREATED:', $warning['node']->lineString, 'We expected the "lineString" property of the first defective node to be "CREATED:", instead it was '.$warning['node']->lineString); break; case 1: - self::assertEquals( '11', $warning['node']->lineIndex, 'We expected the "lineIndex" property of the second defective node to be 11, instead it was ' . $warning['node']->lineIndex ); - self::assertEquals( 'LAST-MODIFIED:', $warning['node']->lineString, 'We expected the "lineString" property of the second defective node to be "LAST-MODIFIED:", instead it was ' . $warning['node']->lineString ); + self::assertEquals('11', $warning['node']->lineIndex, 'We expected the "lineIndex" property of the second defective node to be 11, instead it was '.$warning['node']->lineIndex); + self::assertEquals('LAST-MODIFIED:', $warning['node']->lineString, 'We expected the "lineString" property of the second defective node to be "LAST-MODIFIED:", instead it was '.$warning['node']->lineString); break; } } From ec31757d19813993419dc9a98314c0c5d12f3e1e Mon Sep 17 00:00:00 2001 From: John D'Orazio Date: Wed, 8 May 2024 14:51:44 +0200 Subject: [PATCH 14/85] remove unnecessary error descriptions --- tests/VObject/Component/VCalendarTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/VObject/Component/VCalendarTest.php b/tests/VObject/Component/VCalendarTest.php index 67dc2fe8d..847e17d02 100644 --- a/tests/VObject/Component/VCalendarTest.php +++ b/tests/VObject/Component/VCalendarTest.php @@ -782,18 +782,18 @@ public function testNodeInValidationErrorHasLineIndexAndLineStringProps(): void } self::assertCount(2, $result, 'We expected exactly 2 validation messages, instead we got '.count($result).' results:'.implode(', ', $warningMessages)); foreach ($result as $idx => $warning) { - self::assertArrayHasKey('node', $warning, 'The validation errors should contain a node key'); - self::assertInstanceOf(VObject\Property\ICalendar\DateTime::class, $warning['node'], 'We expected the defective node to be of type Sabre\VObject\Property\ICalendar\DateTime, instead we got type '.gettype($warning['node'])); - self::assertObjectHasProperty('lineIndex', $warning['node'], 'We expected the defective node in the validation errors array to have a "lineIndex" property'); - self::assertObjectHasProperty('lineString', $warning['node'], 'We expected the defective node in the validation errors array to have a "lineString" property'); + self::assertArrayHasKey('node', $warning); + self::assertInstanceOf(VObject\Property\ICalendar\DateTime::class, $warning['node']); + self::assertObjectHasProperty('lineIndex', $warning['node']); + self::assertObjectHasProperty('lineString', $warning['node']); switch ($idx) { case 0: - self::assertEquals('10', $warning['node']->lineIndex, 'We expected the "lineIndex" property of the first defective node to be 10, instead it was '.$warning['node']->lineIndex); - self::assertEquals('CREATED:', $warning['node']->lineString, 'We expected the "lineString" property of the first defective node to be "CREATED:", instead it was '.$warning['node']->lineString); + self::assertEquals('10', $warning['node']->lineIndex); + self::assertEquals('CREATED:', $warning['node']->lineString); break; case 1: - self::assertEquals('11', $warning['node']->lineIndex, 'We expected the "lineIndex" property of the second defective node to be 11, instead it was '.$warning['node']->lineIndex); - self::assertEquals('LAST-MODIFIED:', $warning['node']->lineString, 'We expected the "lineString" property of the second defective node to be "LAST-MODIFIED:", instead it was '.$warning['node']->lineString); + self::assertEquals('11', $warning['node']->lineIndex); + self::assertEquals('LAST-MODIFIED:', $warning['node']->lineString); break; } } From bee4fa7ddab45f1a558bec4a471c8cd5bf911993 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 9 May 2024 13:55:22 +0545 Subject: [PATCH 15/85] Add comments about use of getTimestamp --- lib/ITip/Broker.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index 07f53c598..be6ba29d7 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -333,6 +333,9 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin // Finding all the instances the attendee replied to. foreach ($itipMessage->message->VEVENT as $vevent) { + // Use the Unix timestamp returned by getTimestamp as a unique identifier for the recurrence. + // The Unix timestamp will be the same for an event, even if the reply from the attendee + // used a different format/timezone to express the event date-time. $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master'; $attendee = $vevent->ATTENDEE; $instances[$recurId] = $attendee['PARTSTAT']->getValue(); @@ -346,6 +349,7 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin // all the instances where we have a reply for. $masterObject = null; foreach ($existingObject->VEVENT as $vevent) { + // Use the Unix timestamp returned by getTimestamp as a unique identifier for the recurrence. $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master'; if ('master' === $recurId) { $masterObject = $vevent; @@ -393,6 +397,9 @@ protected function processMessageReply(Message $itipMessage, ?VCalendar $existin $newObject = $recurrenceIterator->getEventObject(); $recurrenceIterator->next(); + // Compare the Unix timestamp returned by getTimestamp with the previously calculated timestamp. + // If they are the same, then this is a matching recurrence, even though its date-time may have + // been expressed in a different format/timezone. if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() === $recurId) { $found = true; } From e5976c9e2ae1d1de17dfe96f92fd2548bf5f83f3 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Fri, 10 May 2024 14:18:46 +0545 Subject: [PATCH 16/85] chore: stop exporting php-cs-fixer config --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index c717ebe6a..be96a4f14 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,5 +3,6 @@ /.gitattributes export-ignore /.gitignore export-ignore /.php_cs.dist export-ignore +/.php-cs-fixer.dist.php export-ignore /CHANGELOG.md export-ignore /phpstan.neon export-ignore From 8bf65e28f082a230e0d18ae17dbe71db21c1ea65 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Tue, 14 May 2024 14:01:47 +0545 Subject: [PATCH 17/85] chore: bump dev dependencies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fc6911071..0040076bc 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ "friendsofphp/php-cs-fixer": "^3.54", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.11" }, "suggest" : { "hoa/bench" : "If you would like to run the benchmark scripts" From 1a6155035abaaceb924b38f32d168c6a696017a8 Mon Sep 17 00:00:00 2001 From: Victor Emanouilov Date: Tue, 14 May 2024 17:37:32 +0300 Subject: [PATCH 18/85] yearly rrule compliance by the iterator when start date does not follow the rrule --- lib/Recur/RRuleIterator.php | 6 +++++- tests/VObject/Recur/RRuleIteratorTest.php | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 888556eea..10269dafb 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -610,7 +610,11 @@ protected function nextYearly(): void // If we advanced to the next month or year, the first // occurrence is always correct. if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) { - break 2; + // only consider byMonth matches, + // otherwise, we don't follow RRule correctly + if (in_array($currentMonth, $this->byMonth)) { + break 2; + } } } diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 740e9fd0c..e6043d438 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -922,6 +922,23 @@ public function testYearlyBySetPosLoop(): void ); } + /** + * This caused an incorrect date to be returned by the rule iterator when + * start date was not on the rrule list. + */ + public function testYearlyStartDateNotOnRRuleList(): void + { + $this->parse( + 'FREQ=YEARLY;BYMONTH=6;BYDAY=-1FR;UNTIL=20250901T000000Z', + '2023-09-01 12:00:00', + [ + '2023-09-01 12:00:00', + '2024-06-28 12:00:00', + '2025-06-27 12:00:00', + ], + ); + } + /** * Something, somewhere produced an ics with an interval set to 0. Because * this means we increase the current day (or week, month) by 0, this also From 72a1fe9dc6d551ef05257abd90b0eaf8bb7d3fae Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Fri, 17 May 2024 13:22:51 +0545 Subject: [PATCH 19/85] test: add more test scenarios for testYearlyStartDateNotOnRRuleList --- tests/VObject/Recur/RRuleIteratorTest.php | 43 +++++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index e6043d438..931a79967 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -925,18 +925,47 @@ public function testYearlyBySetPosLoop(): void /** * This caused an incorrect date to be returned by the rule iterator when * start date was not on the rrule list. + * + * @dataProvider yearlyStartDateNotOnRRuleListProvider */ - public function testYearlyStartDateNotOnRRuleList(): void + public function testYearlyStartDateNotOnRRuleList(string $rule, string $start, array $expected): void { - $this->parse( - 'FREQ=YEARLY;BYMONTH=6;BYDAY=-1FR;UNTIL=20250901T000000Z', - '2023-09-01 12:00:00', + $this->parse($rule, $start, $expected); + } + + public function yearlyStartDateNotOnRRuleListProvider(): array + { + return [ [ + 'FREQ=YEARLY;BYMONTH=6;BYDAY=-1FR;UNTIL=20250901T000000Z', '2023-09-01 12:00:00', - '2024-06-28 12:00:00', - '2025-06-27 12:00:00', + [ + '2023-09-01 12:00:00', + '2024-06-28 12:00:00', + '2025-06-27 12:00:00', + ], ], - ); + [ + 'FREQ=YEARLY;BYMONTH=6;BYDAY=-1FR;UNTIL=20250901T000000Z', + '2023-06-01 12:00:00', + [ + '2023-06-01 12:00:00', + '2023-06-30 12:00:00', + '2024-06-28 12:00:00', + '2025-06-27 12:00:00', + ], + ], + [ + 'FREQ=YEARLY;BYMONTH=6;BYDAY=-1FR;UNTIL=20250901T000000Z', + '2023-05-01 12:00:00', + [ + '2023-05-01 12:00:00', + '2023-06-30 12:00:00', + '2024-06-28 12:00:00', + '2025-06-27 12:00:00', + ], + ], + ]; } /** From 5d7ca0075904c7ba8d534b4f59f5a40b00e89733 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 27 May 2024 16:15:26 +0545 Subject: [PATCH 20/85] throw ParseException when null input is provided --- lib/Parser/MimeDir.php | 6 ++++++ tests/VObject/Parser/MimeDirTest.php | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index ddac9642b..be5d87baf 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -79,6 +79,12 @@ public function parse($input = null, int $options = 0): ?Document $this->setInput($input); } + if (!\is_resource($this->input)) { + // Null was passed as input, but there was no existing input buffer + // There is nothing to parse. + throw new ParseException('No input provided to parse'); + } + if (0 !== $options) { $this->options = $options; } diff --git a/tests/VObject/Parser/MimeDirTest.php b/tests/VObject/Parser/MimeDirTest.php index 24656f886..db5735b0c 100644 --- a/tests/VObject/Parser/MimeDirTest.php +++ b/tests/VObject/Parser/MimeDirTest.php @@ -101,6 +101,25 @@ public function testDecodeUnsupportedInlineCharset(): void $mimeDir->parse($vcard); } + public function provideEmptyParserInput(): array + { + return [ + [null, 'No input provided to parse'], + ['', 'End of document reached prematurely'], + ]; + } + + /** + * @dataProvider provideEmptyParserInput + */ + public function testParseEmpty($input, $expectedExceptionMessage): void + { + $this->expectException(ParseException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + $mimeDir = new MimeDir(); + $mimeDir->parse($input); + } + public function testDecodeWindows1252(): void { $vcard = << Date: Mon, 22 Apr 2024 14:48:32 +0200 Subject: [PATCH 21/85] Reproduce bug where dst leap is passed on to subsequent occurences --- tests/VObject/Recur/RRuleIteratorTest.php | 58 +++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 931a79967..f0f00ad41 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -162,6 +162,64 @@ public function testDailyBySetPosLoop(): void ); } + /** + * @dataProvider dstTransitionProvider + */ + public function testDailyOnDstTransition(string $start, array $expected): void + { + $this->parse( + 'FREQ=DAILY;INTERVAL=1;COUNT=5', + $start, + $expected, + null, + 'Europe/Zurich', + ); + } + + public function dstTransitionProvider(): iterable + { + yield 'On transition start' => [ + 'Start' => '2023-03-24 02:00:00', + 'Expected' => [ + '2023-03-24 02:00:00', + '2023-03-25 02:00:00', + '2023-03-26 03:00:00', + '2023-03-27 02:00:00', + '2023-03-28 02:00:00', + ], + ]; + yield 'During transition' => [ + 'Start' => '2023-03-24 02:15:00', + 'Expected' => [ + '2023-03-24 02:15:00', + '2023-03-25 02:15:00', + '2023-03-26 03:15:00', + '2023-03-27 02:15:00', + '2023-03-28 02:15:00', + ], + ]; + yield 'On transition end' => [ + 'Start' => '2023-03-24 03:00:00', + 'Expected' => [ + '2023-03-24 03:00:00', + '2023-03-25 03:00:00', + '2023-03-26 03:00:00', + '2023-03-27 03:00:00', + '2023-03-28 03:00:00', + ], + ]; + yield 'After transition end' => [ + 'Start' => '2023-03-24 03:15:00', + 'Expected' => [ + '2023-03-24 03:15:00', + '2023-03-25 03:15:00', + '2023-03-26 03:15:00', + '2023-03-27 03:15:00', + '2023-03-28 03:15:00', + ], + ]; + } + public function testWeekly(): void { $this->parse( From b37ef3d72352e26e9e181bc1441c21a05c251fca Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 9 May 2024 14:05:30 +0545 Subject: [PATCH 22/85] Fix test code format --- tests/VObject/Recur/RRuleIteratorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index f0f00ad41..f850aa770 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -162,7 +162,7 @@ public function testDailyBySetPosLoop(): void ); } - /** + /** * @dataProvider dstTransitionProvider */ public function testDailyOnDstTransition(string $start, array $expected): void From 963189bb893d5b28fa6be75afa91233c41a3daf5 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 9 May 2024 15:13:10 +0545 Subject: [PATCH 23/85] Handle summer time start for daily recurrences --- lib/Recur/RRuleIterator.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 10269dafb..4f25f8063 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -159,6 +159,14 @@ public function fastForward(\DateTimeInterface $dt): void */ protected ?\DateTimeInterface $currentDate; + /** + * The number of hours that the next occurrence of an event + * jumped forward, usually because summer time started and + * the requested time-of-day like 0230 did not exist on that + * day. And so the event was scheduled 1 hour later at 0330. + */ + protected int $hourJump = 0; + /** * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly, * yearly. @@ -290,7 +298,23 @@ protected function nextHourly(): void protected function nextDaily(): void { if (!$this->byHour && !$this->byDay) { + $hourOfCurrentDate = (int) $this->currentDate->format('G'); $this->currentDate = $this->currentDate->modify('+'.$this->interval.' days'); + $hourOfNextDate = (int) $this->currentDate->format('G'); + if (0 === $this->hourJump) { + // Remember if the clock time jumped forward on the nextDate. + // That happens if nextDate is a day when summer time starts + // and the event time is in the non-existent hour of the day. + // For example, an event that normally starts at 02:30 will + // have to start at 03:30 on that day. + $this->hourJump = $hourOfNextDate - $hourOfCurrentDate; + } else { + // The hour "jumped" for the previous date, to avoid the non-existent time. + // currentDate got set ahead by (usually) one hour on that day. + // Adjust it back for this next occurrence. + $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); + $this->hourJump = 0; + } return; } From 2feab3f2e78a1240e6afcc3ac9224890a62ea043 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 9 May 2024 15:37:29 +0545 Subject: [PATCH 24/85] Handle summer time start for weekly recurrences --- lib/Recur/RRuleIterator.php | 16 ++++++ tests/VObject/Recur/RRuleIteratorTest.php | 62 ++++++++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 4f25f8063..1506f7c23 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -373,7 +373,23 @@ protected function nextDaily(): void protected function nextWeekly(): void { if (!$this->byHour && !$this->byDay) { + $hourOfCurrentDate = (int) $this->currentDate->format('G'); $this->currentDate = $this->currentDate->modify('+'.$this->interval.' weeks'); + $hourOfNextDate = (int) $this->currentDate->format('G'); + if (0 === $this->hourJump) { + // Remember if the clock time jumped forward on the nextDate. + // That happens if nextDate is a day when summer time starts + // and the event time is in the non-existent hour of the day. + // For example, an event that normally starts at 02:30 will + // have to start at 03:30 on that day. + $this->hourJump = $hourOfNextDate - $hourOfCurrentDate; + } else { + // The hour "jumped" for the previous date, to avoid the non-existent time. + // currentDate got set ahead by (usually) one hour on that day. + // Adjust it back for this next occurrence. + $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); + $this->hourJump = 0; + } return; } diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index f850aa770..a2518f529 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -163,7 +163,7 @@ public function testDailyBySetPosLoop(): void } /** - * @dataProvider dstTransitionProvider + * @dataProvider dstDailyTransitionProvider */ public function testDailyOnDstTransition(string $start, array $expected): void { @@ -176,7 +176,7 @@ public function testDailyOnDstTransition(string $start, array $expected): void ); } - public function dstTransitionProvider(): iterable + public function dstDailyTransitionProvider(): iterable { yield 'On transition start' => [ 'Start' => '2023-03-24 02:00:00', @@ -323,6 +323,64 @@ public function testWeeklyByDaySpecificHour(): void ); } + /** + * @dataProvider dstWeeklyTransitionProvider + */ + public function testWeeklyOnDstTransition(string $start, array $expected): void + { + $this->parse( + 'FREQ=WEEKLY;INTERVAL=1;COUNT=5', + $start, + $expected, + null, + 'Europe/Zurich', + ); + } + + public function dstWeeklyTransitionProvider(): iterable + { + yield 'On transition start' => [ + 'Start' => '2023-03-12 02:00:00', + 'Expected' => [ + '2023-03-12 02:00:00', + '2023-03-19 02:00:00', + '2023-03-26 03:00:00', + '2023-04-02 02:00:00', + '2023-04-09 02:00:00', + ], + ]; + yield 'During transition' => [ + 'Start' => '2023-03-12 02:15:00', + 'Expected' => [ + '2023-03-12 02:15:00', + '2023-03-19 02:15:00', + '2023-03-26 03:15:00', + '2023-04-02 02:15:00', + '2023-04-09 02:15:00', + ], + ]; + yield 'On transition end' => [ + 'Start' => '2023-03-12 03:00:00', + 'Expected' => [ + '2023-03-12 03:00:00', + '2023-03-19 03:00:00', + '2023-03-26 03:00:00', + '2023-04-02 03:00:00', + '2023-04-09 03:00:00', + ], + ]; + yield 'After transition end' => [ + 'Start' => '2023-03-12 03:15:00', + 'Expected' => [ + '2023-03-12 03:15:00', + '2023-03-19 03:15:00', + '2023-03-26 03:15:00', + '2023-04-02 03:15:00', + '2023-04-09 03:15:00', + ], + ]; + } + public function testMonthly(): void { $this->parse( From 3b37fbc6a57312eaa55f200ce6d5fbeadc436f74 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 9 May 2024 15:45:34 +0545 Subject: [PATCH 25/85] Handle summer time start for monthly recurrences --- lib/Recur/RRuleIterator.php | 16 +++++++ tests/VObject/Recur/RRuleIteratorTest.php | 58 +++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 1506f7c23..dccb4fc5d 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -448,7 +448,23 @@ protected function nextMonthly(): void // occur to the next month. We Must skip these invalid // entries. if ($currentDayOfMonth < 29) { + $hourOfCurrentDate = (int) $this->currentDate->format('G'); $this->currentDate = $this->currentDate->modify('+'.$this->interval.' months'); + $hourOfNextDate = (int) $this->currentDate->format('G'); + if (0 === $this->hourJump) { + // Remember if the clock time jumped forward on the nextDate. + // That happens if nextDate is a day when summer time starts + // and the event time is in the non-existent hour of the day. + // For example, an event that normally starts at 02:30 will + // have to start at 03:30 on that day. + $this->hourJump = $hourOfNextDate - $hourOfCurrentDate; + } else { + // The hour "jumped" for the previous date, to avoid the non-existent time. + // currentDate got set ahead by (usually) one hour on that day. + // Adjust it back for this next occurrence. + $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); + $this->hourJump = 0; + } } else { $increase = 0; do { diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index a2518f529..21391b77c 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -530,6 +530,64 @@ public function testMonthlyByDayBySetPos(): void ); } + /** + * @dataProvider dstMonthlyTransitionProvider + */ + public function testMonthlyOnDstTransition(string $start, array $expected): void + { + $this->parse( + 'FREQ=MONTHLY;INTERVAL=1;COUNT=5', + $start, + $expected, + null, + 'Europe/Zurich', + ); + } + + public function dstMonthlyTransitionProvider(): iterable + { + yield 'On transition start' => [ + 'Start' => '2023-01-26 02:00:00', + 'Expected' => [ + '2023-01-26 02:00:00', + '2023-02-26 02:00:00', + '2023-03-26 03:00:00', + '2023-04-26 02:00:00', + '2023-05-26 02:00:00', + ], + ]; + yield 'During transition' => [ + 'Start' => '2023-01-26 02:15:00', + 'Expected' => [ + '2023-01-26 02:15:00', + '2023-02-26 02:15:00', + '2023-03-26 03:15:00', + '2023-04-26 02:15:00', + '2023-05-26 02:15:00', + ], + ]; + yield 'On transition end' => [ + 'Start' => '2023-01-26 03:00:00', + 'Expected' => [ + '2023-01-26 03:00:00', + '2023-02-26 03:00:00', + '2023-03-26 03:00:00', + '2023-04-26 03:00:00', + '2023-05-26 03:00:00', + ], + ]; + yield 'After transition end' => [ + 'Start' => '2023-01-26 03:15:00', + 'Expected' => [ + '2023-01-26 03:15:00', + '2023-02-26 03:15:00', + '2023-03-26 03:15:00', + '2023-04-26 03:15:00', + '2023-05-26 03:15:00', + ], + ]; + } + public function testYearly(): void { $this->parse( From f4a0bba64f5de9ae98fdf9f93fca0e1df88afade Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 9 May 2024 15:53:12 +0545 Subject: [PATCH 26/85] Handle summer time start for yearly recurrences --- lib/Recur/RRuleIterator.php | 16 +++++++ tests/VObject/Recur/RRuleIteratorTest.php | 58 +++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index dccb4fc5d..508ac002b 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -642,7 +642,23 @@ protected function nextYearly(): void } // The easiest form + $hourOfCurrentDate = (int) $this->currentDate->format('G'); $this->currentDate = $this->currentDate->modify('+'.$this->interval.' years'); + $hourOfNextDate = (int) $this->currentDate->format('G'); + if (0 === $this->hourJump) { + // Remember if the clock time jumped forward on the nextDate. + // That happens if nextDate is a day when summer time starts + // and the event time is in the non-existent hour of the day. + // For example, an event that normally starts at 02:30 will + // have to start at 03:30 on that day. + $this->hourJump = $hourOfNextDate - $hourOfCurrentDate; + } else { + // The hour "jumped" for the previous date, to avoid the non-existent time. + // currentDate got set ahead by (usually) one hour on that day. + // Adjust it back for this next occurrence. + $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); + $this->hourJump = 0; + } return; } diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 21391b77c..144f042a0 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -884,6 +884,64 @@ public function testYearlyByDayByWeekNo(): void ); } + /** + * @dataProvider dstYearlyTransitionProvider + */ + public function testYearlyOnDstTransition(string $start, array $expected): void + { + $this->parse( + 'FREQ=YEARLY;INTERVAL=1;COUNT=5', + $start, + $expected, + null, + 'Europe/Zurich', + ); + } + + public function dstYearlyTransitionProvider(): iterable + { + yield 'On transition start' => [ + 'Start' => '2021-03-26 02:00:00', + 'Expected' => [ + '2021-03-26 02:00:00', + '2022-03-26 02:00:00', + '2023-03-26 03:00:00', + '2024-03-26 02:00:00', + '2025-03-26 02:00:00', + ], + ]; + yield 'During transition' => [ + 'Start' => '2021-03-26 02:15:00', + 'Expected' => [ + '2021-03-26 02:15:00', + '2022-03-26 02:15:00', + '2023-03-26 03:15:00', + '2024-03-26 02:15:00', + '2025-03-26 02:15:00', + ], + ]; + yield 'On transition end' => [ + 'Start' => '2021-03-26 03:00:00', + 'Expected' => [ + '2021-03-26 03:00:00', + '2022-03-26 03:00:00', + '2023-03-26 03:00:00', + '2024-03-26 03:00:00', + '2025-03-26 03:00:00', + ], + ]; + yield 'After transition end' => [ + 'Start' => '2021-03-26 03:15:00', + 'Expected' => [ + '2021-03-26 03:15:00', + '2022-03-26 03:15:00', + '2023-03-26 03:15:00', + '2024-03-26 03:15:00', + '2025-03-26 03:15:00', + ], + ]; + } + public function testFastForward(): void { // The idea is that we're fast-forwarding too far in the future, so From 778177c996c9c8145b5ffc72339669f24247b1b1 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 9 May 2024 16:07:36 +0545 Subject: [PATCH 27/85] Refactor summer time start logic into advanceTheDate function --- lib/Recur/RRuleIterator.php | 99 ++++++++++++------------------------- 1 file changed, 31 insertions(+), 68 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 508ac002b..eca07f732 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -284,6 +284,33 @@ public function fastForward(\DateTimeInterface $dt): void /* Functions that advance the iterator {{{ */ + /** + * Advances currentDate by the interval. + * Takes into account the case where summer time starts and + * the event time on that day may have had to be advanced, + * usually by 1 hour. + */ + protected function advanceTheDate(string $interval): void + { + $hourOfCurrentDate = (int) $this->currentDate->format('G'); + $this->currentDate = $this->currentDate->modify($interval); + $hourOfNextDate = (int) $this->currentDate->format('G'); + if (0 === $this->hourJump) { + // Remember if the clock time jumped forward on the next date. + // That happens if the next date is a day when summer time starts + // and the event time is in the non-existent hour of the day. + // For example, an event that normally starts at 02:30 will + // have to start at 03:30 on that day. + $this->hourJump = $hourOfNextDate - $hourOfCurrentDate; + } else { + // The hour "jumped" for the previous date, to avoid the non-existent time. + // currentDate got set ahead by (usually) 1 hour on that day. + // Adjust it back for this next occurrence. + $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); + $this->hourJump = 0; + } + } + /** * Does the processing for advancing the iterator for hourly frequency. */ @@ -298,23 +325,7 @@ protected function nextHourly(): void protected function nextDaily(): void { if (!$this->byHour && !$this->byDay) { - $hourOfCurrentDate = (int) $this->currentDate->format('G'); - $this->currentDate = $this->currentDate->modify('+'.$this->interval.' days'); - $hourOfNextDate = (int) $this->currentDate->format('G'); - if (0 === $this->hourJump) { - // Remember if the clock time jumped forward on the nextDate. - // That happens if nextDate is a day when summer time starts - // and the event time is in the non-existent hour of the day. - // For example, an event that normally starts at 02:30 will - // have to start at 03:30 on that day. - $this->hourJump = $hourOfNextDate - $hourOfCurrentDate; - } else { - // The hour "jumped" for the previous date, to avoid the non-existent time. - // currentDate got set ahead by (usually) one hour on that day. - // Adjust it back for this next occurrence. - $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); - $this->hourJump = 0; - } + $this->advanceTheDate('+'.$this->interval.' days'); return; } @@ -373,23 +384,7 @@ protected function nextDaily(): void protected function nextWeekly(): void { if (!$this->byHour && !$this->byDay) { - $hourOfCurrentDate = (int) $this->currentDate->format('G'); - $this->currentDate = $this->currentDate->modify('+'.$this->interval.' weeks'); - $hourOfNextDate = (int) $this->currentDate->format('G'); - if (0 === $this->hourJump) { - // Remember if the clock time jumped forward on the nextDate. - // That happens if nextDate is a day when summer time starts - // and the event time is in the non-existent hour of the day. - // For example, an event that normally starts at 02:30 will - // have to start at 03:30 on that day. - $this->hourJump = $hourOfNextDate - $hourOfCurrentDate; - } else { - // The hour "jumped" for the previous date, to avoid the non-existent time. - // currentDate got set ahead by (usually) one hour on that day. - // Adjust it back for this next occurrence. - $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); - $this->hourJump = 0; - } + $this->advanceTheDate('+'.$this->interval.' weeks'); return; } @@ -448,23 +443,7 @@ protected function nextMonthly(): void // occur to the next month. We Must skip these invalid // entries. if ($currentDayOfMonth < 29) { - $hourOfCurrentDate = (int) $this->currentDate->format('G'); - $this->currentDate = $this->currentDate->modify('+'.$this->interval.' months'); - $hourOfNextDate = (int) $this->currentDate->format('G'); - if (0 === $this->hourJump) { - // Remember if the clock time jumped forward on the nextDate. - // That happens if nextDate is a day when summer time starts - // and the event time is in the non-existent hour of the day. - // For example, an event that normally starts at 02:30 will - // have to start at 03:30 on that day. - $this->hourJump = $hourOfNextDate - $hourOfCurrentDate; - } else { - // The hour "jumped" for the previous date, to avoid the non-existent time. - // currentDate got set ahead by (usually) one hour on that day. - // Adjust it back for this next occurrence. - $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); - $this->hourJump = 0; - } + $this->advanceTheDate('+'.$this->interval.' months'); } else { $increase = 0; do { @@ -642,23 +621,7 @@ protected function nextYearly(): void } // The easiest form - $hourOfCurrentDate = (int) $this->currentDate->format('G'); - $this->currentDate = $this->currentDate->modify('+'.$this->interval.' years'); - $hourOfNextDate = (int) $this->currentDate->format('G'); - if (0 === $this->hourJump) { - // Remember if the clock time jumped forward on the nextDate. - // That happens if nextDate is a day when summer time starts - // and the event time is in the non-existent hour of the day. - // For example, an event that normally starts at 02:30 will - // have to start at 03:30 on that day. - $this->hourJump = $hourOfNextDate - $hourOfCurrentDate; - } else { - // The hour "jumped" for the previous date, to avoid the non-existent time. - // currentDate got set ahead by (usually) one hour on that day. - // Adjust it back for this next occurrence. - $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); - $this->hourJump = 0; - } + $this->advanceTheDate('+'.$this->interval.' years'); return; } From fb5689a29390488b5dc3934514b503d2859004d5 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 9 May 2024 17:16:55 +0545 Subject: [PATCH 28/85] Handle summer time start for hourly recurrences --- lib/Recur/RRuleIterator.php | 21 ++++ tests/VObject/Recur/RRuleIteratorTest.php | 116 ++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index eca07f732..2f320036e 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -316,7 +316,28 @@ protected function advanceTheDate(string $interval): void */ protected function nextHourly(): void { + $hourOfCurrentDate = (int) $this->currentDate->format('G'); $this->currentDate = $this->currentDate->modify('+'.$this->interval.' hours'); + if (0 === $this->hourJump) { + // Remember if the clock time jumped forward on the next occurrence. + // That happens if the next event time is on a day when summer time starts + // and the event time is in the non-existent hour of the day. + // For example, an event that normally starts at 02:30 will + // have to start at 03:30 on that day. + // If the interval is just 1 hour, then there is no "jumping back" to do. + // The events that day will happen, for example, at 0130 0330 0430 0530... + if ($this->interval > 1) { + $expectedHourOfNextDate = ($hourOfCurrentDate + $this->interval) % 24; + $actualHourOfNextDate = (int) $this->currentDate->format('G'); + $this->hourJump = $actualHourOfNextDate - $expectedHourOfNextDate; + } + } else { + // The hour "jumped" for the previous occurrence, to avoid the non-existent time. + // currentDate got set ahead by (usually) 1 hour on that day. + // Adjust it back for this next occurrence. + $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); + $this->hourJump = 0; + } } /** diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 144f042a0..51640a956 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -29,6 +29,122 @@ public function testHourly(): void ); } + /** + * @dataProvider dst2HourlyTransitionProvider + */ + public function test2HourlyOnDstTransition(string $start, array $expected): void + { + $this->parse( + 'FREQ=HOURLY;INTERVAL=2;COUNT=5', + $start, + $expected, + null, + 'Europe/Zurich', + ); + } + + public function dst2HourlyTransitionProvider(): iterable + { + yield 'On transition start' => [ + 'Start' => '2023-03-26 00:00:00', + 'Expected' => [ + '2023-03-26 00:00:00', + '2023-03-26 03:00:00', + '2023-03-26 04:00:00', + '2023-03-26 06:00:00', + '2023-03-26 08:00:00', + ], + ]; + yield 'During transition' => [ + 'Start' => '2023-03-26 00:15:00', + 'Expected' => [ + '2023-03-26 00:15:00', + '2023-03-26 03:15:00', + '2023-03-26 04:15:00', + '2023-03-26 06:15:00', + '2023-03-26 08:15:00', + ], + ]; + yield 'On transition end' => [ + 'Start' => '2023-03-26 01:00:00', + 'Expected' => [ + '2023-03-26 01:00:00', + '2023-03-26 03:00:00', + '2023-03-26 05:00:00', + '2023-03-26 07:00:00', + '2023-03-26 09:00:00', + ], + ]; + yield 'After transition end' => [ + 'Start' => '2023-03-26 01:15:00', + 'Expected' => [ + '2023-03-26 01:15:00', + '2023-03-26 03:15:00', + '2023-03-26 05:15:00', + '2023-03-26 07:15:00', + '2023-03-26 09:15:00', + ], + ]; + } + + /** + * @dataProvider dst6HourlyTransitionProvider + */ + public function testHourlyOnDstTransition(string $start, array $expected): void + { + $this->parse( + 'FREQ=HOURLY;INTERVAL=6;COUNT=5', + $start, + $expected, + null, + 'Europe/Zurich', + ); + } + + public function dst6HourlyTransitionProvider(): iterable + { + yield 'On transition start' => [ + 'Start' => '2023-03-25 20:00:00', + 'Expected' => [ + '2023-03-25 20:00:00', + '2023-03-26 03:00:00', + '2023-03-26 08:00:00', + '2023-03-26 14:00:00', + '2023-03-26 20:00:00', + ], + ]; + yield 'During transition' => [ + 'Start' => '2023-03-25 20:15:00', + 'Expected' => [ + '2023-03-25 20:15:00', + '2023-03-26 03:15:00', + '2023-03-26 08:15:00', + '2023-03-26 14:15:00', + '2023-03-26 20:15:00', + ], + ]; + yield 'On transition end' => [ + 'Start' => '2023-03-25 21:00:00', + 'Expected' => [ + '2023-03-25 21:00:00', + '2023-03-26 03:00:00', + '2023-03-26 09:00:00', + '2023-03-26 15:00:00', + '2023-03-26 21:00:00', + ], + ]; + yield 'After transition end' => [ + 'Start' => '2023-03-25 21:15:00', + 'Expected' => [ + '2023-03-25 21:15:00', + '2023-03-26 03:15:00', + '2023-03-26 09:15:00', + '2023-03-26 15:15:00', + '2023-03-26 21:15:00', + ], + ]; + } + public function testDaily(): void { $this->parse( From 018789e4456d07dd953133b1102ef3dee66950ad Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 9 May 2024 17:21:14 +0545 Subject: [PATCH 29/85] Refactor advanceTheDate --- lib/Recur/RRuleIterator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 2f320036e..f19ef0596 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -292,16 +292,16 @@ public function fastForward(\DateTimeInterface $dt): void */ protected function advanceTheDate(string $interval): void { - $hourOfCurrentDate = (int) $this->currentDate->format('G'); + $hourOfPreviousDate = (int) $this->currentDate->format('G'); $this->currentDate = $this->currentDate->modify($interval); - $hourOfNextDate = (int) $this->currentDate->format('G'); if (0 === $this->hourJump) { // Remember if the clock time jumped forward on the next date. // That happens if the next date is a day when summer time starts // and the event time is in the non-existent hour of the day. // For example, an event that normally starts at 02:30 will // have to start at 03:30 on that day. - $this->hourJump = $hourOfNextDate - $hourOfCurrentDate; + $hourOfNextDate = (int) $this->currentDate->format('G'); + $this->hourJump = $hourOfNextDate - $hourOfPreviousDate; } else { // The hour "jumped" for the previous date, to avoid the non-existent time. // currentDate got set ahead by (usually) 1 hour on that day. From d0cb455e70ff05a85dac55fe83e6b56de77cd395 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Fri, 17 May 2024 14:23:41 +0545 Subject: [PATCH 30/85] fix: refactor advanceTheDate --- lib/Recur/RRuleIterator.php | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index f19ef0596..f562d750d 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -286,29 +286,16 @@ public function fastForward(\DateTimeInterface $dt): void /** * Advances currentDate by the interval. - * Takes into account the case where summer time starts and - * the event time on that day may have had to be advanced, - * usually by 1 hour. + * The time is set from the original startDate. + * If the recurrence is on a day when summer time started, then the + * time on that day may have jumped forward, for example, from 0230 to 0330. + * Using the original time means that the next recurrence will be calculated + * based on the original start time and the day/week/month/year interval. + * So the start time of the next occurrence can correctly revert to 0230. */ protected function advanceTheDate(string $interval): void { - $hourOfPreviousDate = (int) $this->currentDate->format('G'); - $this->currentDate = $this->currentDate->modify($interval); - if (0 === $this->hourJump) { - // Remember if the clock time jumped forward on the next date. - // That happens if the next date is a day when summer time starts - // and the event time is in the non-existent hour of the day. - // For example, an event that normally starts at 02:30 will - // have to start at 03:30 on that day. - $hourOfNextDate = (int) $this->currentDate->format('G'); - $this->hourJump = $hourOfNextDate - $hourOfPreviousDate; - } else { - // The hour "jumped" for the previous date, to avoid the non-existent time. - // currentDate got set ahead by (usually) 1 hour on that day. - // Adjust it back for this next occurrence. - $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); - $this->hourJump = 0; - } + $this->currentDate = $this->currentDate->modify($interval.' '.$this->startDate->format('H:i:s')); } /** From eef9fa6003d7d000d8836bc2d8dc11dc726ace63 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Fri, 17 May 2024 15:28:30 +0545 Subject: [PATCH 31/85] Handle case when BYMONTHDAY falls on summer time start --- lib/Recur/RRuleIterator.php | 6 +++++- tests/VObject/Recur/RRuleIteratorTest.php | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index f562d750d..0c810d947 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -508,11 +508,15 @@ protected function nextMonthly(): void } } + // Set the currentDate to the year and month that we are in, and the day of the month that we have selected. + // That day could be a day when summer time starts, and if the time of the event is, for example, 0230, + // then 0230 will not be a valid time on that day. So always apply the start time from the original startDate. + // The "modify" method will set the time forward to 0330, for example, if needed. $this->currentDate = $this->currentDate->setDate( (int) $this->currentDate->format('Y'), (int) $this->currentDate->format('n'), (int) $occurrence - ); + )->modify($this->startDate->format('H:i:s')); } /** diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 51640a956..d8dda554c 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -553,6 +553,26 @@ public function testMonthlyByMonthDay(): void ); } + public function testMonthlyByMonthDayDstTransition(): void + { + $this->parse( + 'FREQ=MONTHLY;INTERVAL=1;COUNT=8;BYMONTHDAY=1,26', + '2023-01-01 02:15:00', + [ + '2023-01-01 02:15:00', + '2023-01-26 02:15:00', + '2023-02-01 02:15:00', + '2023-02-26 02:15:00', + '2023-03-01 02:15:00', + '2023-03-26 03:15:00', + '2023-04-01 02:15:00', + '2023-04-26 02:15:00', + ], + null, + 'Europe/Zurich', + ); + } + public function testMonthlyByDay(): void { $this->parse( From 9b20d5e927a632a7ce6dc6a61eaf4bd18c05671e Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Fri, 17 May 2024 15:50:44 +0545 Subject: [PATCH 32/85] Handle case when day at or near end of month falls on summer time start --- lib/Recur/RRuleIterator.php | 2 +- tests/VObject/Recur/RRuleIteratorTest.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 0c810d947..4b19f77ca 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -457,7 +457,7 @@ protected function nextMonthly(): void do { ++$increase; $tempDate = clone $this->currentDate; - $tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months'); + $tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months '.$this->startDate->format('H:i:s')); } while ($tempDate->format('j') != $currentDayOfMonth); $this->currentDate = $tempDate; } diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index d8dda554c..ee40c3707 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -722,6 +722,16 @@ public function dstMonthlyTransitionProvider(): iterable '2023-05-26 03:15:00', ], ]; + yield 'During transition on 31st day of month' => [ + 'Start' => '2024-01-31 02:15:00', + 'Expected' => [ + '2024-01-31 02:15:00', + '2024-03-31 03:15:00', + '2024-05-31 02:15:00', + '2024-07-31 02:15:00', + '2024-08-31 02:15:00', + ], + ]; } public function testYearly(): void From 102909efa694abd1e8574f0d2a3e9e71dcd099a0 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 27 May 2024 14:08:12 +0545 Subject: [PATCH 33/85] refactor hourly time jump logic into adjustForTimeJumpsOfHourlyEvent private method --- lib/Recur/RRuleIterator.php | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 4b19f77ca..aa6261968 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -299,12 +299,10 @@ protected function advanceTheDate(string $interval): void } /** - * Does the processing for advancing the iterator for hourly frequency. + * Does the processing for adjusting the time of multi-hourly events when summer time starts. */ - protected function nextHourly(): void + private function adjustForTimeJumpsOfHourlyEvent(\DateTimeInterface $previousEventDateTime): void { - $hourOfCurrentDate = (int) $this->currentDate->format('G'); - $this->currentDate = $this->currentDate->modify('+'.$this->interval.' hours'); if (0 === $this->hourJump) { // Remember if the clock time jumped forward on the next occurrence. // That happens if the next event time is on a day when summer time starts @@ -312,9 +310,9 @@ protected function nextHourly(): void // For example, an event that normally starts at 02:30 will // have to start at 03:30 on that day. // If the interval is just 1 hour, then there is no "jumping back" to do. - // The events that day will happen, for example, at 0130 0330 0430 0530... + // The events that day will happen, for example, at 0030 0130 0330 0430 0530... if ($this->interval > 1) { - $expectedHourOfNextDate = ($hourOfCurrentDate + $this->interval) % 24; + $expectedHourOfNextDate = ((int) $previousEventDateTime->format('G') + $this->interval) % 24; $actualHourOfNextDate = (int) $this->currentDate->format('G'); $this->hourJump = $actualHourOfNextDate - $expectedHourOfNextDate; } @@ -327,6 +325,16 @@ protected function nextHourly(): void } } + /** + * Does the processing for advancing the iterator for hourly frequency. + */ + protected function nextHourly(): void + { + $previousEventDateTime = clone $this->currentDate; + $this->currentDate = $this->currentDate->modify('+'.$this->interval.' hours'); + $this->adjustForTimeJumpsOfHourlyEvent($previousEventDateTime); + } + /** * Does the processing for advancing the iterator for daily frequency. */ From 85d72e0d4d3e7d31149c5cc90d402c7f4fd05534 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 27 May 2024 14:31:48 +0545 Subject: [PATCH 34/85] refactor original start time calculation into startTime method --- lib/Recur/RRuleIterator.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index aa6261968..f1fd03e3f 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -284,6 +284,16 @@ public function fastForward(\DateTimeInterface $dt): void /* Functions that advance the iterator {{{ */ + /** + * Gets the original start time of the RRULE. + * + * The value is formatted as a string with 24-hour:minute:second + */ + protected function startTime(): string + { + return $this->startDate->format('H:i:s'); + } + /** * Advances currentDate by the interval. * The time is set from the original startDate. @@ -295,7 +305,7 @@ public function fastForward(\DateTimeInterface $dt): void */ protected function advanceTheDate(string $interval): void { - $this->currentDate = $this->currentDate->modify($interval.' '.$this->startDate->format('H:i:s')); + $this->currentDate = $this->currentDate->modify($interval.' '.$this->startTime()); } /** @@ -465,7 +475,7 @@ protected function nextMonthly(): void do { ++$increase; $tempDate = clone $this->currentDate; - $tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months '.$this->startDate->format('H:i:s')); + $tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months '.$this->startTime()); } while ($tempDate->format('j') != $currentDayOfMonth); $this->currentDate = $tempDate; } @@ -524,7 +534,7 @@ protected function nextMonthly(): void (int) $this->currentDate->format('Y'), (int) $this->currentDate->format('n'), (int) $occurrence - )->modify($this->startDate->format('H:i:s')); + )->modify($this->startTime()); } /** From cc112fbde94450699a957bfc4be48caa8e699f77 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 27 May 2024 14:33:03 +0545 Subject: [PATCH 35/85] refactor adjustForTimeJumpsOfHourlyEvent to be protected --- lib/Recur/RRuleIterator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index f1fd03e3f..84f761bce 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -311,7 +311,7 @@ protected function advanceTheDate(string $interval): void /** * Does the processing for adjusting the time of multi-hourly events when summer time starts. */ - private function adjustForTimeJumpsOfHourlyEvent(\DateTimeInterface $previousEventDateTime): void + protected function adjustForTimeJumpsOfHourlyEvent(\DateTimeInterface $previousEventDateTime): void { if (0 === $this->hourJump) { // Remember if the clock time jumped forward on the next occurrence. From 9d68c7aff72db1a78330dc13e3ca175874b27ac4 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 30 May 2024 12:31:10 +0545 Subject: [PATCH 36/85] Handle summer time start for weekly BYDAY recurrences --- lib/Recur/RRuleIterator.php | 2 +- tests/VObject/Recur/RRuleIteratorTest.php | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 84f761bce..a90ae9087 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -432,7 +432,7 @@ protected function nextWeekly(): void if ($this->byHour) { $this->currentDate = $this->currentDate->modify('+1 hours'); } else { - $this->currentDate = $this->currentDate->modify('+1 days'); + $this->advanceTheDate('+1 days'); } // Current day of the week diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index ee40c3707..08060a450 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -439,6 +439,24 @@ public function testWeeklyByDaySpecificHour(): void ); } + public function testWeeklyByDaySpecificHourOnDstTransition(): void + { + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;BYDAY=SA,SU', + '2023-03-11 02:30:00', + [ + '2023-03-11 02:30:00', + '2023-03-12 02:30:00', + '2023-03-25 02:30:00', + '2023-03-26 03:30:00', + '2023-04-08 02:30:00', + '2023-04-09 02:30:00', + ], + null, + 'Europe/Zurich', + ); + } + /** * @dataProvider dstWeeklyTransitionProvider */ From 5a3dd88c084c20ee87334ba7e54434f16449dad0 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 30 May 2024 12:52:42 +0545 Subject: [PATCH 37/85] Add test case for Weekly BYDAY with BYHOUR on summer-time --- tests/VObject/Recur/RRuleIteratorTest.php | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 08060a450..e56d0c63b 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -457,6 +457,34 @@ public function testWeeklyByDaySpecificHourOnDstTransition(): void ); } + public function testWeeklyByDayByHourOnDstTransition(): void + { + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;BYDAY=SA,SU;WKST=MO;BYHOUR=2,14', + '2023-03-11 02:00:00', + [ + '2023-03-11 02:00:00', + '2023-03-11 14:00:00', + '2023-03-12 02:00:00', + '2023-03-12 14:00:00', + '2023-03-25 02:00:00', + '2023-03-25 14:00:00', + // 02:00:00 does not exist on 2023-03-26 because of summer-time start. + // The current implementation logic does not schedule a recurrence on + // the morning of 2023-03-26. But maybe it should schedule one at 03:00:00. + // The RFC is silent about the required behavior in this case. + // '2023-03-26 03:00:00', + '2023-03-26 14:00:00', + '2023-04-08 02:00:00', + '2023-04-08 14:00:00', + '2023-04-09 02:00:00', + '2023-04-09 14:00:00', + ], + null, + 'Europe/Zurich', + ); + } + /** * @dataProvider dstWeeklyTransitionProvider */ From 9039f90fc6d2ee4a54ba53e841f9da651eb7bb92 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 30 May 2024 13:49:01 +0545 Subject: [PATCH 38/85] Add test cases and fix YEARLY with BYMONTH on summer-time transition --- lib/Recur/RRuleIterator.php | 2 +- tests/VObject/Recur/RRuleIteratorTest.php | 45 +++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index a90ae9087..6ce3e609f 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -732,7 +732,7 @@ protected function nextYearly(): void (int) $currentYear, (int) $currentMonth, (int) $currentDayOfMonth - ); + )->modify($this->startTime()); return; } diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index e56d0c63b..2a206e56e 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -661,6 +661,31 @@ public function testMonthlyByDayUntil(): void ); } + public function testMonthlyByDayOnDstTransition(): void + { + $this->parse( + 'FREQ=MONTHLY;INTERVAL=2;COUNT=13;BYDAY=SU', + '2023-01-01 02:30:00', + [ + '2023-01-01 02:30:00', + '2023-01-08 02:30:00', + '2023-01-15 02:30:00', + '2023-01-22 02:30:00', + '2023-01-29 02:30:00', + '2023-03-05 02:30:00', + '2023-03-12 02:30:00', + '2023-03-19 02:30:00', + '2023-03-26 03:30:00', + '2023-05-07 02:30:00', + '2023-05-14 02:30:00', + '2023-05-21 02:30:00', + '2023-05-28 02:30:00', + ], + null, + 'Europe/Zurich', + ); + } + public function testMonthlyByDayUntilWithImpossibleNextOccurrence(): void { $this->parse( @@ -831,6 +856,26 @@ public function testYearlyByMonth(): void ); } + public function testYearlyByMonthOnDstTransition(): void + { + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=2;BYMONTH=3,9', + '2019-03-26 02:30:00', + [ + '2019-03-26 02:30:00', + '2019-09-26 02:30:00', + '2021-03-26 02:30:00', + '2021-09-26 02:30:00', + '2023-03-26 03:30:00', + '2023-09-26 02:30:00', + '2025-03-26 02:30:00', + '2025-09-26 02:30:00', + ], + null, + 'Europe/Zurich', + ); + } + public function testYearlyByMonthInvalidValue1(): void { $this->expectException(InvalidDataException::class); From 1d0d0bdd2ca4ca3cc71d36145c50c340d92561f8 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 30 May 2024 14:01:25 +0545 Subject: [PATCH 39/85] Add test cases and fix YEARLY with BYMONTH BYDAY on summer-time transition --- lib/Recur/RRuleIterator.php | 2 +- tests/VObject/Recur/RRuleIteratorTest.php | 25 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 6ce3e609f..ff276a697 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -715,7 +715,7 @@ protected function nextYearly(): void (int) $currentYear, (int) $currentMonth, (int) $occurrence - ); + )->modify($this->startTime()); return; } else { diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 2a206e56e..ea049a170 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -934,6 +934,31 @@ public function testYearlyByMonthByDay(): void ); } + public function testYearlyByMonthByDayOnDstTransition(): void + { + $this->parse( + 'FREQ=YEARLY;COUNT=13;INTERVAL=2;BYMONTH=3;BYDAY=SU', + '2021-03-07 02:30:00', + [ + '2021-03-07 02:30:00', + '2021-03-14 02:30:00', + '2021-03-21 02:30:00', + '2021-03-28 03:30:00', + '2023-03-05 02:30:00', + '2023-03-12 02:30:00', + '2023-03-19 02:30:00', + '2023-03-26 03:30:00', + '2025-03-02 02:30:00', + '2025-03-09 02:30:00', + '2025-03-16 02:30:00', + '2025-03-23 02:30:00', + '2025-03-30 03:30:00', + ], + null, + 'Europe/Zurich', + ); + } + public function testYearlyNewYearsDay(): void { $this->parse( From 4f21237a5038bd71e5c6879581dadd655f3fa4a6 Mon Sep 17 00:00:00 2001 From: Jarik Marwede Date: Thu, 1 Aug 2024 14:01:55 +0200 Subject: [PATCH 40/85] Fix VTODO validation for PERCENT-COMPLETE --- lib/Component/VTodo.php | 2 +- tests/VObject/Component/VTodoTest.php | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/Component/VTodo.php b/lib/Component/VTodo.php index bda49da74..cab672ce5 100644 --- a/lib/Component/VTodo.php +++ b/lib/Component/VTodo.php @@ -104,7 +104,7 @@ public function getValidationRules(): array 'LAST-MODIFIED' => '?', 'LOCATION' => '?', 'ORGANIZER' => '?', - 'PERCENT' => '?', + 'PERCENT-COMPLETE' => '?', 'PRIORITY' => '?', 'RECURRENCE-ID' => '?', 'SEQUENCE' => '?', diff --git a/tests/VObject/Component/VTodoTest.php b/tests/VObject/Component/VTodoTest.php index 808675b36..3b39cd979 100644 --- a/tests/VObject/Component/VTodoTest.php +++ b/tests/VObject/Component/VTodoTest.php @@ -168,4 +168,31 @@ public function testValidateDueBeforeDateTimeStart(): void 'DUE must occur after DTSTART', ], $messages); } + + public function testValidateDuplicatePercentComplete(): void + { + $input = <<validate(); + $messages = []; + foreach ($warnings as $warning) { + $messages[] = $warning['message']; + } + + self::assertEquals([ + 'PERCENT-COMPLETE MUST NOT appear more than once in a VTODO component', + ], $messages); + } } From c14420f7ea61e006a2129d034c6741a041165104 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 1 Aug 2024 22:12:00 +0545 Subject: [PATCH 41/85] test: extra property is allowed in VTODO --- tests/VObject/Component/VTodoTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/VObject/Component/VTodoTest.php b/tests/VObject/Component/VTodoTest.php index 3b39cd979..899d93982 100644 --- a/tests/VObject/Component/VTodoTest.php +++ b/tests/VObject/Component/VTodoTest.php @@ -88,6 +88,31 @@ public function testValidate(): void self::assertEquals([], $messages); } + public function testValidateExtraProperty(): void + { + $input = <<validate(); + $messages = []; + foreach ($warnings as $warning) { + $messages[] = $warning['message']; + } + + self::assertEquals([], $messages); + } + public function testValidateInvalid(): void { $input = << Date: Wed, 28 Aug 2024 10:53:46 +0545 Subject: [PATCH 42/85] chore: adjust code to make php-cs-fixer and phpstan pass --- bin/bench.php | 2 +- bin/bench_freebusygenerator.php | 6 +++--- bin/bench_manipulatevcard.php | 4 ++-- bin/rrulebench.php | 4 ++-- composer.json | 4 ++-- tests/VObject/ITip/BrokerDeleteEventTest.php | 10 ++++++---- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/bin/bench.php b/bin/bench.php index 0a2736fa6..98994d1b6 100755 --- a/bin/bench.php +++ b/bin/bench.php @@ -7,6 +7,6 @@ $start = microtime(true); -$lol = Sabre\VObject\Reader::read($data); +$lol = VObject\Reader::read($data); echo 'time: '.(microtime(true) - $start)."\n"; diff --git a/bin/bench_freebusygenerator.php b/bin/bench_freebusygenerator.php index ae859f487..bc2210532 100644 --- a/bin/bench_freebusygenerator.php +++ b/bin/bench_freebusygenerator.php @@ -3,7 +3,7 @@ include __DIR__.'/../vendor/autoload.php'; if ($argc < 2) { - echo 'sabre/vobject ', Sabre\VObject\Version::VERSION, " freebusy benchmark\n"; + echo 'sabre/vobject ', VObject\Version::VERSION, " freebusy benchmark\n"; echo "\n"; echo "This script can be used to measure the speed of generating a\n"; echo "free-busy report based on a calendar.\n"; @@ -19,7 +19,7 @@ $bench = new Hoa\Bench\Bench(); $bench->parse->start(); -$vcal = Sabre\VObject\Reader::read(fopen($inputFile, 'r')); +$vcal = VObject\Reader::read(fopen($inputFile, 'r')); $bench->parse->stop(); @@ -31,7 +31,7 @@ $bench->fb->start(); for ($i = 0; $i < $repeat; ++$i) { - $fb = new Sabre\VObject\FreeBusyGenerator($start, $end, $vcal, $timeZone); + $fb = new VObject\FreeBusyGenerator($start, $end, $vcal, $timeZone); $results = $fb->getResult(); } $bench->fb->stop(); diff --git a/bin/bench_manipulatevcard.php b/bin/bench_manipulatevcard.php index 96a506907..790912023 100644 --- a/bin/bench_manipulatevcard.php +++ b/bin/bench_manipulatevcard.php @@ -3,7 +3,7 @@ include __DIR__.'/../vendor/autoload.php'; if ($argc < 2) { - echo 'sabre/vobject ', Sabre\VObject\Version::VERSION, " manipulation benchmark\n"; + echo 'sabre/vobject ', VObject\Version::VERSION, " manipulation benchmark\n"; echo "\n"; echo "This script can be used to measure the speed of opening a large amount of\n"; echo "vcards, making a few alterations and serializing them again.\n"; @@ -17,7 +17,7 @@ $input = file_get_contents($inputFile); -$splitter = new Sabre\VObject\Splitter\VCard($input); +$splitter = new VObject\Splitter\VCard($input); $bench = new Hoa\Bench\Bench(); diff --git a/bin/rrulebench.php b/bin/rrulebench.php index c8be08d46..bb31053b8 100644 --- a/bin/rrulebench.php +++ b/bin/rrulebench.php @@ -3,7 +3,7 @@ include __DIR__.'/../vendor/autoload.php'; if ($argc < 4) { - echo 'sabre/vobject ', Sabre\VObject\Version::VERSION, " RRULE benchmark\n"; + echo 'sabre/vobject ', VObject\Version::VERSION, " RRULE benchmark\n"; echo "\n"; echo "This script can be used to measure the speed of the 'recurrence expansion'\n"; echo 'system.'; @@ -18,7 +18,7 @@ $bench->parse->start(); echo "Parsing.\n"; -$vobj = Sabre\VObject\Reader::read(fopen($inputFile, 'r')); +$vobj = VObject\Reader::read(fopen($inputFile, 'r')); $bench->parse->stop(); diff --git a/composer.json b/composer.json index 0040076bc..4e1aa271a 100644 --- a/composer.json +++ b/composer.json @@ -38,10 +38,10 @@ "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "^3.54", + "friendsofphp/php-cs-fixer": "^3.63", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.12" }, "suggest" : { "hoa/bench" : "If you would like to run the benchmark scripts" diff --git a/tests/VObject/ITip/BrokerDeleteEventTest.php b/tests/VObject/ITip/BrokerDeleteEventTest.php index 4de219f82..81f2b7cac 100644 --- a/tests/VObject/ITip/BrokerDeleteEventTest.php +++ b/tests/VObject/ITip/BrokerDeleteEventTest.php @@ -2,6 +2,8 @@ namespace Sabre\VObject\ITip; +use Sabre\VObject\Version; + class BrokerDeleteEventTest extends BrokerTester { public function testOrganizerDeleteWithDtend(): void @@ -24,7 +26,7 @@ public function testOrganizerDeleteWithDtend(): void $newMessage = null; - $version = \Sabre\VObject\Version::VERSION; + $version = Version::VERSION; $expected = [ [ @@ -107,7 +109,7 @@ public function testOrganizerDeleteWithDuration(): void $newMessage = null; - $version = \Sabre\VObject\Version::VERSION; + $version = Version::VERSION; $expected = [ [ @@ -190,7 +192,7 @@ public function testAttendeeDeleteWithDtend(): void $newMessage = null; - $version = \Sabre\VObject\Version::VERSION; + $version = Version::VERSION; $expected = [ [ @@ -245,7 +247,7 @@ public function testAttendeeReplyWithDuration(): void $newMessage = null; - $version = \Sabre\VObject\Version::VERSION; + $version = Version::VERSION; $expected = [ [ From e1cc24614bc026f5c8d7a7bf3da63a5d42a2b168 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Wed, 28 Aug 2024 15:03:36 +0545 Subject: [PATCH 43/85] Use php-cs-fixer 3.62.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4e1aa271a..f2c378d63 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "^3.63", + "friendsofphp/php-cs-fixer": "3.62.0", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", "phpstan/phpstan": "^1.12" From d734e21997cf1a7260c50cc9fb75d76c98731666 Mon Sep 17 00:00:00 2001 From: Thomas P Date: Fri, 7 Apr 2023 15:17:21 +0200 Subject: [PATCH 44/85] fix(VAlarm) Alarm related to DTSTART of VTODO Following RFC 5545 the start of a VTODO is DTSTART not DUE Fixing the tests in consequence --- lib/Component/VAlarm.php | 8 +------- tests/VObject/Component/VAlarmTest.php | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/Component/VAlarm.php b/lib/Component/VAlarm.php index 3e5797b0a..abe61768e 100644 --- a/lib/Component/VAlarm.php +++ b/lib/Component/VAlarm.php @@ -39,13 +39,7 @@ public function getEffectiveTriggerTime(): \DateTimeImmutable /** @var VEvent|VTodo $parentComponent */ $parentComponent = $this->parent; if ('START' === $related) { - if ('VTODO' === $parentComponent->name) { - $propName = 'DUE'; - } else { - $propName = 'DTSTART'; - } - - $effectiveTrigger = $parentComponent->$propName->getDateTime(); + $effectiveTrigger = $parentComponent->DTSTART->getDateTime(); } else { if ('VTODO' === $parentComponent->name) { $endProp = 'DUE'; diff --git a/tests/VObject/Component/VAlarmTest.php b/tests/VObject/Component/VAlarmTest.php index 4694d6363..a67991ff4 100644 --- a/tests/VObject/Component/VAlarmTest.php +++ b/tests/VObject/Component/VAlarmTest.php @@ -123,6 +123,19 @@ public function timeRangeTestData(): array $tests[] = [$valarm7, new \DateTime('2012-02-25 01:00:00'), new \DateTime('2012-03-05 01:00:00'), true]; $tests[] = [$valarm7, new \DateTime('2012-03-25 01:00:00'), new \DateTime('2012-04-05 01:00:00'), false]; + // Relation to start time of todo + $valarm8 = $calendar->createComponent('VALARM'); + $valarm8->TRIGGER = '-P1D'; + $valarm8->TRIGGER['VALUE'] = 'DURATION'; + + $vtodo8 = $calendar->createComponent('VTODO'); + $vtodo8->DTSTART = '20120301T130000Z'; + $vtodo8->DUE = '20120401T130000Z'; + $vtodo8->add($valarm8); + + $tests[] = [$valarm8, new \DateTime('2012-02-25 01:00:00'), new \DateTime('2012-03-05 01:00:00'), true]; + $tests[] = [$valarm8, new \DateTime('2012-03-25 01:00:00'), new \DateTime('2012-04-05 01:00:00'), false]; + return $tests; } @@ -151,7 +164,7 @@ public function testInTimeRangeBuggy(): void DTSTAMP:20121003T064931Z UID:b848cb9a7bb16e464a06c222ca1f8102@examle.com STATUS:NEEDS-ACTION -DUE:20121005T000000Z +DTSTART:20121005T000000Z SUMMARY:Task 1 CATEGORIES:AlarmCategory BEGIN:VALARM From 88889586fed3461be2ccd3ba45815a24331e1e02 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Wed, 28 Aug 2024 17:18:04 +0545 Subject: [PATCH 45/85] chore: use php-cs-fixer 3.63 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f2c378d63..4e1aa271a 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "3.62.0", + "friendsofphp/php-cs-fixer": "^3.63", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", "phpstan/phpstan": "^1.12" From 554c2c7646733791a80bccacd47b86e0fd65aa17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:54:18 +0200 Subject: [PATCH 46/85] docs: Remove outdated build status in README.md fixes #670 --- README.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/README.md b/README.md index 659e3fa83..fa600fec2 100644 --- a/README.md +++ b/README.md @@ -27,21 +27,6 @@ Usage * [Working with iCalendar](http://sabre.io/vobject/icalendar/) - -Build status ------------- - -| branch | status | -| ------ | ------ | -| master | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=master)](https://travis-ci.org/sabre-io/vobject) | -| 3.5 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=3.5)](https://travis-ci.org/sabre-io/vobject) | -| 3.4 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=3.4)](https://travis-ci.org/sabre-io/vobject) | -| 3.1 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=3.1)](https://travis-ci.org/sabre-io/vobject) | -| 2.1 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=2.1)](https://travis-ci.org/sabre-io/vobject) | -| 2.0 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=2.0)](https://travis-ci.org/sabre-io/vobject) | - - - Support ------- From 57492ab9032a6819f10e17913f2d81e7ed8c2aaf Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Wed, 4 Sep 2024 20:14:42 +0545 Subject: [PATCH 47/85] chore: apply php-cs-fixer 3.64 changes --- lib/Parser/Json.php | 2 +- .../VObject/ITip/BrokerAttendeeReplyTest.php | 24 ++++++++-------- tests/VObject/ITip/BrokerDeleteEventTest.php | 12 ++++---- tests/VObject/ITip/BrokerNewEventTest.php | 14 +++++----- tests/VObject/ITip/BrokerUpdateEventTest.php | 28 +++++++++---------- tests/VObject/Parser/MimeDirTest.php | 2 +- tests/VObject/VCardConverterTest.php | 4 +-- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/lib/Parser/Json.php b/lib/Parser/Json.php index df87114a1..6403af4e9 100644 --- a/lib/Parser/Json.php +++ b/lib/Parser/Json.php @@ -131,7 +131,7 @@ public function parseProperty(array $jProp): Property list( $propertyName, $parameters, - $valueType + $valueType, ) = $jProp; $propertyName = strtoupper($propertyName); diff --git a/tests/VObject/ITip/BrokerAttendeeReplyTest.php b/tests/VObject/ITip/BrokerAttendeeReplyTest.php index 6590356bb..75d2cf06f 100644 --- a/tests/VObject/ITip/BrokerAttendeeReplyTest.php +++ b/tests/VObject/ITip/BrokerAttendeeReplyTest.php @@ -61,7 +61,7 @@ public function testAccepted(): void ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -179,7 +179,7 @@ public function testAcceptedWithTz(): void ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -325,7 +325,7 @@ public function testRecurringReply(): void ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -464,7 +464,7 @@ public function testRecurringAllDay(): void ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -556,7 +556,7 @@ public function testNoChangeForceSend(): void ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; $this->parse($oldMessage, $newMessage, $expected); @@ -659,7 +659,7 @@ public function testCreateReplyByException(): void ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; $this->parse($oldMessage, $newMessage, $expected); @@ -728,7 +728,7 @@ public function testCreateReplyByExceptionTz(): void ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; $this->parse($oldMessage, $newMessage, $expected); @@ -797,7 +797,7 @@ public function testCreateReplyByExceptionAllDay(): void ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; $this->parse($oldMessage, $newMessage, $expected); @@ -857,7 +857,7 @@ public function testDeclined(): void ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -1039,7 +1039,7 @@ public function testAcceptedAllDay(): void ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -1110,7 +1110,7 @@ public function testReplyNoMasterEvent(): void ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -1198,7 +1198,7 @@ public function testPartyCrasher(): void END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; diff --git a/tests/VObject/ITip/BrokerDeleteEventTest.php b/tests/VObject/ITip/BrokerDeleteEventTest.php index 81f2b7cac..2572ed284 100644 --- a/tests/VObject/ITip/BrokerDeleteEventTest.php +++ b/tests/VObject/ITip/BrokerDeleteEventTest.php @@ -54,7 +54,7 @@ public function testOrganizerDeleteWithDtend(): void ATTENDEE;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], [ @@ -82,7 +82,7 @@ public function testOrganizerDeleteWithDtend(): void ATTENDEE;CN=Two:mailto:two@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -137,7 +137,7 @@ public function testOrganizerDeleteWithDuration(): void ATTENDEE;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], [ @@ -165,7 +165,7 @@ public function testOrganizerDeleteWithDuration(): void ATTENDEE;CN=Two:mailto:two@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -220,7 +220,7 @@ public function testAttendeeDeleteWithDtend(): void ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -275,7 +275,7 @@ public function testAttendeeReplyWithDuration(): void ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; diff --git a/tests/VObject/ITip/BrokerNewEventTest.php b/tests/VObject/ITip/BrokerNewEventTest.php index d85455295..b0b27f41c 100644 --- a/tests/VObject/ITip/BrokerNewEventTest.php +++ b/tests/VObject/ITip/BrokerNewEventTest.php @@ -183,7 +183,7 @@ public function testRecurrenceInvite(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], [ 'uid' => 'foobar', @@ -221,7 +221,7 @@ public function testRecurrenceInvite(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], [ 'uid' => 'foobar', @@ -248,7 +248,7 @@ public function testRecurrenceInvite(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -312,7 +312,7 @@ public function testRecurrenceInvite2(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], [ 'uid' => 'foobar', @@ -349,7 +349,7 @@ public function testRecurrenceInvite2(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], [ 'uid' => 'foobar', @@ -376,7 +376,7 @@ public function testRecurrenceInvite2(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -427,7 +427,7 @@ public function testRecurrenceInvite3(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; diff --git a/tests/VObject/ITip/BrokerUpdateEventTest.php b/tests/VObject/ITip/BrokerUpdateEventTest.php index 1497bb711..738c769e8 100644 --- a/tests/VObject/ITip/BrokerUpdateEventTest.php +++ b/tests/VObject/ITip/BrokerUpdateEventTest.php @@ -71,7 +71,7 @@ public function testInviteChange(): void ATTENDEE;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], [ 'uid' => 'foobar', @@ -101,7 +101,7 @@ public function testInviteChange(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], [ 'uid' => 'foobar', @@ -131,7 +131,7 @@ public function testInviteChange(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -193,7 +193,7 @@ public function testInviteChangeFromNonSchedulingToSchedulingObject(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -251,7 +251,7 @@ public function testInviteChangeFromSchedulingToNonSchedulingObject(): void ATTENDEE;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -350,7 +350,7 @@ public function testRemoveInstance(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -427,7 +427,7 @@ public function testInviteChangeSignificantChange(): void ATTENDEE;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], [ 'uid' => 'foobar', @@ -457,7 +457,7 @@ public function testInviteChangeSignificantChange(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], [ 'uid' => 'foobar', @@ -487,7 +487,7 @@ public function testInviteChangeSignificantChange(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -555,7 +555,7 @@ public function testInviteNoChange(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -623,7 +623,7 @@ public function testInviteNoChangeForceSend(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -691,7 +691,7 @@ public function testInviteRemoveAttendees(): void ATTENDEE;CN=One:mailto:one@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], [ 'uid' => 'foobar', @@ -719,7 +719,7 @@ public function testInviteRemoveAttendees(): void ATTENDEE;CN=Two:mailto:two@example.org END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; @@ -808,7 +808,7 @@ public function testInviteChangeExdateOrder(): void DTSTAMP:**ANY** END:VEVENT END:VCALENDAR -ICS +ICS, ], ]; diff --git a/tests/VObject/Parser/MimeDirTest.php b/tests/VObject/Parser/MimeDirTest.php index db5735b0c..28edaaabc 100644 --- a/tests/VObject/Parser/MimeDirTest.php +++ b/tests/VObject/Parser/MimeDirTest.php @@ -256,7 +256,7 @@ public function provideBrokenVCalendar(): array SEQUENCE:0 END:VEVENT END:VCALENDAR -EOF +EOF, ]]; } } diff --git a/tests/VObject/VCardConverterTest.php b/tests/VObject/VCardConverterTest.php index db778261d..bb52be8a9 100644 --- a/tests/VObject/VCardConverterTest.php +++ b/tests/VObject/VCardConverterTest.php @@ -461,7 +461,7 @@ public function testAnniversary(): void // Swapping input and output list( $input, - $output + $output, ) = [ $output, $input, @@ -520,7 +520,7 @@ public function testMultipleAnniversaries(): void // Swapping input and output list( $input, - $output + $output, ) = [ $output, $input, From 9e72aca46937c6edd3bad66d79d9d56d7143bb9b Mon Sep 17 00:00:00 2001 From: Andreas Heigl Date: Tue, 10 Sep 2024 16:24:31 +0200 Subject: [PATCH 48/85] Allow unknown value data types for VALUE Currently value-types that are not known will cause the parser to throw an InvalidDataException. In RFC5545, Section 3.2.20 is described though that the allowed values for the "VALUE" data-type can include x-names and iana-tokens. These can be any string that might - or might not - be understood by the parser. The description clearly states that "Applications MUST preserve the value data for x-name and iana-token value that they don't recognize without attempting to interpret or parse the value data" This means that the content of the "VALUE" part should not be interpreted at all the moment the parser doesn't find a match in the corresponding mapping table. But it should also not hard-fail when someone sets a VALUE that might not be understood. In such a case the default should be used instead. --- lib/Document.php | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/Document.php b/lib/Document.php index 02eaf13c7..757bddb66 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -180,23 +180,30 @@ public function createProperty(string $name, $value = null, ?array $parameters = $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 (isset($parameters['VALUE'])) { + $class = $this->getClassNameForPropertyValue($parameters['VALUE']); + } + if ($valueType) { // The valueType argument comes first to figure out the correct // class. $class = $this->getClassNameForPropertyValue($valueType); } + // 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 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); - } + $class = $this->getClassNameForPropertyName($name); } + if (is_null($parameters)) { $parameters = []; } From 3bfeaee59202913f4a8b476a83a763d99ca34975 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Tue, 17 Sep 2024 21:25:09 +0200 Subject: [PATCH 49/85] refactor: Replace dirname(__FILE__) with __DIR__ Signed-off-by: Christoph Wurst --- tests/VObject/Issue153Test.php | 2 +- tests/VObject/Issue64Test.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/VObject/Issue153Test.php b/tests/VObject/Issue153Test.php index 83e56cf50..9b9d7ec0e 100644 --- a/tests/VObject/Issue153Test.php +++ b/tests/VObject/Issue153Test.php @@ -8,7 +8,7 @@ class Issue153Test extends TestCase { public function testRead(): void { - $obj = Reader::read(file_get_contents(dirname(__FILE__).'/issue153.vcf')); + $obj = Reader::read(file_get_contents(__DIR__.'/issue153.vcf')); self::assertEquals('Test Benutzer', (string) $obj->FN); } } diff --git a/tests/VObject/Issue64Test.php b/tests/VObject/Issue64Test.php index 110f91fa2..f9e7dbf96 100644 --- a/tests/VObject/Issue64Test.php +++ b/tests/VObject/Issue64Test.php @@ -8,7 +8,7 @@ class Issue64Test extends TestCase { public function testRead(): void { - $vcard = Reader::read(file_get_contents(dirname(__FILE__).'/issue64.vcf')); + $vcard = Reader::read(file_get_contents(__DIR__.'/issue64.vcf')); $vcard = $vcard->convert(Document::VCARD30); $vcard = $vcard->serialize(); From 3473e7c2b11682082ab191fb7319181b3a298047 Mon Sep 17 00:00:00 2001 From: Mathieu Rochette Date: Fri, 11 Oct 2024 17:00:18 +0200 Subject: [PATCH 50/85] Throw InvalidDataException when RRule is invalid --- lib/Property/ICalendar/Recur.php | 9 ++++++++- tests/VObject/Property/ICalendar/RecurTest.php | 11 +++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/Property/ICalendar/Recur.php b/lib/Property/ICalendar/Recur.php index 9a1e9e643..10081673e 100644 --- a/lib/Property/ICalendar/Recur.php +++ b/lib/Property/ICalendar/Recur.php @@ -189,7 +189,14 @@ public static function stringToArray(string $value): array if (empty($part)) { continue; } - list($partName, $partValue) = explode('=', $part); + + $parts = explode('=', $part); + + if (2 !== count($parts)) { + throw new InvalidDataException('The supplied iCalendar RRULE part is incorrect: '.$part); + } + + list($partName, $partValue) = $parts; // The value itself had multiple values.. if (false !== strpos($partValue, ',')) { diff --git a/tests/VObject/Property/ICalendar/RecurTest.php b/tests/VObject/Property/ICalendar/RecurTest.php index b2750f03b..0adf30215 100644 --- a/tests/VObject/Property/ICalendar/RecurTest.php +++ b/tests/VObject/Property/ICalendar/RecurTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\InvalidDataException; use Sabre\VObject\Node; use Sabre\VObject\Reader; @@ -194,6 +195,16 @@ public function testValidateStripNoFreq(): void ); } + public function testUnrepairableRRule(): void + { + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'IAmNotARRule'); + + $this->expectException(InvalidDataException::class); + + $property->validate(Node::REPAIR); + } + public function testValidateInvalidByMonthRruleWithRepair(): void { $calendar = new VCalendar(); From ab14f5e937e5f39137a6854d764a9808caaf3de2 Mon Sep 17 00:00:00 2001 From: Mathieu Rochette Date: Fri, 11 Oct 2024 17:13:32 +0200 Subject: [PATCH 51/85] fixup: fix test --- tests/VObject/Property/ICalendar/RecurTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/VObject/Property/ICalendar/RecurTest.php b/tests/VObject/Property/ICalendar/RecurTest.php index 0adf30215..99d2525c6 100644 --- a/tests/VObject/Property/ICalendar/RecurTest.php +++ b/tests/VObject/Property/ICalendar/RecurTest.php @@ -197,11 +197,9 @@ public function testValidateStripNoFreq(): void public function testUnrepairableRRule(): void { + $this->expectException(InvalidDataException::class); $calendar = new VCalendar(); $property = $calendar->createProperty('RRULE', 'IAmNotARRule'); - - $this->expectException(InvalidDataException::class); - $property->validate(Node::REPAIR); } From 46bce0e35960512fc53e7ffcba87553b57904809 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 14 Oct 2024 10:25:36 +0545 Subject: [PATCH 52/85] chore: add PHP 8.4 to CI of master --- .github/workflows/ci.yml | 14 ++++++++++---- .php-cs-fixer.dist.php | 4 ++++ composer.json | 4 ++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 775b3389f..ae1b8b7ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,12 +12,18 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3'] + php-versions: ['8.0', '8.1', '8.2', '8.3'] coverage: ['pcov'] + code-style: ['yes'] code-analysis: ['no'] include: - php-versions: '7.4' - coverage: 'none' + coverage: 'pcov' + code-style: 'yes' + code-analysis: 'yes' + - php-versions: '8.4' + coverage: 'pcov' + code-style: 'yes' code-analysis: 'yes' steps: - name: Checkout @@ -48,8 +54,8 @@ jobs: run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Code Analysis (PHP CS-Fixer) - if: matrix.code-analysis == 'yes' - run: php vendor/bin/php-cs-fixer fix --dry-run --diff + if: matrix.code-style == 'yes' + run: PHP_CS_FIXER_IGNORE_ENV=true php vendor/bin/php-cs-fixer fix --dry-run --diff - name: Code Analysis (PHPStan) if: matrix.code-analysis == 'yes' diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index e981862b7..4d464eb3b 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -10,6 +10,10 @@ $config->setRules([ '@PSR1' => true, '@Symfony' => true, + 'nullable_type_declaration' => [ + 'syntax' => 'question_mark', + ], + 'nullable_type_declaration_for_default_null_value' => true, ]); return $config; diff --git a/composer.json b/composer.json index 4e1aa271a..92743ebc9 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "^3.63", + "friendsofphp/php-cs-fixer": "^3.64", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", "phpstan/phpstan": "^1.12" @@ -94,7 +94,7 @@ "phpstan analyse lib tests --memory-limit 1G" ], "cs-fixer": [ - "php-cs-fixer fix" + "PHP_CS_FIXER_IGNORE_ENV=true php-cs-fixer fix" ], "phpunit": [ "phpunit --configuration tests/phpunit.xml" From a2f05dd817750a368dc5d1c5b06a28489f6a0a7b Mon Sep 17 00:00:00 2001 From: SebastianKrupinski Date: Wed, 28 Aug 2024 13:52:37 -0400 Subject: [PATCH 53/85] fix: check if event status was changed to cancelled and generate appropriate message Signed-off-by: SebastianKrupinski --- lib/ITip/Broker.php | 9 +-- tests/VObject/ITip/BrokerUpdateEventTest.php | 62 ++++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index be6ba29d7..76ee0c71e 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -503,10 +503,11 @@ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, $icalMsg->add(clone $timezone); } - if (!$attendee['newInstances']) { - // If there are no instances the attendee is a part of, it - // means the attendee was removed and we need to send him a - // CANCEL. + if (!$attendee['newInstances'] || 'CANCELLED' === $eventInfo['status']) { + // If there are no instances the attendee is a part of, it means + // the attendee was removed and we need to send them a CANCEL message. + // Also If the meeting STATUS property was changed to CANCELLED + // we need to send the attendee a CANCEL message. $message->method = 'CANCEL'; $icalMsg->METHOD = $message->method; diff --git a/tests/VObject/ITip/BrokerUpdateEventTest.php b/tests/VObject/ITip/BrokerUpdateEventTest.php index 738c769e8..030860bd0 100644 --- a/tests/VObject/ITip/BrokerUpdateEventTest.php +++ b/tests/VObject/ITip/BrokerUpdateEventTest.php @@ -814,4 +814,66 @@ public function testInviteChangeExdateOrder(): void $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } + + public function testInviteStatusCancelled(): void + { + $oldMessage = << 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + } } From f7cef9ce9e122e7837d15aef0305c371d074881d Mon Sep 17 00:00:00 2001 From: Andreas Heigl Date: Mon, 14 Oct 2024 21:22:50 +0200 Subject: [PATCH 54/85] Add tests for skipping unknown parameters THis adds a test to assure that an unknown value is skipped. The value `DATETIME` is not known. It should be `DATE-TIME`. Adding the value of DATETIME previopusly resulted in an Error. This now tests that it actually works and that the unknown parameter is actually also kept as specified by the RFC --- tests/VObject/PropertyTest.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/VObject/PropertyTest.php b/tests/VObject/PropertyTest.php index 5a34383f1..c1a3af8f2 100644 --- a/tests/VObject/PropertyTest.php +++ b/tests/VObject/PropertyTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Component\VCard; +use Sabre\VObject\Property\ICalendar\DateTime; class PropertyTest extends TestCase { @@ -391,4 +392,18 @@ public function testValidateBadEncodingVCard21(): void self::assertEquals('ENCODING=B is not valid for this document type.', $result[0]['message']); self::assertEquals(3, $result[0]['level']); } + + public function testUnknownValuesWillBeIgnored(): void + { + $cal = new VCalendar(); + $property = $cal->createProperty('DTSTAMP', '20240101T000000Z', ['VALUE' => 'DATETIME']); + + self::assertEquals("DTSTAMP;VALUE=DATETIME:20240101T000000Z\r\n", $property->serialize()); + + self::assertInstanceOf(DateTime::class, $property); + self::assertCount(1, $property->parameters()); + self::assertInstanceOf(Parameter::class, $property->parameters['VALUE']); + self::assertEquals('VALUE', $property->parameters['VALUE']->name); + self::assertEquals('DATETIME', $property->parameters['VALUE']->getValue()); + } } From 1f22f4a59ef8d79f53788ece6cf1dc59fb43a096 Mon Sep 17 00:00:00 2001 From: Andreas Heigl Date: Mon, 14 Oct 2024 21:24:28 +0200 Subject: [PATCH 55/85] Rework handling of values that are not recognized This will remove the necesity to calculate the class name twice in immediate succession should the declared valuetype not be set. --- lib/Document.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Document.php b/lib/Document.php index 757bddb66..29463059c 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -186,8 +186,8 @@ public function createProperty(string $name, $value = null, ?array $parameters = // 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 (isset($parameters['VALUE'])) { - $class = $this->getClassNameForPropertyValue($parameters['VALUE']); + if (!$valueType) { + $valueType = $parameters['VALUE'] ?? null; } if ($valueType) { From 48f48cfd025b0e171aaf4379ad260e499ca7dd7c Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Tue, 22 Oct 2024 11:57:36 +0545 Subject: [PATCH 56/85] chore: forward port 4.5.6 --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ lib/Version.php | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7891d698..01a3531c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ ChangeLog ========= +4.5.6 (2024-10-14) +------------------ + +* #662: add PHP 8.4 to CI (@phil-davis) +* #669: fix: check if event status was changed to cancelled and generate appropriate message (@SebastianKrupinski) +* #673: Fix Implicitly marking parameter $param as nullable is deprecated (@cedric-anne) +* #675: refactor: Replace dirname(__FILE__) with __DIR__ (@ChristophWurst) +* #692: Throw InvalidDataException when RRule is invalid (@mathroc) + +4.5.5 (2024-07-02) +------------------ + +* #632: Add PHP 8.3 to CI (@phil-davis) +* #649: add lineIndex and lineString properties to a Property Node (@JohnRDOrazio) +* #652: ITip\Broker: handle timezones in replies to exception events (@gharlan) +* #654: chore: stop exporting php-cs-fixer config (@phil-davis) +* #656: Yearly rrule compliance by the iterator (@kroky) +* #658: throw ParseException when null input is provided (@phil-davis) +* #653: Handle summer time jumps in event recurrences (@phil-davis) + +4.5.4 (2023-11-09) +------------------ + +* #619: fixed issue: Undefined index: plusminus (@sash04ek) +* #622: Create Issue467Test.php for 619 (@sash04ek) +* #623: docs: fix type definition for oldCalendar (@kesselb) + +4.5.3 (2023-01-22) +------------------ + +* #607: Apply 'Support sabre/xml v4' and needed code changes to 4.5 branch (@phil-davis) 4.5.2 (2023-01-20) ------------------ diff --git a/lib/Version.php b/lib/Version.php index 893f272d3..060c69a30 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - public const VERSION = '4.5.2'; + public const VERSION = '4.5.6'; } From 8a5903658af533f7391eb7b33a5b5165abb915b3 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Sun, 1 Dec 2024 14:25:49 +0545 Subject: [PATCH 57/85] chore: bump php-cs-fixer to 3.65 --- composer.json | 2 +- lib/Component/VCalendar.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 92743ebc9..1e5c219da 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "^3.64", + "friendsofphp/php-cs-fixer": "^3.65", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", "phpstan/phpstan": "^1.12" diff --git a/lib/Component/VCalendar.php b/lib/Component/VCalendar.php index 7b6e66e88..5163c042a 100644 --- a/lib/Component/VCalendar.php +++ b/lib/Component/VCalendar.php @@ -164,7 +164,7 @@ public function getDocumentType(): int * * @param string|null $componentName filter by component name * - * @return VObject\Component[] + * @return Component[] */ public function getBaseComponents(?string $componentName = null): array { From f1ff6425f0549412b52915da91eab52869182905 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:33:51 +0000 Subject: [PATCH 58/85] chore(deps): bump codecov/codecov-action from 4 to 5 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae1b8b7ad..35af67bcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,5 +65,5 @@ jobs: run: vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover clover.xml - name: Code Coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 if: matrix.coverage != 'none' From d1161c86e2d294ef4cfdd97c7c9385a76404cfe2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:57:44 +0000 Subject: [PATCH 59/85] chore(deps-dev): update phpstan/phpstan requirement from ^1.12 to ^2.0 Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version. - [Release notes](https://github.com/phpstan/phpstan/releases) - [Changelog](https://github.com/phpstan/phpstan/blob/2.0.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.12.0...2.0.3) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1e5c219da..58b8312d2 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ "friendsofphp/php-cs-fixer": "^3.65", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", - "phpstan/phpstan": "^1.12" + "phpstan/phpstan": "^2.0" }, "suggest" : { "hoa/bench" : "If you would like to run the benchmark scripts" From 778bb46517b0150e4d2bd650623b5a7ab6cbe601 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 17 Apr 2025 13:15:27 +0545 Subject: [PATCH 60/85] chore: bump dev dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 58b8312d2..64e2f0e16 100644 --- a/composer.json +++ b/composer.json @@ -38,10 +38,10 @@ "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "^3.65", + "friendsofphp/php-cs-fixer": "^3.75", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", - "phpstan/phpstan": "^2.0" + "phpstan/phpstan": "^2.1" }, "suggest" : { "hoa/bench" : "If you would like to run the benchmark scripts" From b86fa76f0adbda81d372af098586add41675b0a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:49:25 +0000 Subject: [PATCH 61/85] chore(deps): bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35af67bcc..f732fadf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: code-analysis: 'yes' steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php From 21a5ee4dc00eb703624f782036cfce06711308d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20M=C3=B8llerh=C3=B8j?= Date: Thu, 4 Sep 2025 09:13:05 +0200 Subject: [PATCH 62/85] Improve check for empty property names Specfically avoid the property name "0" crashes the parser with a 'LogicException: This code should not be reachable' --- lib/Parser/MimeDir.php | 4 ++-- tests/VObject/Parser/MimeDirTest.php | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index be5d87baf..c87ccbf97 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -415,7 +415,7 @@ protected function readProperty(string $line) $property['value'] = $match['propValue']; continue; } - if (isset($match['name']) && $match['name']) { + if (isset($match['name']) && 0 < strlen($match['name'])) { $property['name'] = strtoupper($match['name']); continue; } @@ -428,7 +428,7 @@ protected function readProperty(string $line) if (\is_null($property['value'])) { $property['value'] = ''; } - if (!$property['name']) { + if (!isset($property['name']) || 0 == strlen($property['name'])) { if ($this->options & self::OPTION_IGNORE_INVALID_LINES) { return false; } diff --git a/tests/VObject/Parser/MimeDirTest.php b/tests/VObject/Parser/MimeDirTest.php index 28edaaabc..904d164c4 100644 --- a/tests/VObject/Parser/MimeDirTest.php +++ b/tests/VObject/Parser/MimeDirTest.php @@ -259,4 +259,21 @@ public function provideBrokenVCalendar(): array EOF, ]]; } + + public function testPropertyName0(): void + { + $iCal = <<parse($iCal); + self::assertEquals('test', $vevent->VEVENT->{0}->getValue()); + } } From 6eee31d6663b886676828763aa3db3cd801e76eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20M=C3=B8llerh=C3=B8j?= Date: Thu, 4 Sep 2025 09:21:26 +0200 Subject: [PATCH 63/85] Ran php-cs-fixer (on files I have not modified myself) --- tests/VObject/Component/VAvailabilityTest.php | 6 +- tests/VObject/Parser/XmlTest.php | 297 ++++++------------ 2 files changed, 101 insertions(+), 202 deletions(-) diff --git a/tests/VObject/Component/VAvailabilityTest.php b/tests/VObject/Component/VAvailabilityTest.php index 263eeebda..67499e98e 100644 --- a/tests/VObject/Component/VAvailabilityTest.php +++ b/tests/VObject/Component/VAvailabilityTest.php @@ -435,8 +435,7 @@ protected function template(array $properties) … END:VAVAILABILITY END:VCALENDAR -VCAL - , +VCAL, $properties ); } @@ -459,8 +458,7 @@ protected function templateAvailable(array $properties) END:AVAILABLE END:VAVAILABILITY END:VCALENDAR -VCAL - , +VCAL, $properties ); } diff --git a/tests/VObject/Parser/XmlTest.php b/tests/VObject/Parser/XmlTest.php index 2276f7fdf..6491d087b 100644 --- a/tests/VObject/Parser/XmlTest.php +++ b/tests/VObject/Parser/XmlTest.php @@ -47,8 +47,7 @@ public function testRFC6321Example1(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". // VERSION comes first because this is required by vCard 4.0. 'VERSION:2.0'."\n". @@ -270,8 +269,7 @@ public function testRFC6321Section3Part2(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'END:VCALENDAR'."\n" ); @@ -299,8 +297,7 @@ public function testRFC6321Section3Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'BEGIN:VTIMEZONE'."\n". 'END:VTIMEZONE'."\n". @@ -340,8 +337,7 @@ public function testRFC6321Section3Part4Part1Part2(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'GEO:37.386013;-122.082932'."\n". 'END:VCALENDAR'."\n" @@ -367,8 +363,7 @@ public function testRFC6321Section3Part4Part1Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'REQUEST-STATUS:2.0;Success'."\n". 'END:VCALENDAR'."\n" @@ -389,8 +384,7 @@ public function testRFC6321Section3Part4Part1Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'REQUEST-STATUS:3.1;Invalid property value;DTSTART:96-Apr-01'."\n". 'END:VCALENDAR'."\n" @@ -411,8 +405,7 @@ public function testRFC6321Section3Part4Part1Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'REQUEST-STATUS:2.8;Success\, repeating event ignored. Scheduled as a single'."\n". ' event.;RRULE:FREQ=WEEKLY\;INTERVAL=2'."\n". @@ -433,8 +426,7 @@ public function testRFC6321Section3Part4Part1Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'REQUEST-STATUS:4.1;Event conflict. Date-time is busy.'."\n". 'END:VCALENDAR'."\n" @@ -455,8 +447,7 @@ public function testRFC6321Section3Part4Part1Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'REQUEST-STATUS:3.7;Invalid calendar user;ATTENDEE:mailto:jsmith@example.com'."\n". 'END:VCALENDAR'."\n" @@ -480,8 +471,7 @@ public function testRFC6321Section3Part6Part1(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'ATTACH:SGVsbG8gV29ybGQh'."\n". 'END:VCALENDAR'."\n" @@ -500,8 +490,7 @@ public function testRFC6321Section3Part6Part1(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'ATTACH:SGVsbG8gV29ybGQh'."\n". 'END:VCALENDAR'."\n" @@ -528,8 +517,7 @@ public function testRFC6321Section3Part6Part2(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'ATTENDEE;RSVP=true:mailto:cyrus@example.com'."\n". 'END:VCALENDAR'."\n" @@ -553,8 +541,7 @@ public function testRFC6321Section3Part6Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'ATTENDEE:mailto:cyrus@example.com'."\n". 'END:VCALENDAR'."\n" @@ -578,8 +565,7 @@ public function testRFC6321Section3Part6Part4(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'DTSTART;VALUE=DATE:20110517'."\n". 'END:VCALENDAR'."\n" @@ -603,8 +589,7 @@ public function testRFC6321Section3Part6Part5(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'DTSTART:20110517T120000'."\n". 'END:VCALENDAR'."\n" @@ -628,8 +613,7 @@ public function testRFC6321Section3Part6Part6(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'DURATION:P1D'."\n". 'END:VCALENDAR'."\n" @@ -662,8 +646,7 @@ public function testRFC6321Section3Part6Part8(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'FOO:42'."\n". 'END:VCALENDAR'."\n" @@ -681,8 +664,7 @@ public function testRFC6321Section3Part6Part8(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'FOO:-42'."\n". 'END:VCALENDAR'."\n" @@ -709,8 +691,7 @@ public function testRFC6321Section3Part6Part9(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'FREEBUSY:20110517T120000/P1H'."\n". 'END:VCALENDAR'."\n" @@ -731,8 +712,7 @@ public function testRFC6321Section3Part6Part9(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'FREEBUSY:20110517T120000/20120517T120000'."\n". 'END:VCALENDAR'."\n" @@ -761,8 +741,7 @@ public function testRFC6321Section3Part6Part10(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RRULE:FREQ=YEARLY;COUNT=5;BYDAY=-1SU;BYMONTH=10'."\n". 'END:VCALENDAR'."\n" @@ -786,8 +765,7 @@ public function testRFC6321Section3Part6Part11(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'CALSCALE:GREGORIAN'."\n". 'END:VCALENDAR'."\n" @@ -811,8 +789,7 @@ public function testRFC6321Section3Part6Part12(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'FOO:120000'."\n". 'END:VCALENDAR'."\n" @@ -836,8 +813,7 @@ public function testRFC6321Section3Part6Part13(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'ATTACH:http://calendar.example.com'."\n". 'END:VCALENDAR'."\n" @@ -862,8 +838,7 @@ public function testRFC6321Section3Part6Part14(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'TZOFFSETFROM:-0500'."\n". 'END:VCALENDAR'."\n" @@ -882,8 +857,7 @@ public function testRFC6321Section3Part6Part14(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'TZOFFSETFROM:+0100'."\n". 'END:VCALENDAR'."\n" @@ -907,8 +881,7 @@ public function testRFC6321Section5(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'X-PROPERTY:20110512T120000Z'."\n". 'END:VCALENDAR'."\n" @@ -931,8 +904,7 @@ public function testRFC6321Section5(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'DTSTART;X-PARAM=PT30M:20110512T130000Z'."\n". 'END:VCALENDAR'."\n" @@ -953,8 +925,7 @@ public function testRDateWithDateTime(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE:20080205T191224Z'."\n". 'END:VCALENDAR'."\n" @@ -973,8 +944,7 @@ public function testRDateWithDateTime(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE:20080205T191224Z,20090205T191224Z'."\n". 'END:VCALENDAR'."\n" @@ -995,8 +965,7 @@ public function testRDateWithDate(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE:20081006'."\n". 'END:VCALENDAR'."\n" @@ -1016,8 +985,7 @@ public function testRDateWithDate(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE:20081006,20091006,20101006'."\n". 'END:VCALENDAR'."\n" @@ -1046,8 +1014,7 @@ public function testRDateWithPeriod(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H'."\n". 'END:VCALENDAR'."\n" @@ -1077,8 +1044,7 @@ public function testRDateWithPeriod(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H,20080102T150000/PT1'."\n". ' H'."\n". @@ -1108,8 +1074,7 @@ public function testRFC6351Basic(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'FN:J. Doe'."\n". @@ -1149,8 +1114,7 @@ public function testRFC6351Example1(): void My web page! -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'FN:J. Doe'."\n". @@ -1183,8 +1147,7 @@ public function testRFC6351Section5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TEL;TYPE="voice,video":tel:+1-555-555-555'."\n". @@ -1207,8 +1170,7 @@ public function testRFC6351Section5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TEL;TYPE="voice,video":tel:+1-555-555-555'."\n". @@ -1244,8 +1206,7 @@ public function testRFC6351Section5Group(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TEL:tel:+1-555-555-556'."\n". @@ -1276,8 +1237,7 @@ public function testRFC6351Section5Part1NoNamespace(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'X-MY-PROP;PREF=1:value goes here'."\n". @@ -1300,8 +1260,7 @@ public function testRFC6351ValueDateWithYearMonthDay(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:20150128'."\n". @@ -1324,8 +1283,7 @@ public function testRFC6351ValueDateWithYearMonth(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:2015-01'."\n". @@ -1348,8 +1306,7 @@ public function testRFC6351ValueDateWithMonth(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:--01'."\n". @@ -1372,8 +1329,7 @@ public function testRFC6351ValueDateWithMonthDay(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:--0128'."\n". @@ -1396,8 +1352,7 @@ public function testRFC6351ValueDateWithDay(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28'."\n". @@ -1420,8 +1375,7 @@ public function testRFC6351ValueTimeWithHour(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:13'."\n". @@ -1444,8 +1398,7 @@ public function testRFC6351ValueTimeWithHourMinute(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:1353'."\n". @@ -1468,8 +1421,7 @@ public function testRFC6351ValueTimeWithHourMinuteSecond(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:135301'."\n". @@ -1492,8 +1444,7 @@ public function testRFC6351ValueTimeWithMinute(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:-53'."\n". @@ -1516,8 +1467,7 @@ public function testRFC6351ValueTimeWithMinuteSecond(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:-5301'."\n". @@ -1573,8 +1523,7 @@ public function testRFC6351ValueTimeWithSecondZ(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:--01Z'."\n". @@ -1597,8 +1546,7 @@ public function testRFC6351ValueTimeWithSecondTZ(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:--01+1234'."\n". @@ -1621,8 +1569,7 @@ public function testRFC6351ValueDateTimeWithYearMonthDayHour(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:20150128T13'."\n". @@ -1645,8 +1592,7 @@ public function testRFC6351ValueDateTimeWithMonthDayHour(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:--0128T13'."\n". @@ -1669,8 +1615,7 @@ public function testRFC6351ValueDateTimeWithDayHour(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28T13'."\n". @@ -1693,8 +1638,7 @@ public function testRFC6351ValueDateTimeWithDayHourMinute(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28T1353'."\n". @@ -1717,8 +1661,7 @@ public function testRFC6351ValueDateTimeWithDayHourMinuteSecond(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28T135301'."\n". @@ -1741,8 +1684,7 @@ public function testRFC6351ValueDateTimeWithDayHourZ(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28T13Z'."\n". @@ -1765,8 +1707,7 @@ public function testRFC6351ValueDateTimeWithDayHourTZ(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28T13+1234'."\n". @@ -1789,8 +1730,7 @@ public function testRFC6350Section6Part1Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'SOURCE:ldap://ldap.example.com/cn=Babs%20Jensen\,%20o=Babsco\,%20c=US'."\n". @@ -1813,8 +1753,7 @@ public function testRFC6350Section6Part1Part4(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'KIND:individual'."\n". @@ -1837,8 +1776,7 @@ public function testRFC6350Section6Part2Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'FN:Mr. John Q. Public\, Esq.'."\n". @@ -1865,8 +1803,7 @@ public function testRFC6350Section6Part2Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'N:Stevenson;John;Philip\,Paul;Dr.;Jr.\,M.D.\,A.C.P.'."\n". @@ -1890,8 +1827,7 @@ public function testRFC6350Section6Part2Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'NICKNAME:Jim,Jimmie'."\n". @@ -1914,8 +1850,7 @@ public function testRFC6350Section6Part2Part4(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'PHOTO:http://www.example.com/pub/photos/jqpublic.gif'."\n". @@ -1935,8 +1870,7 @@ public function testRFC6350Section6Part2Part5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:19531015T231000Z'."\n". @@ -1956,8 +1890,7 @@ public function testRFC6350Section6Part2Part6(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'ANNIVERSARY:19960415'."\n". @@ -1981,8 +1914,7 @@ public function testRFC6350Section6Part2Part7(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'GENDER:Jim;Jimmie'."\n". @@ -2011,8 +1943,7 @@ public function testRFC6350Section6Part3Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'ADR:;;123 Main Street;Any Town;CA;91921-1234;U.S.A.'."\n". @@ -2050,8 +1981,7 @@ public function testRFC6350Section6Part4Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TEL;TYPE=home:tel:+33-01-23-45-67'."\n". @@ -2073,8 +2003,7 @@ public function testRFC6350Section6Part4Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TEL;TYPE=home:tel:+33-01-23-45-67'."\n". @@ -2102,8 +2031,7 @@ public function testRFC6350Section6Part4Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'EMAIL;TYPE=work:jqpublic@xyz.example.com'."\n". @@ -2131,8 +2059,7 @@ public function testRFC6350Section6Part4Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'IMPP;PREF=1:xmpp:alice@example.com'."\n". @@ -2163,8 +2090,7 @@ public function testRFC6350Section6Part4Part4(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'LANG;TYPE=work;PREF=2:en'."\n". @@ -2187,8 +2113,7 @@ public function testRFC6350Section6Part5Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TZ:Raleigh/North America'."\n". @@ -2211,8 +2136,7 @@ public function testRFC6350Section6Part5Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'GEO:geo:37.386013\,-122.082932'."\n". @@ -2229,8 +2153,7 @@ public function testRFC6350Section6Part5Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'GEO:geo:37.386013\,-122.082932'."\n". @@ -2253,8 +2176,7 @@ public function testRFC6350Section6Part6Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TITLE:Research Scientist'."\n". @@ -2277,8 +2199,7 @@ public function testRFC6350Section6Part6Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'ROLE:Project Leader'."\n". @@ -2301,8 +2222,7 @@ public function testRFC6350Section6Part6Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'LOGO:http://www.example.com/pub/logos/abccorp.jpg'."\n". @@ -2327,8 +2247,7 @@ public function testRFC6350Section6Part6Part4(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'ORG:ABC\, Inc.;North American Division;Marketing'."\n". @@ -2351,8 +2270,7 @@ public function testRFC6350Section6Part6Part5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'MEMBER:urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af'."\n". @@ -2378,8 +2296,7 @@ public function testRFC6350Section6Part6Part5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'MEMBER:mailto:subscriber1@example.com'."\n". @@ -2410,8 +2327,7 @@ public function testRFC6350Section6Part6Part6(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'RELATED;TYPE=friend:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6'."\n". @@ -2437,8 +2353,7 @@ public function testRFC6350Section6Part7Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY'."\n". @@ -2461,8 +2376,7 @@ public function testRFC6350Section6Part7Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'NOTE:Foo\, bar'."\n". @@ -2485,8 +2399,7 @@ public function testRFC6350Section6Part7Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'PRODID:-//ONLINE DIRECTORY//NONSGML Version 1//EN'."\n". @@ -2506,8 +2419,7 @@ public function testRFC6350Section6Part7Part4(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'REV:19951031T222710Z'."\n". @@ -2530,8 +2442,7 @@ public function testRFC6350Section6Part7Part5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'SOUND:CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com'."\n". @@ -2548,8 +2459,7 @@ public function testRFC6350Section6Part7Part5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'SOUND:CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com'."\n". @@ -2572,8 +2482,7 @@ public function testRFC6350Section6Part7Part6(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'UID:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6'."\n". @@ -2597,8 +2506,7 @@ public function testRFC6350Section6Part7Part7(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'CLIENTPIDMAP:1;urn:uuid:3df403f4-5924-4bb7-b077-3c711d9eb34b'."\n". @@ -2621,8 +2529,7 @@ public function testRFC6350Section6Part7Part8(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'URL:http://example.org/restaurant.french/~chezchic.html'."\n". @@ -2641,8 +2548,7 @@ public function testRFC6350Section6Part7Part9(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'END:VCARD'."\n" @@ -2669,8 +2575,7 @@ public function testRFC6350Section6Part8Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'KEY;MEDIATYPE=application/pgp-keys:ftp://example.com/keys/jdoe'."\n". @@ -2698,8 +2603,7 @@ public function testRFC6350Section6Part9Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'FBURL;PREF=1:http://www.example.com/busy/janedoe'."\n". @@ -2722,8 +2626,7 @@ public function testRFC6350Section6Part9Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'CALADRURI:http://example.com/calendar/jdoe'."\n". @@ -2751,8 +2654,7 @@ public function testRFC6350Section6Part9Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'CALURI;PREF=1:http://cal.example.com/calA'."\n". @@ -2775,8 +2677,7 @@ public function testRFC6350SectionAPart3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'CAPURI:http://cap.example.com/capA'."\n". From 0ddabb7c8fe357b66210fe9b3cc1d6646bee8241 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Tue, 16 Sep 2025 11:40:41 +0545 Subject: [PATCH 64/85] chore(tests): apply php-cs-fixer format changes to test code --- composer.json | 2 +- tests/VObject/Component/VAvailabilityTest.php | 6 +- tests/VObject/Parser/XmlTest.php | 297 ++++++------------ 3 files changed, 102 insertions(+), 203 deletions(-) diff --git a/composer.json b/composer.json index 64e2f0e16..4e0c4d067 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "^3.75", + "friendsofphp/php-cs-fixer": "^3.87", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", "phpstan/phpstan": "^2.1" diff --git a/tests/VObject/Component/VAvailabilityTest.php b/tests/VObject/Component/VAvailabilityTest.php index 263eeebda..67499e98e 100644 --- a/tests/VObject/Component/VAvailabilityTest.php +++ b/tests/VObject/Component/VAvailabilityTest.php @@ -435,8 +435,7 @@ protected function template(array $properties) … END:VAVAILABILITY END:VCALENDAR -VCAL - , +VCAL, $properties ); } @@ -459,8 +458,7 @@ protected function templateAvailable(array $properties) END:AVAILABLE END:VAVAILABILITY END:VCALENDAR -VCAL - , +VCAL, $properties ); } diff --git a/tests/VObject/Parser/XmlTest.php b/tests/VObject/Parser/XmlTest.php index 2276f7fdf..6491d087b 100644 --- a/tests/VObject/Parser/XmlTest.php +++ b/tests/VObject/Parser/XmlTest.php @@ -47,8 +47,7 @@ public function testRFC6321Example1(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". // VERSION comes first because this is required by vCard 4.0. 'VERSION:2.0'."\n". @@ -270,8 +269,7 @@ public function testRFC6321Section3Part2(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'END:VCALENDAR'."\n" ); @@ -299,8 +297,7 @@ public function testRFC6321Section3Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'BEGIN:VTIMEZONE'."\n". 'END:VTIMEZONE'."\n". @@ -340,8 +337,7 @@ public function testRFC6321Section3Part4Part1Part2(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'GEO:37.386013;-122.082932'."\n". 'END:VCALENDAR'."\n" @@ -367,8 +363,7 @@ public function testRFC6321Section3Part4Part1Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'REQUEST-STATUS:2.0;Success'."\n". 'END:VCALENDAR'."\n" @@ -389,8 +384,7 @@ public function testRFC6321Section3Part4Part1Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'REQUEST-STATUS:3.1;Invalid property value;DTSTART:96-Apr-01'."\n". 'END:VCALENDAR'."\n" @@ -411,8 +405,7 @@ public function testRFC6321Section3Part4Part1Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'REQUEST-STATUS:2.8;Success\, repeating event ignored. Scheduled as a single'."\n". ' event.;RRULE:FREQ=WEEKLY\;INTERVAL=2'."\n". @@ -433,8 +426,7 @@ public function testRFC6321Section3Part4Part1Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'REQUEST-STATUS:4.1;Event conflict. Date-time is busy.'."\n". 'END:VCALENDAR'."\n" @@ -455,8 +447,7 @@ public function testRFC6321Section3Part4Part1Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'REQUEST-STATUS:3.7;Invalid calendar user;ATTENDEE:mailto:jsmith@example.com'."\n". 'END:VCALENDAR'."\n" @@ -480,8 +471,7 @@ public function testRFC6321Section3Part6Part1(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'ATTACH:SGVsbG8gV29ybGQh'."\n". 'END:VCALENDAR'."\n" @@ -500,8 +490,7 @@ public function testRFC6321Section3Part6Part1(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'ATTACH:SGVsbG8gV29ybGQh'."\n". 'END:VCALENDAR'."\n" @@ -528,8 +517,7 @@ public function testRFC6321Section3Part6Part2(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'ATTENDEE;RSVP=true:mailto:cyrus@example.com'."\n". 'END:VCALENDAR'."\n" @@ -553,8 +541,7 @@ public function testRFC6321Section3Part6Part3(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'ATTENDEE:mailto:cyrus@example.com'."\n". 'END:VCALENDAR'."\n" @@ -578,8 +565,7 @@ public function testRFC6321Section3Part6Part4(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'DTSTART;VALUE=DATE:20110517'."\n". 'END:VCALENDAR'."\n" @@ -603,8 +589,7 @@ public function testRFC6321Section3Part6Part5(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'DTSTART:20110517T120000'."\n". 'END:VCALENDAR'."\n" @@ -628,8 +613,7 @@ public function testRFC6321Section3Part6Part6(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'DURATION:P1D'."\n". 'END:VCALENDAR'."\n" @@ -662,8 +646,7 @@ public function testRFC6321Section3Part6Part8(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'FOO:42'."\n". 'END:VCALENDAR'."\n" @@ -681,8 +664,7 @@ public function testRFC6321Section3Part6Part8(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'FOO:-42'."\n". 'END:VCALENDAR'."\n" @@ -709,8 +691,7 @@ public function testRFC6321Section3Part6Part9(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'FREEBUSY:20110517T120000/P1H'."\n". 'END:VCALENDAR'."\n" @@ -731,8 +712,7 @@ public function testRFC6321Section3Part6Part9(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'FREEBUSY:20110517T120000/20120517T120000'."\n". 'END:VCALENDAR'."\n" @@ -761,8 +741,7 @@ public function testRFC6321Section3Part6Part10(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RRULE:FREQ=YEARLY;COUNT=5;BYDAY=-1SU;BYMONTH=10'."\n". 'END:VCALENDAR'."\n" @@ -786,8 +765,7 @@ public function testRFC6321Section3Part6Part11(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'CALSCALE:GREGORIAN'."\n". 'END:VCALENDAR'."\n" @@ -811,8 +789,7 @@ public function testRFC6321Section3Part6Part12(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'FOO:120000'."\n". 'END:VCALENDAR'."\n" @@ -836,8 +813,7 @@ public function testRFC6321Section3Part6Part13(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'ATTACH:http://calendar.example.com'."\n". 'END:VCALENDAR'."\n" @@ -862,8 +838,7 @@ public function testRFC6321Section3Part6Part14(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'TZOFFSETFROM:-0500'."\n". 'END:VCALENDAR'."\n" @@ -882,8 +857,7 @@ public function testRFC6321Section3Part6Part14(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'TZOFFSETFROM:+0100'."\n". 'END:VCALENDAR'."\n" @@ -907,8 +881,7 @@ public function testRFC6321Section5(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'X-PROPERTY:20110512T120000Z'."\n". 'END:VCALENDAR'."\n" @@ -931,8 +904,7 @@ public function testRFC6321Section5(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'DTSTART;X-PARAM=PT30M:20110512T130000Z'."\n". 'END:VCALENDAR'."\n" @@ -953,8 +925,7 @@ public function testRDateWithDateTime(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE:20080205T191224Z'."\n". 'END:VCALENDAR'."\n" @@ -973,8 +944,7 @@ public function testRDateWithDateTime(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE:20080205T191224Z,20090205T191224Z'."\n". 'END:VCALENDAR'."\n" @@ -995,8 +965,7 @@ public function testRDateWithDate(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE:20081006'."\n". 'END:VCALENDAR'."\n" @@ -1016,8 +985,7 @@ public function testRDateWithDate(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE:20081006,20091006,20101006'."\n". 'END:VCALENDAR'."\n" @@ -1046,8 +1014,7 @@ public function testRDateWithPeriod(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H'."\n". 'END:VCALENDAR'."\n" @@ -1077,8 +1044,7 @@ public function testRDateWithPeriod(): void -XML - , +XML, 'BEGIN:VCALENDAR'."\n". 'RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H,20080102T150000/PT1'."\n". ' H'."\n". @@ -1108,8 +1074,7 @@ public function testRFC6351Basic(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'FN:J. Doe'."\n". @@ -1149,8 +1114,7 @@ public function testRFC6351Example1(): void My web page! -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'FN:J. Doe'."\n". @@ -1183,8 +1147,7 @@ public function testRFC6351Section5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TEL;TYPE="voice,video":tel:+1-555-555-555'."\n". @@ -1207,8 +1170,7 @@ public function testRFC6351Section5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TEL;TYPE="voice,video":tel:+1-555-555-555'."\n". @@ -1244,8 +1206,7 @@ public function testRFC6351Section5Group(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TEL:tel:+1-555-555-556'."\n". @@ -1276,8 +1237,7 @@ public function testRFC6351Section5Part1NoNamespace(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'X-MY-PROP;PREF=1:value goes here'."\n". @@ -1300,8 +1260,7 @@ public function testRFC6351ValueDateWithYearMonthDay(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:20150128'."\n". @@ -1324,8 +1283,7 @@ public function testRFC6351ValueDateWithYearMonth(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:2015-01'."\n". @@ -1348,8 +1306,7 @@ public function testRFC6351ValueDateWithMonth(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:--01'."\n". @@ -1372,8 +1329,7 @@ public function testRFC6351ValueDateWithMonthDay(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:--0128'."\n". @@ -1396,8 +1352,7 @@ public function testRFC6351ValueDateWithDay(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28'."\n". @@ -1420,8 +1375,7 @@ public function testRFC6351ValueTimeWithHour(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:13'."\n". @@ -1444,8 +1398,7 @@ public function testRFC6351ValueTimeWithHourMinute(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:1353'."\n". @@ -1468,8 +1421,7 @@ public function testRFC6351ValueTimeWithHourMinuteSecond(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:135301'."\n". @@ -1492,8 +1444,7 @@ public function testRFC6351ValueTimeWithMinute(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:-53'."\n". @@ -1516,8 +1467,7 @@ public function testRFC6351ValueTimeWithMinuteSecond(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:-5301'."\n". @@ -1573,8 +1523,7 @@ public function testRFC6351ValueTimeWithSecondZ(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:--01Z'."\n". @@ -1597,8 +1546,7 @@ public function testRFC6351ValueTimeWithSecondTZ(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:--01+1234'."\n". @@ -1621,8 +1569,7 @@ public function testRFC6351ValueDateTimeWithYearMonthDayHour(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:20150128T13'."\n". @@ -1645,8 +1592,7 @@ public function testRFC6351ValueDateTimeWithMonthDayHour(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:--0128T13'."\n". @@ -1669,8 +1615,7 @@ public function testRFC6351ValueDateTimeWithDayHour(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28T13'."\n". @@ -1693,8 +1638,7 @@ public function testRFC6351ValueDateTimeWithDayHourMinute(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28T1353'."\n". @@ -1717,8 +1661,7 @@ public function testRFC6351ValueDateTimeWithDayHourMinuteSecond(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28T135301'."\n". @@ -1741,8 +1684,7 @@ public function testRFC6351ValueDateTimeWithDayHourZ(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28T13Z'."\n". @@ -1765,8 +1707,7 @@ public function testRFC6351ValueDateTimeWithDayHourTZ(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:---28T13+1234'."\n". @@ -1789,8 +1730,7 @@ public function testRFC6350Section6Part1Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'SOURCE:ldap://ldap.example.com/cn=Babs%20Jensen\,%20o=Babsco\,%20c=US'."\n". @@ -1813,8 +1753,7 @@ public function testRFC6350Section6Part1Part4(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'KIND:individual'."\n". @@ -1837,8 +1776,7 @@ public function testRFC6350Section6Part2Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'FN:Mr. John Q. Public\, Esq.'."\n". @@ -1865,8 +1803,7 @@ public function testRFC6350Section6Part2Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'N:Stevenson;John;Philip\,Paul;Dr.;Jr.\,M.D.\,A.C.P.'."\n". @@ -1890,8 +1827,7 @@ public function testRFC6350Section6Part2Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'NICKNAME:Jim,Jimmie'."\n". @@ -1914,8 +1850,7 @@ public function testRFC6350Section6Part2Part4(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'PHOTO:http://www.example.com/pub/photos/jqpublic.gif'."\n". @@ -1935,8 +1870,7 @@ public function testRFC6350Section6Part2Part5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'BDAY:19531015T231000Z'."\n". @@ -1956,8 +1890,7 @@ public function testRFC6350Section6Part2Part6(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'ANNIVERSARY:19960415'."\n". @@ -1981,8 +1914,7 @@ public function testRFC6350Section6Part2Part7(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'GENDER:Jim;Jimmie'."\n". @@ -2011,8 +1943,7 @@ public function testRFC6350Section6Part3Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'ADR:;;123 Main Street;Any Town;CA;91921-1234;U.S.A.'."\n". @@ -2050,8 +1981,7 @@ public function testRFC6350Section6Part4Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TEL;TYPE=home:tel:+33-01-23-45-67'."\n". @@ -2073,8 +2003,7 @@ public function testRFC6350Section6Part4Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TEL;TYPE=home:tel:+33-01-23-45-67'."\n". @@ -2102,8 +2031,7 @@ public function testRFC6350Section6Part4Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'EMAIL;TYPE=work:jqpublic@xyz.example.com'."\n". @@ -2131,8 +2059,7 @@ public function testRFC6350Section6Part4Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'IMPP;PREF=1:xmpp:alice@example.com'."\n". @@ -2163,8 +2090,7 @@ public function testRFC6350Section6Part4Part4(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'LANG;TYPE=work;PREF=2:en'."\n". @@ -2187,8 +2113,7 @@ public function testRFC6350Section6Part5Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TZ:Raleigh/North America'."\n". @@ -2211,8 +2136,7 @@ public function testRFC6350Section6Part5Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'GEO:geo:37.386013\,-122.082932'."\n". @@ -2229,8 +2153,7 @@ public function testRFC6350Section6Part5Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'GEO:geo:37.386013\,-122.082932'."\n". @@ -2253,8 +2176,7 @@ public function testRFC6350Section6Part6Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'TITLE:Research Scientist'."\n". @@ -2277,8 +2199,7 @@ public function testRFC6350Section6Part6Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'ROLE:Project Leader'."\n". @@ -2301,8 +2222,7 @@ public function testRFC6350Section6Part6Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'LOGO:http://www.example.com/pub/logos/abccorp.jpg'."\n". @@ -2327,8 +2247,7 @@ public function testRFC6350Section6Part6Part4(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'ORG:ABC\, Inc.;North American Division;Marketing'."\n". @@ -2351,8 +2270,7 @@ public function testRFC6350Section6Part6Part5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'MEMBER:urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af'."\n". @@ -2378,8 +2296,7 @@ public function testRFC6350Section6Part6Part5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'MEMBER:mailto:subscriber1@example.com'."\n". @@ -2410,8 +2327,7 @@ public function testRFC6350Section6Part6Part6(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'RELATED;TYPE=friend:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6'."\n". @@ -2437,8 +2353,7 @@ public function testRFC6350Section6Part7Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY'."\n". @@ -2461,8 +2376,7 @@ public function testRFC6350Section6Part7Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'NOTE:Foo\, bar'."\n". @@ -2485,8 +2399,7 @@ public function testRFC6350Section6Part7Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'PRODID:-//ONLINE DIRECTORY//NONSGML Version 1//EN'."\n". @@ -2506,8 +2419,7 @@ public function testRFC6350Section6Part7Part4(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'REV:19951031T222710Z'."\n". @@ -2530,8 +2442,7 @@ public function testRFC6350Section6Part7Part5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'SOUND:CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com'."\n". @@ -2548,8 +2459,7 @@ public function testRFC6350Section6Part7Part5(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'SOUND:CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com'."\n". @@ -2572,8 +2482,7 @@ public function testRFC6350Section6Part7Part6(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'UID:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6'."\n". @@ -2597,8 +2506,7 @@ public function testRFC6350Section6Part7Part7(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'CLIENTPIDMAP:1;urn:uuid:3df403f4-5924-4bb7-b077-3c711d9eb34b'."\n". @@ -2621,8 +2529,7 @@ public function testRFC6350Section6Part7Part8(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'URL:http://example.org/restaurant.french/~chezchic.html'."\n". @@ -2641,8 +2548,7 @@ public function testRFC6350Section6Part7Part9(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'END:VCARD'."\n" @@ -2669,8 +2575,7 @@ public function testRFC6350Section6Part8Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'KEY;MEDIATYPE=application/pgp-keys:ftp://example.com/keys/jdoe'."\n". @@ -2698,8 +2603,7 @@ public function testRFC6350Section6Part9Part1(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'FBURL;PREF=1:http://www.example.com/busy/janedoe'."\n". @@ -2722,8 +2626,7 @@ public function testRFC6350Section6Part9Part2(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'CALADRURI:http://example.com/calendar/jdoe'."\n". @@ -2751,8 +2654,7 @@ public function testRFC6350Section6Part9Part3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'CALURI;PREF=1:http://cal.example.com/calA'."\n". @@ -2775,8 +2677,7 @@ public function testRFC6350SectionAPart3(): void -XML - , +XML, 'BEGIN:VCARD'."\n". 'VERSION:4.0'."\n". 'CAPURI:http://cap.example.com/capA'."\n". From f0f61a106680c1eafaf44b7f5d323d77b2db5400 Mon Sep 17 00:00:00 2001 From: chris Date: Fri, 24 Oct 2025 16:33:44 +0200 Subject: [PATCH 65/85] ci: add tests for PHP 8.4 and 8.5 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f732fadf4..248dbf69c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.0', '8.1', '8.2', '8.3'] + php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] coverage: ['pcov'] code-style: ['yes'] code-analysis: ['no'] From 1b833ad1d12d6c1360917b87ed382bfa3265ba0a Mon Sep 17 00:00:00 2001 From: Christopher Georg Date: Fri, 24 Oct 2025 16:44:23 +0200 Subject: [PATCH 66/85] ci: add tests for PHP 8.4 and 8.5 --- tests/VObject/Property/ICalendar/DateTimeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/VObject/Property/ICalendar/DateTimeTest.php b/tests/VObject/Property/ICalendar/DateTimeTest.php index 00c8be1be..5a70b2641 100644 --- a/tests/VObject/Property/ICalendar/DateTimeTest.php +++ b/tests/VObject/Property/ICalendar/DateTimeTest.php @@ -177,7 +177,7 @@ public function testGetDateTimeCached(): void { $tz = new \DateTimeZone('Europe/Amsterdam'); $dt = new \DateTimeImmutable('1985-07-04 01:30:00', $tz); - $dt->setTimeZone($tz); + $dt = $dt->setTimeZone($tz); $elem = $this->vcal->createProperty('DTSTART'); $elem->setDateTime($dt); From 099a37744a3aaf368bdd339f9c01e34553e8828e Mon Sep 17 00:00:00 2001 From: SebastianKrupinski Date: Wed, 19 Nov 2025 19:19:32 -0500 Subject: [PATCH 67/85] fix: use RDATE in time range check and use all instances Signed-off-by: SebastianKrupinski --- lib/Component/VEvent.php | 2 +- lib/Recur/EventIterator.php | 6 ++++- tests/VObject/Component/VEventTest.php | 35 ++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/Component/VEvent.php b/lib/Component/VEvent.php index e29e56322..6ca196d66 100644 --- a/lib/Component/VEvent.php +++ b/lib/Component/VEvent.php @@ -41,7 +41,7 @@ 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) { diff --git a/lib/Recur/EventIterator.php b/lib/Recur/EventIterator.php index 2c93bcf4e..79a070ee4 100644 --- a/lib/Recur/EventIterator.php +++ b/lib/Recur/EventIterator.php @@ -164,8 +164,12 @@ public function __construct($input, ?string $uid = null, ?\DateTimeZone $timeZon } 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)) { diff --git a/tests/VObject/Component/VEventTest.php b/tests/VObject/Component/VEventTest.php index 50fa626cc..5944ea574 100644 --- a/tests/VObject/Component/VEventTest.php +++ b/tests/VObject/Component/VEventTest.php @@ -89,6 +89,41 @@ public function timeRangeTestData(): array $vevent9->RRULE = 'FREQ=DAILY'; $tests[] = [$vevent9, new \DateTime('2016-10-31'), new \DateTime('2016-12-12'), true]; + // Added this test to check events with RDATE property with multiple dates + $vevent10 = clone $vevent; + $vevent10->DTSTART = '20140901T000000Z'; + $vevent10->DTEND = '20140901T010000Z'; + $vevent10->add('RDATE', ['20141001T000000Z', '20141101T000000Z']); + // DTSTART is the first occurrence + $tests[] = [$vevent10, new \DateTime('2014-09-01'), new \DateTime('2014-09-02'), true]; + // RDATE adds additional occurrences on Oct 1 and Nov 1 + $tests[] = [$vevent10, new \DateTime('2014-10-01'), new \DateTime('2014-10-02'), true]; + $tests[] = [$vevent10, new \DateTime('2014-11-01'), new \DateTime('2014-11-02'), true]; + // No occurrence in December + $tests[] = [$vevent10, new \DateTime('2014-12-01'), new \DateTime('2014-12-31'), false]; + // Range that includes first occurrence + $tests[] = [$vevent10, new \DateTime('2014-08-01'), new \DateTime('2014-09-30'), true]; + // Range that spans all occurrences + $tests[] = [$vevent10, new \DateTime('2014-08-01'), new \DateTime('2014-12-31'), true]; + + // Added this test to check events with RDATE property with multiple instances + $vevent11 = clone $vevent; + $vevent11->DTSTART = '20140901T000000Z'; + $vevent11->DTEND = '20140901T010000Z'; + $vevent11->add('RDATE', '20141001T000000Z'); + $vevent11->add('RDATE', '20141101T000000Z'); + // DTSTART is the first occurrence + $tests[] = [$vevent11, new \DateTime('2014-09-01'), new \DateTime('2014-09-02'), true]; + // RDATE adds additional occurrences on Oct 1 and Nov 1 + $tests[] = [$vevent11, new \DateTime('2014-10-01'), new \DateTime('2014-10-02'), true]; + $tests[] = [$vevent11, new \DateTime('2014-11-01'), new \DateTime('2014-11-02'), true]; + // No occurrence in December + $tests[] = [$vevent11, new \DateTime('2014-12-01'), new \DateTime('2014-12-31'), false]; + // Range that includes first occurrence + $tests[] = [$vevent11, new \DateTime('2014-08-01'), new \DateTime('2014-09-30'), true]; + // Range that spans all occurrences + $tests[] = [$vevent11, new \DateTime('2014-08-01'), new \DateTime('2014-12-31'), true]; + return $tests; } } From 7ba9fa608a2c1a03574ae82450c10efcf74bdfa8 Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Mon, 1 Dec 2025 20:23:55 +0930 Subject: [PATCH 68/85] chore: adjust CI for PHP 8.5 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 248dbf69c..2e862aed9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] + php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4'] coverage: ['pcov'] code-style: ['yes'] code-analysis: ['no'] @@ -21,7 +21,7 @@ jobs: coverage: 'pcov' code-style: 'yes' code-analysis: 'yes' - - php-versions: '8.4' + - php-versions: '8.5' coverage: 'pcov' code-style: 'yes' code-analysis: 'yes' From 61c968defcfdf331e38ac7878e94d9fb15fb4407 Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Mon, 1 Dec 2025 20:27:02 +0930 Subject: [PATCH 69/85] chore: only run php-cs-fixer on PHP 7.4 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e862aed9..dbeab8860 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: matrix: php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4'] coverage: ['pcov'] - code-style: ['yes'] + code-style: ['no'] code-analysis: ['no'] include: - php-versions: '7.4' @@ -23,7 +23,7 @@ jobs: code-analysis: 'yes' - php-versions: '8.5' coverage: 'pcov' - code-style: 'yes' + code-style: 'no' code-analysis: 'yes' steps: - name: Checkout From aaa9462580cd696634037420eb3eed4441479ac1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 01:58:25 +0000 Subject: [PATCH 70/85] chore(deps): bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dbeab8860..0162fe0dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: code-analysis: 'yes' steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php From 0481e4cff74fc559e96bc9b3b468fa095ed4f108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20M=C3=B8llerh=C3=B8j?= Date: Wed, 27 Aug 2025 12:32:11 +0200 Subject: [PATCH 71/85] Issue-722: Fixing problem with broker adding exdate property with floating timezone --- lib/ITip/Broker.php | 3 + tests/VObject/ITip/BrokerUpdateEventTest.php | 180 +++++++++++++++++++ 2 files changed, 183 insertions(+) diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index 76ee0c71e..7adc5494a 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -580,6 +580,9 @@ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, )); } else { $currentEvent->EXDATE = $exceptions; + if ($currentEvent->DTSTART['TZID']) { + $currentEvent->EXDATE['TZID'] = clone $currentEvent->DTSTART['TZID']; + } } } diff --git a/tests/VObject/ITip/BrokerUpdateEventTest.php b/tests/VObject/ITip/BrokerUpdateEventTest.php index 030860bd0..64031f6e7 100644 --- a/tests/VObject/ITip/BrokerUpdateEventTest.php +++ b/tests/VObject/ITip/BrokerUpdateEventTest.php @@ -876,4 +876,184 @@ public function testInviteStatusCancelled(): void $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); } + + /* + * When EXDATE is added by Broker, it needs to be in the correct + * timezone + */ + + public function testExdateTimezone(): void + { + $oldMessage = << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + } + + /* + * When EXDATE is added by Broker, it needs to be in the correct + * timezone, also in case UTC is used + */ + + public function testExdateTimezoneUTC(): void + { + $oldMessage = << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + } } From 0351e784714d40b80437fe7dd22fd3b8ee03a9bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20M=C3=B8llerh=C3=B8j?= Date: Mon, 25 Aug 2025 13:04:08 +0200 Subject: [PATCH 72/85] Fixed problem with variable scope --- lib/ITip/Broker.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index 76ee0c71e..93e2fab6d 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -588,14 +588,14 @@ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, 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'; + if (!isset($currentEventAttendee['PARTSTAT'])) { + $currentEventAttendee['PARTSTAT'] = 'NEEDS-ACTION'; } } } From 96c3152afaf1c6713d4643796cc7ac1aa4165d01 Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Tue, 2 Dec 2025 21:28:07 +0930 Subject: [PATCH 73/85] chore: forward port 4.5.7 --- CHANGELOG.md | 7 +++++++ lib/Version.php | 2 +- tests/VObject/ITip/BrokerUpdateEventTest.php | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01a3531c7..ceb6e2ef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ChangeLog ========= +4.5.7 (2025-04-17) +------------------ + +* #618: Add new exchangetimezone 'Amsterdam, Berlin, Bern, Rom, Stockholm, Wien' (@chr-is) +* #674: Allow unknown value data types for VALUE (@heiglandreas) +* #707: add phpdoc return types for arrayaccess methods (@smhg) + 4.5.6 (2024-10-14) ------------------ diff --git a/lib/Version.php b/lib/Version.php index 060c69a30..ccaf1eb40 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - public const VERSION = '4.5.6'; + public const VERSION = '4.5.7'; } diff --git a/tests/VObject/ITip/BrokerUpdateEventTest.php b/tests/VObject/ITip/BrokerUpdateEventTest.php index 64031f6e7..27368dd57 100644 --- a/tests/VObject/ITip/BrokerUpdateEventTest.php +++ b/tests/VObject/ITip/BrokerUpdateEventTest.php @@ -943,7 +943,7 @@ public function testExdateTimezone(): void 'message' => << << Date: Mon, 8 Sep 2025 12:48:16 +0200 Subject: [PATCH 74/85] Improve error check in Mimedir parser --- lib/Parser/MimeDir.php | 21 +++++++++++++++++++++ tests/VObject/Parser/MimeDirTest.php | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index c87ccbf97..97ff57fbe 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -26,6 +26,11 @@ */ 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. * @@ -360,6 +365,12 @@ protected function readProperty(string $line) 'value' => null, ]; + /* + * Keep track on the last token we parsed in order to do + * better error checking + */ + $lastToken = null; + $lastParam = null; /* @@ -385,10 +396,16 @@ protected function readProperty(string $line) // 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,6 +419,7 @@ protected function readProperty(string $line) $value, ]; } + $lastToken = self::TOKEN_PARAMVALUE; continue; } if (isset($match['paramName'])) { @@ -409,14 +427,17 @@ protected function readProperty(string $line) if (!isset($property['parameters'][$lastParam])) { $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']) && 0 < strlen($match['name'])) { $property['name'] = strtoupper($match['name']); + $lastToken = self::TOKEN_PROPNAME; continue; } diff --git a/tests/VObject/Parser/MimeDirTest.php b/tests/VObject/Parser/MimeDirTest.php index 904d164c4..2fb436d0f 100644 --- a/tests/VObject/Parser/MimeDirTest.php +++ b/tests/VObject/Parser/MimeDirTest.php @@ -276,4 +276,21 @@ public function testPropertyName0(): void $vevent = $mimeDir->parse($iCal); self::assertEquals('test', $vevent->VEVENT->{0}->getValue()); } + + public function testInvalidParameter(): void + { + $this->expectException(ParseException::class); + $this->expectExceptionMessage('Invalid Mimedir file. Line starting at 3: Missing parameter name for parameter value "value2"'); + $vcard = <<parse($vcard); + + echo $vcard->serialize(); + } } From 4bf2c58711dc77c71876618f593c89efc678e881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20M=C3=B8llerh=C3=B8j?= Date: Wed, 10 Sep 2025 11:18:08 +0200 Subject: [PATCH 75/85] Fixed cornercase for recurrent event for organizer and no attendee --- lib/ITip/Broker.php | 5 +- tests/VObject/ITip/BrokerNewEventTest.php | 56 +++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index eae6290d2..3770aca36 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -479,7 +479,6 @@ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, } $messages = []; - foreach ($attendees as $attendee) { // An organizer can also be an attendee. We should not generate any // messages for those. @@ -895,6 +894,9 @@ protected function parseEventInfo(VCalendar $calendar): array $timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone(); } } + + $instances[$recurId] = $vevent; + if (isset($vevent->ATTENDEE)) { foreach ($vevent->ATTENDEE as $attendee) { if ($this->scheduleAgentServerRules @@ -933,7 +935,6 @@ protected function parseEventInfo(VCalendar $calendar): array ]; } } - $instances[$recurId] = $vevent; } foreach ($this->significantChangeProperties as $prop) { diff --git a/tests/VObject/ITip/BrokerNewEventTest.php b/tests/VObject/ITip/BrokerNewEventTest.php index b0b27f41c..db8441027 100644 --- a/tests/VObject/ITip/BrokerNewEventTest.php +++ b/tests/VObject/ITip/BrokerNewEventTest.php @@ -574,4 +574,60 @@ public function testNoOrganizerHasAttendee(): void $this->parse(null, $message, [], 'mailto:strunk@example.org'); } + + public function testAttendeeRemoval(): void + { + $message = << 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:white@example.org', + 'recipientName' => 'White', + 'message' => $expectedMessage, + ], + ]; + + $this->parse(null, $message, $expected, 'mailto:strunk@example.org'); + } } From 93f212c00f8892425b793e6358d940b5b2b633f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 08:50:17 +0100 Subject: [PATCH 76/85] chore(deps): bump actions/cache from 4 to 5 (#744) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0162fe0dc..f5a7d67ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ${{ steps.composer-cache.outputs.dir }} # Use composer.json for key, if composer.lock is not committed. From 49d53f187089ec9a67917e564a87c6dea89ea839 Mon Sep 17 00:00:00 2001 From: SebastianKrupinski Date: Tue, 30 Dec 2025 13:48:29 -0500 Subject: [PATCH 77/85] fix: send participation reply on fresh event Signed-off-by: SebastianKrupinski --- lib/ITip/Broker.php | 25 ++++++--- .../VObject/ITip/BrokerAttendeeReplyTest.php | 52 +++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index 76ee0c71e..e621c6e20 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -246,16 +246,29 @@ public function parseEvent($calendar, $userHref, $oldCalendar = null): array $baseCalendar = $oldCalendar; } + // Check if the user is the organizer if (in_array($eventInfo['organizer'], $userHref)) { 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)) { + // 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; } } diff --git a/tests/VObject/ITip/BrokerAttendeeReplyTest.php b/tests/VObject/ITip/BrokerAttendeeReplyTest.php index 75d2cf06f..f766d4c57 100644 --- a/tests/VObject/ITip/BrokerAttendeeReplyTest.php +++ b/tests/VObject/ITip/BrokerAttendeeReplyTest.php @@ -1198,6 +1198,58 @@ public function testPartyCrasher(): void END:VEVENT END:VCALENDAR +ICS, + ], + ]; + + $this->parse($oldMessage, $newMessage, $expected); + } + + public function testNewEventWithReply(): void + { + $oldMessage = null; + + $newMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => << Date: Mon, 12 Jan 2026 16:28:29 +0545 Subject: [PATCH 78/85] Prepare 4.5.8 --- CHANGELOG.md | 12 ++++++++++++ lib/Version.php | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ceb6e2ef1..819cf2b93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ ChangeLog ========= + +4.5.8 (2026-01-12) +------------------ + +* #721: Fix variable scope (@KristofferFM) +* #723: Broker shouldn't add EXDATE property with floating timezone (@KristofferFM) +* #726: Improved check on property name (@KristofferFM) +* #727: Improve error checks in Mimedir parser (@KristofferFM) +* #728: Removing attendee from scheduled recurring event should generate an EXDATE property (@KristofferFM) +* #735: use RDATE in time range check and use all instances (@SebastianKrupinski) +* #743: send participation reply on fresh event (@SebastianKrupinski) + 4.5.7 (2025-04-17) ------------------ diff --git a/lib/Version.php b/lib/Version.php index ccaf1eb40..decd42475 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - public const VERSION = '4.5.7'; + public const VERSION = '4.5.8'; } From b0147924e512c6c657bcd5905ccc94137c291c24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 03:53:09 +0000 Subject: [PATCH 79/85] chore(deps): bump codecov/codecov-action from 5 to 6 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5...v6) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5a7d67ad..08c0b0a2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,5 +65,5 @@ jobs: run: vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover clover.xml - name: Code Coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 if: matrix.coverage != 'none' From 8f3c7e71a5acc4f9458042f0b63479fa1753c709 Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Thu, 2 Apr 2026 14:02:29 +0930 Subject: [PATCH 80/85] chore: apply cs-fixer changes php-cs-fixer wants to make quite a few changes. --- bin/mergeduplicates.php | 75 ++++++++++++++--------------- composer.json | 2 +- lib/Component.php | 29 ++++++----- lib/Component/VAlarm.php | 4 +- lib/Component/VTodo.php | 4 +- lib/DateTimeParser.php | 4 +- lib/Document.php | 4 +- lib/ITip/Broker.php | 4 +- lib/ITip/Message.php | 7 ++- lib/Parameter.php | 33 ++++++------- lib/Parser/MimeDir.php | 17 +++---- lib/Property.php | 4 +- lib/Property/ICalendar/DateTime.php | 4 +- lib/Property/Text.php | 19 ++++---- lib/Recur/EventIterator.php | 7 ++- lib/Recur/RRuleIterator.php | 31 ++++++------ lib/Splitter/ICalendar.php | 4 +- 17 files changed, 121 insertions(+), 131 deletions(-) diff --git a/bin/mergeduplicates.php b/bin/mergeduplicates.php index 5e48c3c34..cd5517266 100755 --- a/bin/mergeduplicates.php +++ b/bin/mergeduplicates.php @@ -96,53 +96,52 @@ function write($vcard) $collectedNames[$fn] = $vcard; ++$stats['Unique cards']; continue; - } else { - // Starting comparison for all properties. We only check if properties - // in the current vcard exactly appear in the earlier vcard as well. - foreach ($vcard->children() as $newProp) { - if (in_array($newProp->name, $ignoredProperties)) { - // We don't care about properties such as UID and REV. - continue; - } - $ok = false; - foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) { - if ($compareProp->serialize() === $newProp->serialize()) { - $ok = true; - break; - } + } + // Starting comparison for all properties. We only check if properties + // in the current vcard exactly appear in the earlier vcard as well. + foreach ($vcard->children() as $newProp) { + if (in_array($newProp->name, $ignoredProperties)) { + // We don't care about properties such as UID and REV. + continue; + } + $ok = false; + foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) { + if ($compareProp->serialize() === $newProp->serialize()) { + $ok = true; + break; } + } - if (!$ok) { - if ('EMAIL' === $newProp->name || 'TEL' === $newProp->name) { - // We're going to make another attempt to find this - // property, this time just by value. If we find it, we - // consider it a success. - foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) { - if ($compareProp->getValue() === $newProp->getValue()) { - $ok = true; - break; - } - } - - if (!$ok) { - // Merging the new value in the old vcard. - $collectedNames[$fn]->add(clone $newProp); + if (!$ok) { + if ('EMAIL' === $newProp->name || 'TEL' === $newProp->name) { + // We're going to make another attempt to find this + // property, this time just by value. If we find it, we + // consider it a success. + foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) { + if ($compareProp->getValue() === $newProp->getValue()) { $ok = true; - ++$stats['Merged values']; + break; } } - } - if (!$ok) { - // echo $newProp->serialize() . " does not appear in earlier vcard!\n"; - ++$stats['Error']; - if ($debug) { - fwrite($debug, "Missing '".$newProp->name."' property in duplicate. Earlier vcard:\n".$collectedNames[$fn]->serialize()."\n\nLater:\n".$vcard->serialize()."\n\n"); + if (!$ok) { + // Merging the new value in the old vcard. + $collectedNames[$fn]->add(clone $newProp); + $ok = true; + ++$stats['Merged values']; } + } + } - $vcard->destroy(); - continue 2; + if (!$ok) { + // echo $newProp->serialize() . " does not appear in earlier vcard!\n"; + ++$stats['Error']; + if ($debug) { + fwrite($debug, "Missing '".$newProp->name."' property in duplicate. Earlier vcard:\n".$collectedNames[$fn]->serialize()."\n\nLater:\n".$vcard->serialize()."\n\n"); } + + $vcard->destroy(); + continue 2; } } diff --git a/composer.json b/composer.json index 4e0c4d067..561906176 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "sabre/xml" : "^3.0 || ^4.0" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "^3.87", + "friendsofphp/php-cs-fixer": "^3.94", "phpunit/phpunit" : "^9.6", "phpunit/php-invoker" : "^2.0 || ^3.1", "phpstan/phpstan": "^2.1" diff --git a/lib/Component.php b/lib/Component.php index 95bde3d40..2a08a082e 100644 --- a/lib/Component.php +++ b/lib/Component.php @@ -222,16 +222,16 @@ public function select(string $name): array 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, + function ($child) use ($group) { + return $child instanceof Property && (null !== $child->group ? strtoupper($child->group) : '') === $group; + } + ); } // If we got to this point, it means there was no 'name' specified for @@ -422,13 +422,12 @@ public function __get(string $name): ?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; } /** diff --git a/lib/Component/VAlarm.php b/lib/Component/VAlarm.php index abe61768e..0cb6ab1de 100644 --- a/lib/Component/VAlarm.php +++ b/lib/Component/VAlarm.php @@ -96,9 +96,9 @@ public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end } return false; - } else { - return $start <= $effectiveTrigger && $end > $effectiveTrigger; } + + return $start <= $effectiveTrigger && $end > $effectiveTrigger; } /** diff --git a/lib/Component/VTodo.php b/lib/Component/VTodo.php index cab672ce5..edf511ef6 100644 --- a/lib/Component/VTodo.php +++ b/lib/Component/VTodo.php @@ -54,9 +54,9 @@ public function isInTimeRange(\DateTimeInterface $start, \DateTimeInterface $end 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; diff --git a/lib/DateTimeParser.php b/lib/DateTimeParser.php index a8130b8bc..712a40faf 100644 --- a/lib/DateTimeParser.php +++ b/lib/DateTimeParser.php @@ -205,9 +205,9 @@ public static function parse(string $date, $referenceTz = null) return self::parseDuration($date); } elseif (8 === strlen($date)) { return self::parseDate($date, $referenceTz); - } else { - return self::parseDateTime($date, $referenceTz); } + + return self::parseDateTime($date, $referenceTz); } /** diff --git a/lib/Document.php b/lib/Document.php index 29463059c..aa3a404a7 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -119,9 +119,9 @@ 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 call_user_func_array([$this, 'createProperty'], func_get_args()); } /** diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index 51d91b673..3559f7e37 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -789,9 +789,9 @@ protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, $message->message = $icalMsg; return [$message]; - } else { - return []; } + + return []; } /** diff --git a/lib/ITip/Message.php b/lib/ITip/Message.php index 9aa93996a..6f59eebc2 100644 --- a/lib/ITip/Message.php +++ b/lib/ITip/Message.php @@ -107,10 +107,9 @@ public function getScheduleStatus() { if (!$this->scheduleStatus) { return false; - } else { - list($scheduleStatus) = explode(';', $this->scheduleStatus); - - return $scheduleStatus; } + list($scheduleStatus) = explode(';', $this->scheduleStatus); + + return $scheduleStatus; } } diff --git a/lib/Parameter.php b/lib/Parameter.php index 1900cb634..9952d3d2b 100644 --- a/lib/Parameter.php +++ b/lib/Parameter.php @@ -177,9 +177,9 @@ public function getValue(): ?string { if (is_array($this->value)) { return implode(',', $this->value); - } else { - return $this->value; } + + return $this->value; } /** @@ -201,9 +201,9 @@ public function getParts(): array return $this->value; } elseif (is_null($this->value)) { return []; - } else { - return [$this->value]; } + + return [$this->value]; } /** @@ -278,20 +278,19 @@ function ($out, $item) { // 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; } ); } diff --git a/lib/Parser/MimeDir.php b/lib/Parser/MimeDir.php index 97ff57fbe..7718dba04 100644 --- a/lib/Parser/MimeDir.php +++ b/lib/Parser/MimeDir.php @@ -235,16 +235,15 @@ protected function parseLine(string $line) } 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; } /** diff --git a/lib/Property.php b/lib/Property.php index f4d0bdc0b..56b571e51 100644 --- a/lib/Property.php +++ b/lib/Property.php @@ -155,9 +155,9 @@ public function getParts(): array return []; } elseif (is_array($this->value)) { return $this->value; - } else { - return [$this->value]; } + + return [$this->value]; } /** diff --git a/lib/Property/ICalendar/DateTime.php b/lib/Property/ICalendar/DateTime.php index 631a73b08..4f2ff4261 100644 --- a/lib/Property/ICalendar/DateTime.php +++ b/lib/Property/ICalendar/DateTime.php @@ -263,9 +263,9 @@ public function getJsonValue(): array 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 ); diff --git a/lib/Property/Text.php b/lib/Property/Text.php index c59029059..363c3fa4f 100644 --- a/lib/Property/Text.php +++ b/lib/Property/Text.php @@ -241,22 +241,21 @@ public function serialize(): string } 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($str, 0, -1); } /** diff --git a/lib/Recur/EventIterator.php b/lib/Recur/EventIterator.php index 79a070ee4..d0317e14b 100644 --- a/lib/Recur/EventIterator.php +++ b/lib/Recur/EventIterator.php @@ -232,11 +232,10 @@ public function getDtEnd(): ?\DateTimeImmutable } 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'); } /** diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index ff276a697..87e185b1c 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -717,25 +717,22 @@ protected function nextYearly(): void (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 - )->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($currentMonth, $this->byMonth)); + $this->currentDate = $this->currentDate->setDate( + (int) $currentYear, + (int) $currentMonth, + (int) $currentDayOfMonth + )->modify($this->startTime()); } /* }}} */ diff --git a/lib/Splitter/ICalendar.php b/lib/Splitter/ICalendar.php index 79f0bd443..edbba68cc 100644 --- a/lib/Splitter/ICalendar.php +++ b/lib/Splitter/ICalendar.php @@ -96,8 +96,8 @@ public function getNext(): ?Component } return $object; - } else { - return null; } + + return null; } } From ebc4c1a49a19250ab57b048c87ab3cb96778a147 Mon Sep 17 00:00:00 2001 From: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:25:05 +0100 Subject: [PATCH 81/85] fix(itip): handle null old calendar/event Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com> --- lib/ITip/Broker.php | 2 +- .../VObject/ITip/BrokerAttendeeReplyTest.php | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php index 3559f7e37..9bcdcc2a2 100644 --- a/lib/ITip/Broker.php +++ b/lib/ITip/Broker.php @@ -679,7 +679,7 @@ protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, // 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'] ?? [])) { if (isset($instances[$exDate])) { $instances[$exDate]['newstatus'] = 'DECLINED'; } else { diff --git a/tests/VObject/ITip/BrokerAttendeeReplyTest.php b/tests/VObject/ITip/BrokerAttendeeReplyTest.php index f766d4c57..c33a8007d 100644 --- a/tests/VObject/ITip/BrokerAttendeeReplyTest.php +++ b/tests/VObject/ITip/BrokerAttendeeReplyTest.php @@ -1256,4 +1256,73 @@ public function testNewEventWithReply(): void $this->parse($oldMessage, $newMessage, $expected); } + + /** + * Test that adding EXDATE entries when oldEventInfo doesn't have exdate + * (e.g., when updating a newly created event) doesn't cause a TypeError. + * + * This tests the scenario where an attendee receives a new recurring event + * invitation with EXDATE entries already present. Since there's no old calendar, + * oldEventInfo is initialized with only minimal keys and lacks 'exdate'. + */ + public function testAddExdateWithoutPreviousExdate(): void + { + // No old message - this is a new event invitation + $oldMessage = null; + + // New message: recurring event with EXDATE already present + $newMessage = << 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => null, + 'recipient' => 'mailto:organizer@example.org', + 'recipientName' => null, + 'message' => <<parse($oldMessage, $newMessage, $expected); + } } From fdf34ea83adbe2806d1ecd5748e4bc8e1a09d9f9 Mon Sep 17 00:00:00 2001 From: Albert Nimtz Date: Tue, 24 Jun 2025 16:41:39 +0200 Subject: [PATCH 82/85] feat(VCard): add getByTypes method (#717) --- lib/Component/VCard.php | 28 +++++++++++++++++++++++++++ tests/VObject/Component/VCardTest.php | 21 ++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/lib/Component/VCard.php b/lib/Component/VCard.php index 6bbec5717..0dafb674b 100644 --- a/lib/Component/VCard.php +++ b/lib/Component/VCard.php @@ -409,6 +409,34 @@ public function getByType(string $propertyName, string $type) 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. */ diff --git a/tests/VObject/Component/VCardTest.php b/tests/VObject/Component/VCardTest.php index 3851d97d9..ff4979ed4 100644 --- a/tests/VObject/Component/VCardTest.php +++ b/tests/VObject/Component/VCardTest.php @@ -147,6 +147,27 @@ public function testGetByType(): void self::assertNull($vcard->getByType('ADR', 'non-existent')); } + public function testGetByTypes(): void + { + $vcard = <<getByTypes('TEL', ['home', 'cell'])->getValue()); + self::assertEquals('665544332211', $vcard->getByTypes('TEL', ['work', 'cell'])->getValue()); + self::assertEquals('7778889994455', $vcard->getByTypes('TEL', ['work'])->getValue()); + self::assertEquals('555555555', $vcard->getByTypes('TEL', ['external'])->getValue()); + self::assertNull($vcard->getByTypes('TEL', ['non-existent'])); + self::assertNull($vcard->getByTypes('EMAIL', ['non-existent'])); + } + public function testPreferredNoPref(): void { $vcard = << Date: Mon, 13 Apr 2026 13:13:44 +0200 Subject: [PATCH 83/85] fix(timezonedata): Replace deprecated IANA timezone names Systems without the tzdata-legacy package, i.e. (Ubuntu 24.04+) reject deprecated names. Examples: Europe/Kiev, Asia/Calcutta, America/Godthab This causes FindFromTimezoneMap::find() to throw. Update all 13 occurrences across windowszones, lotuszones and exchangezones maps to their canonical replacements and wrap DateTimeZone instantiation in try/catch for resilience against future renames. Comes with unit test --- lib/TimezoneGuesser/FindFromTimezoneMap.php | 12 +++- lib/timezonedata/exchangezones.php | 6 +- lib/timezonedata/lotuszones.php | 6 +- lib/timezonedata/windowszones.php | 14 ++-- .../FindFromTimezoneMapTest.php | 65 +++++++++++++++++++ 5 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 tests/VObject/TimezoneGuesser/FindFromTimezoneMapTest.php diff --git a/lib/TimezoneGuesser/FindFromTimezoneMap.php b/lib/TimezoneGuesser/FindFromTimezoneMap.php index 83466b28e..3b691b3f1 100644 --- a/lib/TimezoneGuesser/FindFromTimezoneMap.php +++ b/lib/TimezoneGuesser/FindFromTimezoneMap.php @@ -22,7 +22,11 @@ public function find(string $tzid, ?bool $failIfUncertain = false): ?\DateTimeZo { // 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 $e) { + return null; + } } // Some Microsoft products prefix the offset first, so let's strip that off @@ -34,7 +38,11 @@ public function find(string $tzid, ?bool $failIfUncertain = false): ?\DateTimeZo } $tzidAlternate = $matches[3]; if ($this->hasTzInMap($tzidAlternate)) { - return new \DateTimeZone($this->getTzFromMap($tzidAlternate)); + try { + return new \DateTimeZone($this->getTzFromMap($tzidAlternate)); + } catch (\Exception $e) { + return null; + } } } diff --git a/lib/timezonedata/exchangezones.php b/lib/timezonedata/exchangezones.php index 3e7eace13..f42944b3b 100644 --- a/lib/timezonedata/exchangezones.php +++ b/lib/timezonedata/exchangezones.php @@ -40,12 +40,12 @@ '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', @@ -72,7 +72,7 @@ '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', diff --git a/lib/timezonedata/lotuszones.php b/lib/timezonedata/lotuszones.php index 9115ac743..45e76f7d6 100644 --- a/lib/timezonedata/lotuszones.php +++ b/lib/timezonedata/lotuszones.php @@ -34,7 +34,7 @@ '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 @@ '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', diff --git a/lib/timezonedata/windowszones.php b/lib/timezonedata/windowszones.php index 335007983..2dd9120e3 100644 --- a/lib/timezonedata/windowszones.php +++ b/lib/timezonedata/windowszones.php @@ -20,7 +20,7 @@ '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 @@ '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 @@ '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 @@ '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', diff --git a/tests/VObject/TimezoneGuesser/FindFromTimezoneMapTest.php b/tests/VObject/TimezoneGuesser/FindFromTimezoneMapTest.php new file mode 100644 index 000000000..abda23823 --- /dev/null +++ b/tests/VObject/TimezoneGuesser/FindFromTimezoneMapTest.php @@ -0,0 +1,65 @@ +find($mapKey); + + self::assertNotNull($tz, "Expected '$mapKey' to resolve to '$expectedOlson'"); + self::assertSame($expectedOlson, $tz->getName()); + } + + public function updatedTimezoneProvider(): array + { + return [ + // windowszones.php + ['FLE Standard Time', 'Europe/Kyiv'], + ['India Standard Time', 'Asia/Kolkata'], + ['Nepal Standard Time', 'Asia/Kathmandu'], + ['Myanmar Standard Time', 'Asia/Yangon'], + ['Greenland Standard Time', 'America/Nuuk'], + ['Argentina Standard Time', 'America/Argentina/Buenos_Aires'], + ['US Eastern Standard Time', 'America/Indiana/Indianapolis'], + // lotuszones.php + ['India', 'Asia/Kolkata'], + ['Myanmar', 'Asia/Yangon'], + // exchangezones.php + ['Kolkata, Chennai, Mumbai, New Delhi, India Standard Time', 'Asia/Kolkata'], + ['Rangoon', 'Asia/Yangon'], + ]; + } + + /** + * Verify that the Microsoft-offset-prefix stripping path still works + * with updated timezone values. + */ + public function testMicrosoftOffsetPrefixStripping(): void + { + $finder = new FindFromTimezoneMap(); + $tz = $finder->find('(UTC+02:00) FLE Standard Time'); + + self::assertNotNull($tz); + self::assertSame('Europe/Kyiv', $tz->getName()); + } + + public function testUnknownTimezoneReturnsNull(): void + { + $finder = new FindFromTimezoneMap(); + + self::assertNull($finder->find('This/Does_Not_Exist')); + } +} From 091d6d83cadc7ee8ccfa94057dff6189c921cebc Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Sun, 31 May 2026 21:31:28 +0930 Subject: [PATCH 84/85] fix: document that FindFromTimezoneMap:find does not throw --- lib/TimezoneGuesser/FindFromTimezoneMap.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/TimezoneGuesser/FindFromTimezoneMap.php b/lib/TimezoneGuesser/FindFromTimezoneMap.php index 3b691b3f1..f203937bc 100644 --- a/lib/TimezoneGuesser/FindFromTimezoneMap.php +++ b/lib/TimezoneGuesser/FindFromTimezoneMap.php @@ -18,6 +18,9 @@ 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. From 85d5d6ff9a638b4244fff876de3341ea5c413b8f Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Sun, 31 May 2026 22:58:37 +0930 Subject: [PATCH 85/85] chore: changelogs for 4.5.9 and 4.6.0 --- CHANGELOG.md | 9 +++++++++ lib/Version.php | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 819cf2b93..f69e8e57a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ ChangeLog ========= +4.6.0 (2026-05-31) +------------------ +* #718 feat(VCard): add getByTypes method (@JimKnoxx) +* #756 fix: Update deprecated timezone names (@ralflang) + +4.5.9 (2026-05-31) +------------------ +* #759 fix(itip): handle null old calendar/event (@ChristophWurst) + 4.5.8 (2026-01-12) ------------------ diff --git a/lib/Version.php b/lib/Version.php index decd42475..72391f59a 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -14,5 +14,5 @@ class Version /** * Full version number. */ - public const VERSION = '4.5.8'; + public const VERSION = '4.6.0'; }