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