From f5d95d5a92fe1c918b3e5f6e9bb6c908aadeea14 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 29 Nov 2022 15:54:43 +0100 Subject: [PATCH] Use Sabre/VObject v4, Partial PHP8 support --- plugins/calendar/calendar.php | 2 +- plugins/calendar/composer.json | 2 +- .../drivers/caldav/caldav_calendar.php | 6 +- .../calendar/drivers/caldav/caldav_driver.php | 1 + plugins/calendar/drivers/calendar_driver.php | 4 +- .../drivers/database/database_driver.php | 5 +- .../calendar/drivers/kolab/kolab_driver.php | 6 +- plugins/libcalendaring/README | 2 +- plugins/libcalendaring/composer.json | 4 +- .../lib/libcalendaring_datetime.php | 29 +++ .../lib/libcalendaring_itip.php | 4 +- .../lib/libcalendaring_recurrence.php | 16 +- plugins/libcalendaring/libcalendaring.php | 12 +- plugins/libcalendaring/libvcalendar.php | 46 +++-- ...calendaring.php => LibcalendaringTest.php} | 12 +- ...{libvcalendar.php => LibvcalendarTest.php} | 176 +++++++++--------- plugins/libkolab/composer.json | 3 +- .../libkolab/lib/kolab_date_recurrence.php | 11 +- plugins/libkolab/lib/kolab_format.php | 40 ++-- plugins/libkolab/lib/kolab_format_event.php | 6 +- plugins/libkolab/lib/kolab_format_task.php | 2 +- plugins/libkolab/lib/kolab_format_xcal.php | 17 +- plugins/libkolab/lib/kolab_storage_cache.php | 2 +- .../libkolab/lib/kolab_storage_dataset.php | 19 +- .../libkolab/lib/kolab_storage_dav_cache.php | 22 +-- .../libkolab/lib/kolab_storage_dav_folder.php | 6 +- plugins/libkolab/libkolab.php | 2 +- ...rrence.php => KolabDateRecurrenceTest.php} | 12 +- ..._config.php => KolabStorageConfigTest.php} | 9 +- ..._folder.php => KolabStorageFolderTest.php} | 9 +- plugins/libkolab/tests/README.md | 2 +- 31 files changed, 274 insertions(+), 215 deletions(-) create mode 100644 plugins/libcalendaring/lib/libcalendaring_datetime.php rename plugins/libcalendaring/tests/{libcalendaring.php => LibcalendaringTest.php} (91%) rename plugins/libcalendaring/tests/{libvcalendar.php => LibvcalendarTest.php} (71%) rename plugins/libkolab/tests/{kolab_date_recurrence.php => KolabDateRecurrenceTest.php} (97%) rename plugins/libkolab/tests/{kolab_storage_config.php => KolabStorageConfigTest.php} (96%) rename plugins/libkolab/tests/{kolab_storage_folder.php => KolabStorageFolderTest.php} (96%) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 83926a18..5d2965ca 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -1312,7 +1312,7 @@ $("#rcmfd_new_category").keypress(function(event) { if (!empty($change['date'])) { $dt = $lib->adjust_timezone($change['date']); - if ($dt instanceof DateTime) { + if ($dt instanceof DateTimeInterface) { $change['date'] = $this->rc->format_date($dt, $dtformat, false); } } diff --git a/plugins/calendar/composer.json b/plugins/calendar/composer.json index 0f7028a2..c1bfe40e 100644 --- a/plugins/calendar/composer.json +++ b/plugins/calendar/composer.json @@ -24,7 +24,7 @@ } ], "require": { - "php": ">=5.4.0", + "php": ">=7.4.0", "roundcube/plugin-installer": ">=0.1.3", "kolab/libcalendaring": ">=3.4.0", "kolab/libkolab": ">=3.4.0" diff --git a/plugins/calendar/drivers/caldav/caldav_calendar.php b/plugins/calendar/drivers/caldav/caldav_calendar.php index ac0d8b41..40648922 100644 --- a/plugins/calendar/drivers/caldav/caldav_calendar.php +++ b/plugins/calendar/drivers/caldav/caldav_calendar.php @@ -745,12 +745,14 @@ class caldav_calendar extends kolab_storage_dav_folder // Modify invitation status class name, when invitation calendars are disabled // we'll use opacity only for declined/needs-action events - $record['className'] = str_replace('-invitation', '', $record['className']); + $record['className'] = !empty($record['className']) ? str_replace('-invitation', '', $record['className']) : ''; } // add instance identifier to first occurrence (master event) $recurrence_id_format = libcalendaring::recurrence_id_format($master_event ? $master_event : $record); - if (!$noinst && !empty($record['recurrence']) && empty($record['recurrence_id']) && empty($record['_instance'])) { + if (!$noinst && !empty($record['recurrence']) && !empty($record['start']) + && empty($record['recurrence_id']) && empty($record['_instance']) + ) { $record['_instance'] = $record['start']->format($recurrence_id_format); } else if (isset($record['recurrence_date']) && is_a($record['recurrence_date'], 'DateTime')) { diff --git a/plugins/calendar/drivers/caldav/caldav_driver.php b/plugins/calendar/drivers/caldav/caldav_driver.php index b26c2f23..1f6c0bdd 100644 --- a/plugins/calendar/drivers/caldav/caldav_driver.php +++ b/plugins/calendar/drivers/caldav/caldav_driver.php @@ -136,6 +136,7 @@ class caldav_driver extends kolab_driver $parents = array_keys($this->calendars); foreach ($folders as $id => $cal) { + $parent_id = null; /* $path = explode('/', $cal->name); diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php index 2c0fc6c8..c81421e7 100644 --- a/plugins/calendar/drivers/calendar_driver.php +++ b/plugins/calendar/drivers/calendar_driver.php @@ -790,8 +790,8 @@ abstract class calendar_driver try { $bday = $contact['birthday']; - if (!$bday instanceof DateTime) { - $bday = new DateTime($bday, new DateTimezone('UTC')); + if (!$bday instanceof DateTimeInterface) { + $bday = new DateTime($bday, new DateTimeZone('UTC')); } $bday->_dateonly = true; } diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index caa53316..e1ee1490 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -587,7 +587,8 @@ class database_driver extends calendar_driver $b = isset($event[$prop]) ? $event[$prop] : null; if (!empty($event['allday']) && ($prop == 'start' || $prop == 'end') - && $a instanceof DateTime && $b instanceof DateTime + && $a instanceof DateTimeInterface + && $b instanceof DateTimeInterface ) { $a = $a->format('Y-m-d'); $b = $b->format('Y-m-d'); @@ -1464,7 +1465,7 @@ class database_driver extends calendar_driver private function serialize_alarms($valarms) { foreach ((array)$valarms as $i => $alarm) { - if ($alarm['trigger'] instanceof DateTime) { + if ($alarm['trigger'] instanceof DateTimeInterface) { $valarms[$i]['trigger'] = '@' . $alarm['trigger']->format('c'); } } diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index 42ef7cf6..b4cee8cc 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -1339,7 +1339,7 @@ class kolab_driver extends calendar_driver $recurrence_id = rcube_utils::anytodatetime($exception['_instance'], $old['start']->getTimezone()); } - if ($recurrence_id instanceof DateTime) { + if ($recurrence_id instanceof DateTimeInterface) { $recurrence_id->add($date_shift); $event['recurrence']['EXCEPTIONS'][$i]['recurrence_date'] = $recurrence_id; $event['recurrence']['EXCEPTIONS'][$i]['_instance'] = $recurrence_id->format($recurrence_id_format); @@ -1604,13 +1604,13 @@ class kolab_driver extends calendar_driver public static function merge_exception_dates(&$event, $overlay) { // compute date offset from the exception - if ($overlay['start'] instanceof DateTime && $overlay['recurrence_date'] instanceof DateTime) { + if ($overlay['start'] instanceof DateTimeInterface && $overlay['recurrence_date'] instanceof DateTimeInterface) { $date_offset = $overlay['recurrence_date']->diff($overlay['start']); } foreach (['start', 'end'] as $prop) { $value = $overlay[$prop]; - if (isset($event[$prop]) && $event[$prop] instanceof DateTime) { + if (isset($event[$prop]) && $event[$prop] instanceof DateTimeInterface) { // set date value if overlay is an exception of the current instance if (substr($overlay['_instance'], 0, 8) == substr($event['_instance'], 0, 8)) { $event[$prop]->setDate(intval($value->format('Y')), intval($value->format('n')), intval($value->format('j'))); diff --git a/plugins/libcalendaring/README b/plugins/libcalendaring/README index 11c10f02..55d40c33 100644 --- a/plugins/libcalendaring/README +++ b/plugins/libcalendaring/README @@ -11,6 +11,6 @@ Provides utility functions for calendar-related modules such as iCal parsing and exporting is done with the help of the Sabre VObject library [1]. It needs to be insalled with Roundcube using composer: - $ composer require "sabre/vobject" "~3.3.3" + $ composer require "sabre/vobject" "~4.5.1" [1]: http://sabre.io/vobject/ diff --git a/plugins/libcalendaring/composer.json b/plugins/libcalendaring/composer.json index 498c7689..b70587af 100644 --- a/plugins/libcalendaring/composer.json +++ b/plugins/libcalendaring/composer.json @@ -24,8 +24,8 @@ } ], "require": { - "php": ">=5.4.0", + "php": ">=7.4.0", "roundcube/plugin-installer": ">=0.1.3", - "sabre/vobject": "~3.5.3" + "sabre/vobject": "~4.5.1" } } diff --git a/plugins/libcalendaring/lib/libcalendaring_datetime.php b/plugins/libcalendaring/lib/libcalendaring_datetime.php new file mode 100644 index 00000000..95bdb94e --- /dev/null +++ b/plugins/libcalendaring/lib/libcalendaring_datetime.php @@ -0,0 +1,29 @@ += 8.1 + * + * @author Aleksander Machniak + * + * Copyright (C) 2022, Apheleia IT AG + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class libcalendaring_datetime extends DateTime +{ + public $_dateonly = false; +} diff --git a/plugins/libcalendaring/lib/libcalendaring_itip.php b/plugins/libcalendaring/lib/libcalendaring_itip.php index c59f3740..27ac89d8 100644 --- a/plugins/libcalendaring/lib/libcalendaring_itip.php +++ b/plugins/libcalendaring/lib/libcalendaring_itip.php @@ -472,7 +472,7 @@ class libcalendaring_itip if ($key == 'allday') { $event[$key] = $event[$key] == 'true'; } - $value = $existing[$key] instanceof DateTime ? $existing[$key]->format('c') : $existing[$key]; + $value = $existing[$key] instanceof DateTimeInterface ? $existing[$key]->format('c') : $existing[$key]; $num++; $got += intval($value == $event[$key]); } @@ -671,7 +671,7 @@ class libcalendaring_itip // For replies we need more metadata foreach (array('start', 'end', 'due', 'allday', 'recurrence', 'location') as $key) { if (isset($event[$key])) { - $metadata[$key] = $event[$key] instanceof DateTime ? $event[$key]->format('c') : $event[$key]; + $metadata[$key] = $event[$key] instanceof DateTimeInterface ? $event[$key]->format('c') : $event[$key]; } } } diff --git a/plugins/libcalendaring/lib/libcalendaring_recurrence.php b/plugins/libcalendaring/lib/libcalendaring_recurrence.php index 4eebd68d..8a336112 100644 --- a/plugins/libcalendaring/lib/libcalendaring_recurrence.php +++ b/plugins/libcalendaring/lib/libcalendaring_recurrence.php @@ -35,7 +35,7 @@ class libcalendaring_recurrence /** * Default constructor * - * @param object calendar The calendar plugin instance + * @param calendar The calendar plugin instance */ function __construct($lib) { @@ -49,8 +49,8 @@ class libcalendaring_recurrence /** * Initialize recurrence engine * - * @param array The recurrence properties - * @param object DateTime The recurrence start date + * @param array The recurrence properties + * @param DateTime The recurrence start date */ public function init($recurrence, $start = null) { @@ -80,7 +80,7 @@ class libcalendaring_recurrence /** * Setter for (new) recurrence start date * - * @param object DateTime The recurrence start date + * @param DateTime The recurrence start date */ public function set_start($start) { @@ -94,7 +94,7 @@ class libcalendaring_recurrence /** * Get date/time of the next occurence of this event * - * @return mixed DateTime object or False if recurrence ended + * @return DateTime|int|false object or False if recurrence ended */ public function next() { @@ -127,15 +127,15 @@ class libcalendaring_recurrence public function end() { // recurrence end date is given - if ($this->recurrence['UNTIL'] instanceof DateTime) { + if ($this->recurrence['UNTIL'] instanceof DateTimeInterface) { return $this->recurrence['UNTIL']; } // take the last RDATE entry if set if (is_array($this->recurrence['RDATE']) && !empty($this->recurrence['RDATE'])) { $last = end($this->recurrence['RDATE']); - if ($last instanceof DateTime) { - return $last; + if ($last instanceof DateTimeInterface) { + return $last; } } diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php index 29f26171..6d7c2d6a 100644 --- a/plugins/libcalendaring/libcalendaring.php +++ b/plugins/libcalendaring/libcalendaring.php @@ -194,7 +194,7 @@ class libcalendaring extends rcube_plugin $dt = rcube_utils::anytodatetime($dt); } - if ($dt instanceof DateTime && empty($dt->_dateonly) && !$dateonly) { + if ($dt instanceof DateTimeInterface && empty($dt->_dateonly) && !$dateonly) { $dt->setTimezone($this->timezone); } @@ -517,8 +517,8 @@ class libcalendaring extends rcube_plugin */ public static function to_client_alarms($valarms) { - return array_map(function($alarm){ - if ($alarm['trigger'] instanceof DateTime) { + return array_map(function($alarm) { + if ($alarm['trigger'] instanceof DateTimeInterface) { $alarm['trigger'] = '@' . $alarm['trigger']->format('U'); } else if ($trigger = libcalendaring::parse_alarm_value($alarm['trigger'])) { @@ -601,7 +601,7 @@ class libcalendaring extends rcube_plugin break; } - if ($trigger instanceof DateTime) { + if ($trigger instanceof DateTimeInterface) { $text .= ' ' . $rcube->gettext(array( 'name' => 'libcalendaring.alarmat', 'vars' => array('datetime' => $rcube->format_date($trigger)) @@ -681,7 +681,7 @@ class libcalendaring extends rcube_plugin foreach ($rec['valarms'] as $alarm) { $notify_time = null; - if ($alarm['trigger'] instanceof DateTime) { + if ($alarm['trigger'] instanceof DateTimeInterface) { $notify_time = $alarm['trigger']; } else if (is_string($alarm['trigger'])) { @@ -1335,7 +1335,7 @@ class libcalendaring extends rcube_plugin { $instance_date = !empty($event['recurrence_date']) ? $event['recurrence_date'] : $event['start']; - if ($instance_date instanceof DateTime) { + if ($instance_date instanceof DateTimeInterface) { // According to RFC5545 (3.8.4.4) RECURRENCE-ID format should // be date/date-time depending on the main event type, not the exception if ($allday === null) { diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php index c8f3cb1e..17629e04 100644 --- a/plugins/libcalendaring/libvcalendar.php +++ b/plugins/libcalendaring/libvcalendar.php @@ -24,6 +24,8 @@ use \Sabre\VObject; use \Sabre\VObject\DateTimeParser; +require_once __DIR__ . '/lib/libcalendaring_datetime.php'; + /** * Class to parse and build vCalendar (iCalendar) files * @@ -421,7 +423,7 @@ class libvcalendar implements Iterator } // map other attributes to internal fields - foreach ($ve->children as $prop) { + foreach ($ve->children() as $prop) { if (!($prop instanceof VObject\Property)) continue; @@ -640,7 +642,7 @@ class libvcalendar implements Iterator $trigger = null; $alarm = array(); - foreach ($valarm->children as $prop) { + foreach ($valarm->children() as $prop) { $value = strval($prop); switch ($prop->name) { @@ -708,14 +710,14 @@ class libvcalendar implements Iterator } // assign current timezone to event start/end - if (!empty($event['start']) && $event['start'] instanceof DateTime) { + if (!empty($event['start']) && $event['start'] instanceof DateTimeInterface) { $this->_apply_timezone($event['start']); } else { unset($event['start']); } - if (!empty($event['end']) && $event['end'] instanceof DateTime) { + if (!empty($event['end']) && $event['end'] instanceof DateTimeInterface) { $this->_apply_timezone($event['end']); } else { @@ -752,7 +754,7 @@ class libvcalendar implements Iterator // For date-only we'll keep the date and time intact if (!empty($date->_dateonly)) { - $dt = new DateTime(null, $this->timezone); + $dt = new libcalendaring_datetime(null, $this->timezone); $dt->setDate($date->format('Y'), $date->format('n'), $date->format('j')); $dt->setTime($date->format('G'), $date->format('i'), 0); $date = $dt; @@ -770,7 +772,7 @@ class libvcalendar implements Iterator $this->freebusy = array('_type' => 'freebusy', 'periods' => array()); $seen = array(); - foreach ($ve->children as $prop) { + foreach ($ve->children() as $prop) { if (!($prop instanceof VObject\Property)) continue; @@ -857,26 +859,29 @@ class libvcalendar implements Iterator public static function convert_datetime($prop, $as_array = false) { if (empty($prop)) { - return $as_array ? array() : null; + return $as_array ? [] : null; } - else if ($prop instanceof VObject\Property\iCalendar\DateTime) { + + if ($prop instanceof VObject\Property\ICalendar\DateTime) { if (count($prop->getDateTimes()) > 1) { - $dt = array(); + $dt = []; $dateonly = !$prop->hasTime(); + foreach ($prop->getDateTimes() as $item) { + $item = libcalendaring_datetime::createFromImmutable($item); $item->_dateonly = $dateonly; $dt[] = $item; } } else { - $dt = $prop->getDateTime(); + $dt = libcalendaring_datetime::createFromImmutable($prop->getDateTime()); if (!$prop->hasTime()) { $dt->_dateonly = true; } } } - else if ($prop instanceof VObject\Property\iCalendar\Period) { - $dt = array(); + else if ($prop instanceof VObject\Property\ICalendar\Period) { + $dt = []; foreach ($prop->getParts() as $val) { try { list($start, $end) = explode('/', $val); @@ -891,20 +896,21 @@ class libvcalendar implements Iterator else { $end = DateTimeParser::parseDateTime($end); } - $dt[] = array($start, $end); + + $dt[] = [libcalendaring_datetime::createFromImmutable($start), libcalendaring_datetime::createFromImmutable($end)]; } catch (Exception $e) { // ignore single date parse errors } } } - else if ($prop instanceof \DateTime) { - $dt = $prop; + else if ($prop instanceof \DateTimeInterface) { + $dt = libcalendaring_datetime::createFromImmutable($prop); } // force return value to array if requested if ($as_array && !is_array($dt)) { - $dt = empty($dt) ? array() : array($dt); + $dt = empty($dt) ? [] : [$dt]; } return $dt; @@ -1082,7 +1088,7 @@ class libvcalendar implements Iterator } // we're exporting a recurrence instance only - if (!$recurrence_id && !empty($event['recurrence_date']) && $event['recurrence_date'] instanceof DateTime) { + if (!$recurrence_id && !empty($event['recurrence_date']) && $event['recurrence_date'] instanceof DateTimeInterface) { $recurrence_id = $this->datetime_prop($cal, 'RECURRENCE-ID', $event['recurrence_date'], false, !empty($event['allday'])); if (!empty($event['thisandfuture'])) { $recurrence_id->add('RANGE', 'THISANDFUTURE'); @@ -1124,7 +1130,7 @@ class libvcalendar implements Iterator // add EXDATEs each one per line (for Thunderbird Lightning) if (is_array($exdates)) { foreach ($exdates as $exdate) { - if ($exdate instanceof DateTime) { + if ($exdate instanceof DateTimeInterface) { $ve->add($this->datetime_prop($cal, 'EXDATE', $exdate)); } } @@ -1186,7 +1192,7 @@ class libvcalendar implements Iterator foreach ($event['valarms'] as $alarm) { $va = $cal->createComponent('VALARM'); $va->action = $alarm['action']; - if ($alarm['trigger'] instanceof DateTime) { + if ($alarm['trigger'] instanceof DateTimeInterface) { $va->add($this->datetime_prop($cal, 'TRIGGER', $alarm['trigger'], true, null, true)); } else { @@ -1228,7 +1234,7 @@ class libvcalendar implements Iterator if (!empty($val[3])) { $va->add('TRIGGER', $val[3]); } - else if ($val[0] instanceof DateTime) { + else if ($val[0] instanceof DateTimeInterface) { $va->add($this->datetime_prop($cal, 'TRIGGER', $val[0], true, null, true)); } $ve->add($va); diff --git a/plugins/libcalendaring/tests/libcalendaring.php b/plugins/libcalendaring/tests/LibcalendaringTest.php similarity index 91% rename from plugins/libcalendaring/tests/libcalendaring.php rename to plugins/libcalendaring/tests/LibcalendaringTest.php index e93e0b32..12595300 100644 --- a/plugins/libcalendaring/tests/libcalendaring.php +++ b/plugins/libcalendaring/tests/LibcalendaringTest.php @@ -21,9 +21,9 @@ * along with this program. If not, see . */ -class libcalendaring_test extends PHPUnit\Framework\TestCase +class LibcalendaringTest extends PHPUnit\Framework\TestCase { - function setUp() + function setUp(): void { require_once __DIR__ . '/../libcalendaring.php'; } @@ -176,9 +176,9 @@ class libcalendaring_test extends PHPUnit\Framework\TestCase $s = libcalendaring::to_rrule($rrule); - $this->assertRegExp('/FREQ='.$rrule['FREQ'].'/', $s, "Recurrence Frequence"); - $this->assertRegExp('/INTERVAL='.$rrule['INTERVAL'].'/', $s, "Recurrence Interval"); - $this->assertRegExp('/BYDAY='.$rrule['BYDAY'].'/', $s, "Recurrence BYDAY"); - $this->assertRegExp('/UNTIL=20250501T160000Z/', $s, "Recurrence End date (in UTC)"); + $this->assertMatchesRegularExpression('/FREQ='.$rrule['FREQ'].'/', $s, "Recurrence Frequence"); + $this->assertMatchesRegularExpression('/INTERVAL='.$rrule['INTERVAL'].'/', $s, "Recurrence Interval"); + $this->assertMatchesRegularExpression('/BYDAY='.$rrule['BYDAY'].'/', $s, "Recurrence BYDAY"); + $this->assertMatchesRegularExpression('/UNTIL=20250501T160000Z/', $s, "Recurrence End date (in UTC)"); } } diff --git a/plugins/libcalendaring/tests/libvcalendar.php b/plugins/libcalendaring/tests/LibvcalendarTest.php similarity index 71% rename from plugins/libcalendaring/tests/libvcalendar.php rename to plugins/libcalendaring/tests/LibvcalendarTest.php index 8872fea7..9c055836 100644 --- a/plugins/libcalendaring/tests/libvcalendar.php +++ b/plugins/libcalendaring/tests/LibvcalendarTest.php @@ -21,9 +21,11 @@ * along with this program. If not, see . */ -class libvcalendar_test extends PHPUnit\Framework\TestCase +class LibvcalendarTest extends PHPUnit\Framework\TestCase { - function setUp() + private $attachment_data; + + function setUp(): void { require_once __DIR__ . '/../libvcalendar.php'; require_once __DIR__ . '/../libcalendaring.php'; @@ -41,12 +43,12 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $this->assertEquals(1, count($events)); $event = $events[0]; - $this->assertInstanceOf('DateTime', $event['created'], "'created' property is DateTime object"); - $this->assertInstanceOf('DateTime', $event['changed'], "'changed' property is DateTime object"); + $this->assertInstanceOf('DateTimeInterface', $event['created'], "'created' property is DateTime object"); + $this->assertInstanceOf('DateTimeInterface', $event['changed'], "'changed' property is DateTime object"); $this->assertEquals('UTC', $event['created']->getTimezone()->getName(), "'created' date is in UTC"); - $this->assertInstanceOf('DateTime', $event['start'], "'start' property is DateTime object"); - $this->assertInstanceOf('DateTime', $event['end'], "'end' property is DateTime object"); + $this->assertInstanceOf('DateTimeInterface', $event['start'], "'start' property is DateTime object"); + $this->assertInstanceOf('DateTimeInterface', $event['end'], "'end' property is DateTime object"); $this->assertEquals('08-01', $event['start']->format('m-d'), "Start date is August 1st"); $this->assertTrue($event['allday'], "All-day event flag"); @@ -81,7 +83,7 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase { $ical = new libvcalendar(); $ical->fopen(__DIR__ . '/resources/multiple-rdate.ics', 'UTF-8'); - $events = array(); + $events = []; foreach ($ical as $event) { $events[] = $event; } @@ -98,7 +100,7 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $event = $events[0]; $this->assertEquals(1, count($events), "Import event data"); - $this->assertInstanceOf('DateTime', $event['created'], "Created date field"); + $this->assertInstanceOf('DateTimeInterface', $event['created'], "Created date field"); $this->assertFalse(array_key_exists('changed', $event), "No changed date field"); } @@ -139,7 +141,7 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $attachment = $event['attachments'][0]; $this->assertEquals('text/html', $attachment['mimetype'], "Attachment mimetype attribute"); $this->assertEquals('calendar.html', $attachment['name'], "Attachment filename (X-LABEL) attribute"); - $this->assertContains('Kalender', $attachment['data'], "Attachment content (decoded)"); + $this->assertStringContainsString('Kalender', $attachment['data'], "Attachment content (decoded)"); // recurrence rules $events = $ical->import_from_file(__DIR__ . '/resources/recurring.ics', 'UTF-8'); @@ -150,10 +152,10 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $this->assertEquals('MONTHLY', $rrule['FREQ'], "Recurrence frequency"); $this->assertEquals('1', $rrule['INTERVAL'], "Recurrence interval"); $this->assertEquals('3WE', $rrule['BYDAY'], "Recurrence frequency"); - $this->assertInstanceOf('DateTime', $rrule['UNTIL'], "Recurrence end date"); + $this->assertInstanceOf('DateTimeInterface', $rrule['UNTIL'], "Recurrence end date"); $this->assertEquals(2, count($rrule['EXDATE']), "Recurrence EXDATEs"); - $this->assertInstanceOf('DateTime', $rrule['EXDATE'][0], "Recurrence EXDATE as DateTime"); + $this->assertInstanceOf('DateTimeInterface', $rrule['EXDATE'][0], "Recurrence EXDATE as DateTime"); $this->assertTrue(is_array($rrule['EXCEPTIONS'])); $this->assertEquals(1, count($rrule['EXCEPTIONS']), "Recurrence Exceptions"); @@ -161,7 +163,7 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $exception = $rrule['EXCEPTIONS'][0]; $this->assertEquals($event['uid'], $event['uid'], "Exception UID"); $this->assertEquals('Recurring Test (Exception)', $exception['title'], "Exception title"); - $this->assertInstanceOf('DateTime', $exception['start'], "Exception start"); + $this->assertInstanceOf('DateTimeInterface', $exception['start'], "Exception start"); // categories, class $this->assertEquals('libcalendaring tests', join(',', (array)$event['categories']), "Event categories"); @@ -169,7 +171,7 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase // parse a recurrence chain instance $events = $ical->import_from_file(__DIR__ . '/resources/recurrence-id.ics', 'UTF-8'); $this->assertEquals(1, count($events), "Fall back to Component::getComponents() when getBaseComponents() is empty"); - $this->assertInstanceOf('DateTime', $events[0]['recurrence_date'], "Recurrence-ID as date"); + $this->assertInstanceOf('DateTimeInterface', $events[0]['recurrence_date'], "Recurrence-ID as date"); $this->assertTrue($events[0]['thisandfuture'], "Range=THISANDFUTURE"); $this->assertEquals(count($events[0]['exceptions']), 1, "Second VEVENT as exception"); @@ -218,16 +220,16 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $this->assertEquals('EMAIL', $valarm['action'], "Second alarm item (action)"); $this->assertEquals('-P1D', $valarm['trigger'], "Second alarm item (trigger)"); $this->assertEquals('This is the reminder message', $valarm['summary'], "Email alarm text"); - $this->assertInstanceOf('DateTime', $event['valarms'][2]['trigger'], "Absolute trigger date/time"); + $this->assertInstanceOf('DateTimeInterface', $event['valarms'][2]['trigger'], "Absolute trigger date/time"); // test alarms export - $ics = $ical->export(array($event)); - $this->assertContains('ACTION:DISPLAY', $ics, "Display alarm block"); - $this->assertContains('ACTION:EMAIL', $ics, "Email alarm block"); - $this->assertContains('DESCRIPTION:This is the first event reminder', $ics, "Alarm description"); - $this->assertContains('SUMMARY:This is the reminder message', $ics, "Email alarm summary"); - $this->assertContains('ATTENDEE:mailto:reminder-recipient@example.org', $ics, "Email alarm recipient"); - $this->assertContains('TRIGGER;VALUE=DATE-TIME:20130812', $ics, "Date-Time trigger"); + $ics = $ical->export([$event]); + $this->assertStringContainsString('ACTION:DISPLAY', $ics, "Display alarm block"); + $this->assertStringContainsString('ACTION:EMAIL', $ics, "Email alarm block"); + $this->assertStringContainsString('DESCRIPTION:This is the first event reminder', $ics, "Alarm description"); + $this->assertStringContainsString('SUMMARY:This is the reminder message', $ics, "Email alarm summary"); + $this->assertStringContainsString('ATTENDEE:mailto:reminder-recipient@example.org', $ics, "Email alarm recipient"); + $this->assertStringContainsString('TRIGGER;VALUE=DATE-TIME:20130812', $ics, "Date-Time trigger"); } /** @@ -281,7 +283,7 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $this->assertEquals("Kolab, Thomas", $event['attendees'][3]['name'], "Unescaped"); $ics = $ical->export($events); - $this->assertContains('ATTENDEE;CN="Kolab, Thomas";PARTSTAT=', $ics, "Quoted attendee parameters"); + $this->assertStringContainsString('ATTENDEE;CN="Kolab, Thomas";PARTSTAT=', $ics, "Quoted attendee parameters"); } /** @@ -294,8 +296,8 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $event = $events[0]; $this->assertEquals(9, count($event['recurrence']['RDATE'])); - $this->assertInstanceOf('DateTime', $event['recurrence']['RDATE'][0]); - $this->assertInstanceOf('DateTime', $event['recurrence']['RDATE'][1]); + $this->assertInstanceOf('DateTimeInterface', $event['recurrence']['RDATE'][0]); + $this->assertInstanceOf('DateTimeInterface', $event['recurrence']['RDATE'][1]); } /** @@ -307,8 +309,8 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $ical->import_from_file(__DIR__ . '/resources/freebusy.ifb', 'UTF-8'); $freebusy = $ical->freebusy; - $this->assertInstanceOf('DateTime', $freebusy['start'], "'start' property is DateTime object"); - $this->assertInstanceOf('DateTime', $freebusy['end'], "'end' property is DateTime object"); + $this->assertInstanceOf('DateTimeInterface', $freebusy['start'], "'start' property is DateTime object"); + $this->assertInstanceOf('DateTimeInterface', $freebusy['end'], "'end' property is DateTime object"); $this->assertEquals(11, count($freebusy['periods']), "Number of freebusy periods defined"); $periods = $ical->get_busy_periods(); $this->assertEquals(9, count($periods), "Number of busy periods found"); @@ -325,7 +327,7 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $freebusy = $ical->freebusy; $this->assertEquals(0, count($freebusy['periods']), "Ignore 0-length freebudy periods"); - $this->assertContains('dummy', $freebusy['comment'], "Parse comment"); + $this->assertStringContainsString('dummy', $freebusy['comment'], "Parse comment"); } function test_vtodo() @@ -334,8 +336,8 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $tasks = $ical->import_from_file(__DIR__ . '/resources/vtodo.ics', 'UTF-8', true); $task = $tasks[0]; - $this->assertInstanceOf('DateTime', $task['start'], "'start' property is DateTime object"); - $this->assertInstanceOf('DateTime', $task['due'], "'due' property is DateTime object"); + $this->assertInstanceOf('DateTimeInterface', $task['start'], "'start' property is DateTime object"); + $this->assertInstanceOf('DateTimeInterface', $task['due'], "'due' property is DateTime object"); $this->assertEquals('-1D:DISPLAY', $task['alarms'], "Taks alarm value"); $this->assertEquals('IN-PROCESS', $task['status'], "Task status property"); $this->assertEquals(1, count($task['x-custom']), "Custom properties"); @@ -346,14 +348,12 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $this->assertEquals('COMPLETED', $completed['status'], "Task status=completed when COMPLETED property is present"); $this->assertEquals(100, $completed['complete'], "Task percent complete value"); - $ics = $ical->export(array($completed)); - $this->assertRegExp('/COMPLETED(;VALUE=DATE-TIME)?:[0-9TZ]+/', $ics, "Export COMPLETED property"); + $ics = $ical->export([$completed]); + $this->assertMatchesRegularExpression('/COMPLETED(;VALUE=DATE-TIME)?:[0-9TZ]+/', $ics, "Export COMPLETED property"); } /** * Test for iCal export from internal hash array representation - * - * */ function test_export() { @@ -372,46 +372,46 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $event['start']->setTimezone(new DateTimezone('America/Montreal')); $event['end']->setTimezone(new DateTimezone('Europe/Berlin')); - $ics = $ical->export(array($event), 'REQUEST', false, array($this, 'get_attachment_data'), true); + $ics = $ical->export([$event], 'REQUEST', false, [$this, 'get_attachment_data'], true); - $this->assertContains('BEGIN:VCALENDAR', $ics, "VCALENDAR encapsulation BEGIN"); + $this->assertStringContainsString('BEGIN:VCALENDAR', $ics, "VCALENDAR encapsulation BEGIN"); - $this->assertContains('BEGIN:VTIMEZONE', $ics, "VTIMEZONE encapsulation BEGIN"); - $this->assertContains('TZID:Europe/Berlin', $ics, "Timezone ID"); - $this->assertContains('TZOFFSETFROM:+0100', $ics, "Timzone transition FROM"); - $this->assertContains('TZOFFSETTO:+0200', $ics, "Timzone transition TO"); - $this->assertContains('TZOFFSETFROM:-0400', $ics, "TZOFFSETFROM with negative offset (Bug T428)"); - $this->assertContains('TZOFFSETTO:-0500', $ics, "TZOFFSETTO with negative offset (Bug T428)"); - $this->assertContains('END:VTIMEZONE', $ics, "VTIMEZONE encapsulation END"); + $this->assertStringContainsString('BEGIN:VTIMEZONE', $ics, "VTIMEZONE encapsulation BEGIN"); + $this->assertStringContainsString('TZID:Europe/Berlin', $ics, "Timezone ID"); + $this->assertStringContainsString('TZOFFSETFROM:+0100', $ics, "Timzone transition FROM"); + $this->assertStringContainsString('TZOFFSETTO:+0200', $ics, "Timzone transition TO"); + $this->assertStringContainsString('TZOFFSETFROM:-0400', $ics, "TZOFFSETFROM with negative offset (Bug T428)"); + $this->assertStringContainsString('TZOFFSETTO:-0500', $ics, "TZOFFSETTO with negative offset (Bug T428)"); + $this->assertStringContainsString('END:VTIMEZONE', $ics, "VTIMEZONE encapsulation END"); - $this->assertContains('BEGIN:VEVENT', $ics, "VEVENT encapsulation BEGIN"); + $this->assertStringContainsString('BEGIN:VEVENT', $ics, "VEVENT encapsulation BEGIN"); $this->assertSame(2, substr_count($ics, 'DTSTAMP'), "Duplicate DTSTAMP (T1148)"); - $this->assertContains('UID:ac6b0aee-2519-4e5c-9a25-48c57064c9f0', $ics, "Event UID"); - $this->assertContains('SEQUENCE:' . $event['sequence'], $ics, "Export Sequence number"); - $this->assertContains('DESCRIPTION:*Exported by', $ics, "Export Description"); - $this->assertContains('ORGANIZER;CN=Rolf Test:mailto:rolf@', $ics, "Export organizer"); - $this->assertRegExp('/ATTENDEE.*;ROLE=REQ-PARTICIPANT/', $ics, "Export Attendee ROLE"); - $this->assertRegExp('/ATTENDEE.*;PARTSTAT=NEEDS-ACTION/', $ics, "Export Attendee Status"); - $this->assertRegExp('/ATTENDEE.*;RSVP=TRUE/', $ics, "Export Attendee RSVP"); - $this->assertRegExp('/:mailto:rolf2@/', $ics, "Export Attendee mailto:"); + $this->assertStringContainsString('UID:ac6b0aee-2519-4e5c-9a25-48c57064c9f0', $ics, "Event UID"); + $this->assertStringContainsString('SEQUENCE:' . $event['sequence'], $ics, "Export Sequence number"); + $this->assertStringContainsString('DESCRIPTION:*Exported by', $ics, "Export Description"); + $this->assertStringContainsString('ORGANIZER;CN=Rolf Test:mailto:rolf@', $ics, "Export organizer"); + $this->assertMatchesRegularExpression('/ATTENDEE.*;ROLE=REQ-PARTICIPANT/', $ics, "Export Attendee ROLE"); + $this->assertMatchesRegularExpression('/ATTENDEE.*;PARTSTAT=NEEDS-ACTION/', $ics, "Export Attendee Status"); + $this->assertMatchesRegularExpression('/ATTENDEE.*;RSVP=TRUE/', $ics, "Export Attendee RSVP"); + $this->assertMatchesRegularExpression('/:mailto:rolf2@/', $ics, "Export Attendee mailto:"); $rrule = $event['recurrence']; - $this->assertRegExp('/RRULE:.*FREQ='.$rrule['FREQ'].'/', $ics, "Export Recurrence Frequence"); - $this->assertRegExp('/RRULE:.*INTERVAL='.$rrule['INTERVAL'].'/', $ics, "Export Recurrence Interval"); - $this->assertRegExp('/RRULE:.*UNTIL=20140718T215959Z/', $ics, "Export Recurrence End date"); - $this->assertRegExp('/RRULE:.*BYDAY='.$rrule['BYDAY'].'/', $ics, "Export Recurrence BYDAY"); - $this->assertRegExp('/EXDATE.*:20131218/', $ics, "Export Recurrence EXDATE"); + $this->assertMatchesRegularExpression('/RRULE:.*FREQ='.$rrule['FREQ'].'/', $ics, "Export Recurrence Frequence"); + $this->assertMatchesRegularExpression('/RRULE:.*INTERVAL='.$rrule['INTERVAL'].'/', $ics, "Export Recurrence Interval"); + $this->assertMatchesRegularExpression('/RRULE:.*UNTIL=20140718T215959Z/', $ics, "Export Recurrence End date"); + $this->assertMatchesRegularExpression('/RRULE:.*BYDAY='.$rrule['BYDAY'].'/', $ics, "Export Recurrence BYDAY"); + $this->assertMatchesRegularExpression('/EXDATE.*:20131218/', $ics, "Export Recurrence EXDATE"); - $this->assertContains('BEGIN:VALARM', $ics, "Export VALARM"); - $this->assertContains('TRIGGER;RELATED=END:-PT12H', $ics, "Export Alarm trigger"); + $this->assertStringContainsString('BEGIN:VALARM', $ics, "Export VALARM"); + $this->assertStringContainsString('TRIGGER;RELATED=END:-PT12H', $ics, "Export Alarm trigger"); - $this->assertRegExp('/ATTACH.*;VALUE=BINARY/', $ics, "Embed attachment"); - $this->assertRegExp('/ATTACH.*;ENCODING=BASE64/', $ics, "Attachment B64 encoding"); - $this->assertRegExp('!ATTACH.*;FMTTYPE=text/html!', $ics, "Attachment mimetype"); - $this->assertRegExp('!ATTACH.*;X-LABEL=calendar.html!', $ics, "Attachment filename with X-LABEL"); + $this->assertMatchesRegularExpression('/ATTACH.*;VALUE=BINARY/', $ics, "Embed attachment"); + $this->assertMatchesRegularExpression('/ATTACH.*;ENCODING=BASE64/', $ics, "Attachment B64 encoding"); + $this->assertMatchesRegularExpression('!ATTACH.*;FMTTYPE=text/html!', $ics, "Attachment mimetype"); + $this->assertMatchesRegularExpression('!ATTACH.*;X-LABEL=calendar.html!', $ics, "Attachment filename with X-LABEL"); - $this->assertContains('END:VEVENT', $ics, "VEVENT encapsulation END"); - $this->assertContains('END:VCALENDAR', $ics, "VCALENDAR encapsulation END"); + $this->assertStringContainsString('END:VEVENT', $ics, "VEVENT encapsulation END"); + $this->assertStringContainsString('END:VCALENDAR', $ics, "VCALENDAR encapsulation END"); } /** @@ -429,8 +429,8 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $num = count($events); $ics = $ical->export($events, null, false); - $this->assertContains('BEGIN:VCALENDAR', $ics, "VCALENDAR encapsulation BEGIN"); - $this->assertContains('END:VCALENDAR', $ics, "VCALENDAR encapsulation END"); + $this->assertStringContainsString('BEGIN:VCALENDAR', $ics, "VCALENDAR encapsulation BEGIN"); + $this->assertStringContainsString('END:VCALENDAR', $ics, "VCALENDAR encapsulation END"); $this->assertEquals($num, substr_count($ics, 'BEGIN:VEVENT'), "VEVENT encapsulation BEGIN"); $this->assertEquals($num, substr_count($ics, 'END:VEVENT'), "VEVENT encapsulation END"); } @@ -460,7 +460,7 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $exception2['end']->setDate(2013, 11, 13); $exception2['title'] = 'Recurring Exception'; - $events[0]['recurrence']['EXCEPTIONS'] = array($exception1, $exception2); + $events[0]['recurrence']['EXCEPTIONS'] = [$exception1, $exception2]; $ics = $ical->export($events, null, false); @@ -469,30 +469,30 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $this->assertEquals($num, substr_count($ics, 'UID:'.$event['uid']), "Recurrence Exceptions with same UID"); $this->assertEquals($num, substr_count($ics, 'END:VEVENT'), "VEVENT encapsulation END"); - $this->assertContains('RECURRENCE-ID;TZID=Europe/Zurich:20130814', $ics, "Recurrence-ID (1) being the exception date"); - $this->assertContains('RECURRENCE-ID;TZID=Europe/Zurich:20131113', $ics, "Recurrence-ID (2) being the exception date"); - $this->assertContains('SUMMARY:'.$exception2['title'], $ics, "Exception title"); + $this->assertStringContainsString('RECURRENCE-ID;TZID=Europe/Zurich:20130814', $ics, "Recurrence-ID (1) being the exception date"); + $this->assertStringContainsString('RECURRENCE-ID;TZID=Europe/Zurich:20131113', $ics, "Recurrence-ID (2) being the exception date"); + $this->assertStringContainsString('SUMMARY:'.$exception2['title'], $ics, "Exception title"); } function test_export_valid_rrules() { - $event = array( + $event = [ 'uid' => '1234567890', 'start' => new DateTime('now'), 'end' => new DateTime('now + 30min'), 'title' => 'test_export_valid_rrules', - 'recurrence' => array( + 'recurrence' => [ 'FREQ' => 'DAILY', 'COUNT' => 5, - 'EXDATE' => array(), - 'RDATE' => array(), - ), - ); + 'EXDATE' => [], + 'RDATE' => [], + ], + ]; $ical = new libvcalendar(); - $ics = $ical->export(array($event), null, false, null, false); + $ics = $ical->export([$event], null, false, null, false); - $this->assertNotContains('EXDATE=', $ics); - $this->assertNotContains('RDATE=', $ics); + $this->assertStringNotContainsString('EXDATE=', $ics); + $this->assertStringNotContainsString('RDATE=', $ics); } /** @@ -504,7 +504,7 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $events = $ical->import_from_file(__DIR__ . '/resources/multiple-rdate.ics', 'UTF-8'); $ics = $ical->export($events, null, false); - $this->assertContains('RDATE:20140520T020000Z', $ics, "VALUE=PERIOD is translated into single DATE-TIME values"); + $this->assertStringContainsString('RDATE:20140520T020000Z', $ics, "VALUE=PERIOD is translated into single DATE-TIME values"); } /** @@ -522,8 +522,8 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase ob_end_clean(); $this->assertTrue($return, "Return true on successful writing"); - $this->assertContains('BEGIN:VCALENDAR', $output, "VCALENDAR encapsulation BEGIN"); - $this->assertContains('END:VCALENDAR', $output, "VCALENDAR encapsulation END"); + $this->assertStringContainsString('BEGIN:VCALENDAR', $output, "VCALENDAR encapsulation BEGIN"); + $this->assertStringContainsString('END:VCALENDAR', $output, "VCALENDAR encapsulation END"); $this->assertEquals($num, substr_count($output, 'BEGIN:VEVENT'), "VEVENT encapsulation BEGIN"); $this->assertEquals($num, substr_count($output, 'END:VEVENT'), "VEVENT encapsulation END"); } @@ -537,10 +537,10 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase $utctime = $ical->datetime_prop($cal, 'DTSTART', new DateTime('2013-09-01 12:00:00', new DateTimeZone('UTC'))); $asutctime = $ical->datetime_prop($cal, 'DTSTART', new DateTime('2013-09-01 12:00:00', new DateTimeZone('Europe/Berlin')), true); - $this->assertContains('TZID=Europe/Berlin', $localtime->serialize()); - $this->assertContains('VALUE=DATE', $localdate->serialize()); - $this->assertContains('20130901T120000Z', $utctime->serialize()); - $this->assertContains('20130901T100000Z', $asutctime->serialize()); + $this->assertStringContainsString('TZID=Europe/Berlin', $localtime->serialize()); + $this->assertStringContainsString('VALUE=DATE', $localdate->serialize()); + $this->assertStringContainsString('20130901T120000Z', $utctime->serialize()); + $this->assertStringContainsString('20130901T100000Z', $asutctime->serialize()); } function test_get_vtimezone() @@ -578,8 +578,8 @@ class libvcalendar_test extends PHPUnit\Framework\TestCase // DateTimezone as input data $vtz = libvcalendar::get_vtimezone(new DateTimezone('Pacific/Chatham')); $this->assertInstanceOf('\Sabre\VObject\Component', $vtz); - $this->assertContains('TZOFFSETFROM:+1245', $vtz->serialize()); - $this->assertContains('TZOFFSETTO:+1345', $vtz->serialize()); + $this->assertStringContainsString('TZOFFSETFROM:+1245', $vtz->serialize()); + $this->assertStringContainsString('TZOFFSETTO:+1345', $vtz->serialize()); // Making sure VTIMEZOONE contains at least one STANDARD/DAYLIGHT component // when there's only one transition in specified time period (T5626) diff --git a/plugins/libkolab/composer.json b/plugins/libkolab/composer.json index a2cbe149..8a00d7a6 100644 --- a/plugins/libkolab/composer.json +++ b/plugins/libkolab/composer.json @@ -24,8 +24,9 @@ } ], "require": { - "php": ">=5.3.0", + "php": ">=7.4.0", "roundcube/plugin-installer": ">=0.1.3", + "kolab/libcalendaring": ">=3.4.0", "pear/http_request2": "~2.3.0", "caxy/php-htmldiff": "~0.1.7" } diff --git a/plugins/libkolab/lib/kolab_date_recurrence.php b/plugins/libkolab/lib/kolab_date_recurrence.php index 3197cde5..9b09074b 100644 --- a/plugins/libkolab/lib/kolab_date_recurrence.php +++ b/plugins/libkolab/lib/kolab_date_recurrence.php @@ -4,7 +4,7 @@ * Recurrence computation class for xcal-based Kolab format objects * * Utility class to compute instances of recurring events. - * It requires the libcalendaring PHP module to be installed and loaded. + * It requires the libcalendaring PHP extension to be installed and loaded. * * @version @package_version@ * @author Thomas Bruederli @@ -24,6 +24,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + class kolab_date_recurrence { private /* EventCal */ $engine; @@ -63,9 +64,9 @@ class kolab_date_recurrence /** * Get date/time of the next occurence of this event * - * @param boolean Return a Unix timestamp instead of a DateTime object + * @param bool Return a Unix timestamp instead of a DateTime object * - * @return mixed DateTime object/unix timestamp or False if recurrence ended + * @return DateTime|int|false Object/unix timestamp or False if recurrence ended */ public function next_start($timestamp = false) { @@ -127,7 +128,7 @@ class kolab_date_recurrence $event = $this->object->to_array(); // recurrence end date is given - if ($event['recurrence']['UNTIL'] instanceof DateTime) { + if ($event['recurrence']['UNTIL'] instanceof DateTimeInterface) { return $event['recurrence']['UNTIL']; } @@ -139,7 +140,7 @@ class kolab_date_recurrence } // determine a reasonable end date if none given - if (!$event['recurrence']['COUNT'] && $event['end'] instanceof DateTime) { + if (!$event['recurrence']['COUNT'] && $event['end'] instanceof DateTimeInterface) { $end_dt = clone $event['end']; $end_dt->add(new DateInterval('P100Y')); diff --git a/plugins/libkolab/lib/kolab_format.php b/plugins/libkolab/lib/kolab_format.php index cde67fad..cf24c293 100644 --- a/plugins/libkolab/lib/kolab_format.php +++ b/plugins/libkolab/lib/kolab_format.php @@ -25,6 +25,8 @@ * along with this program. If not, see . */ +require_once __DIR__ . '/../../libcalendaring/lib/libcalendaring_datetime.php'; + abstract class kolab_format { public static $timezone; @@ -211,7 +213,7 @@ abstract class kolab_format { // use timezone information from datetime or global setting if (!$tz && $tz !== false) { - if ($datetime instanceof DateTime) + if ($datetime instanceof DateTimeInterface) $tz = $datetime->getTimezone(); if (!$tz) $tz = self::$timezone; @@ -222,19 +224,19 @@ abstract class kolab_format try { // got a unix timestamp (in UTC) if (is_numeric($datetime)) { - $datetime = new DateTime('@'.$datetime, new DateTimeZone('UTC')); + $datetime = new libcalendaring_datetime('@'.$datetime, new DateTimeZone('UTC')); if ($tz) $datetime->setTimezone($tz); } else if (is_string($datetime) && strlen($datetime)) { - $datetime = $tz ? new DateTime($datetime, $tz) : new DateTime($datetime); + $datetime = $tz ? new libcalendaring_datetime($datetime, $tz) : new libcalendaring_datetime($datetime); } - else if ($datetime instanceof DateTime) { + else if ($datetime instanceof DateTimeInterface) { $datetime = clone $datetime; } } catch (Exception $e) {} - if ($datetime instanceof DateTime) { + if ($datetime instanceof DateTimeInterface) { if ($dest_tz instanceof DateTimeZone && $dest_tz !== $datetime->getTimezone()) { $datetime->setTimezone($dest_tz); $tz = $dest_tz; @@ -272,7 +274,7 @@ abstract class kolab_format * @param cDateTime The libkolabxml datetime object * @param DateTimeZone The timezone to convert the date to * - * @return DateTime PHP datetime instance + * @return libcalendaring_datetime PHP datetime instance */ public static function php_datetime($cdt, $dest_tz = null) { @@ -280,19 +282,23 @@ abstract class kolab_format return null; } - $d = new DateTime; - $d->setTimezone($dest_tz ?: self::$timezone); + $d = new libcalendaring_datetime(null, self::$timezone); - try { - if ($tzs = $cdt->timezone()) { - $tz = new DateTimeZone($tzs); - $d->setTimezone($tz); - } - else if ($cdt->isUTC()) { - $d->setTimezone(new DateTimeZone('UTC')); - } + if ($dest_tz) { + $d->setTimezone($dest_tz); + } + else { + try { + if ($tzs = $cdt->timezone()) { + $tz = new DateTimeZone($tzs); + $d->setTimezone($tz); + } + else if ($cdt->isUTC()) { + $d->setTimezone(new DateTimeZone('UTC')); + } + } + catch (Exception $e) { } } - catch (Exception $e) { } $d->setDate($cdt->year(), $cdt->month(), $cdt->day()); diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php index e8393a42..860adb65 100644 --- a/plugins/libkolab/lib/kolab_format_event.php +++ b/plugins/libkolab/lib/kolab_format_event.php @@ -112,7 +112,7 @@ class kolab_format_event extends kolab_format_xcal } } - if ($object['recurrence_date'] && $object['recurrence_date'] instanceof DateTime) { + if ($object['recurrence_date'] && $object['recurrence_date'] instanceof DateTimeInterface) { if ($object['recurrence']) { // unset recurrence_date for master events with rrule $object['recurrence_date'] = null; @@ -236,9 +236,9 @@ class kolab_format_event extends kolab_format_xcal $object = $this->to_array(); $recurrence_id_format = libkolab::recurrence_id_format($object); - $instance_id = $recurrence_id instanceof DateTime ? $recurrence_id->format($recurrence_id_format) : strval($recurrence_id); + $instance_id = $recurrence_id instanceof DateTimeInterface ? $recurrence_id->format($recurrence_id_format) : strval($recurrence_id); - if ($object['recurrence_date'] instanceof DateTime) { + if ($object['recurrence_date'] instanceof DateTimeInterface) { if ($object['recurrence_date']->format($recurrence_id_format) == $instance_id) { $result = $object; } diff --git a/plugins/libkolab/lib/kolab_format_task.php b/plugins/libkolab/lib/kolab_format_task.php index bc57e6b9..23ba2cc2 100644 --- a/plugins/libkolab/lib/kolab_format_task.php +++ b/plugins/libkolab/lib/kolab_format_task.php @@ -123,7 +123,7 @@ class kolab_format_task extends kolab_format_xcal */ public function get_reference_date() { - if ($this->data['due'] && $this->data['due'] instanceof DateTime) { + if ($this->data['due'] && $this->data['due'] instanceof DateTimeInterface) { return $this->data['due']; } diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php index 633d8125..72d8b5ed 100644 --- a/plugins/libkolab/lib/kolab_format_xcal.php +++ b/plugins/libkolab/lib/kolab_format_xcal.php @@ -166,7 +166,7 @@ abstract class kolab_format_xcal extends kolab_format } } - if ($object['start'] instanceof DateTime) { + if ($object['start'] instanceof DateTimeInterface) { $start_tz = $object['start']->getTimezone(); } @@ -183,7 +183,7 @@ abstract class kolab_format_xcal extends kolab_format } else if ($until = self::php_datetime($rr->end(), $start_tz)) { $refdate = $this->get_reference_date(); - if ($refdate && $refdate instanceof DateTime && !$refdate->_dateonly) { + if ($refdate && $refdate instanceof DateTimeInterface && empty($refdate->_dateonly)) { $until->setTime($refdate->format('G'), $refdate->format('i'), 0); } $object['recurrence']['UNTIL'] = $until; @@ -412,7 +412,7 @@ abstract class kolab_format_xcal extends kolab_format $this->obj->setOrganizer($organizer); } - if ($object['start'] instanceof DateTime) { + if ($object['start'] instanceof DateTimeInterface) { $start_tz = $object['start']->getTimezone(); } @@ -536,7 +536,7 @@ abstract class kolab_format_xcal extends kolab_format $alarm = new Alarm(strval($valarm['summary'] ?: $object['title'])); } - if (is_object($valarm['trigger']) && $valarm['trigger'] instanceof DateTime) { + if (is_object($valarm['trigger']) && $valarm['trigger'] instanceof DateTimeInterface) { $alarm->setStart(self::get_datetime($valarm['trigger'], new DateTimeZone('UTC'))); } else if (preg_match('/^@([0-9]+)$/', $valarm['trigger'], $m)) { @@ -618,7 +618,7 @@ abstract class kolab_format_xcal extends kolab_format */ public function get_reference_date() { - if ($this->data['start'] && $this->data['start'] instanceof DateTime) { + if ($this->data['start'] && $this->data['start'] instanceof DateTimeInterface) { return $this->data['start']; } @@ -719,7 +719,12 @@ abstract class kolab_format_xcal extends kolab_format foreach ($this->_scheduling_properties ?: self::$scheduling_properties as $prop) { $a = $old[$prop]; $b = $object[$prop]; - if ($object['allday'] && ($prop == 'start' || $prop == 'end') && $a instanceof DateTime && $b instanceof DateTime) { + + if ($object['allday'] + && ($prop == 'start' || $prop == 'end') + && $a instanceof DateTimeInterface + && $b instanceof DateTimeInterface + ) { $a = $a->format('Y-m-d'); $b = $b->format('Y-m-d'); } diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php index e9591484..ff5bd86e 100644 --- a/plugins/libkolab/lib/kolab_storage_cache.php +++ b/plugins/libkolab/lib/kolab_storage_cache.php @@ -1051,7 +1051,7 @@ class kolab_storage_cache foreach ($this->data_props as $prop) { if (isset($object[$prop])) { $data[$prop] = $object[$prop]; - if ($data[$prop] instanceof DateTime) { + if ($data[$prop] instanceof DateTimeInterface) { $data[$prop] = array( 'cl' => 'DateTime', 'dt' => $data[$prop]->format('Y-m-d H:i:s'), diff --git a/plugins/libkolab/lib/kolab_storage_dataset.php b/plugins/libkolab/lib/kolab_storage_dataset.php index 97717e2a..5953805b 100644 --- a/plugins/libkolab/lib/kolab_storage_dataset.php +++ b/plugins/libkolab/lib/kolab_storage_dataset.php @@ -72,7 +72,7 @@ class kolab_storage_dataset implements Iterator, ArrayAccess, Countable /*** Implement PHP Countable interface ***/ - public function count() + public function count(): int { return count($this->index); } @@ -80,7 +80,7 @@ class kolab_storage_dataset implements Iterator, ArrayAccess, Countable /*** Implement PHP ArrayAccess interface ***/ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { if (is_string($value)) { $uid = $value; @@ -106,16 +106,17 @@ class kolab_storage_dataset implements Iterator, ArrayAccess, Countable } } - public function offsetExists($offset) + public function offsetExists($offset): bool { return isset($this->index[$offset]); } - public function offsetUnset($offset) + public function offsetUnset($offset): void { unset($this->index[$offset]); } + #[ReturnTypeWillChange] public function offsetGet($offset) { if (isset($this->chunk[$offset])) { @@ -163,28 +164,28 @@ class kolab_storage_dataset implements Iterator, ArrayAccess, Countable /*** Implement PHP Iterator interface ***/ + #[ReturnTypeWillChange] public function current() { return $this->offsetGet($this->iteratorkey); } - public function key() + public function key(): int { return $this->iteratorkey; } - public function next() + public function next(): void { $this->iteratorkey++; - return $this->valid(); } - public function rewind() + public function rewind(): void { $this->iteratorkey = 0; } - public function valid() + public function valid(): bool { return !empty($this->index[$this->iteratorkey]); } diff --git a/plugins/libkolab/lib/kolab_storage_dav_cache.php b/plugins/libkolab/lib/kolab_storage_dav_cache.php index e22518f9..5dec0fa9 100644 --- a/plugins/libkolab/lib/kolab_storage_dav_cache.php +++ b/plugins/libkolab/lib/kolab_storage_dav_cache.php @@ -627,7 +627,7 @@ class kolab_storage_dav_cache extends kolab_storage_cache foreach ($this->data_props as $prop) { if (isset($object[$prop])) { $data[$prop] = $object[$prop]; - if ($data[$prop] instanceof DateTime) { + if ($data[$prop] instanceof DateTimeInterface) { $data[$prop] = array( 'cl' => 'DateTime', 'dt' => $data[$prop]->format('Y-m-d H:i:s'), @@ -651,17 +651,11 @@ class kolab_storage_dav_cache extends kolab_storage_cache */ protected function _unserialize($sql_arr, $noread = false, $fast_mode = false) { - if (!empty($sql_arr['data'])) { - if ($object = json_decode($sql_arr['data'], true)) { - $object['_type'] = $sql_arr['type'] ?: $this->folder->type; - $object['uid'] = $sql_arr['uid']; - $object['etag'] = $sql_arr['etag']; - } - } - - if (!empty($fast_mode) && !empty($object)) { + if (!empty($sql_arr['data']) && ($object = json_decode($sql_arr['data'], true))) { foreach ($this->data_props as $prop) { - if (isset($object[$prop]) && is_array($object[$prop]) && $object[$prop]['cl'] == 'DateTime') { + if (isset($object[$prop]) && is_array($object[$prop]) + && isset($object[$prop]['cl']) && $object[$prop]['cl'] == 'DateTime' + ) { $object[$prop] = new DateTime($object[$prop]['dt'], new DateTimeZone($object[$prop]['tz'])); } else if (!isset($object[$prop]) && isset($sql_arr[$prop])) { @@ -677,6 +671,12 @@ class kolab_storage_dav_cache extends kolab_storage_cache $object['changed'] = new DateTime($sql_arr['changed']); } + $object['_type'] = !empty($sql_arr['type']) ? $sql_arr['type'] : $this->folder->type; + $object['uid'] = $sql_arr['uid']; + $object['etag'] = $sql_arr['etag']; + } + + if (!empty($fast_mode) && !empty($object)) { unset($object['_raw']); } else if ($noread) { diff --git a/plugins/libkolab/lib/kolab_storage_dav_folder.php b/plugins/libkolab/lib/kolab_storage_dav_folder.php index e73a6882..8bf48c7f 100644 --- a/plugins/libkolab/lib/kolab_storage_dav_folder.php +++ b/plugins/libkolab/lib/kolab_storage_dav_folder.php @@ -29,16 +29,16 @@ class kolab_storage_dav_folder extends kolab_storage_folder /** * Object constructor */ - public function __construct($dav, $attributes, $type_annotation = '') + public function __construct($dav, $attributes, $type = '') { $this->attributes = $attributes; $this->href = $this->attributes['href']; - $this->id = md5($this->dav->url . '/' . $this->href); + $this->id = md5($dav->url . '/' . $this->href); $this->dav = $dav; $this->valid = true; - list($this->type, $suffix) = explode('.', $type_annotation); + list($this->type, $suffix) = strpos($type, '.') ? explode('.', $type) : [$type, '']; $this->default = $suffix == 'default'; $this->subtype = $this->default ? '' : $suffix; diff --git a/plugins/libkolab/libkolab.php b/plugins/libkolab/libkolab.php index fad740f9..6c5e7a7a 100644 --- a/plugins/libkolab/libkolab.php +++ b/plugins/libkolab/libkolab.php @@ -184,7 +184,7 @@ class libkolab extends rcube_plugin array_walk($result['changes'], function(&$change) use ($dtformat, $rcmail) { if ($change['date']) { $dt = rcube_utils::anytodatetime($change['date']); - if ($dt instanceof DateTime) { + if ($dt instanceof DateTimeInterface) { $change['date'] = $rcmail->format_date($dt, $dtformat); } } diff --git a/plugins/libkolab/tests/kolab_date_recurrence.php b/plugins/libkolab/tests/KolabDateRecurrenceTest.php similarity index 97% rename from plugins/libkolab/tests/kolab_date_recurrence.php rename to plugins/libkolab/tests/KolabDateRecurrenceTest.php index ed3e3bd3..3c51aa19 100644 --- a/plugins/libkolab/tests/kolab_date_recurrence.php +++ b/plugins/libkolab/tests/KolabDateRecurrenceTest.php @@ -21,9 +21,9 @@ * along with this program. If not, see . */ -class kolab_date_recurrence_test extends PHPUnit\Framework\TestCase +class KolabDateRecurrenceTest extends PHPUnit\Framework\TestCase { - function setUp() + function setUp(): void { $rcube = rcmail::get_instance(); $rcube->plugins->load_plugin('libkolab', true, true); @@ -251,11 +251,11 @@ class kolab_date_recurrence_test extends PHPUnit\Framework\TestCase date_default_timezone_set('America/New_York'); $start = new DateTime('2017-08-31 11:00:00', new DateTimeZone('Europe/Berlin')); - $event = array( + $event = [ 'start' => $start, - 'recurrence' => array('FREQ' => 'WEEKLY', 'INTERVAL' => '1'), + 'recurrence' => ['FREQ' => 'WEEKLY', 'INTERVAL' => '1'], 'allday' => true, - ); + ]; $object = kolab_format::factory('event', 3.0); $object->set($event); @@ -265,6 +265,6 @@ class kolab_date_recurrence_test extends PHPUnit\Framework\TestCase $this->assertEquals($start->format('2017-09-07 H:i:s'), $next['start']->format('Y-m-d H:i:s'), 'Same time'); $this->assertEquals($start->getTimezone()->getName(), $next['start']->getTimezone()->getName(), 'Same timezone'); - $this->assertSame($next['start']->_dateonly, true, '_dateonly flag'); + $this->assertSame(true, $next['start']->_dateonly, '_dateonly flag'); } } diff --git a/plugins/libkolab/tests/kolab_storage_config.php b/plugins/libkolab/tests/KolabStorageConfigTest.php similarity index 96% rename from plugins/libkolab/tests/kolab_storage_config.php rename to plugins/libkolab/tests/KolabStorageConfigTest.php index d0c0ba3f..9305cefb 100644 --- a/plugins/libkolab/tests/kolab_storage_config.php +++ b/plugins/libkolab/tests/KolabStorageConfigTest.php @@ -1,6 +1,6 @@ 'Archive', @@ -29,7 +29,7 @@ class kolab_storage_config_test extends PHPUnit\Framework\TestCase ); private $url_other = 'imap:///user/lucy.white%40example.org/Mailings/378?message-id=%3C22448899%40example.org%3E&date=Tue%2C+14+Apr+2015+14%3A14%3A30+%2B0200&subject=Happy+Holidays'; - public static function setUpBeforeClass() + public static function setUpBeforeClass(): void { $rcube = rcmail::get_instance(); $rcube->plugins->load_plugin('libkolab', true, true); @@ -38,11 +38,14 @@ class kolab_storage_config_test extends PHPUnit\Framework\TestCase return; } + // Unset mock'ed storage from the Roundcube core tests + $rcmail->storage = null; + if ($rcube->config->get('tests_username')) { $authenticated = $rcube->login( $rcube->config->get('tests_username'), $rcube->config->get('tests_password'), - $rcube->config->get('default_host'), + $rcube->config->get('imap_host', $rcube->config->get('default_host')), false ); diff --git a/plugins/libkolab/tests/kolab_storage_folder.php b/plugins/libkolab/tests/KolabStorageFolderTest.php similarity index 96% rename from plugins/libkolab/tests/kolab_storage_folder.php rename to plugins/libkolab/tests/KolabStorageFolderTest.php index c242b979..5b73968f 100644 --- a/plugins/libkolab/tests/kolab_storage_folder.php +++ b/plugins/libkolab/tests/KolabStorageFolderTest.php @@ -21,9 +21,9 @@ * along with this program. If not, see . */ -class kolab_storage_folder_test extends PHPUnit\Framework\TestCase +class KolabStorageFolderTest extends PHPUnit\Framework\TestCase { - public static function setUpBeforeClass() + public static function setUpBeforeClass(): void { // load libkolab plugin $rcmail = rcmail::get_instance(); @@ -33,11 +33,14 @@ class kolab_storage_folder_test extends PHPUnit\Framework\TestCase return; } + // Unset mock'ed storage from the Roundcube core tests + $rcmail->storage = null; + if ($rcmail->config->get('tests_username')) { $authenticated = $rcmail->login( $rcmail->config->get('tests_username'), $rcmail->config->get('tests_password'), - $rcmail->config->get('default_host'), + $rcmail->config->get('imap_host', $rcmail->config->get('default_host')), false ); diff --git a/plugins/libkolab/tests/README.md b/plugins/libkolab/tests/README.md index 942822bf..d38d573d 100644 --- a/plugins/libkolab/tests/README.md +++ b/plugins/libkolab/tests/README.md @@ -18,7 +18,7 @@ Add these config options used by the libkolab tests: $config['default_host'] = ''; // disable all plugins - $config['plugins'] = array(); + $config['plugins'] = []; ``` WARNING