1<?php
2
3namespace Sabre\VObject\Component;
4
5use DateTimeZone;
6use Sabre\VObject;
7
8class VCalendarTest extends \PHPUnit_Framework_TestCase {
9
10    use VObject\PHPUnitAssertions;
11
12    /**
13     * @dataProvider expandData
14     */
15    function testExpand($input, $output, $timeZone = 'UTC', $start = '2011-12-01', $end = '2011-12-31') {
16
17        $vcal = VObject\Reader::read($input);
18
19        $timeZone = new DateTimeZone($timeZone);
20
21        $vcal = $vcal->expand(
22            new \DateTime($start),
23            new \DateTime($end),
24            $timeZone
25        );
26
27        // This will normalize the output
28        $output = VObject\Reader::read($output)->serialize();
29
30        $this->assertVObjectEqualsVObject($output, $vcal->serialize());
31
32    }
33
34    function expandData() {
35
36        $tests = [];
37
38        // No data
39        $input = 'BEGIN:VCALENDAR
40CALSCALE:GREGORIAN
41VERSION:2.0
42END:VCALENDAR
43';
44
45        $output = $input;
46        $tests[] = [$input,$output];
47
48
49        // Simple events
50        $input = 'BEGIN:VCALENDAR
51CALSCALE:GREGORIAN
52VERSION:2.0
53BEGIN:VEVENT
54UID:bla
55SUMMARY:InExpand
56DTSTART;VALUE=DATE:20111202
57END:VEVENT
58BEGIN:VEVENT
59UID:bla2
60SUMMARY:NotInExpand
61DTSTART;VALUE=DATE:20120101
62END:VEVENT
63END:VCALENDAR
64';
65
66        $output = 'BEGIN:VCALENDAR
67CALSCALE:GREGORIAN
68VERSION:2.0
69BEGIN:VEVENT
70UID:bla
71SUMMARY:InExpand
72DTSTART;VALUE=DATE:20111202
73END:VEVENT
74END:VCALENDAR
75';
76
77        $tests[] = [$input, $output];
78
79        // Removing timezone info
80        $input = 'BEGIN:VCALENDAR
81CALSCALE:GREGORIAN
82VERSION:2.0
83BEGIN:VTIMEZONE
84TZID:Europe/Paris
85END:VTIMEZONE
86BEGIN:VEVENT
87UID:bla4
88SUMMARY:RemoveTZ info
89DTSTART;TZID=Europe/Paris:20111203T130102
90END:VEVENT
91END:VCALENDAR
92';
93
94        $output = 'BEGIN:VCALENDAR
95CALSCALE:GREGORIAN
96VERSION:2.0
97BEGIN:VEVENT
98UID:bla4
99SUMMARY:RemoveTZ info
100DTSTART:20111203T120102Z
101END:VEVENT
102END:VCALENDAR
103';
104
105        $tests[] = [$input, $output];
106
107        // Removing timezone info from sub-components. See Issue #278
108        $input = 'BEGIN:VCALENDAR
109CALSCALE:GREGORIAN
110VERSION:2.0
111BEGIN:VTIMEZONE
112TZID:Europe/Paris
113END:VTIMEZONE
114BEGIN:VEVENT
115UID:bla4
116SUMMARY:RemoveTZ info
117DTSTART;TZID=Europe/Paris:20111203T130102
118BEGIN:VALARM
119TRIGGER;VALUE=DATE-TIME;TZID=America/New_York:20151209T133200
120END:VALARM
121END:VEVENT
122END:VCALENDAR
123';
124
125        $output = 'BEGIN:VCALENDAR
126CALSCALE:GREGORIAN
127VERSION:2.0
128BEGIN:VEVENT
129UID:bla4
130SUMMARY:RemoveTZ info
131DTSTART:20111203T120102Z
132BEGIN:VALARM
133TRIGGER;VALUE=DATE-TIME:20151209T183200Z
134END:VALARM
135END:VEVENT
136END:VCALENDAR
137';
138
139        $tests[] = [$input, $output];
140
141        // Recurrence rule
142        $input = 'BEGIN:VCALENDAR
143CALSCALE:GREGORIAN
144VERSION:2.0
145BEGIN:VEVENT
146UID:bla6
147SUMMARY:Testing RRule
148DTSTART:20111125T120000Z
149DTEND:20111125T130000Z
150RRULE:FREQ=WEEKLY
151END:VEVENT
152END:VCALENDAR
153';
154
155        $output = 'BEGIN:VCALENDAR
156CALSCALE:GREGORIAN
157VERSION:2.0
158BEGIN:VEVENT
159UID:bla6
160SUMMARY:Testing RRule
161DTSTART:20111202T120000Z
162DTEND:20111202T130000Z
163RECURRENCE-ID:20111202T120000Z
164END:VEVENT
165BEGIN:VEVENT
166UID:bla6
167SUMMARY:Testing RRule
168DTSTART:20111209T120000Z
169DTEND:20111209T130000Z
170RECURRENCE-ID:20111209T120000Z
171END:VEVENT
172BEGIN:VEVENT
173UID:bla6
174SUMMARY:Testing RRule
175DTSTART:20111216T120000Z
176DTEND:20111216T130000Z
177RECURRENCE-ID:20111216T120000Z
178END:VEVENT
179BEGIN:VEVENT
180UID:bla6
181SUMMARY:Testing RRule
182DTSTART:20111223T120000Z
183DTEND:20111223T130000Z
184RECURRENCE-ID:20111223T120000Z
185END:VEVENT
186BEGIN:VEVENT
187UID:bla6
188SUMMARY:Testing RRule
189DTSTART:20111230T120000Z
190DTEND:20111230T130000Z
191RECURRENCE-ID:20111230T120000Z
192END:VEVENT
193END:VCALENDAR
194';
195
196        $tests[] = [$input, $output];
197
198        // Recurrence rule + override
199        $input = 'BEGIN:VCALENDAR
200CALSCALE:GREGORIAN
201VERSION:2.0
202BEGIN:VEVENT
203UID:bla6
204SUMMARY:Testing RRule2
205DTSTART:20111125T120000Z
206DTEND:20111125T130000Z
207RRULE:FREQ=WEEKLY
208END:VEVENT
209BEGIN:VEVENT
210UID:bla6
211RECURRENCE-ID:20111209T120000Z
212DTSTART:20111209T140000Z
213DTEND:20111209T150000Z
214SUMMARY:Override!
215END:VEVENT
216END:VCALENDAR
217';
218
219        $output = 'BEGIN:VCALENDAR
220CALSCALE:GREGORIAN
221VERSION:2.0
222BEGIN:VEVENT
223UID:bla6
224SUMMARY:Testing RRule2
225DTSTART:20111202T120000Z
226DTEND:20111202T130000Z
227RECURRENCE-ID:20111202T120000Z
228END:VEVENT
229BEGIN:VEVENT
230UID:bla6
231RECURRENCE-ID:20111209T120000Z
232DTSTART:20111209T140000Z
233DTEND:20111209T150000Z
234SUMMARY:Override!
235END:VEVENT
236BEGIN:VEVENT
237UID:bla6
238SUMMARY:Testing RRule2
239DTSTART:20111216T120000Z
240DTEND:20111216T130000Z
241RECURRENCE-ID:20111216T120000Z
242END:VEVENT
243BEGIN:VEVENT
244UID:bla6
245SUMMARY:Testing RRule2
246DTSTART:20111223T120000Z
247DTEND:20111223T130000Z
248RECURRENCE-ID:20111223T120000Z
249END:VEVENT
250BEGIN:VEVENT
251UID:bla6
252SUMMARY:Testing RRule2
253DTSTART:20111230T120000Z
254DTEND:20111230T130000Z
255RECURRENCE-ID:20111230T120000Z
256END:VEVENT
257END:VCALENDAR
258';
259
260        $tests[] = [$input, $output];
261
262        // Floating dates and times.
263        $input = <<<ICS
264BEGIN:VCALENDAR
265VERSION:2.0
266BEGIN:VEVENT
267UID:bla1
268DTSTART:20141112T195000
269END:VEVENT
270BEGIN:VEVENT
271UID:bla2
272DTSTART;VALUE=DATE:20141112
273END:VEVENT
274BEGIN:VEVENT
275UID:bla3
276DTSTART;VALUE=DATE:20141112
277RRULE:FREQ=DAILY;COUNT=2
278END:VEVENT
279END:VCALENDAR
280ICS;
281
282        $output = <<<ICS
283BEGIN:VCALENDAR
284VERSION:2.0
285BEGIN:VEVENT
286UID:bla1
287DTSTART:20141112T225000Z
288END:VEVENT
289BEGIN:VEVENT
290UID:bla2
291DTSTART;VALUE=DATE:20141112
292END:VEVENT
293BEGIN:VEVENT
294UID:bla3
295DTSTART;VALUE=DATE:20141112
296RECURRENCE-ID;VALUE=DATE:20141112
297END:VEVENT
298BEGIN:VEVENT
299UID:bla3
300DTSTART;VALUE=DATE:20141113
301RECURRENCE-ID;VALUE=DATE:20141113
302END:VEVENT
303END:VCALENDAR
304ICS;
305
306        $tests[] = [$input, $output, 'America/Argentina/Buenos_Aires', '2014-01-01', '2015-01-01'];
307
308        // Recurrence rule with no valid instances
309        $input = 'BEGIN:VCALENDAR
310CALSCALE:GREGORIAN
311VERSION:2.0
312BEGIN:VEVENT
313UID:bla6
314SUMMARY:Testing RRule3
315DTSTART:20111125T120000Z
316DTEND:20111125T130000Z
317RRULE:FREQ=WEEKLY;COUNT=1
318EXDATE:20111125T120000Z
319END:VEVENT
320END:VCALENDAR
321';
322
323        $output = 'BEGIN:VCALENDAR
324CALSCALE:GREGORIAN
325VERSION:2.0
326END:VCALENDAR
327';
328
329        $tests[] = [$input, $output];
330        return $tests;
331
332    }
333
334    /**
335     * @expectedException \Sabre\VObject\InvalidDataException
336     */
337    function testBrokenEventExpand() {
338
339        $input = 'BEGIN:VCALENDAR
340CALSCALE:GREGORIAN
341VERSION:2.0
342BEGIN:VEVENT
343RRULE:FREQ=WEEKLY
344DTSTART;VALUE=DATE:20111202
345END:VEVENT
346END:VCALENDAR
347';
348        $vcal = VObject\Reader::read($input);
349        $vcal->expand(
350            new \DateTime('2011-12-01'),
351            new \DateTime('2011-12-31')
352        );
353
354    }
355
356    function testGetDocumentType() {
357
358        $vcard = new VCalendar();
359        $vcard->VERSION = '2.0';
360        $this->assertEquals(VCalendar::ICALENDAR20, $vcard->getDocumentType());
361
362    }
363
364    function testValidateCorrect() {
365
366        $input = 'BEGIN:VCALENDAR
367CALSCALE:GREGORIAN
368VERSION:2.0
369PRODID:foo
370BEGIN:VEVENT
371DTSTART;VALUE=DATE:20111202
372DTSTAMP:20140122T233226Z
373UID:foo
374END:VEVENT
375END:VCALENDAR
376';
377
378        $vcal = VObject\Reader::read($input);
379        $this->assertEquals([], $vcal->validate(), 'Got an error');
380
381    }
382
383    function testValidateNoVersion() {
384
385        $input = 'BEGIN:VCALENDAR
386CALSCALE:GREGORIAN
387PRODID:foo
388BEGIN:VEVENT
389DTSTART;VALUE=DATE:20111202
390UID:foo
391DTSTAMP:20140122T234434Z
392END:VEVENT
393END:VCALENDAR
394';
395
396        $vcal = VObject\Reader::read($input);
397        $this->assertEquals(1, count($vcal->validate()));
398
399    }
400
401    function testValidateWrongVersion() {
402
403        $input = 'BEGIN:VCALENDAR
404CALSCALE:GREGORIAN
405VERSION:3.0
406PRODID:foo
407BEGIN:VEVENT
408DTSTART;VALUE=DATE:20111202
409UID:foo
410DTSTAMP:20140122T234434Z
411END:VEVENT
412END:VCALENDAR
413';
414
415        $vcal = VObject\Reader::read($input);
416        $this->assertEquals(1, count($vcal->validate()));
417
418    }
419
420    function testValidateNoProdId() {
421
422        $input = 'BEGIN:VCALENDAR
423CALSCALE:GREGORIAN
424VERSION:2.0
425BEGIN:VEVENT
426DTSTART;VALUE=DATE:20111202
427UID:foo
428DTSTAMP:20140122T234434Z
429END:VEVENT
430END:VCALENDAR
431';
432
433        $vcal = VObject\Reader::read($input);
434        $this->assertEquals(1, count($vcal->validate()));
435
436    }
437
438    function testValidateDoubleCalScale() {
439
440        $input = 'BEGIN:VCALENDAR
441VERSION:2.0
442PRODID:foo
443CALSCALE:GREGORIAN
444CALSCALE:GREGORIAN
445BEGIN:VEVENT
446DTSTART;VALUE=DATE:20111202
447UID:foo
448DTSTAMP:20140122T234434Z
449END:VEVENT
450END:VCALENDAR
451';
452
453        $vcal = VObject\Reader::read($input);
454        $this->assertEquals(1, count($vcal->validate()));
455
456    }
457
458    function testValidateDoubleMethod() {
459
460        $input = 'BEGIN:VCALENDAR
461VERSION:2.0
462PRODID:foo
463METHOD:REQUEST
464METHOD:REQUEST
465BEGIN:VEVENT
466DTSTART;VALUE=DATE:20111202
467UID:foo
468DTSTAMP:20140122T234434Z
469END:VEVENT
470END:VCALENDAR
471';
472
473        $vcal = VObject\Reader::read($input);
474        $this->assertEquals(1, count($vcal->validate()));
475
476    }
477
478    function testValidateTwoMasterEvents() {
479
480        $input = 'BEGIN:VCALENDAR
481VERSION:2.0
482PRODID:foo
483METHOD:REQUEST
484BEGIN:VEVENT
485DTSTART;VALUE=DATE:20111202
486UID:foo
487DTSTAMP:20140122T234434Z
488END:VEVENT
489BEGIN:VEVENT
490DTSTART;VALUE=DATE:20111202
491UID:foo
492DTSTAMP:20140122T234434Z
493END:VEVENT
494END:VCALENDAR
495';
496
497        $vcal = VObject\Reader::read($input);
498        $this->assertEquals(1, count($vcal->validate()));
499
500    }
501
502    function testValidateOneMasterEvent() {
503
504        $input = 'BEGIN:VCALENDAR
505VERSION:2.0
506PRODID:foo
507METHOD:REQUEST
508BEGIN:VEVENT
509DTSTART;VALUE=DATE:20111202
510UID:foo
511DTSTAMP:20140122T234434Z
512END:VEVENT
513BEGIN:VEVENT
514DTSTART;VALUE=DATE:20111202
515UID:foo
516DTSTAMP:20140122T234434Z
517RECURRENCE-ID;VALUE=DATE:20111202
518END:VEVENT
519END:VCALENDAR
520';
521
522        $vcal = VObject\Reader::read($input);
523        $this->assertEquals(0, count($vcal->validate()));
524
525    }
526
527    function testGetBaseComponent() {
528
529        $input = 'BEGIN:VCALENDAR
530VERSION:2.0
531PRODID:foo
532METHOD:REQUEST
533BEGIN:VEVENT
534SUMMARY:test
535DTSTART;VALUE=DATE:20111202
536UID:foo
537DTSTAMP:20140122T234434Z
538END:VEVENT
539BEGIN:VEVENT
540DTSTART;VALUE=DATE:20111202
541UID:foo
542DTSTAMP:20140122T234434Z
543RECURRENCE-ID;VALUE=DATE:20111202
544END:VEVENT
545END:VCALENDAR
546';
547
548        $vcal = VObject\Reader::read($input);
549
550        $result = $vcal->getBaseComponent();
551        $this->assertEquals('test', $result->SUMMARY->getValue());
552
553    }
554
555    function testGetBaseComponentNoResult() {
556
557        $input = 'BEGIN:VCALENDAR
558VERSION:2.0
559PRODID:foo
560METHOD:REQUEST
561BEGIN:VEVENT
562SUMMARY:test
563RECURRENCE-ID;VALUE=DATE:20111202
564DTSTART;VALUE=DATE:20111202
565UID:foo
566DTSTAMP:20140122T234434Z
567END:VEVENT
568BEGIN:VEVENT
569DTSTART;VALUE=DATE:20111202
570UID:foo
571DTSTAMP:20140122T234434Z
572RECURRENCE-ID;VALUE=DATE:20111202
573END:VEVENT
574END:VCALENDAR
575';
576
577        $vcal = VObject\Reader::read($input);
578
579        $result = $vcal->getBaseComponent();
580        $this->assertNull($result);
581
582    }
583
584    function testGetBaseComponentWithFilter() {
585
586        $input = 'BEGIN:VCALENDAR
587VERSION:2.0
588PRODID:foo
589METHOD:REQUEST
590BEGIN:VEVENT
591SUMMARY:test
592DTSTART;VALUE=DATE:20111202
593UID:foo
594DTSTAMP:20140122T234434Z
595END:VEVENT
596BEGIN:VEVENT
597DTSTART;VALUE=DATE:20111202
598UID:foo
599DTSTAMP:20140122T234434Z
600RECURRENCE-ID;VALUE=DATE:20111202
601END:VEVENT
602END:VCALENDAR
603';
604
605        $vcal = VObject\Reader::read($input);
606
607        $result = $vcal->getBaseComponent('VEVENT');
608        $this->assertEquals('test', $result->SUMMARY->getValue());
609
610    }
611
612    function testGetBaseComponentWithFilterNoResult() {
613
614        $input = 'BEGIN:VCALENDAR
615VERSION:2.0
616PRODID:foo
617METHOD:REQUEST
618BEGIN:VTODO
619SUMMARY:test
620UID:foo
621DTSTAMP:20140122T234434Z
622END:VTODO
623END:VCALENDAR
624';
625
626        $vcal = VObject\Reader::read($input);
627
628        $result = $vcal->getBaseComponent('VEVENT');
629        $this->assertNull($result);
630
631    }
632
633    function testNoComponents() {
634
635        $input = <<<ICS
636BEGIN:VCALENDAR
637VERSION:2.0
638PRODID:vobject
639END:VCALENDAR
640ICS;
641
642        $this->assertValidate(
643            $input,
644            0,
645            3,
646           "An iCalendar object must have at least 1 component."
647        );
648
649    }
650
651    function testCalDAVNoComponents() {
652
653        $input = <<<ICS
654BEGIN:VCALENDAR
655VERSION:2.0
656PRODID:vobject
657BEGIN:VTIMEZONE
658TZID:America/Toronto
659END:VTIMEZONE
660END:VCALENDAR
661ICS;
662
663        $this->assertValidate(
664            $input,
665            VCalendar::PROFILE_CALDAV,
666            3,
667           "A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL)."
668        );
669
670    }
671
672    function testCalDAVMultiUID() {
673
674        $input = <<<ICS
675BEGIN:VCALENDAR
676VERSION:2.0
677PRODID:vobject
678BEGIN:VEVENT
679UID:foo
680DTSTAMP:20150109T184500Z
681DTSTART:20150109T184500Z
682END:VEVENT
683BEGIN:VEVENT
684UID:bar
685DTSTAMP:20150109T184500Z
686DTSTART:20150109T184500Z
687END:VEVENT
688END:VCALENDAR
689ICS;
690
691        $this->assertValidate(
692            $input,
693            VCalendar::PROFILE_CALDAV,
694            3,
695           "A calendar object on a CalDAV server may only have components with the same UID."
696        );
697
698    }
699
700    function testCalDAVMultiComponent() {
701
702        $input = <<<ICS
703BEGIN:VCALENDAR
704VERSION:2.0
705PRODID:vobject
706BEGIN:VEVENT
707UID:foo
708RECURRENCE-ID:20150109T185200Z
709DTSTAMP:20150109T184500Z
710DTSTART:20150109T184500Z
711END:VEVENT
712BEGIN:VTODO
713UID:foo
714DTSTAMP:20150109T184500Z
715DTSTART:20150109T184500Z
716END:VTODO
717END:VCALENDAR
718ICS;
719
720        $this->assertValidate(
721            $input,
722            VCalendar::PROFILE_CALDAV,
723            3,
724           "A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL)."
725        );
726
727    }
728
729    function testCalDAVMETHOD() {
730
731        $input = <<<ICS
732BEGIN:VCALENDAR
733VERSION:2.0
734METHOD:PUBLISH
735PRODID:vobject
736BEGIN:VEVENT
737UID:foo
738RECURRENCE-ID:20150109T185200Z
739DTSTAMP:20150109T184500Z
740DTSTART:20150109T184500Z
741END:VEVENT
742END:VCALENDAR
743ICS;
744
745        $this->assertValidate(
746            $input,
747            VCalendar::PROFILE_CALDAV,
748            3,
749           "A calendar object on a CalDAV server MUST NOT have a METHOD property."
750        );
751
752    }
753
754    function assertValidate($ics, $options, $expectedLevel, $expectedMessage = null) {
755
756        $vcal = VObject\Reader::read($ics);
757        $result = $vcal->validate($options);
758
759        $this->assertValidateResult($result, $expectedLevel, $expectedMessage);
760
761    }
762
763    function assertValidateResult($input, $expectedLevel, $expectedMessage = null) {
764
765        $messages = [];
766        foreach ($input as $warning) {
767            $messages[] = $warning['message'];
768        }
769
770        if ($expectedLevel === 0) {
771            $this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages));
772        } else {
773            $this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages));
774
775            $this->assertEquals($expectedMessage, $input[0]['message']);
776            $this->assertEquals($expectedLevel, $input[0]['level']);
777        }
778
779    }
780
781
782}
783