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
260RECURRENCE-ID;VALUE=DATE:20141112
261END:VEVENT
262BEGIN:VEVENT
263UID:bla3
264DTSTART;VALUE=DATE:20141113
265RECURRENCE-ID;VALUE=DATE:20141113
266END:VEVENT
267END:VCALENDAR
268ICS;
269
270        $tests[] = array($input, $output, 'America/Argentina/Buenos_Aires', '2014-01-01', '2015-01-01');
271
272        // Recurrence rule with no valid instances
273        $input = 'BEGIN:VCALENDAR
274CALSCALE:GREGORIAN
275VERSION:2.0
276BEGIN:VEVENT
277UID:bla6
278SUMMARY:Testing RRule3
279DTSTART:20111125T120000Z
280DTEND:20111125T130000Z
281RRULE:FREQ=WEEKLY;COUNT=1
282EXDATE:20111125T120000Z
283END:VEVENT
284END:VCALENDAR
285';
286
287        $output = 'BEGIN:VCALENDAR
288CALSCALE:GREGORIAN
289VERSION:2.0
290END:VCALENDAR
291';
292
293        $tests[] = array($input, $output);
294        return $tests;
295
296    }
297
298    /**
299     * @expectedException LogicException
300     */
301    public function testBrokenEventExpand() {
302
303        $input = 'BEGIN:VCALENDAR
304CALSCALE:GREGORIAN
305VERSION:2.0
306BEGIN:VEVENT
307RRULE:FREQ=WEEKLY
308DTSTART;VALUE=DATE:20111202
309END:VEVENT
310END:VCALENDAR
311';
312        $vcal = VObject\Reader::read($input);
313        $vcal->expand(
314            new \DateTime('2011-12-01'),
315            new \DateTime('2011-12-31')
316        );
317
318    }
319
320    function testGetDocumentType() {
321
322        $vcard = new VCalendar();
323        $vcard->VERSION = '2.0';
324        $this->assertEquals(VCalendar::ICALENDAR20, $vcard->getDocumentType());
325
326    }
327
328    function testValidateCorrect() {
329
330        $input = 'BEGIN:VCALENDAR
331CALSCALE:GREGORIAN
332VERSION:2.0
333PRODID:foo
334BEGIN:VEVENT
335DTSTART;VALUE=DATE:20111202
336DTSTAMP:20140122T233226Z
337UID:foo
338END:VEVENT
339END:VCALENDAR
340';
341
342        $vcal = VObject\Reader::read($input);
343        $this->assertEquals(array(), $vcal->validate(), 'Got an error');
344
345    }
346
347    function testValidateNoVersion() {
348
349        $input = 'BEGIN:VCALENDAR
350CALSCALE:GREGORIAN
351PRODID:foo
352BEGIN:VEVENT
353DTSTART;VALUE=DATE:20111202
354UID:foo
355DTSTAMP:20140122T234434Z
356END:VEVENT
357END:VCALENDAR
358';
359
360        $vcal = VObject\Reader::read($input);
361        $this->assertEquals(1, count($vcal->validate()));
362
363    }
364
365    function testValidateWrongVersion() {
366
367        $input = 'BEGIN:VCALENDAR
368CALSCALE:GREGORIAN
369VERSION:3.0
370PRODID:foo
371BEGIN:VEVENT
372DTSTART;VALUE=DATE:20111202
373UID:foo
374DTSTAMP:20140122T234434Z
375END:VEVENT
376END:VCALENDAR
377';
378
379        $vcal = VObject\Reader::read($input);
380        $this->assertEquals(1, count($vcal->validate()));
381
382    }
383
384    function testValidateNoProdId() {
385
386        $input = 'BEGIN:VCALENDAR
387CALSCALE:GREGORIAN
388VERSION:2.0
389BEGIN:VEVENT
390DTSTART;VALUE=DATE:20111202
391UID:foo
392DTSTAMP:20140122T234434Z
393END:VEVENT
394END:VCALENDAR
395';
396
397        $vcal = VObject\Reader::read($input);
398        $this->assertEquals(1, count($vcal->validate()));
399
400    }
401
402    function testValidateDoubleCalScale() {
403
404        $input = 'BEGIN:VCALENDAR
405VERSION:2.0
406PRODID:foo
407CALSCALE:GREGORIAN
408CALSCALE:GREGORIAN
409BEGIN:VEVENT
410DTSTART;VALUE=DATE:20111202
411UID:foo
412DTSTAMP:20140122T234434Z
413END:VEVENT
414END:VCALENDAR
415';
416
417        $vcal = VObject\Reader::read($input);
418        $this->assertEquals(1, count($vcal->validate()));
419
420    }
421
422    function testValidateDoubleMethod() {
423
424        $input = 'BEGIN:VCALENDAR
425VERSION:2.0
426PRODID:foo
427METHOD:REQUEST
428METHOD:REQUEST
429BEGIN:VEVENT
430DTSTART;VALUE=DATE:20111202
431UID:foo
432DTSTAMP:20140122T234434Z
433END:VEVENT
434END:VCALENDAR
435';
436
437        $vcal = VObject\Reader::read($input);
438        $this->assertEquals(1, count($vcal->validate()));
439
440    }
441
442    function testValidateTwoMasterEvents() {
443
444        $input = 'BEGIN:VCALENDAR
445VERSION:2.0
446PRODID:foo
447METHOD:REQUEST
448BEGIN:VEVENT
449DTSTART;VALUE=DATE:20111202
450UID:foo
451DTSTAMP:20140122T234434Z
452END:VEVENT
453BEGIN:VEVENT
454DTSTART;VALUE=DATE:20111202
455UID:foo
456DTSTAMP:20140122T234434Z
457END:VEVENT
458END:VCALENDAR
459';
460
461        $vcal = VObject\Reader::read($input);
462        $this->assertEquals(1, count($vcal->validate()));
463
464    }
465
466    function testValidateOneMasterEvent() {
467
468        $input = 'BEGIN:VCALENDAR
469VERSION:2.0
470PRODID:foo
471METHOD:REQUEST
472BEGIN:VEVENT
473DTSTART;VALUE=DATE:20111202
474UID:foo
475DTSTAMP:20140122T234434Z
476END:VEVENT
477BEGIN:VEVENT
478DTSTART;VALUE=DATE:20111202
479UID:foo
480DTSTAMP:20140122T234434Z
481RECURRENCE-ID;VALUE=DATE:20111202
482END:VEVENT
483END:VCALENDAR
484';
485
486        $vcal = VObject\Reader::read($input);
487        $this->assertEquals(0, count($vcal->validate()));
488
489    }
490
491    function testGetBaseComponent() {
492
493        $input = 'BEGIN:VCALENDAR
494VERSION:2.0
495PRODID:foo
496METHOD:REQUEST
497BEGIN:VEVENT
498SUMMARY:test
499DTSTART;VALUE=DATE:20111202
500UID:foo
501DTSTAMP:20140122T234434Z
502END:VEVENT
503BEGIN:VEVENT
504DTSTART;VALUE=DATE:20111202
505UID:foo
506DTSTAMP:20140122T234434Z
507RECURRENCE-ID;VALUE=DATE:20111202
508END:VEVENT
509END:VCALENDAR
510';
511
512        $vcal = VObject\Reader::read($input);
513
514        $result = $vcal->getBaseComponent();
515        $this->assertEquals('test', $result->SUMMARY->getValue());
516
517    }
518
519    function testGetBaseComponentNoResult() {
520
521        $input = 'BEGIN:VCALENDAR
522VERSION:2.0
523PRODID:foo
524METHOD:REQUEST
525BEGIN:VEVENT
526SUMMARY:test
527RECURRENCE-ID;VALUE=DATE:20111202
528DTSTART;VALUE=DATE:20111202
529UID:foo
530DTSTAMP:20140122T234434Z
531END:VEVENT
532BEGIN:VEVENT
533DTSTART;VALUE=DATE:20111202
534UID:foo
535DTSTAMP:20140122T234434Z
536RECURRENCE-ID;VALUE=DATE:20111202
537END:VEVENT
538END:VCALENDAR
539';
540
541        $vcal = VObject\Reader::read($input);
542
543        $result = $vcal->getBaseComponent();
544        $this->assertNull($result);
545
546    }
547
548    function testNoComponents() {
549
550        $input = <<<ICS
551BEGIN:VCALENDAR
552VERSION:2.0
553PRODID:vobject
554END:VCALENDAR
555ICS;
556
557        $this->assertValidate(
558            $input,
559            0,
560            3,
561           "An iCalendar object must have at least 1 component."
562        );
563
564    }
565
566    function testCalDAVNoComponents() {
567
568        $input = <<<ICS
569BEGIN:VCALENDAR
570VERSION:2.0
571PRODID:vobject
572BEGIN:VTIMEZONE
573TZID:America/Toronto
574END:VTIMEZONE
575END:VCALENDAR
576ICS;
577
578        $this->assertValidate(
579            $input,
580            VCalendar::PROFILE_CALDAV,
581            3,
582           "A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL)."
583        );
584
585    }
586
587    function testCalDAVMultiUID() {
588
589        $input = <<<ICS
590BEGIN:VCALENDAR
591VERSION:2.0
592PRODID:vobject
593BEGIN:VEVENT
594UID:foo
595DTSTAMP:20150109T184500Z
596DTSTART:20150109T184500Z
597END:VEVENT
598BEGIN:VEVENT
599UID:bar
600DTSTAMP:20150109T184500Z
601DTSTART:20150109T184500Z
602END:VEVENT
603END:VCALENDAR
604ICS;
605
606        $this->assertValidate(
607            $input,
608            VCalendar::PROFILE_CALDAV,
609            3,
610           "A calendar object on a CalDAV server may only have components with the same UID."
611        );
612
613    }
614
615    function testCalDAVMultiComponent() {
616
617        $input = <<<ICS
618BEGIN:VCALENDAR
619VERSION:2.0
620PRODID:vobject
621BEGIN:VEVENT
622UID:foo
623RECURRENCE-ID:20150109T185200Z
624DTSTAMP:20150109T184500Z
625DTSTART:20150109T184500Z
626END:VEVENT
627BEGIN:VTODO
628UID:foo
629DTSTAMP:20150109T184500Z
630DTSTART:20150109T184500Z
631END:VTODO
632END:VCALENDAR
633ICS;
634
635        $this->assertValidate(
636            $input,
637            VCalendar::PROFILE_CALDAV,
638            3,
639           "A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL)."
640        );
641
642    }
643
644    function testCalDAVMETHOD() {
645
646        $input = <<<ICS
647BEGIN:VCALENDAR
648VERSION:2.0
649METHOD:PUBLISH
650PRODID:vobject
651BEGIN:VEVENT
652UID:foo
653RECURRENCE-ID:20150109T185200Z
654DTSTAMP:20150109T184500Z
655DTSTART:20150109T184500Z
656END:VEVENT
657END:VCALENDAR
658ICS;
659
660        $this->assertValidate(
661            $input,
662            VCalendar::PROFILE_CALDAV,
663            3,
664           "A calendar object on a CalDAV server MUST NOT have a METHOD property."
665        );
666
667    }
668
669    function assertValidate($ics, $options, $expectedLevel, $expectedMessage = null) {
670
671        $vcal = VObject\Reader::read($ics);
672        $result = $vcal->validate($options);
673
674        $this->assertValidateResult($result, $expectedLevel, $expectedMessage);
675
676    }
677
678    function assertValidateResult($input, $expectedLevel, $expectedMessage = null) {
679
680        $messages = array();
681        foreach($input as $warning) {
682            $messages[] = $warning['message'];
683        }
684
685        if ($expectedLevel === 0) {
686            $this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages));
687        } else {
688            $this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages));
689
690            $this->assertEquals($expectedMessage, $input[0]['message']);
691            $this->assertEquals($expectedLevel, $input[0]['level']);
692        }
693
694    }
695
696
697}
698