1<?php
2
3namespace Sabre\VObject\Component;
4
5use DateTimeImmutable;
6use DateTimeZone;
7use Sabre\VObject;
8use Sabre\VObject\Reader;
9
10/**
11 * We use `RFCxxx` has a placeholder for the
12 * https://tools.ietf.org/html/draft-daboo-calendar-availability-05 name.
13 */
14class VAvailabilityTest extends \PHPUnit_Framework_TestCase {
15
16    function testVAvailabilityComponent() {
17
18        $vcal = <<<VCAL
19BEGIN:VCALENDAR
20BEGIN:VAVAILABILITY
21END:VAVAILABILITY
22END:VCALENDAR
23VCAL;
24        $document = Reader::read($vcal);
25
26        $this->assertInstanceOf(__NAMESPACE__ . '\VAvailability', $document->VAVAILABILITY);
27
28    }
29
30    function testGetEffectiveStartEnd() {
31
32        $vcal = <<<VCAL
33BEGIN:VCALENDAR
34BEGIN:VAVAILABILITY
35DTSTART:20150717T162200Z
36DTEND:20150717T172200Z
37END:VAVAILABILITY
38END:VCALENDAR
39VCAL;
40
41        $document = Reader::read($vcal);
42        $tz = new DateTimeZone('UTC');
43        $this->assertEquals(
44            [
45                new DateTimeImmutable('2015-07-17 16:22:00', $tz),
46                new DateTimeImmutable('2015-07-17 17:22:00', $tz),
47            ],
48            $document->VAVAILABILITY->getEffectiveStartEnd()
49        );
50
51    }
52
53    function testGetEffectiveStartDuration() {
54
55        $vcal = <<<VCAL
56BEGIN:VCALENDAR
57BEGIN:VAVAILABILITY
58DTSTART:20150717T162200Z
59DURATION:PT1H
60END:VAVAILABILITY
61END:VCALENDAR
62VCAL;
63
64        $document = Reader::read($vcal);
65        $tz = new DateTimeZone('UTC');
66        $this->assertEquals(
67            [
68                new DateTimeImmutable('2015-07-17 16:22:00', $tz),
69                new DateTimeImmutable('2015-07-17 17:22:00', $tz),
70            ],
71            $document->VAVAILABILITY->getEffectiveStartEnd()
72        );
73
74    }
75
76    function testGetEffectiveStartEndUnbound() {
77
78        $vcal = <<<VCAL
79BEGIN:VCALENDAR
80BEGIN:VAVAILABILITY
81END:VAVAILABILITY
82END:VCALENDAR
83VCAL;
84
85        $document = Reader::read($vcal);
86        $this->assertEquals(
87            [
88                null,
89                null,
90            ],
91            $document->VAVAILABILITY->getEffectiveStartEnd()
92        );
93
94    }
95
96    function testIsInTimeRangeUnbound() {
97
98        $vcal = <<<VCAL
99BEGIN:VCALENDAR
100BEGIN:VAVAILABILITY
101END:VAVAILABILITY
102END:VCALENDAR
103VCAL;
104
105        $document = Reader::read($vcal);
106        $this->assertTrue(
107            $document->VAVAILABILITY->isInTimeRange(new DateTimeImmutable('2015-07-17'), new DateTimeImmutable('2015-07-18'))
108        );
109
110    }
111
112    function testIsInTimeRangeOutside() {
113
114        $vcal = <<<VCAL
115BEGIN:VCALENDAR
116BEGIN:VAVAILABILITY
117DTSTART:20140101T000000Z
118DTEND:20140102T000000Z
119END:VAVAILABILITY
120END:VCALENDAR
121VCAL;
122
123        $document = Reader::read($vcal);
124        $this->assertFalse(
125            $document->VAVAILABILITY->isInTimeRange(new DateTimeImmutable('2015-07-17'), new DateTimeImmutable('2015-07-18'))
126        );
127
128    }
129
130    function testRFCxxxSection3_1_availabilityprop_required() {
131
132        // UID and DTSTAMP are present.
133        $this->assertIsValid(Reader::read(
134<<<VCAL
135BEGIN:VCALENDAR
136VERSION:2.0
137PRODID:-//id
138BEGIN:VAVAILABILITY
139UID:foo@test
140DTSTAMP:20111005T133225Z
141END:VAVAILABILITY
142END:VCALENDAR
143VCAL
144        ));
145
146        // UID and DTSTAMP are missing.
147        $this->assertIsNotValid(Reader::read(
148<<<VCAL
149BEGIN:VCALENDAR
150VERSION:2.0
151PRODID:-//id
152BEGIN:VAVAILABILITY
153END:VAVAILABILITY
154END:VCALENDAR
155VCAL
156        ));
157
158        // DTSTAMP is missing.
159        $this->assertIsNotValid(Reader::read(
160<<<VCAL
161BEGIN:VCALENDAR
162VERSION:2.0
163PRODID:-//id
164BEGIN:VAVAILABILITY
165UID:foo@test
166END:VAVAILABILITY
167END:VCALENDAR
168VCAL
169        ));
170
171        // UID is missing.
172        $this->assertIsNotValid(Reader::read(
173<<<VCAL
174BEGIN:VCALENDAR
175VERSION:2.0
176PRODID:-//id
177BEGIN:VAVAILABILITY
178DTSTAMP:20111005T133225Z
179END:VAVAILABILITY
180END:VCALENDAR
181VCAL
182        ));
183
184    }
185
186    function testRFCxxxSection3_1_availabilityprop_optional_once() {
187
188        $properties = [
189            'BUSYTYPE:BUSY',
190            'CLASS:PUBLIC',
191            'CREATED:20111005T135125Z',
192            'DESCRIPTION:Long bla bla',
193            'DTSTART:20111005T020000',
194            'LAST-MODIFIED:20111005T135325Z',
195            'ORGANIZER:mailto:foo@example.com',
196            'PRIORITY:1',
197            'SEQUENCE:0',
198            'SUMMARY:Bla bla',
199            'URL:http://example.org/'
200        ];
201
202        // They are all present, only once.
203        $this->assertIsValid(Reader::read($this->template($properties)));
204
205        // We duplicate each one to see if it fails.
206        foreach ($properties as $property) {
207            $this->assertIsNotValid(Reader::read($this->template([
208                $property,
209                $property
210            ])));
211        }
212
213    }
214
215    function testRFCxxxSection3_1_availabilityprop_dtend_duration() {
216
217        // Only DTEND.
218        $this->assertIsValid(Reader::read($this->template([
219            'DTEND:21111005T133225Z'
220        ])));
221
222        // Only DURATION.
223        $this->assertIsValid(Reader::read($this->template([
224            'DURATION:PT1H'
225        ])));
226
227        // Both (not allowed).
228        $this->assertIsNotValid(Reader::read($this->template([
229            'DTEND:21111005T133225Z',
230            'DURATION:PT1H'
231        ])));
232    }
233
234    function testAvailableSubComponent() {
235
236        $vcal = <<<VCAL
237BEGIN:VCALENDAR
238BEGIN:VAVAILABILITY
239BEGIN:AVAILABLE
240END:AVAILABLE
241END:VAVAILABILITY
242END:VCALENDAR
243VCAL;
244        $document = Reader::read($vcal);
245
246        $this->assertInstanceOf(__NAMESPACE__, $document->VAVAILABILITY->AVAILABLE);
247
248    }
249
250    function testRFCxxxSection3_1_availableprop_required() {
251
252        // UID, DTSTAMP and DTSTART are present.
253        $this->assertIsValid(Reader::read(
254<<<VCAL
255BEGIN:VCALENDAR
256VERSION:2.0
257PRODID:-//id
258BEGIN:VAVAILABILITY
259UID:foo@test
260DTSTAMP:20111005T133225Z
261BEGIN:AVAILABLE
262UID:foo@test
263DTSTAMP:20111005T133225Z
264DTSTART:20111005T133225Z
265END:AVAILABLE
266END:VAVAILABILITY
267END:VCALENDAR
268VCAL
269        ));
270
271        // UID, DTSTAMP and DTSTART are missing.
272        $this->assertIsNotValid(Reader::read(
273<<<VCAL
274BEGIN:VCALENDAR
275VERSION:2.0
276PRODID:-//id
277BEGIN:VAVAILABILITY
278UID:foo@test
279DTSTAMP:20111005T133225Z
280BEGIN:AVAILABLE
281END:AVAILABLE
282END:VAVAILABILITY
283END:VCALENDAR
284VCAL
285        ));
286
287        // UID is missing.
288        $this->assertIsNotValid(Reader::read(
289<<<VCAL
290BEGIN:VCALENDAR
291VERSION:2.0
292PRODID:-//id
293BEGIN:VAVAILABILITY
294UID:foo@test
295DTSTAMP:20111005T133225Z
296BEGIN:AVAILABLE
297DTSTAMP:20111005T133225Z
298DTSTART:20111005T133225Z
299END:AVAILABLE
300END:VAVAILABILITY
301END:VCALENDAR
302VCAL
303        ));
304
305        // DTSTAMP is missing.
306        $this->assertIsNotValid(Reader::read(
307<<<VCAL
308BEGIN:VCALENDAR
309VERSION:2.0
310PRODID:-//id
311BEGIN:VAVAILABILITY
312UID:foo@test
313DTSTAMP:20111005T133225Z
314BEGIN:AVAILABLE
315UID:foo@test
316DTSTART:20111005T133225Z
317END:AVAILABLE
318END:VAVAILABILITY
319END:VCALENDAR
320VCAL
321        ));
322
323        // DTSTART is missing.
324        $this->assertIsNotValid(Reader::read(
325<<<VCAL
326BEGIN:VCALENDAR
327VERSION:2.0
328PRODID:-//id
329BEGIN:VAVAILABILITY
330UID:foo@test
331DTSTAMP:20111005T133225Z
332BEGIN:AVAILABLE
333UID:foo@test
334DTSTAMP:20111005T133225Z
335END:AVAILABLE
336END:VAVAILABILITY
337END:VCALENDAR
338VCAL
339        ));
340
341    }
342
343    function testRFCxxxSection3_1_available_dtend_duration() {
344
345        // Only DTEND.
346        $this->assertIsValid(Reader::read($this->templateAvailable([
347            'DTEND:21111005T133225Z'
348        ])));
349
350        // Only DURATION.
351        $this->assertIsValid(Reader::read($this->templateAvailable([
352            'DURATION:PT1H'
353        ])));
354
355        // Both (not allowed).
356        $this->assertIsNotValid(Reader::read($this->templateAvailable([
357            'DTEND:21111005T133225Z',
358            'DURATION:PT1H'
359        ])));
360    }
361
362    function testRFCxxxSection3_1_available_optional_once() {
363
364        $properties = [
365            'CREATED:20111005T135125Z',
366            'DESCRIPTION:Long bla bla',
367            'LAST-MODIFIED:20111005T135325Z',
368            'RECURRENCE-ID;RANGE=THISANDFUTURE:19980401T133000Z',
369            'RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR',
370            'SUMMARY:Bla bla'
371        ];
372
373        // They are all present, only once.
374        $this->assertIsValid(Reader::read($this->templateAvailable($properties)));
375
376        // We duplicate each one to see if it fails.
377        foreach ($properties as $property) {
378            $this->assertIsNotValid(Reader::read($this->templateAvailable([
379                $property,
380                $property
381            ])));
382        }
383
384    }
385    function testRFCxxxSection3_2() {
386
387        $this->assertEquals(
388            'BUSY',
389            Reader::read($this->templateAvailable([
390                'BUSYTYPE:BUSY'
391            ]))
392                ->VAVAILABILITY
393                ->AVAILABLE
394                ->BUSYTYPE
395                ->getValue()
396        );
397
398        $this->assertEquals(
399            'BUSY-UNAVAILABLE',
400            Reader::read($this->templateAvailable([
401                'BUSYTYPE:BUSY-UNAVAILABLE'
402            ]))
403                ->VAVAILABILITY
404                ->AVAILABLE
405                ->BUSYTYPE
406                ->getValue()
407        );
408
409        $this->assertEquals(
410            'BUSY-TENTATIVE',
411            Reader::read($this->templateAvailable([
412                'BUSYTYPE:BUSY-TENTATIVE'
413            ]))
414                ->VAVAILABILITY
415                ->AVAILABLE
416                ->BUSYTYPE
417                ->getValue()
418        );
419
420    }
421
422    protected function assertIsValid(VObject\Document $document) {
423
424        $validationResult = $document->validate();
425        if ($validationResult) {
426            $messages = array_map(function($item) { return $item['message']; }, $validationResult);
427            $this->fail('Failed to assert that the supplied document is a valid document. Validation messages: ' . implode(', ', $messages));
428        }
429        $this->assertEmpty($document->validate());
430
431    }
432
433    protected function assertIsNotValid(VObject\Document $document) {
434
435        $this->assertNotEmpty($document->validate());
436
437    }
438
439    protected function template(array $properties) {
440
441        return $this->_template(
442            <<<VCAL
443BEGIN:VCALENDAR
444VERSION:2.0
445PRODID:-//id
446BEGIN:VAVAILABILITY
447UID:foo@test
448DTSTAMP:20111005T133225Z
449
450END:VAVAILABILITY
451END:VCALENDAR
452VCAL
453,
454            $properties
455        );
456
457    }
458
459    protected function templateAvailable(array $properties) {
460
461        return $this->_template(
462            <<<VCAL
463BEGIN:VCALENDAR
464VERSION:2.0
465PRODID:-//id
466BEGIN:VAVAILABILITY
467UID:foo@test
468DTSTAMP:20111005T133225Z
469BEGIN:AVAILABLE
470UID:foo@test
471DTSTAMP:20111005T133225Z
472DTSTART:20111005T133225Z
473
474END:AVAILABLE
475END:VAVAILABILITY
476END:VCALENDAR
477VCAL
478,
479            $properties
480        );
481
482    }
483
484    protected function _template($template, array $properties) {
485
486        return str_replace('', implode("\r\n", $properties), $template);
487
488    }
489
490}
491