1 | <?php |
||
2 | /* |
||
3 | * SPDX-License-Identifier: AGPL-3.0-only |
||
4 | * SPDX-FileCopyrightText: Copyright 2016 - 2018 Kopano b.v. |
||
5 | * SPDX-FileCopyrightText: Copyright 2020 - 2024 grommunio GmbH |
||
6 | * |
||
7 | * grommunio CalDAV backend class which handles calendar related activities. |
||
8 | */ |
||
9 | |||
10 | namespace grommunio\DAV; |
||
11 | |||
12 | use Sabre\CalDAV\Backend\AbstractBackend; |
||
13 | use Sabre\CalDAV\Backend\SchedulingSupport; |
||
14 | use Sabre\CalDAV\Backend\SyncSupport; |
||
15 | |||
16 | class GrommunioCalDavBackend extends AbstractBackend implements SchedulingSupport, SyncSupport { |
||
17 | /* |
||
18 | * TODO IMPLEMENT |
||
19 | * |
||
20 | * SubscriptionSupport, |
||
21 | * SharingSupport, |
||
22 | * |
||
23 | */ |
||
24 | |||
25 | private $logger; |
||
26 | protected $gDavBackend; |
||
27 | |||
28 | public const FILE_EXTENSION = '.ics'; |
||
29 | // TODO: implement Task support - Issue: #10 |
||
30 | public const MESSAGE_CLASSES = ['IPM.Appointment' /* , 'IPM.Note' */]; |
||
31 | public const CONTAINER_CLASS = 'IPF.Appointment'; |
||
32 | public const CONTAINER_CLASSES = ['IPF.Appointment', 'IPF.Task']; |
||
33 | |||
34 | /** |
||
35 | * Constructor. |
||
36 | */ |
||
37 | public function __construct(GrommunioDavBackend $gDavBackend, GLogger $glogger) { |
||
38 | $this->gDavBackend = $gDavBackend; |
||
39 | $this->logger = $glogger; |
||
40 | } |
||
41 | |||
42 | /** |
||
43 | * Returns a list of calendars for a principal. |
||
44 | * |
||
45 | * Every project is an array with the following keys: |
||
46 | * * id, a unique id that will be used by other functions to modify the |
||
47 | * calendar. This can be the same as the uri or a database key. |
||
48 | * * uri. This is just the 'base uri' or 'filename' of the calendar. |
||
49 | * * principaluri. The owner of the calendar. Almost always the same as |
||
50 | * principalUri passed to this method. |
||
51 | * |
||
52 | * Furthermore it can contain webdav properties in clark notation. A very |
||
53 | * common one is '{DAV:}displayname'. |
||
54 | * |
||
55 | * Many clients also require: |
||
56 | * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set |
||
57 | * For this property, you can just return an instance of |
||
58 | * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet. |
||
59 | * |
||
60 | * If you return {http://sabredav.org/ns}read-only and set the value to 1, |
||
61 | * ACL will automatically be put in read-only mode. |
||
62 | * |
||
63 | * @param string $principalUri |
||
64 | * |
||
65 | * @return array |
||
66 | */ |
||
67 | public function getCalendarsForUser($principalUri) { |
||
68 | $this->logger->trace("principalUri: %s", $principalUri); |
||
69 | |||
70 | return $this->gDavBackend->GetFolders($principalUri, static::CONTAINER_CLASSES); |
||
71 | } |
||
72 | |||
73 | /** |
||
74 | * Creates a new calendar for a principal. |
||
75 | * |
||
76 | * If the creation was a success, an id must be returned that can be used |
||
77 | * to reference this calendar in other methods, such as updateCalendar. |
||
78 | * |
||
79 | * @param string $principalUri |
||
80 | * @param string $calendarUri |
||
81 | * |
||
82 | * @return string |
||
83 | */ |
||
84 | public function createCalendar($principalUri, $calendarUri, array $properties) { |
||
85 | $this->logger->trace("principalUri: %s - calendarUri: %s - properties: %s", $principalUri, $calendarUri, $properties); |
||
86 | |||
87 | // TODO Add displayname |
||
88 | return $this->gDavBackend->CreateFolder($principalUri, $calendarUri, static::CONTAINER_CLASS, ""); |
||
89 | } |
||
90 | |||
91 | /** |
||
92 | * Delete a calendar and all its objects. |
||
93 | * |
||
94 | * @param string $calendarId |
||
95 | */ |
||
96 | public function deleteCalendar($calendarId) { |
||
97 | $this->logger->trace("calendarId: %s", $calendarId); |
||
98 | $success = $this->gDavBackend->DeleteFolder($calendarId); |
||
0 ignored issues
–
show
Unused Code
introduced
by
![]() |
|||
99 | // TODO evaluate $success |
||
100 | } |
||
101 | |||
102 | /** |
||
103 | * Returns all calendar objects within a calendar. |
||
104 | * |
||
105 | * Every item contains an array with the following keys: |
||
106 | * * calendardata - The iCalendar-compatible calendar data |
||
107 | * * uri - a unique key which will be used to construct the uri. This can |
||
108 | * be any arbitrary string, but making sure it ends with '.ics' is a |
||
109 | * good idea. This is only the basename, or filename, not the full |
||
110 | * path. |
||
111 | * * lastmodified - a timestamp of the last modification time |
||
112 | * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: |
||
113 | * ' "abcdef"') |
||
114 | * * size - The size of the calendar objects, in bytes. |
||
115 | * * component - optional, a string containing the type of object, such |
||
116 | * as 'vevent' or 'vtodo'. If specified, this will be used to populate |
||
117 | * the Content-Type header. |
||
118 | * |
||
119 | * Note that the etag is optional, but it's highly encouraged to return for |
||
120 | * speed reasons. |
||
121 | * |
||
122 | * The calendardata is also optional. If it's not returned |
||
123 | * 'getCalendarObject' will be called later, which *is* expected to return |
||
124 | * calendardata. |
||
125 | * |
||
126 | * If neither etag or size are specified, the calendardata will be |
||
127 | * used/fetched to determine these numbers. If both are specified the |
||
128 | * amount of times this is needed is reduced by a great degree. |
||
129 | * |
||
130 | * @param string $calendarId |
||
131 | * |
||
132 | * @return array |
||
133 | */ |
||
134 | public function getCalendarObjects($calendarId) { |
||
135 | $result = $this->gDavBackend->GetObjects($calendarId, static::FILE_EXTENSION, ['types' => static::MESSAGE_CLASSES]); |
||
136 | $this->logger->trace("calendarId: %s found %d objects", $calendarId, count($result)); |
||
137 | |||
138 | return $result; |
||
139 | } |
||
140 | |||
141 | /** |
||
142 | * Performs a calendar-query on the contents of this calendar. |
||
143 | * |
||
144 | * The calendar-query is defined in RFC4791 : CalDAV. Using the |
||
145 | * calendar-query it is possible for a client to request a specific set of |
||
146 | * object, based on contents of iCalendar properties, date-ranges and |
||
147 | * iCalendar component types (VTODO, VEVENT). |
||
148 | * |
||
149 | * This method should just return a list of (relative) urls that match this |
||
150 | * query. |
||
151 | * |
||
152 | * The list of filters are specified as an array. The exact array is |
||
153 | * documented by \Sabre\CalDAV\CalendarQueryParser. |
||
154 | * |
||
155 | * Note that it is extremely likely that getCalendarObject for every path |
||
156 | * returned from this method will be called almost immediately after. You |
||
157 | * may want to anticipate this to speed up these requests. |
||
158 | * |
||
159 | * This method provides a default implementation, which parses *all* the |
||
160 | * iCalendar objects in the specified calendar. |
||
161 | * |
||
162 | * This default may well be good enough for personal use, and calendars |
||
163 | * that aren't very large. But if you anticipate high usage, big calendars |
||
164 | * or high loads, you are strongly advised to optimize certain paths. |
||
165 | * |
||
166 | * The best way to do so is override this method and to optimize |
||
167 | * specifically for 'common filters'. |
||
168 | * |
||
169 | * Requests that are extremely common are: |
||
170 | * * requests for just VEVENTS |
||
171 | * * requests for just VTODO |
||
172 | * * requests with a time-range-filter on either VEVENT or VTODO. |
||
173 | * |
||
174 | * ..and combinations of these requests. It may not be worth it to try to |
||
175 | * handle every possible situation and just rely on the (relatively |
||
176 | * easy to use) CalendarQueryValidator to handle the rest. |
||
177 | * |
||
178 | * Note that especially time-range-filters may be difficult to parse. A |
||
179 | * time-range filter specified on a VEVENT must for instance also handle |
||
180 | * recurrence rules correctly. |
||
181 | * A good example of how to interpret all these filters can also simply |
||
182 | * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct |
||
183 | * as possible, so it gives you a good idea on what type of stuff you need |
||
184 | * to think of. |
||
185 | * |
||
186 | * @param mixed $calendarId |
||
187 | * |
||
188 | * @return array |
||
189 | */ |
||
190 | public function calendarQuery($calendarId, array $filters) { |
||
191 | $start = $end = null; |
||
192 | $types = []; |
||
193 | foreach ($filters['comp-filters'] as $filter) { |
||
194 | |||
195 | if ($filter['name'] == 'VEVENT') { |
||
196 | $types[] = 'IPM.Appointment'; |
||
197 | } |
||
198 | elseif ($filter['name'] == 'VTODO') { |
||
199 | $types[] = 'IPM.Task'; |
||
200 | } |
||
201 | |||
202 | /* will this work on tasks? */ |
||
203 | if (is_array($filter['time-range']) && isset($filter['time-range']['start'], $filter['time-range']['end'])) { |
||
204 | $start = $filter['time-range']['start']->getTimestamp(); |
||
205 | $end = $filter['time-range']['end']->getTimestamp(); |
||
206 | } |
||
207 | } |
||
208 | |||
209 | $objfilters = []; |
||
210 | if ($start != null && $end != null) { |
||
211 | $objfilters["start"] = $start; |
||
212 | $objfilters["end"] = $end; |
||
213 | } |
||
214 | if (!empty($types)) { |
||
215 | $objfilters["types"] = $types; |
||
216 | } |
||
217 | |||
218 | $objects = $this->gDavBackend->GetObjects($calendarId, static::FILE_EXTENSION, $objfilters); |
||
219 | $result = []; |
||
220 | foreach ($objects as $object) { |
||
221 | $result[] = $object['uri']; |
||
222 | } |
||
223 | |||
224 | return $result; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Returns information from a single calendar object, based on its object uri. |
||
229 | * |
||
230 | * The object uri is only the basename, or filename and not a full path. |
||
231 | * |
||
232 | * The returned array must have the same keys as getCalendarObjects. The |
||
233 | * 'calendardata' object is required here though, while it's not required |
||
234 | * for getCalendarObjects. |
||
235 | * |
||
236 | * This method must return null if the object did not exist. |
||
237 | * |
||
238 | * @param string $calendarId |
||
239 | * @param string $objectUri |
||
240 | * @param resource $mapifolder optional mapifolder resource, used if available |
||
241 | * |
||
242 | * @return null|array |
||
243 | */ |
||
244 | public function getCalendarObject($calendarId, $objectUri, $mapifolder = null) { |
||
245 | $this->logger->trace("calendarId: %s - objectUri: %s - mapifolder: %s", $calendarId, $objectUri, $mapifolder); |
||
246 | |||
247 | if (!$mapifolder) { |
||
0 ignored issues
–
show
|
|||
248 | $mapifolder = $this->gDavBackend->GetMapiFolder($calendarId); |
||
249 | } |
||
250 | |||
251 | $mapimessage = $this->gDavBackend->GetMapiMessageForId($calendarId, $objectUri, $mapifolder, static::FILE_EXTENSION); |
||
252 | if (!$mapimessage) { |
||
253 | $this->logger->info("Object NOT FOUND"); |
||
254 | |||
255 | return null; |
||
256 | } |
||
257 | |||
258 | $realId = $this->gDavBackend->GetIdOfMapiMessage($calendarId, $mapimessage); |
||
259 | |||
260 | // this should be cached or moved to gDavBackend |
||
261 | $session = $this->gDavBackend->GetSession(); |
||
262 | $ab = $this->gDavBackend->GetAddressBook(); |
||
263 | |||
264 | $ics = mapi_mapitoical($session, $ab, $mapimessage, []); |
||
265 | if (!$ics && mapi_last_hresult()) { |
||
266 | $this->logger->error("Error generating ical, error code: 0x%08X", mapi_last_hresult()); |
||
267 | $ics = null; |
||
268 | } |
||
269 | elseif (!$ics) { |
||
270 | $this->logger->error("Error generating ical, unknown error"); |
||
271 | $ics = null; |
||
272 | } |
||
273 | |||
274 | $props = mapi_getprops($mapimessage, [PR_LAST_MODIFICATION_TIME]); |
||
275 | |||
276 | $r = [ |
||
277 | 'id' => $realId, |
||
278 | 'uri' => $realId . static::FILE_EXTENSION, |
||
279 | 'etag' => '"' . $props[PR_LAST_MODIFICATION_TIME] . '"', |
||
280 | 'lastmodified' => $props[PR_LAST_MODIFICATION_TIME], |
||
281 | 'calendarid' => $calendarId, |
||
282 | 'size' => strlen($ics), |
||
283 | 'calendardata' => $ics, |
||
284 | ]; |
||
285 | $this->logger->trace("returned data id: %s - size: %d - etag: %s", $r['id'], $r['size'], $r['etag']); |
||
286 | |||
287 | return $r; |
||
288 | } |
||
289 | |||
290 | /** |
||
291 | * Creates a new calendar object. |
||
292 | * |
||
293 | * The object uri is only the basename, or filename and not a full path. |
||
294 | * |
||
295 | * It is possible return an etag from this function, which will be used in |
||
296 | * the response to this PUT request. Note that the ETag must be surrounded |
||
297 | * by double-quotes. |
||
298 | * |
||
299 | * However, you should only really return this ETag if you don't mangle the |
||
300 | * calendar-data. If the result of a subsequent GET to this object is not |
||
301 | * the exact same as this request body, you should omit the ETag. |
||
302 | * |
||
303 | * @param mixed $calendarId |
||
304 | * @param string $objectUri |
||
305 | * @param string $calendarData |
||
306 | * |
||
307 | * @return null|string |
||
308 | */ |
||
309 | public function createCalendarObject($calendarId, $objectUri, $calendarData) { |
||
310 | $this->logger->trace("calendarId: %s - objectUri: %s", $calendarId, $objectUri); |
||
311 | $objectId = $this->gDavBackend->GetObjectIdFromObjectUri($objectUri, static::FILE_EXTENSION); |
||
312 | $folder = $this->gDavBackend->GetMapiFolder($calendarId); |
||
313 | $mapimessage = $this->gDavBackend->CreateObject($calendarId, $folder, $objectId); |
||
314 | $retval = $this->setData($calendarId, $mapimessage, $calendarData); |
||
315 | if (!$retval) { |
||
316 | return null; |
||
317 | } |
||
318 | |||
319 | return '"' . $retval . '"'; |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * Updates an existing calendarobject, based on its uri. |
||
324 | * |
||
325 | * The object uri is only the basename, or filename and not a full path. |
||
326 | * |
||
327 | * It is possible return an etag from this function, which will be used in |
||
328 | * the response to this PUT request. Note that the ETag must be surrounded |
||
329 | * by double-quotes. |
||
330 | * |
||
331 | * However, you should only really return this ETag if you don't mangle the |
||
332 | * calendar-data. If the result of a subsequent GET to this object is not |
||
333 | * the exact same as this request body, you should omit the ETag. |
||
334 | * |
||
335 | * @param mixed $calendarId |
||
336 | * @param string $objectUri |
||
337 | * @param string $calendarData |
||
338 | * |
||
339 | * @return null|string |
||
340 | */ |
||
341 | public function updateCalendarObject($calendarId, $objectUri, $calendarData) { |
||
342 | $this->logger->trace("calendarId: %s - objectUri: %s", $calendarId, $objectUri); |
||
343 | |||
344 | $folder = $this->gDavBackend->GetMapiFolder($calendarId); |
||
0 ignored issues
–
show
|
|||
345 | $mapimessage = $this->gDavBackend->GetMapiMessageForId($calendarId, $objectUri, null, static::FILE_EXTENSION); |
||
346 | $retval = $this->setData($calendarId, $mapimessage, $calendarData); |
||
347 | if (!$retval) { |
||
348 | return null; |
||
349 | } |
||
350 | |||
351 | return '"' . $retval . '"'; |
||
352 | } |
||
353 | |||
354 | /** |
||
355 | * Sets data for a calendar item. |
||
356 | * |
||
357 | * @param mixed $calendarId |
||
358 | * @param mixed $mapimessage |
||
359 | * @param string $ics |
||
360 | * |
||
361 | * @return null|string |
||
362 | */ |
||
363 | private function setData($calendarId, $mapimessage, $ics) { |
||
364 | // this should be cached or moved to gDavBackend |
||
365 | $store = $this->gDavBackend->GetStoreById($calendarId); |
||
366 | $session = $this->gDavBackend->GetSession(); |
||
367 | $ab = $this->gDavBackend->GetAddressBook(); |
||
368 | |||
369 | // Evolution sends daylight/standard information in the ical data |
||
370 | // and some values are not supported by Outlook/Exchange. |
||
371 | // Strip that data and leave only the last occurrences of |
||
372 | // daylight/standard information. |
||
373 | // @see GRAM-52 |
||
374 | |||
375 | $xLicLocation = stripos($ics, 'X-LIC-LOCATION:'); |
||
376 | if (($xLicLocation !== false) && |
||
377 | ( |
||
378 | substr_count($ics, 'BEGIN:DAYLIGHT', $xLicLocation) > 0 || |
||
379 | substr_count($ics, 'BEGIN:STANDARD', $xLicLocation) > 0 |
||
380 | )) { |
||
381 | $firstDaytime = stripos($ics, 'BEGIN:DAYLIGHT', $xLicLocation); |
||
382 | $firstStandard = stripos($ics, 'BEGIN:STANDARD', $xLicLocation); |
||
383 | |||
384 | $lastDaytime = strripos($ics, 'BEGIN:DAYLIGHT', $xLicLocation); |
||
385 | $lastStandard = strripos($ics, 'BEGIN:STANDARD', $xLicLocation); |
||
386 | |||
387 | // the first part of ics until the first piece of standard/daytime information |
||
388 | $cutStart = $firstDaytime < $firstStandard ? $firstDaytime : $firstStandard; |
||
389 | |||
390 | if ($lastDaytime > $lastStandard) { |
||
391 | // the part of the ics with the last piece of standard/daytime information |
||
392 | $cutEnd = $lastDaytime; |
||
393 | |||
394 | // the positions of the last piece of standard information |
||
395 | $cut1 = $lastStandard; |
||
396 | $cut2 = strripos($ics, 'END:STANDARD', $lastStandard) + 14; // strlen('END:STANDARD') |
||
397 | } |
||
398 | else { |
||
399 | // the part of the ics with the last piece of standard/daytime information |
||
400 | $cutEnd = $lastStandard; |
||
401 | |||
402 | // the positions of the last piece of daylight information |
||
403 | $cut1 = $lastDaytime; |
||
404 | $cut2 = strripos($ics, 'END:DAYLIGHT', $lastDaytime) + 14; // strlen('END:DAYLIGHT') |
||
405 | } |
||
406 | |||
407 | $ics = substr($ics, 0, $cutStart) . substr($ics, $cut1, $cut2 - $cut1) . substr($ics, $cutEnd); |
||
408 | $this->logger->trace("newics: %s", $ics); |
||
409 | } |
||
410 | |||
411 | $ok = mapi_icaltomapi($session, $store, $ab, $mapimessage, $ics, false); |
||
412 | if (!$ok && mapi_last_hresult()) { |
||
413 | $this->logger->error("Error updating mapi object, error code: 0x%08X", mapi_last_hresult()); |
||
414 | |||
415 | return null; |
||
416 | } |
||
417 | if (!$ok) { |
||
418 | $this->logger->error("Error updating mapi object, unknown error"); |
||
419 | |||
420 | return null; |
||
421 | } |
||
422 | |||
423 | $propList = MapiProps::GetAppointmentProperties(); |
||
424 | $defaultProps = MapiProps::GetDefaultAppoinmentProperties(); |
||
425 | $propsToSet = $this->gDavBackend->GetPropsToSet($calendarId, $mapimessage, $propList, $defaultProps); |
||
426 | if (!empty($propsToSet)) { |
||
427 | mapi_setprops($mapimessage, $propsToSet); |
||
428 | } |
||
429 | |||
430 | mapi_savechanges($mapimessage); |
||
431 | $props = mapi_getprops($mapimessage, [PR_LAST_MODIFICATION_TIME]); |
||
432 | |||
433 | return $props[PR_LAST_MODIFICATION_TIME]; |
||
434 | } |
||
435 | |||
436 | /** |
||
437 | * Deletes an existing calendar object. |
||
438 | * |
||
439 | * The object uri is only the basename, or filename and not a full path. |
||
440 | * |
||
441 | * @param string $calendarId |
||
442 | * @param string $objectUri |
||
443 | */ |
||
444 | public function deleteCalendarObject($calendarId, $objectUri) { |
||
445 | $this->logger->trace("calendarId: %s - objectUri: %s", $calendarId, $objectUri); |
||
446 | |||
447 | $mapifolder = $this->gDavBackend->GetMapiFolder($calendarId); |
||
448 | |||
449 | // to delete we need the PR_ENTRYID of the message |
||
450 | // TODO move this part to GrommunioDavBackend |
||
451 | $mapimessage = $this->gDavBackend->GetMapiMessageForId($calendarId, $objectUri, $mapifolder, static::FILE_EXTENSION); |
||
452 | $props = mapi_getprops($mapimessage, [PR_ENTRYID]); |
||
453 | mapi_folder_deletemessages($mapifolder, [$props[PR_ENTRYID]]); |
||
454 | } |
||
455 | |||
456 | /** |
||
457 | * Return a single scheduling object. |
||
458 | * |
||
459 | * TODO: Add implementation. |
||
460 | * |
||
461 | * @param string $principalUri |
||
462 | * @param string $objectUri |
||
463 | * |
||
464 | * @return array |
||
465 | */ |
||
466 | public function getSchedulingObject($principalUri, $objectUri) { |
||
467 | $this->logger->trace("principalUri: %s - objectUri: %s", $principalUri, $objectUri); |
||
468 | |||
469 | return []; |
||
470 | } |
||
471 | |||
472 | /** |
||
473 | * Returns scheduling objects for the principal URI. |
||
474 | * |
||
475 | * TODO: Add implementation. |
||
476 | * |
||
477 | * @param string $principalUri |
||
478 | * |
||
479 | * @return array |
||
480 | */ |
||
481 | public function getSchedulingObjects($principalUri) { |
||
482 | $this->logger->trace("principalUri: %s", $principalUri); |
||
483 | |||
484 | return []; |
||
485 | } |
||
486 | |||
487 | /** |
||
488 | * Delete scheduling object. |
||
489 | * |
||
490 | * TODO: Add implementation. |
||
491 | * |
||
492 | * @param string $principalUri |
||
493 | * @param string $objectUri |
||
494 | */ |
||
495 | public function deleteSchedulingObject($principalUri, $objectUri) { |
||
496 | $this->logger->trace("principalUri: %s - objectUri: %s", $principalUri, $objectUri); |
||
497 | } |
||
498 | |||
499 | /** |
||
500 | * Create a new scheduling object. |
||
501 | * |
||
502 | * TODO: Add implementation. |
||
503 | * |
||
504 | * @param string $principalUri |
||
505 | * @param string $objectUri |
||
506 | * @param string $objectData |
||
507 | */ |
||
508 | public function createSchedulingObject($principalUri, $objectUri, $objectData) { |
||
509 | $this->logger->trace("principalUri: %s - objectUri: %s - objectData: %s", $principalUri, $objectUri, $objectData); |
||
510 | } |
||
511 | |||
512 | /** |
||
513 | * Return CTAG for scheduling inbox. |
||
514 | * |
||
515 | * TODO: Add implementation. |
||
516 | * |
||
517 | * @param string $principalUri |
||
518 | * |
||
519 | * @return string |
||
520 | */ |
||
521 | public function getSchedulingInboxCtag($principalUri) { |
||
522 | $this->logger->trace("principalUri: %s", $principalUri); |
||
523 | |||
524 | return "empty"; |
||
525 | } |
||
526 | |||
527 | /** |
||
528 | * The getChanges method returns all the changes that have happened, since |
||
529 | * the specified syncToken in the specified calendar. |
||
530 | * |
||
531 | * This function should return an array, such as the following: |
||
532 | * |
||
533 | * [ |
||
534 | * 'syncToken' => 'The current synctoken', |
||
535 | * 'added' => [ |
||
536 | * 'new.txt', |
||
537 | * ], |
||
538 | * 'modified' => [ |
||
539 | * 'modified.txt', |
||
540 | * ], |
||
541 | * 'deleted' => [ |
||
542 | * 'foo.php.bak', |
||
543 | * 'old.txt' |
||
544 | * ] |
||
545 | * ); |
||
546 | * |
||
547 | * The returned syncToken property should reflect the *current* syncToken |
||
548 | * of the calendar, as reported in the {http://sabredav.org/ns}sync-token |
||
549 | * property This is * needed here too, to ensure the operation is atomic. |
||
550 | * |
||
551 | * If the $syncToken argument is specified as null, this is an initial |
||
552 | * sync, and all members should be reported. |
||
553 | * |
||
554 | * The modified property is an array of nodenames that have changed since |
||
555 | * the last token. |
||
556 | * |
||
557 | * The deleted property is an array with nodenames, that have been deleted |
||
558 | * from collection. |
||
559 | * |
||
560 | * The $syncLevel argument is basically the 'depth' of the report. If it's |
||
561 | * 1, you only have to report changes that happened only directly in |
||
562 | * immediate descendants. If it's 2, it should also include changes from |
||
563 | * the nodes below the child collections. (grandchildren) |
||
564 | * |
||
565 | * The $limit argument allows a client to specify how many results should |
||
566 | * be returned at most. If the limit is not specified, it should be treated |
||
567 | * as infinite. |
||
568 | * |
||
569 | * If the limit (infinite or not) is higher than you're willing to return, |
||
570 | * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. |
||
571 | * |
||
572 | * If the syncToken is expired (due to data cleanup) or unknown, you must |
||
573 | * return null. |
||
574 | * |
||
575 | * The limit is 'suggestive'. You are free to ignore it. |
||
576 | * |
||
577 | * @param string $calendarId |
||
578 | * @param string $syncToken |
||
579 | * @param int $syncLevel |
||
580 | * @param int $limit |
||
581 | * |
||
582 | * @return array |
||
583 | */ |
||
584 | public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) { |
||
585 | $this->logger->trace("calendarId: %s - syncToken: %s - syncLevel: %d - limit: %d", $calendarId, $syncToken, $syncLevel, $limit); |
||
586 | |||
587 | return $this->gDavBackend->Sync($calendarId, $syncToken, static::FILE_EXTENSION, $limit, ['types' => static::MESSAGE_CLASSES]); |
||
588 | } |
||
589 | } |
||
590 |