<?php
declare(strict_types=1);
namespace Sabre\CalDAV;
use Sabre\VObject;
class CalendarQueryValidatorTest extends \PHPUnit\Framework\TestCase
{
public function testTopLevelFail()
{
$validator = new CalendarQueryValidator();
$vcal = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
END:VEVENT
END:VCALENDAR
ICS;
$vcal = VObject\Reader::read($vcal);
$this->assertFalse($validator->validate($vcal, ['name' => 'VFOO']));
}
/**
* @param string $icalObject
* @param array $filters
* @param int $outcome
* @dataProvider provider
*/
public function testValid($icalObject, $filters, $outcome)
{
$validator = new CalendarQueryValidator();
// Wrapping filter in a VCALENDAR component filter, as this is always
// there anyway.
$filters = [
'name' => 'VCALENDAR',
'comp-filters' => [$filters],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => null,
];
$vObject = VObject\Reader::read($icalObject);
switch ($outcome) {
case 0:
$this->assertFalse($validator->validate($vObject, $filters));
break;
case 1:
$this->assertTrue($validator->validate($vObject, $filters));
break;
case -1:
try {
$validator->validate($vObject, $filters);
$this->fail('This test was supposed to fail');
} catch (\Exception $e) {
// We need to test something to be valid for phpunit strict
// mode.
$this->assertTrue(true);
} catch (\Throwable $e) {
// PHP7
$this->assertTrue(true);
}
break;
}
}
public function provider()
{
$blob1 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
SUMMARY:hi
END:VEVENT
END:VCALENDAR
yow;
$blob2 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
SUMMARY:hi
BEGIN:VALARM
ACTION:DISPLAY
END:VALARM
END:VEVENT
END:VCALENDAR
yow;
$blob3 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
SUMMARY:hi
DTSTART;VALUE=DATE:20110704
END:VEVENT
END:VCALENDAR
yow;
$blob4 = <<<yow
BEGIN:VCARD
VERSION:3.0
FN:Evert
END:VCARD
yow;
$blob5 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
DTEND:20110102T120000Z
END:VEVENT
END:VCALENDAR
yow;
$blob6 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
DURATION:PT5H
END:VEVENT
END:VCALENDAR
yow;
$blob7 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART;VALUE=DATE:20110101
END:VEVENT
END:VCALENDAR
yow;
$blob8 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
END:VEVENT
END:VCALENDAR
yow;
$blob9 = <<<yow
BEGIN:VCALENDAR
BEGIN:VTODO
DTSTART:20110101T120000Z
DURATION:PT1H
END:VTODO
END:VCALENDAR
yow;
$blob10 = <<<yow
BEGIN:VCALENDAR
BEGIN:VTODO
DTSTART:20110101T120000Z
DUE:20110101T130000Z
END:VTODO
END:VCALENDAR
yow;
$blob11 = <<<yow
BEGIN:VCALENDAR
BEGIN:VTODO
DTSTART:20110101T120000Z
END:VTODO
END:VCALENDAR
yow;
$blob12 = <<<yow
BEGIN:VCALENDAR
BEGIN:VTODO
DUE:20110101T130000Z
END:VTODO
END:VCALENDAR
yow;
$blob13 = <<<yow
BEGIN:VCALENDAR
BEGIN:VTODO
COMPLETED:20110101T130000Z
CREATED:20110101T110000Z
END:VTODO
END:VCALENDAR
yow;
$blob14 = <<<yow
BEGIN:VCALENDAR
BEGIN:VTODO
COMPLETED:20110101T130000Z
END:VTODO
END:VCALENDAR
yow;
$blob15 = <<<yow
BEGIN:VCALENDAR
BEGIN:VTODO
CREATED:20110101T110000Z
END:VTODO
END:VCALENDAR
yow;
$blob16 = <<<yow
BEGIN:VCALENDAR
BEGIN:VTODO
END:VTODO
END:VCALENDAR
yow;
$blob17 = <<<yow
BEGIN:VCALENDAR
BEGIN:VJOURNAL
END:VJOURNAL
END:VCALENDAR
yow;
$blob18 = <<<yow
BEGIN:VCALENDAR
BEGIN:VJOURNAL
DTSTART:20110101T120000Z
END:VJOURNAL
END:VCALENDAR
yow;
$blob19 = <<<yow
BEGIN:VCALENDAR
BEGIN:VJOURNAL
DTSTART;VALUE=DATE:20110101
END:VJOURNAL
END:VCALENDAR
yow;
$blob20 = <<<yow
BEGIN:VCALENDAR
BEGIN:VFREEBUSY
END:VFREEBUSY
END:VCALENDAR
yow;
$blob21 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
BEGIN:VALARM
TRIGGER:-PT1H
END:VALARM
END:VEVENT
END:VCALENDAR
yow;
$blob22 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
BEGIN:VALARM
TRIGGER;VALUE=DURATION:-PT1H
END:VALARM
END:VEVENT
END:VCALENDAR
yow;
$blob23 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
BEGIN:VALARM
TRIGGER;VALUE=DURATION;RELATED=END:-PT1H
END:VALARM
END:VEVENT
END:VCALENDAR
yow;
$blob24 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
DTEND:20110101T130000Z
BEGIN:VALARM
TRIGGER;VALUE=DURATION;RELATED=END:-PT2H
END:VALARM
END:VEVENT
END:VCALENDAR
yow;
$blob25 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
DURATION:PT1H
BEGIN:VALARM
TRIGGER;VALUE=DURATION;RELATED=END:-PT2H
END:VALARM
END:VEVENT
END:VCALENDAR
yow;
$blob26 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
DURATION:PT1H
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20110101T110000Z
END:VALARM
END:VEVENT
END:VCALENDAR
yow;
$blob27 = <<<yow
BEGIN:VCALENDAR
BEGIN:VTODO
DTSTART:20110101T120000Z
DUE:20110101T130000Z
BEGIN:VALARM
TRIGGER;VALUE=DURATION;RELATED=END:-PT2H
END:VALARM
END:VTODO
END:VCALENDAR
yow;
$blob28 = <<<yow
BEGIN:VCALENDAR
BEGIN:VJOURNAL
DTSTART:20110101T120000Z
BEGIN:VALARM
TRIGGER;VALUE=DURATION;RELATED=END:-PT2H
END:VALARM
END:VJOURNAL
END:VCALENDAR
yow;
$blob29 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
DURATION:PT1H
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20110101T090000Z
REPEAT:2
DURATION:PT1H
END:VALARM
END:VEVENT
END:VCALENDAR
yow;
$blob30 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
DURATION:PT1H
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20110101T090000Z
DURATION:PT1H
END:VALARM
END:VEVENT
END:VCALENDAR
yow;
$blob31 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20080101T120000Z
DURATION:PT1H
RRULE:FREQ=YEARLY
END:VEVENT
END:VCALENDAR
yow;
$blob32 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART:20080102T120000Z
DURATION:PT1H
RRULE:FREQ=YEARLY
END:VEVENT
END:VCALENDAR
yow;
$blob33 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART;VALUE=DATE:20120628
RRULE:FREQ=DAILY
END:VEVENT
END:VCALENDAR
yow;
$blob34 = <<<yow
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:foobar
DTSTART;VALUE=DATE:20120628
RRULE:FREQ=DAILY
BEGIN:VALARM
TRIGGER:P52W
END:VALARM
END:VEVENT
END:VCALENDAR
yow;
$filter1 = [
'name' => 'VEVENT',
'comp-filters' => [],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => null,
];
$filter2 = $filter1;
$filter2['name'] = 'VTODO';
$filter3 = $filter1;
$filter3['is-not-defined'] = true;
$filter4 = $filter1;
$filter4['name'] = 'VTODO';
$filter4['is-not-defined'] = true;
$filter5 = $filter1;
$filter5['comp-filters'] = [
[
'name' => 'VALARM',
'is-not-defined' => false,
'comp-filters' => [],
'prop-filters' => [],
'time-range' => null,
],
];
$filter6 = $filter1;
$filter6['prop-filters'] = [
[
'name' => 'SUMMARY',
'is-not-defined' => false,
'param-filters' => [],
'time-range' => null,
'text-match' => null,
],
];
$filter7 = $filter6;
$filter7['prop-filters'][0]['name'] = 'DESCRIPTION';
$filter8 = $filter6;
$filter8['prop-filters'][0]['is-not-defined'] = true;
$filter9 = $filter7;
$filter9['prop-filters'][0]['is-not-defined'] = true;
$filter10 = $filter5;
$filter10['prop-filters'] = $filter6['prop-filters'];
// Param filters
$filter11 = $filter1;
$filter11['prop-filters'] = [
[
'name' => 'DTSTART',
'is-not-defined' => false,
'param-filters' => [
[
'name' => 'VALUE',
'is-not-defined' => false,
'text-match' => null,
],
],
'time-range' => null,
'text-match' => null,
],
];
$filter12 = $filter11;
$filter12['prop-filters'][0]['param-filters'][0]['name'] = 'TZID';
$filter13 = $filter11;
$filter13['prop-filters'][0]['param-filters'][0]['is-not-defined'] = true;
$filter14 = $filter12;
$filter14['prop-filters'][0]['param-filters'][0]['is-not-defined'] = true;
// Param text filter
$filter15 = $filter11;
$filter15['prop-filters'][0]['param-filters'][0]['text-match'] = [
'collation' => 'i;ascii-casemap',
'value' => 'dAtE',
'negate-condition' => false,
];
$filter16 = $filter15;
$filter16['prop-filters'][0]['param-filters'][0]['text-match']['collation'] = 'i;octet';
$filter17 = $filter15;
$filter17['prop-filters'][0]['param-filters'][0]['text-match']['negate-condition'] = true;
$filter18 = $filter15;
$filter18['prop-filters'][0]['param-filters'][0]['text-match']['negate-condition'] = true;
$filter18['prop-filters'][0]['param-filters'][0]['text-match']['collation'] = 'i;octet';
// prop + text
$filter19 = $filter5;
$filter19['comp-filters'][0]['prop-filters'] = [
[
'name' => 'action',
'is-not-defined' => false,
'time-range' => null,
'param-filters' => [],
'text-match' => [
'collation' => 'i;ascii-casemap',
'value' => 'display',
'negate-condition' => false,
],
],
];
// Time range
$filter20 = [
'name' => 'VEVENT',
'comp-filters' => [],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => [
'start' => new \DateTime('2011-01-01 10:00:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-01-01 13:00:00', new \DateTimeZone('GMT')),
],
];
// Time range, no end date
$filter21 = $filter20;
$filter21['time-range']['end'] = null;
// Time range, no start date
$filter22 = $filter20;
$filter22['time-range']['start'] = null;
// Time range, other dates
$filter23 = $filter20;
$filter23['time-range'] = [
'start' => new \DateTime('2011-02-01 10:00:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-02-01 13:00:00', new \DateTimeZone('GMT')),
];
// Time range
$filter24 = [
'name' => 'VTODO',
'comp-filters' => [],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => [
'start' => new \DateTime('2011-01-01 12:45:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-01-01 13:15:00', new \DateTimeZone('GMT')),
],
];
// Time range, other dates (1 month in the future)
$filter25 = $filter24;
$filter25['time-range'] = [
'start' => new \DateTime('2011-02-01 10:00:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-02-01 13:00:00', new \DateTimeZone('GMT')),
];
$filter26 = $filter24;
$filter26['time-range'] = [
'start' => new \DateTime('2011-01-01 11:45:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-01-01 12:15:00', new \DateTimeZone('GMT')),
];
// Time range for VJOURNAL
$filter27 = [
'name' => 'VJOURNAL',
'comp-filters' => [],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => [
'start' => new \DateTime('2011-01-01 12:45:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-01-01 13:15:00', new \DateTimeZone('GMT')),
],
];
$filter28 = $filter27;
$filter28['time-range'] = [
'start' => new \DateTime('2011-01-01 11:45:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-01-01 12:15:00', new \DateTimeZone('GMT')),
];
// Time range for VFREEBUSY
$filter29 = [
'name' => 'VFREEBUSY',
'comp-filters' => [],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => [
'start' => new \DateTime('2011-01-01 12:45:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-01-01 13:15:00', new \DateTimeZone('GMT')),
],
];
// Time range filter on property
$filter30 = [
'name' => 'VEVENT',
'comp-filters' => [],
'prop-filters' => [
[
'name' => 'DTSTART',
'is-not-defined' => false,
'param-filters' => [],
'time-range' => [
'start' => new \DateTime('2011-01-01 10:00:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-01-01 13:00:00', new \DateTimeZone('GMT')),
],
'text-match' => null,
],
],
'is-not-defined' => false,
'time-range' => null,
];
// Time range for alarm
$filter31 = [
'name' => 'VEVENT',
'prop-filters' => [],
'comp-filters' => [
[
'name' => 'VALARM',
'is-not-defined' => false,
'comp-filters' => [],
'prop-filters' => [],
'time-range' => [
'start' => new \DateTime('2011-01-01 10:45:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-01-01 11:15:00', new \DateTimeZone('GMT')),
],
'text-match' => null,
],
],
'is-not-defined' => false,
'time-range' => null,
];
$filter32 = $filter31;
$filter32['comp-filters'][0]['time-range'] = [
'start' => new \DateTime('2011-01-01 11:45:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-01-01 12:15:00', new \DateTimeZone('GMT')),
];
$filter33 = $filter31;
$filter33['name'] = 'VTODO';
$filter34 = $filter32;
$filter34['name'] = 'VTODO';
$filter35 = $filter31;
$filter35['name'] = 'VJOURNAL';
$filter36 = $filter32;
$filter36['name'] = 'VJOURNAL';
// Time range filter on non-datetime property
$filter37 = [
'name' => 'VEVENT',
'comp-filters' => [],
'prop-filters' => [
[
'name' => 'SUMMARY',
'is-not-defined' => false,
'param-filters' => [],
'time-range' => [
'start' => new \DateTime('2011-01-01 10:00:00', new \DateTimeZone('GMT')),
'end' => new \DateTime('2011-01-01 13:00:00', new \DateTimeZone('GMT')),
],
'text-match' => null,
],
],
'is-not-defined' => false,
'time-range' => null,
];
$filter38 = [
'name' => 'VEVENT',
'comp-filters' => [],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => [
'start' => new \DateTime('2012-07-01 00:00:00', new \DateTimeZone('UTC')),
'end' => new \DateTime('2012-08-01 00:00:00', new \DateTimeZone('UTC')),
],
];
$filter39 = [
'name' => 'VEVENT',
'comp-filters' => [
[
'name' => 'VALARM',
'comp-filters' => [],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => [
'start' => new \DateTime('2012-09-01 00:00:00', new \DateTimeZone('UTC')),
'end' => new \DateTime('2012-10-01 00:00:00', new \DateTimeZone('UTC')),
],
],
],
'prop-filters' => [],
'is-not-defined' => false,
'time-range' => null,
];
return [
// Component check
[$blob1, $filter1, 1],
[$blob1, $filter2, 0],
[$blob1, $filter3, 0],
[$blob1, $filter4, 1],
// Subcomponent check (4)
[$blob1, $filter5, 0],
[$blob2, $filter5, 1],
// Property checki (6)
[$blob1, $filter6, 1],
[$blob1, $filter7, 0],
[$blob1, $filter8, 0],
[$blob1, $filter9, 1],
// Subcomponent + property (10)
[$blob2, $filter10, 1],
// Param filter (11)
[$blob3, $filter11, 1],
[$blob3, $filter12, 0],
[$blob3, $filter13, 0],
[$blob3, $filter14, 1],
// Param + text (15)
[$blob3, $filter15, 1],
[$blob3, $filter16, 0],
[$blob3, $filter17, 0],
[$blob3, $filter18, 1],
// Prop + text (19)
[$blob2, $filter19, 1],
// Incorrect object (vcard) (20)
[$blob4, $filter1, -1],
// Time-range for event (21)
[$blob5, $filter20, 1],
[$blob6, $filter20, 1],
[$blob7, $filter20, 1],
[$blob8, $filter20, 1],
[$blob5, $filter21, 1],
[$blob5, $filter22, 1],
[$blob5, $filter23, 0],
[$blob6, $filter23, 0],
[$blob7, $filter23, 0],
[$blob8, $filter23, 0],
// Time-range for todo (31)
[$blob9, $filter24, 1],
[$blob9, $filter25, 0],
[$blob9, $filter26, 1],
[$blob10, $filter24, 1],
[$blob10, $filter25, 0],
[$blob10, $filter26, 1],
[$blob11, $filter24, 0],
[$blob11, $filter25, 0],
[$blob11, $filter26, 1],
[$blob12, $filter24, 1],
[$blob12, $filter25, 0],
[$blob12, $filter26, 0],
[$blob13, $filter24, 1],
[$blob13, $filter25, 0],
[$blob13, $filter26, 1],
[$blob14, $filter24, 1],
[$blob14, $filter25, 0],
[$blob14, $filter26, 0],
[$blob15, $filter24, 1],
[$blob15, $filter25, 1],
[$blob15, $filter26, 1],
[$blob16, $filter24, 1],
[$blob16, $filter25, 1],
[$blob16, $filter26, 1],
// Time-range for journals (55)
[$blob17, $filter27, 0],
[$blob17, $filter28, 0],
[$blob18, $filter27, 0],
[$blob18, $filter28, 1],
[$blob19, $filter27, 1],
[$blob19, $filter28, 1],
// Time-range for free-busy (61)
[$blob20, $filter29, -1],
// Time-range on property (62)
[$blob5, $filter30, 1],
[$blob3, $filter37, -1],
[$blob3, $filter30, 0],
// Time-range on alarm in vevent (65)
[$blob21, $filter31, 1],
[$blob21, $filter32, 0],
[$blob22, $filter31, 1],
[$blob22, $filter32, 0],
[$blob23, $filter31, 1],
[$blob23, $filter32, 0],
[$blob24, $filter31, 1],
[$blob24, $filter32, 0],
[$blob25, $filter31, 1],
[$blob25, $filter32, 0],
[$blob26, $filter31, 1],
[$blob26, $filter32, 0],
// Time-range on alarm for vtodo (77)
[$blob27, $filter33, 1],
[$blob27, $filter34, 0],
// Time-range on alarm for vjournal (79)
[$blob28, $filter35, -1],
[$blob28, $filter36, -1],
// Time-range on alarm with duration (81)
[$blob29, $filter31, 1],
[$blob29, $filter32, 0],
[$blob30, $filter31, 0],
[$blob30, $filter32, 0],
// Time-range with RRULE (85)
[$blob31, $filter20, 1],
[$blob32, $filter20, 0],
// Bug reported on mailing list, related to all-day events (87)
//array($blob33, $filter38, 1),
// Event in timerange, but filtered alarm is in the far future (88).
[$blob34, $filter39, 0],
];
}
}