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