1<?php
2
3namespace Sabre\CalDAV;
4
5use Sabre\DAV;
6use Sabre\DAV\Exception\NotFound;
7use Sabre\DAV\MkCol;
8use Sabre\DAVACL;
9use Sabre\HTTP\URLUtil;
10
11/**
12 * The CalendarHome represents a node that is usually in a users'
13 * calendar-homeset.
14 *
15 * It contains all the users' calendars, and can optionally contain a
16 * notifications collection, calendar subscriptions, a users' inbox, and a
17 * users' outbox.
18 *
19 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
20 * @author Evert Pot (http://evertpot.com/)
21 * @license http://sabre.io/license/ Modified BSD License
22 */
23class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
24
25    /**
26     * CalDAV backend
27     *
28     * @var Sabre\CalDAV\Backend\BackendInterface
29     */
30    protected $caldavBackend;
31
32    /**
33     * Principal information
34     *
35     * @var array
36     */
37    protected $principalInfo;
38
39    /**
40     * Constructor
41     *
42     * @param Backend\BackendInterface $caldavBackend
43     * @param mixed $userUri
44     */
45    function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) {
46
47        $this->caldavBackend = $caldavBackend;
48        $this->principalInfo = $principalInfo;
49
50    }
51
52    /**
53     * Returns the name of this object
54     *
55     * @return string
56     */
57    function getName() {
58
59        list(, $name) = URLUtil::splitPath($this->principalInfo['uri']);
60        return $name;
61
62    }
63
64    /**
65     * Updates the name of this object
66     *
67     * @param string $name
68     * @return void
69     */
70    function setName($name) {
71
72        throw new DAV\Exception\Forbidden();
73
74    }
75
76    /**
77     * Deletes this object
78     *
79     * @return void
80     */
81    function delete() {
82
83        throw new DAV\Exception\Forbidden();
84
85    }
86
87    /**
88     * Returns the last modification date
89     *
90     * @return int
91     */
92    function getLastModified() {
93
94        return null;
95
96    }
97
98    /**
99     * Creates a new file under this object.
100     *
101     * This is currently not allowed
102     *
103     * @param string $filename
104     * @param resource $data
105     * @return void
106     */
107    function createFile($filename, $data = null) {
108
109        throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');
110
111    }
112
113    /**
114     * Creates a new directory under this object.
115     *
116     * This is currently not allowed.
117     *
118     * @param string $filename
119     * @return void
120     */
121    function createDirectory($filename) {
122
123        throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
124
125    }
126
127    /**
128     * Returns a single calendar, by name
129     *
130     * @param string $name
131     * @return Calendar
132     */
133    function getChild($name) {
134
135        // Special nodes
136        if ($name === 'inbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
137            return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
138        }
139        if ($name === 'outbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
140            return new Schedule\Outbox($this->principalInfo['uri']);
141        }
142        if ($name === 'notifications' && $this->caldavBackend instanceof Backend\NotificationSupport) {
143            return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
144        }
145
146        // Calendars
147        foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
148            if ($calendar['uri'] === $name) {
149                if ($this->caldavBackend instanceof Backend\SharingSupport) {
150                    if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
151                        return new SharedCalendar($this->caldavBackend, $calendar);
152                    } else {
153                        return new ShareableCalendar($this->caldavBackend, $calendar);
154                    }
155                } else {
156                    return new Calendar($this->caldavBackend, $calendar);
157                }
158            }
159        }
160
161        if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
162            foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
163                if ($subscription['uri'] === $name) {
164                    return new Subscriptions\Subscription($this->caldavBackend, $subscription);
165                }
166            }
167
168        }
169
170        throw new NotFound('Node with name \'' . $name . '\' could not be found');
171
172    }
173
174    /**
175     * Checks if a calendar exists.
176     *
177     * @param string $name
178     * @return bool
179     */
180    function childExists($name) {
181
182        try {
183            return !!$this->getChild($name);
184        } catch (NotFound $e) {
185            return false;
186        }
187
188    }
189
190    /**
191     * Returns a list of calendars
192     *
193     * @return array
194     */
195    function getChildren() {
196
197        $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
198        $objs = [];
199        foreach ($calendars as $calendar) {
200            if ($this->caldavBackend instanceof Backend\SharingSupport) {
201                if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
202                    $objs[] = new SharedCalendar($this->caldavBackend, $calendar);
203                } else {
204                    $objs[] = new ShareableCalendar($this->caldavBackend, $calendar);
205                }
206            } else {
207                $objs[] = new Calendar($this->caldavBackend, $calendar);
208            }
209        }
210
211        if ($this->caldavBackend instanceof Backend\SchedulingSupport) {
212            $objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
213            $objs[] = new Schedule\Outbox($this->principalInfo['uri']);
214        }
215
216        // We're adding a notifications node, if it's supported by the backend.
217        if ($this->caldavBackend instanceof Backend\NotificationSupport) {
218            $objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
219        }
220
221        // If the backend supports subscriptions, we'll add those as well,
222        if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
223            foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
224                $objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription);
225            }
226        }
227
228        return $objs;
229
230    }
231
232    /**
233     * Creates a new calendar or subscription.
234     *
235     * @param string $name
236     * @param MkCol $mkCol
237     * @throws DAV\Exception\InvalidResourceType
238     * @return void
239     */
240    function createExtendedCollection($name, MkCol $mkCol) {
241
242        $isCalendar = false;
243        $isSubscription = false;
244        foreach ($mkCol->getResourceType() as $rt) {
245            switch ($rt) {
246                case '{DAV:}collection' :
247                case '{http://calendarserver.org/ns/}shared-owner' :
248                    // ignore
249                    break;
250                case '{urn:ietf:params:xml:ns:caldav}calendar' :
251                    $isCalendar = true;
252                    break;
253                case '{http://calendarserver.org/ns/}subscribed' :
254                    $isSubscription = true;
255                    break;
256                default :
257                    throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt);
258            }
259        }
260
261        $properties = $mkCol->getRemainingValues();
262        $mkCol->setRemainingResultCode(201);
263
264        if ($isSubscription) {
265            if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) {
266                throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions');
267            }
268            $this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties);
269
270        } elseif ($isCalendar) {
271            $this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
272
273        } else {
274            throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection');
275
276        }
277
278    }
279
280    /**
281     * Returns the owner principal
282     *
283     * This must be a url to a principal, or null if there's no owner
284     *
285     * @return string|null
286     */
287    function getOwner() {
288
289        return $this->principalInfo['uri'];
290
291    }
292
293    /**
294     * Returns a group principal
295     *
296     * This must be a url to a principal, or null if there's no owner
297     *
298     * @return string|null
299     */
300    function getGroup() {
301
302        return null;
303
304    }
305
306    /**
307     * Returns a list of ACE's for this node.
308     *
309     * Each ACE has the following properties:
310     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
311     *     currently the only supported privileges
312     *   * 'principal', a url to the principal who owns the node
313     *   * 'protected' (optional), indicating that this ACE is not allowed to
314     *      be updated.
315     *
316     * @return array
317     */
318    function getACL() {
319
320        return [
321            [
322                'privilege' => '{DAV:}read',
323                'principal' => $this->principalInfo['uri'],
324                'protected' => true,
325            ],
326            [
327                'privilege' => '{DAV:}write',
328                'principal' => $this->principalInfo['uri'],
329                'protected' => true,
330            ],
331            [
332                'privilege' => '{DAV:}read',
333                'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
334                'protected' => true,
335            ],
336            [
337                'privilege' => '{DAV:}write',
338                'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
339                'protected' => true,
340            ],
341            [
342                'privilege' => '{DAV:}read',
343                'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read',
344                'protected' => true,
345            ],
346
347        ];
348
349    }
350
351    /**
352     * Updates the ACL
353     *
354     * This method will receive a list of new ACE's.
355     *
356     * @param array $acl
357     * @return void
358     */
359    function setACL(array $acl) {
360
361        throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
362
363    }
364
365    /**
366     * Returns the list of supported privileges for this node.
367     *
368     * The returned data structure is a list of nested privileges.
369     * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
370     * standard structure.
371     *
372     * If null is returned from this method, the default privilege set is used,
373     * which is fine for most common usecases.
374     *
375     * @return array|null
376     */
377    function getSupportedPrivilegeSet() {
378
379        return null;
380
381    }
382
383    /**
384     * This method is called when a user replied to a request to share.
385     *
386     * This method should return the url of the newly created calendar if the
387     * share was accepted.
388     *
389     * @param string href The sharee who is replying (often a mailto: address)
390     * @param int status One of the SharingPlugin::STATUS_* constants
391     * @param string $calendarUri The url to the calendar thats being shared
392     * @param string $inReplyTo The unique id this message is a response to
393     * @param string $summary A description of the reply
394     * @return null|string
395     */
396    function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) {
397
398        if (!$this->caldavBackend instanceof Backend\SharingSupport) {
399            throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.');
400        }
401
402        return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary);
403
404    }
405
406    /**
407     * Searches through all of a users calendars and calendar objects to find
408     * an object with a specific UID.
409     *
410     * This method should return the path to this object, relative to the
411     * calendar home, so this path usually only contains two parts:
412     *
413     * calendarpath/objectpath.ics
414     *
415     * If the uid is not found, return null.
416     *
417     * This method should only consider * objects that the principal owns, so
418     * any calendars owned by other principals that also appear in this
419     * collection should be ignored.
420     *
421     * @param string $uid
422     * @return string|null
423     */
424    function getCalendarObjectByUID($uid) {
425
426        return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid);
427
428    }
429
430}
431