1<?php
2
3namespace Sabre\CalDAV\Schedule;
4
5use Sabre\HTTP\Request;
6
7class ScheduleDeliverTest extends \Sabre\DAVServerTest {
8
9    public $setupCalDAV = true;
10    public $setupCalDAVScheduling = true;
11    public $setupACL = true;
12    public $autoLogin = 'user1';
13
14    public $caldavCalendars = [
15        [
16            'principaluri' => 'principals/user1',
17            'uri' => 'cal',
18        ],
19        [
20            'principaluri' => 'principals/user2',
21            'uri' => 'cal',
22        ],
23    ];
24
25    function setUp() {
26
27        $this->calendarObjectUri = '/calendars/user1/cal/object.ics';
28
29        parent::setUp();
30
31    }
32
33    function testNewInvite() {
34
35        $newObject = <<<ICS
36BEGIN:VCALENDAR
37BEGIN:VEVENT
38UID:foo
39DTSTART:20140811T230000Z
40ORGANIZER:mailto:user1.sabredav@sabredav.org
41ATTENDEE:mailto:user2.sabredav@sabredav.org
42END:VEVENT
43END:VCALENDAR
44ICS;
45
46        $this->deliver(null, $newObject);
47        $this->assertItemsInInbox('user2', 1);
48
49        $expected = <<<ICS
50BEGIN:VCALENDAR
51BEGIN:VEVENT
52UID:foo
53DTSTART:20140811T230000Z
54ORGANIZER:mailto:user1.sabredav@sabredav.org
55ATTENDEE;SCHEDULE-STATUS=1.2:mailto:user2.sabredav@sabredav.org
56END:VEVENT
57END:VCALENDAR
58ICS;
59
60        $this->assertVObjEquals(
61            $expected,
62            $newObject
63        );
64
65    }
66
67    function testNewOnWrongCollection() {
68
69        $newObject = <<<ICS
70BEGIN:VCALENDAR
71BEGIN:VEVENT
72UID:foo
73DTSTART:20140811T230000Z
74ORGANIZER:mailto:user1.sabredav@sabredav.org
75ATTENDEE:mailto:user2.sabredav@sabredav.org
76END:VEVENT
77END:VCALENDAR
78ICS;
79
80        $this->calendarObjectUri = '/calendars/user1/object.ics';
81        $this->deliver(null, $newObject);
82        $this->assertItemsInInbox('user2', 0);
83
84
85    }
86    function testNewInviteSchedulingDisabled() {
87
88        $newObject = <<<ICS
89BEGIN:VCALENDAR
90BEGIN:VEVENT
91UID:foo
92DTSTART:20140811T230000Z
93ORGANIZER:mailto:user1.sabredav@sabredav.org
94ATTENDEE:mailto:user2.sabredav@sabredav.org
95END:VEVENT
96END:VCALENDAR
97ICS;
98
99        $this->deliver(null, $newObject, true);
100        $this->assertItemsInInbox('user2', 0);
101
102    }
103    function testUpdatedInvite() {
104
105        $newObject = <<<ICS
106BEGIN:VCALENDAR
107BEGIN:VEVENT
108UID:foo
109DTSTART:20140811T230000Z
110ORGANIZER:mailto:user1.sabredav@sabredav.org
111ATTENDEE:mailto:user2.sabredav@sabredav.org
112END:VEVENT
113END:VCALENDAR
114ICS;
115        $oldObject = <<<ICS
116BEGIN:VCALENDAR
117BEGIN:VEVENT
118UID:foo
119DTSTART:20140811T230000Z
120ORGANIZER:mailto:user1.sabredav@sabredav.org
121END:VEVENT
122END:VCALENDAR
123ICS;
124
125        $this->deliver($oldObject, $newObject);
126        $this->assertItemsInInbox('user2', 1);
127
128        $expected = <<<ICS
129BEGIN:VCALENDAR
130BEGIN:VEVENT
131UID:foo
132DTSTART:20140811T230000Z
133ORGANIZER:mailto:user1.sabredav@sabredav.org
134ATTENDEE;SCHEDULE-STATUS=1.2:mailto:user2.sabredav@sabredav.org
135END:VEVENT
136END:VCALENDAR
137ICS;
138
139        $this->assertVObjEquals(
140            $expected,
141            $newObject
142        );
143
144
145    }
146    function testUpdatedInviteSchedulingDisabled() {
147
148        $newObject = <<<ICS
149BEGIN:VCALENDAR
150BEGIN:VEVENT
151UID:foo
152DTSTART:20140811T230000Z
153ORGANIZER:mailto:user1.sabredav@sabredav.org
154ATTENDEE:mailto:user2.sabredav@sabredav.org
155END:VEVENT
156END:VCALENDAR
157ICS;
158        $oldObject = <<<ICS
159BEGIN:VCALENDAR
160BEGIN:VEVENT
161UID:foo
162DTSTART:20140811T230000Z
163ORGANIZER:mailto:user1.sabredav@sabredav.org
164END:VEVENT
165END:VCALENDAR
166ICS;
167
168        $this->deliver($oldObject, $newObject, true);
169        $this->assertItemsInInbox('user2', 0);
170
171    }
172
173    function testUpdatedInviteWrongPath() {
174
175        $newObject = <<<ICS
176BEGIN:VCALENDAR
177BEGIN:VEVENT
178UID:foo
179DTSTART:20140811T230000Z
180ORGANIZER:mailto:user1.sabredav@sabredav.org
181ATTENDEE:mailto:user2.sabredav@sabredav.org
182END:VEVENT
183END:VCALENDAR
184ICS;
185        $oldObject = <<<ICS
186BEGIN:VCALENDAR
187BEGIN:VEVENT
188UID:foo
189DTSTART:20140811T230000Z
190ORGANIZER:mailto:user1.sabredav@sabredav.org
191END:VEVENT
192END:VCALENDAR
193ICS;
194
195        $this->calendarObjectUri = '/calendars/user1/inbox/foo.ics';
196        $this->deliver($oldObject, $newObject);
197        $this->assertItemsInInbox('user2', 0);
198
199    }
200
201    function testDeletedInvite() {
202
203        $newObject = null;
204
205        $oldObject = <<<ICS
206BEGIN:VCALENDAR
207BEGIN:VEVENT
208UID:foo
209DTSTART:20140811T230000Z
210ORGANIZER:mailto:user1.sabredav@sabredav.org
211ATTENDEE:mailto:user2.sabredav@sabredav.org
212END:VEVENT
213END:VCALENDAR
214ICS;
215
216        $this->deliver($oldObject, $newObject);
217        $this->assertItemsInInbox('user2', 1);
218
219    }
220
221    function testDeletedInviteSchedulingDisabled() {
222
223        $newObject = null;
224
225        $oldObject = <<<ICS
226BEGIN:VCALENDAR
227BEGIN:VEVENT
228UID:foo
229DTSTART:20140811T230000Z
230ORGANIZER:mailto:user1.sabredav@sabredav.org
231ATTENDEE:mailto:user2.sabredav@sabredav.org
232END:VEVENT
233END:VCALENDAR
234ICS;
235
236        $this->deliver($oldObject, $newObject, true);
237        $this->assertItemsInInbox('user2', 0);
238
239    }
240
241    /**
242     * A MOVE request will trigger an unbind on a scheduling resource.
243     *
244     * However, we must not treat it as a cancellation, it just got moved to a
245     * different calendar.
246     */
247    function testUnbindIgnoredOnMove() {
248
249        $newObject = null;
250
251        $oldObject = <<<ICS
252BEGIN:VCALENDAR
253BEGIN:VEVENT
254UID:foo
255DTSTART:20140811T230000Z
256ORGANIZER:mailto:user1.sabredav@sabredav.org
257ATTENDEE:mailto:user2.sabredav@sabredav.org
258END:VEVENT
259END:VCALENDAR
260ICS;
261
262
263        $this->server->httpRequest->setMethod('MOVE');
264        $this->deliver($oldObject, $newObject);
265        $this->assertItemsInInbox('user2', 0);
266
267    }
268
269    function testDeletedInviteWrongUrl() {
270
271        $newObject = null;
272
273        $oldObject = <<<ICS
274BEGIN:VCALENDAR
275BEGIN:VEVENT
276UID:foo
277DTSTART:20140811T230000Z
278ORGANIZER:mailto:user1.sabredav@sabredav.org
279ATTENDEE:mailto:user2.sabredav@sabredav.org
280END:VEVENT
281END:VCALENDAR
282ICS;
283
284        $this->calendarObjectUri = '/calendars/user1/inbox/foo.ics';
285        $this->deliver($oldObject, $newObject);
286        $this->assertItemsInInbox('user2', 0);
287
288    }
289
290    function testReply() {
291
292        $oldObject = <<<ICS
293BEGIN:VCALENDAR
294BEGIN:VEVENT
295UID:foo
296DTSTART:20140811T230000Z
297ORGANIZER:mailto:user2.sabredav@sabredav.org
298ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2.sabredav@sabredav.org
299ATTENDEE:mailto:user1.sabredav@sabredav.org
300ATTENDEE:mailto:user3.sabredav@sabredav.org
301END:VEVENT
302END:VCALENDAR
303ICS;
304
305        $newObject = <<<ICS
306BEGIN:VCALENDAR
307BEGIN:VEVENT
308UID:foo
309DTSTART:20140811T230000Z
310ORGANIZER:mailto:user2.sabredav@sabredav.org
311ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2.sabredav@sabredav.org
312ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1.sabredav@sabredav.org
313ATTENDEE:mailto:user3.sabredav@sabredav.org
314END:VEVENT
315END:VCALENDAR
316ICS;
317
318        $this->putPath('calendars/user2/cal/foo.ics', $oldObject);
319
320        $this->deliver($oldObject, $newObject);
321        $this->assertItemsInInbox('user2', 1);
322        $this->assertItemsInInbox('user1', 0);
323
324        $expected = <<<ICS
325BEGIN:VCALENDAR
326BEGIN:VEVENT
327UID:foo
328DTSTART:20140811T230000Z
329ORGANIZER;SCHEDULE-STATUS=1.2:mailto:user2.sabredav@sabredav.org
330ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2.sabredav@sabredav.org
331ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1.sabredav@sabredav.org
332ATTENDEE:mailto:user3.sabredav@sabredav.org
333END:VEVENT
334END:VCALENDAR
335ICS;
336
337        $this->assertVObjEquals(
338            $expected,
339            $newObject
340        );
341
342    }
343
344
345
346    function testInviteUnknownUser() {
347
348        $newObject = <<<ICS
349BEGIN:VCALENDAR
350BEGIN:VEVENT
351UID:foo
352DTSTART:20140811T230000Z
353ORGANIZER:mailto:user1.sabredav@sabredav.org
354ATTENDEE:mailto:user3.sabredav@sabredav.org
355END:VEVENT
356END:VCALENDAR
357ICS;
358
359        $this->deliver(null, $newObject);
360
361        $expected = <<<ICS
362BEGIN:VCALENDAR
363BEGIN:VEVENT
364UID:foo
365DTSTART:20140811T230000Z
366ORGANIZER:mailto:user1.sabredav@sabredav.org
367ATTENDEE;SCHEDULE-STATUS=3.7:mailto:user3.sabredav@sabredav.org
368END:VEVENT
369END:VCALENDAR
370ICS;
371
372        $this->assertVObjEquals(
373            $expected,
374            $newObject
375        );
376
377    }
378
379    function testInviteNoInboxUrl() {
380
381        $newObject = <<<ICS
382BEGIN:VCALENDAR
383BEGIN:VEVENT
384UID:foo
385DTSTART:20140811T230000Z
386ORGANIZER:mailto:user1.sabredav@sabredav.org
387ATTENDEE:mailto:user2.sabredav@sabredav.org
388END:VEVENT
389END:VCALENDAR
390ICS;
391
392        $this->server->on('propFind', function($propFind) {
393            $propFind->set('{' . Plugin::NS_CALDAV . '}schedule-inbox-URL', null, 403);
394        });
395        $this->deliver(null, $newObject);
396
397        $expected = <<<ICS
398BEGIN:VCALENDAR
399BEGIN:VEVENT
400UID:foo
401DTSTART:20140811T230000Z
402ORGANIZER:mailto:user1.sabredav@sabredav.org
403ATTENDEE;SCHEDULE-STATUS=5.2:mailto:user2.sabredav@sabredav.org
404END:VEVENT
405END:VCALENDAR
406ICS;
407
408        $this->assertVObjEquals(
409            $expected,
410            $newObject
411        );
412
413    }
414
415    function testInviteNoCalendarHomeSet() {
416
417        $newObject = <<<ICS
418BEGIN:VCALENDAR
419BEGIN:VEVENT
420UID:foo
421DTSTART:20140811T230000Z
422ORGANIZER:mailto:user1.sabredav@sabredav.org
423ATTENDEE:mailto:user2.sabredav@sabredav.org
424END:VEVENT
425END:VCALENDAR
426ICS;
427
428        $this->server->on('propFind', function($propFind) {
429            $propFind->set('{' . Plugin::NS_CALDAV . '}calendar-home-set', null, 403);
430        });
431        $this->deliver(null, $newObject);
432
433        $expected = <<<ICS
434BEGIN:VCALENDAR
435BEGIN:VEVENT
436UID:foo
437DTSTART:20140811T230000Z
438ORGANIZER:mailto:user1.sabredav@sabredav.org
439ATTENDEE;SCHEDULE-STATUS=5.2:mailto:user2.sabredav@sabredav.org
440END:VEVENT
441END:VCALENDAR
442ICS;
443
444        $this->assertVObjEquals(
445            $expected,
446            $newObject
447        );
448
449    }
450    function testInviteNoDefaultCalendar() {
451
452        $newObject = <<<ICS
453BEGIN:VCALENDAR
454BEGIN:VEVENT
455UID:foo
456DTSTART:20140811T230000Z
457ORGANIZER:mailto:user1.sabredav@sabredav.org
458ATTENDEE:mailto:user2.sabredav@sabredav.org
459END:VEVENT
460END:VCALENDAR
461ICS;
462
463        $this->server->on('propFind', function($propFind) {
464            $propFind->set('{' . Plugin::NS_CALDAV . '}schedule-default-calendar-URL', null, 403);
465        });
466        $this->deliver(null, $newObject);
467
468        $expected = <<<ICS
469BEGIN:VCALENDAR
470BEGIN:VEVENT
471UID:foo
472DTSTART:20140811T230000Z
473ORGANIZER:mailto:user1.sabredav@sabredav.org
474ATTENDEE;SCHEDULE-STATUS=5.2:mailto:user2.sabredav@sabredav.org
475END:VEVENT
476END:VCALENDAR
477ICS;
478
479        $this->assertVObjEquals(
480            $expected,
481            $newObject
482        );
483
484    }
485    function testInviteNoScheduler() {
486
487        $newObject = <<<ICS
488BEGIN:VCALENDAR
489BEGIN:VEVENT
490UID:foo
491DTSTART:20140811T230000Z
492ORGANIZER:mailto:user1.sabredav@sabredav.org
493ATTENDEE:mailto:user2.sabredav@sabredav.org
494END:VEVENT
495END:VCALENDAR
496ICS;
497
498        $this->server->removeAllListeners('schedule');
499        $this->deliver(null, $newObject);
500
501        $expected = <<<ICS
502BEGIN:VCALENDAR
503BEGIN:VEVENT
504UID:foo
505DTSTART:20140811T230000Z
506ORGANIZER:mailto:user1.sabredav@sabredav.org
507ATTENDEE;SCHEDULE-STATUS=5.2:mailto:user2.sabredav@sabredav.org
508END:VEVENT
509END:VCALENDAR
510ICS;
511
512        $this->assertVObjEquals(
513            $expected,
514            $newObject
515        );
516
517    }
518    function testInviteNoACLPlugin() {
519
520        $this->setupACL = false;
521        parent::setUp();
522
523        $newObject = <<<ICS
524BEGIN:VCALENDAR
525BEGIN:VEVENT
526UID:foo
527DTSTART:20140811T230000Z
528ORGANIZER:mailto:user1.sabredav@sabredav.org
529ATTENDEE:mailto:user2.sabredav@sabredav.org
530END:VEVENT
531END:VCALENDAR
532ICS;
533
534        $this->deliver(null, $newObject);
535
536        $expected = <<<ICS
537BEGIN:VCALENDAR
538BEGIN:VEVENT
539UID:foo
540DTSTART:20140811T230000Z
541ORGANIZER:mailto:user1.sabredav@sabredav.org
542ATTENDEE;SCHEDULE-STATUS=5.2:mailto:user2.sabredav@sabredav.org
543END:VEVENT
544END:VCALENDAR
545ICS;
546
547        $this->assertVObjEquals(
548            $expected,
549            $newObject
550        );
551
552    }
553
554    protected $calendarObjectUri;
555
556    function deliver($oldObject, &$newObject, $disableScheduling = false) {
557
558        $this->server->httpRequest->setUrl($this->calendarObjectUri);
559        if ($disableScheduling) {
560            $this->server->httpRequest->setHeader('Schedule-Reply','F');
561        }
562
563        if ($oldObject && $newObject) {
564            // update
565            $this->putPath($this->calendarObjectUri, $oldObject);
566
567            $stream = fopen('php://memory','r+');
568            fwrite($stream, $newObject);
569            rewind($stream);
570            $modified = false;
571
572            $this->server->emit('beforeWriteContent', [
573                $this->calendarObjectUri,
574                $this->server->tree->getNodeForPath($this->calendarObjectUri),
575                &$stream,
576                &$modified
577            ]);
578            if ($modified) {
579                $newObject = $stream;
580            }
581
582        } elseif ($oldObject && !$newObject) {
583            // delete
584            $this->putPath($this->calendarObjectUri, $oldObject);
585
586            $this->caldavSchedulePlugin->beforeUnbind(
587                $this->calendarObjectUri
588            );
589        } else {
590
591            // create
592            $stream = fopen('php://memory','r+');
593            fwrite($stream, $newObject);
594            rewind($stream);
595            $modified = false;
596            $this->server->emit('beforeCreateFile', [
597                $this->calendarObjectUri,
598                &$stream,
599                $this->server->tree->getNodeForPath(dirname($this->calendarObjectUri)),
600                &$modified
601            ]);
602
603            if ($modified) {
604                $newObject = $stream;
605            }
606        }
607
608    }
609
610
611    /**
612     * Creates or updates a node at the specified path.
613     *
614     * This circumvents sabredav's internal server apis, so all events and
615     * access control is skipped.
616     *
617     * @param string $path
618     * @param string $data
619     * @return void
620     */
621    function putPath($path, $data) {
622
623        list($parent, $base) = \Sabre\HTTP\UrlUtil::splitPath($path);
624        $parentNode = $this->server->tree->getNodeForPath($parent);
625
626        /*
627        if ($parentNode->childExists($base)) {
628            $childNode = $parentNode->getChild($base);
629            $childNode->put($data);
630        } else {*/
631            $parentNode->createFile($base, $data);
632        //}
633
634    }
635
636    function assertItemsInInbox($user, $count) {
637
638        $inboxNode = $this->server->tree->getNodeForPath('calendars/'.$user.'/inbox');
639        $this->assertEquals($count, count($inboxNode->getChildren()));
640
641    }
642
643    function assertVObjEquals($expected, $actual) {
644
645        $format = function($data) {
646
647            $data = trim($data, "\r\n");
648            $data = str_replace("\r","", $data);
649            // Unfolding lines.
650            $data = str_replace("\n ", "", $data);
651
652            return $data;
653
654        };
655
656        $this->assertEquals(
657            $format($expected),
658            $format($actual)
659        );
660
661    }
662
663}
664
665