xref: /plugin/davcal/calendarBackendDokuwiki.php (revision 9f1dab8c22c579ae1903b00ba4d7de46f7cbdea4)
1<?php
2
3use \Sabre\VObject;
4use \Sabre\CalDAV;
5use \Sabre\DAV;
6use \Sabre\DAV\Exception\Forbidden;
7/**
8 * PDO CalDAV backend for DokuWiki - based on Sabre's CalDAV backend
9 *
10 * This backend is used to store calendar-data in a PDO database, such as
11 * sqlite or MySQL
12 *
13 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
14 * @author Evert Pot (http://evertpot.com/)
15 * @license http://sabre.io/license/ Modified BSD License
16 */
17class DokuWikiSabreCalendarBackend extends \Sabre\CalDAV\Backend\AbstractBackend
18{
19
20
21    /**
22     * DokuWiki PlugIn Helper
23     */
24    protected $hlp = null;
25    /**
26
27    /**
28     * List of CalDAV properties, and how they map to database fieldnames
29     * Add your own properties by simply adding on to this array.
30     *
31     * Note that only string-based properties are supported here.
32     *
33     * @var array
34     */
35    public $propertyMap = array(
36        '{DAV:}displayname'                                   => 'displayname',
37        '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
38        '{urn:ietf:params:xml:ns:caldav}calendar-timezone'    => 'timezone',
39        //'{http://apple.com/ns/ical/}calendar-order'           => 'calendarorder',
40        //'{http://apple.com/ns/ical/}calendar-color'           => 'calendarcolor',
41    );
42
43    /**
44     * Creates the backend
45     *
46     * @param \PDO $pdo
47     */
48    function __construct(&$hlp)
49    {
50
51        $this->hlp = $hlp;
52
53    }
54
55    /**
56     * Returns a list of calendars for a principal.
57     *
58     * Every project is an array with the following keys:
59     *  * id, a unique id that will be used by other functions to modify the
60     *    calendar. This can be the same as the uri or a database key.
61     *  * uri. This is just the 'base uri' or 'filename' of the calendar.
62     *  * principaluri. The owner of the calendar. Almost always the same as
63     *    principalUri passed to this method.
64     *
65     * Furthermore it can contain webdav properties in clark notation. A very
66     * common one is '{DAV:}displayname'.
67     *
68     * Many clients also require:
69     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
70     * For this property, you can just return an instance of
71     * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet.
72     *
73     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
74     * ACL will automatically be put in read-only mode.
75     *
76     * @param string $principalUri
77     * @return array
78     */
79    function getCalendarsForUser($principalUri)
80    {
81
82        $fields = array_values($this->propertyMap);
83        $fields[] = 'id';
84        $fields[] = 'uri';
85        $fields[] = 'synctoken';
86        $fields[] = 'components';
87        $fields[] = 'principaluri';
88        $fields[] = 'transparent';
89
90        $idInfo = $this->hlp->getCalendarIdsForUser($principalUri);
91        $calendars = array();
92        foreach($idInfo as $id => $data)
93        {
94            $row = $this->hlp->getCalendarSettings($id);
95            $components = array();
96            if ($row['components'])
97            {
98                $components = explode(',', $row['components']);
99            }
100
101            $calendar = array(
102                'id'                                                                 => $row['id'],
103                'uri'                                                                => $row['uri'],
104                'principaluri'                                                       => $principalUri,//Overwrite principaluri from database, we actually don't need it.
105                '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag'                  => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'),
106                '{http://sabredav.org/ns}sync-token'                                 => $row['synctoken'] ? $row['synctoken'] : '0',
107                '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components),
108                //'{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp'         => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
109            );
110            if($idInfo[$row['id']]['readonly'] === true)
111                $calendar['{http://sabredav.org/ns}read-only'] = '1';
112
113
114            foreach ($this->propertyMap as $xmlName => $dbName)
115            {
116                $calendar[$xmlName] = $row[$dbName];
117            }
118
119            $calendars[] = $calendar;
120
121        }
122
123        return $calendars;
124
125    }
126
127    /**
128     * Creates a new calendar for a principal.
129     *
130     * If the creation was a success, an id must be returned that can be used
131     * to reference this calendar in other methods, such as updateCalendar.
132     *
133     * @param string $principalUri
134     * @param string $calendarUri
135     * @param array $properties
136     * @return string
137     */
138    function createCalendar($principalUri, $calendarUri, array $properties)
139    {
140
141        return false;
142    }
143
144    /**
145     * Updates properties for a calendar.
146     *
147     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
148     * To do the actual updates, you must tell this object which properties
149     * you're going to process with the handle() method.
150     *
151     * Calling the handle method is like telling the PropPatch object "I
152     * promise I can handle updating this property".
153     *
154     * Read the PropPatch documenation for more info and examples.
155     *
156     * @param string $calendarId
157     * @param \Sabre\DAV\PropPatch $propPatch
158     * @return void
159     */
160    function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch)
161    {
162
163        $supportedProperties = array_keys($this->propertyMap);
164
165        $propPatch->handle($supportedProperties, function($mutations) use ($calendarId)
166        {
167            foreach ($mutations as $propertyName => $propertyValue)
168            {
169
170                switch ($propertyName)
171                {
172                    case '{DAV:}displayname' :
173                        $this->hlp->updateCalendarName($calendarId, $propertyValue);
174                        break;
175                    case '{urn:ietf:params:xml:ns:caldav}calendar-description':
176                        $this->hlp->updateCalendarDescription($calendarId, $propertyValue);
177                        break;
178                    case '{urn:ietf:params:xml:ns:caldav}calendar-timezone':
179                        $this->hlp->updateCalendarTimezone($calendarId, $propertyValue);
180                        break;
181                    default :
182                        break;
183                }
184
185            }
186            return true;
187
188        });
189
190    }
191
192    /**
193     * Delete a calendar and all it's objects
194     *
195     * @param string $calendarId
196     * @return void
197     */
198    function deleteCalendar($calendarId)
199    {
200        return;
201    }
202
203    /**
204     * Returns all calendar objects within a calendar.
205     *
206     * Every item contains an array with the following keys:
207     *   * calendardata - The iCalendar-compatible calendar data
208     *   * uri - a unique key which will be used to construct the uri. This can
209     *     be any arbitrary string, but making sure it ends with '.ics' is a
210     *     good idea. This is only the basename, or filename, not the full
211     *     path.
212     *   * lastmodified - a timestamp of the last modification time
213     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
214     *   '  "abcdef"')
215     *   * size - The size of the calendar objects, in bytes.
216     *   * component - optional, a string containing the type of object, such
217     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
218     *     the Content-Type header.
219     *
220     * Note that the etag is optional, but it's highly encouraged to return for
221     * speed reasons.
222     *
223     * The calendardata is also optional. If it's not returned
224     * 'getCalendarObject' will be called later, which *is* expected to return
225     * calendardata.
226     *
227     * If neither etag or size are specified, the calendardata will be
228     * used/fetched to determine these numbers. If both are specified the
229     * amount of times this is needed is reduced by a great degree.
230     *
231     * @param string $calendarId
232     * @return array
233     */
234    function getCalendarObjects($calendarId)
235    {
236
237        $arr = $this->hlp->getCalendarObjects($calendarId);
238        $result = array();
239        foreach ($arr as $row)
240        {
241            $result[] = array(
242                'id'           => $row['id'],
243                'uri'          => $row['uri'],
244                'lastmodified' => $row['lastmodified'],
245                'etag'         => '"' . $row['etag'] . '"',
246                'calendarid'   => $row['calendarid'],
247                'size'         => (int)$row['size'],
248                'component'    => strtolower($row['componenttype']),
249            );
250        }
251
252        return $result;
253
254    }
255
256    /**
257     * Returns information from a single calendar object, based on it's object
258     * uri.
259     *
260     * The object uri is only the basename, or filename and not a full path.
261     *
262     * The returned array must have the same keys as getCalendarObjects. The
263     * 'calendardata' object is required here though, while it's not required
264     * for getCalendarObjects.
265     *
266     * This method must return null if the object did not exist.
267     *
268     * @param string $calendarId
269     * @param string $objectUri
270     * @return array|null
271     */
272    function getCalendarObject($calendarId, $objectUri)
273    {
274
275        $row = $this->hlp->getCalendarObjectByUri($calendarId, $objectUri);
276
277        if (!$row)
278            return null;
279
280        return array(
281            'id'            => $row['id'],
282            'uri'           => $row['uri'],
283            'lastmodified'  => $row['lastmodified'],
284            'etag'          => '"' . $row['etag'] . '"',
285            'calendarid'    => $row['calendarid'],
286            'size'          => (int)$row['size'],
287            'calendardata'  => $row['calendardata'],
288            'component'     => strtolower($row['componenttype']),
289         );
290
291    }
292
293    /**
294     * Returns a list of calendar objects.
295     *
296     * This method should work identical to getCalendarObject, but instead
297     * return all the calendar objects in the list as an array.
298     *
299     * If the backend supports this, it may allow for some speed-ups.
300     *
301     * @param mixed $calendarId
302     * @param array $uris
303     * @return array
304     */
305    function getMultipleCalendarObjects($calendarId, array $uris)
306    {
307
308        $arr = $this->hlp->getMultipleCalendarObjectsByUri($calendarId, $uris);
309
310        $result = array();
311        foreach($arr as $row)
312        {
313
314            $result[] = array(
315                'id'           => $row['id'],
316                'uri'          => $row['uri'],
317                'lastmodified' => $row['lastmodified'],
318                'etag'         => '"' . $row['etag'] . '"',
319                'calendarid'   => $row['calendarid'],
320                'size'         => (int)$row['size'],
321                'calendardata' => $row['calendardata'],
322                'component'    => strtolower($row['componenttype']),
323            );
324
325        }
326        return $result;
327
328    }
329
330
331    /**
332     * Creates a new calendar object.
333     *
334     * The object uri is only the basename, or filename and not a full path.
335     *
336     * It is possible return an etag from this function, which will be used in
337     * the response to this PUT request. Note that the ETag must be surrounded
338     * by double-quotes.
339     *
340     * However, you should only really return this ETag if you don't mangle the
341     * calendar-data. If the result of a subsequent GET to this object is not
342     * the exact same as this request body, you should omit the ETag.
343     *
344     * @param mixed $calendarId
345     * @param string $objectUri
346     * @param string $calendarData
347     * @return string|null
348     */
349    function createCalendarObject($calendarId, $objectUri, $calendarData)
350    {
351
352        $etag = $this->hlp->addCalendarEntryToCalendarByICS($calendarId, $objectUri, $calendarData);
353
354        return '"' . $etag . '"';
355    }
356
357    /**
358     * Updates an existing calendarobject, based on it's uri.
359     *
360     * The object uri is only the basename, or filename and not a full path.
361     *
362     * It is possible return an etag from this function, which will be used in
363     * the response to this PUT request. Note that the ETag must be surrounded
364     * by double-quotes.
365     *
366     * However, you should only really return this ETag if you don't mangle the
367     * calendar-data. If the result of a subsequent GET to this object is not
368     * the exact same as this request body, you should omit the ETag.
369     *
370     * @param mixed $calendarId
371     * @param string $objectUri
372     * @param string $calendarData
373     * @return string|null
374     */
375    function updateCalendarObject($calendarId, $objectUri, $calendarData)
376    {
377
378        $etag = $this->hlp->editCalendarEntryToCalendarByICS($calendarId, $objectUri, $calendarData);
379        return '"' . $etag. '"';
380
381    }
382
383
384
385    /**
386     * Deletes an existing calendar object.
387     *
388     * The object uri is only the basename, or filename and not a full path.
389     *
390     * @param string $calendarId
391     * @param string $objectUri
392     * @return void
393     */
394    function deleteCalendarObject($calendarId, $objectUri)
395    {
396        $this->hlp->deleteCalendarEntryForCalendarByUri($calendarId, $objectUri);
397
398    }
399
400    /**
401     * Performs a calendar-query on the contents of this calendar.
402     *
403     * The calendar-query is defined in RFC4791 : CalDAV. Using the
404     * calendar-query it is possible for a client to request a specific set of
405     * object, based on contents of iCalendar properties, date-ranges and
406     * iCalendar component types (VTODO, VEVENT).
407     *
408     * This method should just return a list of (relative) urls that match this
409     * query.
410     *
411     * The list of filters are specified as an array. The exact array is
412     * documented by \Sabre\CalDAV\CalendarQueryParser.
413     *
414     * Note that it is extremely likely that getCalendarObject for every path
415     * returned from this method will be called almost immediately after. You
416     * may want to anticipate this to speed up these requests.
417     *
418     * This method provides a default implementation, which parses *all* the
419     * iCalendar objects in the specified calendar.
420     *
421     * This default may well be good enough for personal use, and calendars
422     * that aren't very large. But if you anticipate high usage, big calendars
423     * or high loads, you are strongly adviced to optimize certain paths.
424     *
425     * The best way to do so is override this method and to optimize
426     * specifically for 'common filters'.
427     *
428     * Requests that are extremely common are:
429     *   * requests for just VEVENTS
430     *   * requests for just VTODO
431     *   * requests with a time-range-filter on a VEVENT.
432     *
433     * ..and combinations of these requests. It may not be worth it to try to
434     * handle every possible situation and just rely on the (relatively
435     * easy to use) CalendarQueryValidator to handle the rest.
436     *
437     * Note that especially time-range-filters may be difficult to parse. A
438     * time-range filter specified on a VEVENT must for instance also handle
439     * recurrence rules correctly.
440     * A good example of how to interprete all these filters can also simply
441     * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
442     * as possible, so it gives you a good idea on what type of stuff you need
443     * to think of.
444     *
445     * This specific implementation (for the PDO) backend optimizes filters on
446     * specific components, and VEVENT time-ranges.
447     *
448     * @param string $calendarId
449     * @param array $filters
450     * @return array
451     */
452    function calendarQuery($calendarId, array $filters)
453    {
454        $result = $this->hlp->calendarQuery($calendarId, $filters);
455        return $result;
456    }
457
458    /**
459     * Searches through all of a users calendars and calendar objects to find
460     * an object with a specific UID.
461     *
462     * This method should return the path to this object, relative to the
463     * calendar home, so this path usually only contains two parts:
464     *
465     * calendarpath/objectpath.ics
466     *
467     * If the uid is not found, return null.
468     *
469     * This method should only consider * objects that the principal owns, so
470     * any calendars owned by other principals that also appear in this
471     * collection should be ignored.
472     *
473     * @param string $principalUri
474     * @param string $uid
475     * @return string|null
476     */
477    function getCalendarObjectByUID($principalUri, $uid)
478    {
479        $calids = array_keys($this->hlp->getCalendarIsForUser($principalUri));
480        $event = $this->hlp->getEventWithUid($uid);
481
482        if(in_array($event['calendarid'], $calids))
483        {
484            $settings = $this->hlp->getCalendarSettings($event['calendarid']);
485            return $settings['uri'] . '/' . $event['uri'];
486        }
487        return null;
488    }
489
490    /**
491     * The getChanges method returns all the changes that have happened, since
492     * the specified syncToken in the specified calendar.
493     *
494     * This function should return an array, such as the following:
495     *
496     * [
497     *   'syncToken' => 'The current synctoken',
498     *   'added'   => [
499     *      'new.txt',
500     *   ],
501     *   'modified'   => [
502     *      'modified.txt',
503     *   ],
504     *   'deleted' => [
505     *      'foo.php.bak',
506     *      'old.txt'
507     *   ]
508     * ];
509     *
510     * The returned syncToken property should reflect the *current* syncToken
511     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
512     * property this is needed here too, to ensure the operation is atomic.
513     *
514     * If the $syncToken argument is specified as null, this is an initial
515     * sync, and all members should be reported.
516     *
517     * The modified property is an array of nodenames that have changed since
518     * the last token.
519     *
520     * The deleted property is an array with nodenames, that have been deleted
521     * from collection.
522     *
523     * The $syncLevel argument is basically the 'depth' of the report. If it's
524     * 1, you only have to report changes that happened only directly in
525     * immediate descendants. If it's 2, it should also include changes from
526     * the nodes below the child collections. (grandchildren)
527     *
528     * The $limit argument allows a client to specify how many results should
529     * be returned at most. If the limit is not specified, it should be treated
530     * as infinite.
531     *
532     * If the limit (infinite or not) is higher than you're willing to return,
533     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
534     *
535     * If the syncToken is expired (due to data cleanup) or unknown, you must
536     * return null.
537     *
538     * The limit is 'suggestive'. You are free to ignore it.
539     *
540     * @param string $calendarId
541     * @param string $syncToken
542     * @param int $syncLevel
543     * @param int $limit
544     * @return array
545     */
546    function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null)
547    {
548        $result = $this->hlp->getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit);
549        return $result;
550    }
551
552    /**
553     * Returns a list of subscriptions for a principal.
554     *
555     * Every subscription is an array with the following keys:
556     *  * id, a unique id that will be used by other functions to modify the
557     *    subscription. This can be the same as the uri or a database key.
558     *  * uri. This is just the 'base uri' or 'filename' of the subscription.
559     *  * principaluri. The owner of the subscription. Almost always the same as
560     *    principalUri passed to this method.
561     *  * source. Url to the actual feed
562     *
563     * Furthermore, all the subscription info must be returned too:
564     *
565     * 1. {DAV:}displayname
566     * 2. {http://apple.com/ns/ical/}refreshrate
567     * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
568     *    should not be stripped).
569     * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
570     *    should not be stripped).
571     * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
572     *    attachments should not be stripped).
573     * 7. {http://apple.com/ns/ical/}calendar-color
574     * 8. {http://apple.com/ns/ical/}calendar-order
575     * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
576     *    (should just be an instance of
577     *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
578     *    default components).
579     *
580     * @param string $principalUri
581     * @return array
582     */
583    function getSubscriptionsForUser($principalUri)
584    {
585        return array();
586
587    }
588
589    /**
590     * Creates a new subscription for a principal.
591     *
592     * If the creation was a success, an id must be returned that can be used to reference
593     * this subscription in other methods, such as updateSubscription.
594     *
595     * @param string $principalUri
596     * @param string $uri
597     * @param array $properties
598     * @return mixed
599     */
600    function createSubscription($principalUri, $uri, array $properties)
601    {
602        return null;
603
604    }
605
606    /**
607     * Updates a subscription
608     *
609     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
610     * To do the actual updates, you must tell this object which properties
611     * you're going to process with the handle() method.
612     *
613     * Calling the handle method is like telling the PropPatch object "I
614     * promise I can handle updating this property".
615     *
616     * Read the PropPatch documenation for more info and examples.
617     *
618     * @param mixed $subscriptionId
619     * @param \Sabre\DAV\PropPatch $propPatch
620     * @return void
621     */
622    function updateSubscription($subscriptionId, DAV\PropPatch $propPatch)
623    {
624        return;
625    }
626
627    /**
628     * Deletes a subscription
629     *
630     * @param mixed $subscriptionId
631     * @return void
632     */
633    function deleteSubscription($subscriptionId)
634    {
635
636        return;
637
638    }
639
640    /**
641     * Returns a single scheduling object.
642     *
643     * The returned array should contain the following elements:
644     *   * uri - A unique basename for the object. This will be used to
645     *           construct a full uri.
646     *   * calendardata - The iCalendar object
647     *   * lastmodified - The last modification date. Can be an int for a unix
648     *                    timestamp, or a PHP DateTime object.
649     *   * etag - A unique token that must change if the object changed.
650     *   * size - The size of the object, in bytes.
651     *
652     * @param string $principalUri
653     * @param string $objectUri
654     * @return array
655     */
656    function getSchedulingObject($principalUri, $objectUri)
657    {
658
659        return null;
660
661    }
662
663    /**
664     * Returns all scheduling objects for the inbox collection.
665     *
666     * These objects should be returned as an array. Every item in the array
667     * should follow the same structure as returned from getSchedulingObject.
668     *
669     * The main difference is that 'calendardata' is optional.
670     *
671     * @param string $principalUri
672     * @return array
673     */
674    function getSchedulingObjects($principalUri)
675    {
676        return null;
677
678    }
679
680    /**
681     * Deletes a scheduling object
682     *
683     * @param string $principalUri
684     * @param string $objectUri
685     * @return void
686     */
687    function deleteSchedulingObject($principalUri, $objectUri)
688    {
689
690        return;
691    }
692
693    /**
694     * Creates a new scheduling object. This should land in a users' inbox.
695     *
696     * @param string $principalUri
697     * @param string $objectUri
698     * @param string $objectData
699     * @return void
700     */
701    function createSchedulingObject($principalUri, $objectUri, $objectData)
702    {
703
704        return;
705
706    }
707
708}
709