]> git.mxchange.org Git - friendica-addons.git/blob - dav/common/dav_caldav_backend_private.inc.php
Heavily refactored, including multiple calendars per user and recurring events. Not...
[friendica-addons.git] / dav / common / dav_caldav_backend_private.inc.php
1 <?php
2
3 class Sabre_CalDAV_Backend_Private extends Sabre_CalDAV_Backend_Common
4 {
5
6
7         /**
8          * @var null|Sabre_CalDAV_Backend_Private
9          */
10         private static $instance = null;
11
12         /**
13          * @static
14          * @return Sabre_CalDAV_Backend_Private
15          */
16         public static function getInstance()
17         {
18                 if (self::$instance == null) {
19                         self::$instance = new Sabre_CalDAV_Backend_Private();
20                 }
21                 return self::$instance;
22         }
23
24
25         /**
26          * @return int
27          */
28         public function getNamespace()
29         {
30                 return CALDAV_NAMESPACE_PRIVATE;
31         }
32
33
34         /**
35          * @obsolete
36          * @param array $calendar
37          * @param int $user
38          * @return array
39          */
40         public function getPermissionsCalendar($calendar, $user)
41         {
42                 if ($calendar["namespace"] == CALDAV_NAMESPACE_PRIVATE && $user == $calendar["namespace_id"]) return array("read"=> true, "write"=> true);
43                 return array("read"=> false, "write"=> false);
44         }
45
46         /**
47          * @obsolete
48          * @param array $calendar
49          * @param int $user
50          * @param string $calendarobject_id
51          * @param null|array $item_arr
52          * @return array
53          */
54         public function getPermissionsItem($calendar, $user, $calendarobject_id, $item_arr = null)
55         {
56                 return $this->getPermissionsCalendar($calendar, $user);
57         }
58
59
60         /**
61          * @param array $row
62          * @param array $calendar
63          * @param string $base_path
64          * @return array
65          */
66         private function jqcal2wdcal($row, $calendar, $base_path)
67         {
68                 $not      = q("SELECT COUNT(*) num FROM %s%snotifications WHERE `calendar_id` = %d AND `calendarobject_id` = %d",
69                         CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($row["calendar_id"]), IntVal($row["calendarobject_id"])
70                 );
71                 $editable = $this->getPermissionsItem($calendar["namespace_id"], $row["calendarobject_id"], $row);
72
73                 $end = wdcal_mySql2PhpTime($row["EndTime"]);
74                 if ($row["IsAllDayEvent"]) $end -= 1;
75
76                 return array(
77                         "jq_id"             => $row["id"],
78                         "ev_id"             => $row["calendarobject_id"],
79                         "summary"           => escape_tags($row["Summary"]),
80                         "start"             => wdcal_mySql2PhpTime($row["StartTime"]),
81                         "end"               => $end,
82                         "is_allday"         => $row["IsAllDayEvent"],
83                         "is_moredays"       => 0,
84                         "is_recurring"      => $row["IsRecurring"],
85                         "color"             => (is_null($row["Color"]) || $row["Color"] == "" ? $calendar["calendarcolor"] : $row["Color"]),
86                         "is_editable"       => ($editable ? 1 : 0),
87                         "is_editable_quick" => ($editable && !$row["IsRecurring"] ? 1 : 0),
88                         "location"          => "Loc.",
89                         "attendees"         => '',
90                         "has_notification"  => ($not[0]["num"] > 0 ? 1 : 0),
91                         "url_detail"        => $base_path . $row["calendarobject_id"] . "/",
92                         "url_edit"          => $base_path . $row["calendarobject_id"] . "/edit/",
93                         "special_type"      => "",
94                 );
95         }
96
97         /**
98          * @param int $calendarId
99          * @param string $sd
100          * @param string $ed
101          * @param string $base_path
102          * @return array
103          */
104         public function listItemsByRange($calendarId, $sd, $ed, $base_path)
105         {
106                 $calendar = Sabre_CalDAV_Backend_Common::loadCalendarById($calendarId);
107                 $von      = wdcal_php2MySqlTime($sd);
108                 $bis      = wdcal_php2MySqlTime($ed);
109
110                 // @TODO Events, die früher angefangen haben, aber noch andauern
111                 $evs = q("SELECT * FROM %s%sjqcalendar WHERE `calendar_id` = %d AND `starttime` between '%s' and '%s'",
112                         CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
113                         IntVal($calendarId), dbesc($von), dbesc($bis));
114
115                 $events = array();
116                 foreach ($evs as $row) $events[] = $this->jqcal2wdcal($row, $calendar, $base_path . $row["calendar_id"] . "/");
117
118                 return $events;
119         }
120
121
122         /**
123          * @param int $calendar_id
124          * @param int $calendarobject_id
125          * @return string
126          */
127         public function getItemDetailRedirect($calendar_id, $calendarobject_id)
128         {
129                 return "/dav/wdcal/$calendar_id/$calendarobject_id/edit/";
130         }
131
132         /**
133          * Returns a list of calendars for a principal.
134          *
135          * Every project is an array with the following keys:
136          *  * id, a unique id that will be used by other functions to modify the
137          *    calendar. This can be the same as the uri or a database key.
138          *  * uri, which the basename of the uri with which the calendar is
139          *    accessed.
140          *  * principaluri. The owner of the calendar. Almost always the same as
141          *    principalUri passed to this method.
142          *
143          * Furthermore it can contain webdav properties in clark notation. A very
144          * common one is '{DAV:}displayname'.
145          *
146          * @param string $principalUri
147          * @return array
148          */
149         public function getCalendarsForUser($principalUri)
150         {
151                 $n = dav_compat_principal2namespace($principalUri);
152                 if ($n["namespace"] != $this->getNamespace()) return array();
153
154                 $cals = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $this->getNamespace(), IntVal($n["namespace_id"]));
155                 $ret  = array();
156                 foreach ($cals as $cal) {
157                         if (in_array($cal["uri"], $GLOBALS["CALDAV_PRIVATE_SYSTEM_CALENDARS"])) continue;
158
159                         $dat = array(
160                                 "id"                                                      => $cal["id"],
161                                 "uri"                                                     => $cal["uri"],
162                                 "principaluri"                                            => $principalUri,
163                                 '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}getctag' => $cal['ctag'] ? $cal['ctag'] : '0',
164                                 "calendar_class"                                          => "Sabre_CalDAV_Calendar",
165                         );
166                         foreach ($this->propertyMap as $key=> $field) $dat[$key] = $cal[$field];
167
168                         $ret[] = $dat;
169                 }
170
171                 return $ret;
172         }
173
174
175         /**
176          * Creates a new calendar for a principal.
177          *
178          * If the creation was a success, an id must be returned that can be used to reference
179          * this calendar in other methods, such as updateCalendar.
180          *
181          * @param string $principalUri
182          * @param string $calendarUri
183          * @param array $properties
184          * @throws Sabre_DAV_Exception
185          * @return string|void
186          */
187         public function createCalendar($principalUri, $calendarUri, array $properties)
188         {
189
190                 $uid = dav_compat_principal2uid($principalUri);
191
192                 $r = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $uid, dbesc($calendarUri));
193                 if (count($r) > 0) throw new Sabre_DAV_Exception("A calendar with this URI already exists");
194
195                 $keys = array("`namespace`", "`namespace_id`", "`ctag`", "`uri`");
196                 $vals = array(CALDAV_NAMESPACE_PRIVATE, IntVal($uid), 1, "'" . dbesc($calendarUri) . "'");
197
198                 // Default value
199                 $sccs       = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
200                 $has_vevent = $has_vtodo = 1;
201                 if (isset($properties[$sccs])) {
202                         if (!($properties[$sccs] instanceof Sabre_CalDAV_Property_SupportedCalendarComponentSet)) {
203                                 throw new Sabre_DAV_Exception('The ' . $sccs . ' property must be of type: Sabre_CalDAV_Property_SupportedCalendarComponentSet');
204                         }
205                         $v          = $properties[$sccs]->getValue();
206                         $has_vevent = $has_vtodo = 0;
207                         foreach ($v as $w) {
208                                 if (mb_strtolower($w) == "vevent") $has_vevent = 1;
209                                 if (mb_strtolower($w) == "vtodo") $has_vtodo = 1;
210                         }
211                 }
212                 $keys[] = "`has_vevent`";
213                 $keys[] = "`has_vtodo`";
214                 $vals[] = $has_vevent;
215                 $vals[] = $has_vtodo;
216
217                 foreach ($this->propertyMap as $xmlName=> $dbName) {
218                         if (isset($properties[$xmlName])) {
219                                 $keys[] = "`$dbName`";
220                                 $vals[] = "'" . dbesc($properties[$xmlName]) . "'";
221                         }
222                 }
223
224                 $sql = sprintf("INSERT INTO %s%scalendars (" . implode(', ', $keys) . ") VALUES (" . implode(', ', $vals) . ")", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
225
226                 q($sql);
227
228                 $x = q("SELECT id FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'",
229                         CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $uid, $calendarUri
230                 );
231                 return $x[0]["id"];
232
233         }
234
235         /**
236          * Updates properties for a calendar.
237          *
238          * The mutations array uses the propertyName in clark-notation as key,
239          * and the array value for the property value. In the case a property
240          * should be deleted, the property value will be null.
241          *
242          * This method must be atomic. If one property cannot be changed, the
243          * entire operation must fail.
244          *
245          * If the operation was successful, true can be returned.
246          * If the operation failed, false can be returned.
247          *
248          * Deletion of a non-existent property is always successful.
249          *
250          * Lastly, it is optional to return detailed information about any
251          * failures. In this case an array should be returned with the following
252          * structure:
253          *
254          * array(
255          *   403 => array(
256          *      '{DAV:}displayname' => null,
257          *   ),
258          *   424 => array(
259          *      '{DAV:}owner' => null,
260          *   )
261          * )
262          *
263          * In this example it was forbidden to update {DAV:}displayname.
264          * (403 Forbidden), which in turn also caused {DAV:}owner to fail
265          * (424 Failed Dependency) because the request needs to be atomic.
266          *
267          * @param string $calendarId
268          * @param array $mutations
269          * @return bool|array
270          */
271         public function updateCalendar($calendarId, array $mutations)
272         {
273
274                 $newValues = array();
275                 $result    = array(
276                         200 => array(), // Ok
277                         403 => array(), // Forbidden
278                         424 => array(), // Failed Dependency
279                 );
280
281                 $hasError = false;
282
283                 foreach ($mutations as $propertyName=> $propertyValue) {
284
285                         // We don't know about this property.
286                         if (!isset($this->propertyMap[$propertyName])) {
287                                 $hasError                   = true;
288                                 $result[403][$propertyName] = null;
289                                 unset($mutations[$propertyName]);
290                                 continue;
291                         }
292
293                         $fieldName             = $this->propertyMap[$propertyName];
294                         $newValues[$fieldName] = $propertyValue;
295
296                 }
297
298                 // If there were any errors we need to fail the request
299                 if ($hasError) {
300                         // Properties has the remaining properties
301                         foreach ($mutations as $propertyName=> $propertyValue) {
302                                 $result[424][$propertyName] = null;
303                         }
304
305                         // Removing unused statuscodes for cleanliness
306                         foreach ($result as $status=> $properties) {
307                                 if (is_array($properties) && count($properties) === 0) unset($result[$status]);
308                         }
309
310                         return $result;
311
312                 }
313
314                 $sql = "`ctag` = `ctag` + 1";
315                 foreach ($newValues as $key=> $val) $sql .= ", `" . $key . "` = '" . dbesc($val) . "'";
316
317                 $sql = sprintf("UPDATE %s%scalendars SET $sql WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
318
319                 q($sql);
320
321                 return true;
322
323         }
324
325
326         /**
327          * Delete a calendar and all it's objects
328          *
329          * @param string $calendarId
330          * @return void
331          */
332         public function deleteCalendar($calendarId)
333         {
334                 q("DELETE FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
335                 q("DELETE FROM %s%scalendars WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
336
337         }
338
339
340         /**
341          * Returns all calendar objects within a calendar.
342          *
343          * Every item contains an array with the following keys:
344          *   * id - unique identifier which will be used for subsequent updates
345          *   * calendardata - The iCalendar-compatible calendar data
346          *   * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
347          *   * lastmodified - a timestamp of the last modification time
348          *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
349          *   '  "abcdef"')
350          *   * calendarid - The calendarid as it was passed to this function.
351          *   * size - The size of the calendar objects, in bytes.
352          *
353          * Note that the etag is optional, but it's highly encouraged to return for
354          * speed reasons.
355          *
356          * The calendardata is also optional. If it's not returned
357          * 'getCalendarObject' will be called later, which *is* expected to return
358          * calendardata.
359          *
360          * If neither etag or size are specified, the calendardata will be
361          * used/fetched to determine these numbers. If both are specified the
362          * amount of times this is needed is reduced by a great degree.
363          *
364          * @param mixed $calendarId
365          * @return array
366          */
367         function getCalendarObjects($calendarId)
368         {
369                 $objs = q("SELECT * FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
370                 $ret  = array();
371                 foreach ($objs as $obj) {
372                         $ret[] = array(
373                                 "id"           => IntVal($obj["id"]),
374                                 "calendardata" => $obj["calendardata"],
375                                 "uri"          => $obj["uri"],
376                                 "lastmodified" => $obj["lastmodified"],
377                                 "calendarid"   => $calendarId,
378                                 "etag"         => $obj["etag"],
379                                 "size"         => IntVal($obj["size"]),
380                         );
381                 }
382                 return $ret;
383         }
384
385         /**
386          * Returns information from a single calendar object, based on it's object
387          * uri.
388          *
389          * The returned array must have the same keys as getCalendarObjects. The
390          * 'calendardata' object is required here though, while it's not required
391          * for getCalendarObjects.
392          *
393          * @param string $calendarId
394          * @param string $objectUri
395          * @throws Sabre_DAV_Exception_NotFound
396          * @return array
397          */
398         function getCalendarObject($calendarId, $objectUri)
399         {
400                 $o = q("SELECT * FROM %s%scalendarobjects WHERE `calendar_id` = %d AND `uri` = '%s'",
401                         CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri));
402                 if (count($o) > 0) {
403                         $o[0]["calendarid"]   = $calendarId;
404                         $o[0]["calendardata"] = str_ireplace("Europe/Belgrade", "Europe/Berlin", $o[0]["calendardata"]);
405                         return $o[0];
406                 } else throw new Sabre_DAV_Exception_NotFound($calendarId . " / " . $objectUri);
407         }
408
409         /**
410          * Creates a new calendar object.
411          *
412          * It is possible return an etag from this function, which will be used in
413          * the response to this PUT request. Note that the ETag must be surrounded
414          * by double-quotes.
415          *
416          * However, you should only really return this ETag if you don't mangle the
417          * calendar-data. If the result of a subsequent GET to this object is not
418          * the exact same as this request body, you should omit the ETag.
419          *
420          * @param mixed $calendarId
421          * @param string $objectUri
422          * @param string $calendarData
423          * @return string|null
424          */
425         function createCalendarObject($calendarId, $objectUri, $calendarData)
426         {
427
428                 $calendarData = icalendar_sanitize_string($calendarData);
429
430                 $extraData = $this->getDenormalizedData($calendarData);
431
432                 q("INSERT INTO %s%scalendarobjects (`calendar_id`, `uri`, `calendardata`, `lastmodified`, `componentType`, `firstOccurence`, `lastOccurence`, `etag`, `size`)
433                         VALUES (%d, '%s', '%s', NOW(), '%s', '%s', '%s', '%s', %d)",
434                         CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri), addslashes($calendarData), dbesc($extraData['componentType']),
435                         dbesc(wdcal_php2MySqlTime($extraData['firstOccurence'])), dbesc(wdcal_php2MySqlTime($extraData['lastOccurence'])), dbesc($extraData["etag"]), IntVal($extraData["size"])
436                 );
437
438                 $this->increaseCalendarCtag($calendarId);
439                 renderCalDavEntry_uri($objectUri);
440
441                 return '"' . $extraData['etag'] . '"';
442         }
443
444         /**
445          * Updates an existing calendarobject, based on it's uri.
446          *
447          * It is possible return an etag from this function, which will be used in
448          * the response to this PUT request. Note that the ETag must be surrounded
449          * by double-quotes.
450          *
451          * However, you should only really return this ETag if you don't mangle the
452          * calendar-data. If the result of a subsequent GET to this object is not
453          * the exact same as this request body, you should omit the ETag.
454          *
455          * @param mixed $calendarId
456          * @param string $objectUri
457          * @param string $calendarData
458          * @return string|null
459          */
460         function updateCalendarObject($calendarId, $objectUri, $calendarData)
461         {
462                 $calendarData = icalendar_sanitize_string($calendarData);
463
464                 $extraData = $this->getDenormalizedData($calendarData);
465
466                 q("UPDATE %s%scalendarobjects SET `calendardata` = '%s', `lastmodified` = NOW(), `etag` = '%s', `size` = %d, `componentType` = '%s', `firstOccurence` = '%s', `lastOccurence` = '%s'
467                         WHERE `calendar_id` = %d AND `uri` = '%s'",
468                         CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($calendarData), dbesc($extraData["etag"]), IntVal($extraData["size"]), dbesc($extraData["componentType"]),
469                         dbesc(wdcal_php2MySqlTime($extraData["firstOccurence"])), dbesc(wdcal_php2MySqlTime($extraData["lastOccurence"])), IntVal($calendarId), dbesc($objectUri));
470
471                 $this->increaseCalendarCtag($calendarId);
472                 renderCalDavEntry_uri($objectUri);
473
474                 return '"' . $extraData['etag'] . '"';
475         }
476
477         /**
478          * Deletes an existing calendar object.
479          *
480          * @param string $calendarId
481          * @param string $objectUri
482          * @throws Sabre_DAV_Exception_NotFound
483          * @return void
484          */
485         function deleteCalendarObject($calendarId, $objectUri)
486         {
487                 $r = q("SELECT `id` FROM %s%scalendarobjects WHERE `calendar_id` = %d AND `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri));
488                 if (count($r) == 0) throw new Sabre_DAV_Exception_NotFound();
489
490                 q("DELETE FROM %s%scalendarobjects WHERE `calendar_id` = %d AND `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri));
491
492                 $this->increaseCalendarCtag($calendarId);
493                 renderCalDavEntry_calobj_id($r[0]["id"]);
494         }
495 }