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