Completed
Branch develop (37f7b7)
by
unknown
24:41
created
htdocs/includes/sabre/psr/log/Psr/Log/LoggerAwareInterface.php 1 patch
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -7,12 +7,12 @@
 block discarded – undo
7 7
  */
8 8
 interface LoggerAwareInterface
9 9
 {
10
-    /**
11
-     * Sets a logger instance on the object.
12
-     *
13
-     * @param LoggerInterface $logger
14
-     *
15
-     * @return void
16
-     */
17
-    public function setLogger(LoggerInterface $logger);
10
+	/**
11
+	 * Sets a logger instance on the object.
12
+	 *
13
+	 * @param LoggerInterface $logger
14
+	 *
15
+	 * @return void
16
+	 */
17
+	public function setLogger(LoggerInterface $logger);
18 18
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/Plugin.php 2 patches
Indentation   +969 added lines, -969 removed lines patch added patch discarded remove patch
@@ -30,918 +30,918 @@  discard block
 block discarded – undo
30 30
  */
31 31
 class Plugin extends DAV\ServerPlugin
32 32
 {
33
-    /**
34
-     * This is the official CalDAV namespace.
35
-     */
36
-    const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
37
-
38
-    /**
39
-     * This is the namespace for the proprietary calendarserver extensions.
40
-     */
41
-    const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
42
-
43
-    /**
44
-     * The hardcoded root for calendar objects. It is unfortunate
45
-     * that we're stuck with it, but it will have to do for now.
46
-     */
47
-    const CALENDAR_ROOT = 'calendars';
48
-
49
-    /**
50
-     * Reference to server object.
51
-     *
52
-     * @var DAV\Server
53
-     */
54
-    protected $server;
55
-
56
-    /**
57
-     * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
58
-     * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
59
-     * capping it to 10M here.
60
-     */
61
-    protected $maxResourceSize = 10000000;
62
-
63
-    /**
64
-     * Use this method to tell the server this plugin defines additional
65
-     * HTTP methods.
66
-     *
67
-     * This method is passed a uri. It should only return HTTP methods that are
68
-     * available for the specified uri.
69
-     *
70
-     * @param string $uri
71
-     *
72
-     * @return array
73
-     */
74
-    public function getHTTPMethods($uri)
75
-    {
76
-        // The MKCALENDAR is only available on unmapped uri's, whose
77
-        // parents extend IExtendedCollection
78
-        list($parent, $name) = Uri\split($uri);
79
-
80
-        if ('' === $uri) {
81
-            $parent = '';
82
-        }
83
-
84
-        $node = $this->server->tree->getNodeForPath($parent);
85
-
86
-        if ($node instanceof DAV\IExtendedCollection) {
87
-            try {
88
-                $node->getChild($name);
89
-            } catch (DAV\Exception\NotFound $e) {
90
-                return ['MKCALENDAR'];
91
-            }
92
-        }
93
-
94
-        return [];
95
-    }
96
-
97
-    /**
98
-     * Returns the path to a principal's calendar home.
99
-     *
100
-     * The return url must not end with a slash.
101
-     * This function should return null in case a principal did not have
102
-     * a calendar home.
103
-     *
104
-     * @param string $principalUrl
105
-     *
106
-     * @return string
107
-     */
108
-    public function getCalendarHomeForPrincipal($principalUrl)
109
-    {
110
-        // The default behavior for most sabre/dav servers is that there is a
111
-        // principals root node, which contains users directly under it.
112
-        //
113
-        // This function assumes that there are two components in a principal
114
-        // path. If there's more, we don't return a calendar home. This
115
-        // excludes things like the calendar-proxy-read principal (which it
116
-        // should).
117
-        $parts = explode('/', trim($principalUrl, '/'));
118
-        if (2 !== count($parts)) {
119
-            return;
120
-        }
121
-        if ('principals' !== $parts[0]) {
122
-            return;
123
-        }
124
-
125
-        return self::CALENDAR_ROOT.'/'.$parts[1];
126
-    }
127
-
128
-    /**
129
-     * Returns a list of features for the DAV: HTTP header.
130
-     *
131
-     * @return array
132
-     */
133
-    public function getFeatures()
134
-    {
135
-        return ['calendar-access', 'calendar-proxy'];
136
-    }
137
-
138
-    /**
139
-     * Returns a plugin name.
140
-     *
141
-     * Using this name other plugins will be able to access other plugins
142
-     * using DAV\Server::getPlugin
143
-     *
144
-     * @return string
145
-     */
146
-    public function getPluginName()
147
-    {
148
-        return 'caldav';
149
-    }
150
-
151
-    /**
152
-     * Returns a list of reports this plugin supports.
153
-     *
154
-     * This will be used in the {DAV:}supported-report-set property.
155
-     * Note that you still need to subscribe to the 'report' event to actually
156
-     * implement them
157
-     *
158
-     * @param string $uri
159
-     *
160
-     * @return array
161
-     */
162
-    public function getSupportedReportSet($uri)
163
-    {
164
-        $node = $this->server->tree->getNodeForPath($uri);
165
-
166
-        $reports = [];
167
-        if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) {
168
-            $reports[] = '{'.self::NS_CALDAV.'}calendar-multiget';
169
-            $reports[] = '{'.self::NS_CALDAV.'}calendar-query';
170
-        }
171
-        if ($node instanceof ICalendar) {
172
-            $reports[] = '{'.self::NS_CALDAV.'}free-busy-query';
173
-        }
174
-        // iCal has a bug where it assumes that sync support is enabled, only
175
-        // if we say we support it on the calendar-home, even though this is
176
-        // not actually the case.
177
-        if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) {
178
-            $reports[] = '{DAV:}sync-collection';
179
-        }
180
-
181
-        return $reports;
182
-    }
183
-
184
-    /**
185
-     * Initializes the plugin.
186
-     */
187
-    public function initialize(DAV\Server $server)
188
-    {
189
-        $this->server = $server;
190
-
191
-        $server->on('method:MKCALENDAR', [$this, 'httpMkCalendar']);
192
-        $server->on('report', [$this, 'report']);
193
-        $server->on('propFind', [$this, 'propFind']);
194
-        $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
195
-        $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
196
-        $server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
197
-        $server->on('afterMethod:GET', [$this, 'httpAfterGET']);
198
-        $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
199
-
200
-        $server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
201
-        $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
202
-
203
-        $server->xml->elementMap['{'.self::NS_CALDAV.'}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
204
-        $server->xml->elementMap['{'.self::NS_CALDAV.'}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport';
205
-        $server->xml->elementMap['{'.self::NS_CALDAV.'}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport';
206
-        $server->xml->elementMap['{'.self::NS_CALDAV.'}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport';
207
-        $server->xml->elementMap['{'.self::NS_CALDAV.'}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar';
208
-        $server->xml->elementMap['{'.self::NS_CALDAV.'}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp';
209
-        $server->xml->elementMap['{'.self::NS_CALDAV.'}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
210
-
211
-        $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';
212
-
213
-        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
214
-        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
215
-
216
-        array_push($server->protectedProperties,
217
-            '{'.self::NS_CALDAV.'}supported-calendar-component-set',
218
-            '{'.self::NS_CALDAV.'}supported-calendar-data',
219
-            '{'.self::NS_CALDAV.'}max-resource-size',
220
-            '{'.self::NS_CALDAV.'}min-date-time',
221
-            '{'.self::NS_CALDAV.'}max-date-time',
222
-            '{'.self::NS_CALDAV.'}max-instances',
223
-            '{'.self::NS_CALDAV.'}max-attendees-per-instance',
224
-            '{'.self::NS_CALDAV.'}calendar-home-set',
225
-            '{'.self::NS_CALDAV.'}supported-collation-set',
226
-            '{'.self::NS_CALDAV.'}calendar-data',
227
-
228
-            // CalendarServer extensions
229
-            '{'.self::NS_CALENDARSERVER.'}getctag',
230
-            '{'.self::NS_CALENDARSERVER.'}calendar-proxy-read-for',
231
-            '{'.self::NS_CALENDARSERVER.'}calendar-proxy-write-for'
232
-        );
233
-
234
-        if ($aclPlugin = $server->getPlugin('acl')) {
235
-            $aclPlugin->principalSearchPropertySet['{'.self::NS_CALDAV.'}calendar-user-address-set'] = 'Calendar address';
236
-        }
237
-    }
238
-
239
-    /**
240
-     * This functions handles REPORT requests specific to CalDAV.
241
-     *
242
-     * @param string $reportName
243
-     * @param mixed  $report
244
-     * @param mixed  $path
245
-     *
246
-     * @return bool|null
247
-     */
248
-    public function report($reportName, $report, $path)
249
-    {
250
-        switch ($reportName) {
251
-            case '{'.self::NS_CALDAV.'}calendar-multiget':
252
-                $this->server->transactionType = 'report-calendar-multiget';
253
-                $this->calendarMultiGetReport($report);
254
-
255
-                return false;
256
-            case '{'.self::NS_CALDAV.'}calendar-query':
257
-                $this->server->transactionType = 'report-calendar-query';
258
-                $this->calendarQueryReport($report);
259
-
260
-                return false;
261
-            case '{'.self::NS_CALDAV.'}free-busy-query':
262
-                $this->server->transactionType = 'report-free-busy-query';
263
-                $this->freeBusyQueryReport($report);
264
-
265
-                return false;
266
-        }
267
-    }
268
-
269
-    /**
270
-     * This function handles the MKCALENDAR HTTP method, which creates
271
-     * a new calendar.
272
-     *
273
-     * @return bool
274
-     */
275
-    public function httpMkCalendar(RequestInterface $request, ResponseInterface $response)
276
-    {
277
-        $body = $request->getBodyAsString();
278
-        $path = $request->getPath();
279
-
280
-        $properties = [];
281
-
282
-        if ($body) {
283
-            try {
284
-                $mkcalendar = $this->server->xml->expect(
285
-                    '{urn:ietf:params:xml:ns:caldav}mkcalendar',
286
-                    $body
287
-                );
288
-            } catch (\Sabre\Xml\ParseException $e) {
289
-                throw new BadRequest($e->getMessage(), 0, $e);
290
-            }
291
-            $properties = $mkcalendar->getProperties();
292
-        }
293
-
294
-        // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
295
-        // subscriptions. Before that it used MKCOL which was the correct way
296
-        // to do this.
297
-        //
298
-        // If the body had a {DAV:}resourcetype, it means we stumbled upon this
299
-        // request, and we simply use it instead of the pre-defined list.
300
-        if (isset($properties['{DAV:}resourcetype'])) {
301
-            $resourceType = $properties['{DAV:}resourcetype']->getValue();
302
-        } else {
303
-            $resourceType = ['{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar'];
304
-        }
305
-
306
-        $this->server->createCollection($path, new MkCol($resourceType, $properties));
307
-
308
-        $response->setStatus(201);
309
-        $response->setHeader('Content-Length', 0);
310
-
311
-        // This breaks the method chain.
312
-        return false;
313
-    }
314
-
315
-    /**
316
-     * PropFind.
317
-     *
318
-     * This method handler is invoked before any after properties for a
319
-     * resource are fetched. This allows us to add in any CalDAV specific
320
-     * properties.
321
-     */
322
-    public function propFind(DAV\PropFind $propFind, DAV\INode $node)
323
-    {
324
-        $ns = '{'.self::NS_CALDAV.'}';
325
-
326
-        if ($node instanceof ICalendarObjectContainer) {
327
-            $propFind->handle($ns.'max-resource-size', $this->maxResourceSize);
328
-            $propFind->handle($ns.'supported-calendar-data', function () {
329
-                return new Xml\Property\SupportedCalendarData();
330
-            });
331
-            $propFind->handle($ns.'supported-collation-set', function () {
332
-                return new Xml\Property\SupportedCollationSet();
333
-            });
334
-        }
335
-
336
-        if ($node instanceof DAVACL\IPrincipal) {
337
-            $principalUrl = $node->getPrincipalUrl();
338
-
339
-            $propFind->handle('{'.self::NS_CALDAV.'}calendar-home-set', function () use ($principalUrl) {
340
-                $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
341
-                if (is_null($calendarHomePath)) {
342
-                    return null;
343
-                }
344
-
345
-                return new LocalHref($calendarHomePath.'/');
346
-            });
347
-            // The calendar-user-address-set property is basically mapped to
348
-            // the {DAV:}alternate-URI-set property.
349
-            $propFind->handle('{'.self::NS_CALDAV.'}calendar-user-address-set', function () use ($node) {
350
-                $addresses = $node->getAlternateUriSet();
351
-                $addresses[] = $this->server->getBaseUri().$node->getPrincipalUrl().'/';
352
-
353
-                return new LocalHref($addresses);
354
-            });
355
-            // For some reason somebody thought it was a good idea to add
356
-            // another one of these properties. We're supporting it too.
357
-            $propFind->handle('{'.self::NS_CALENDARSERVER.'}email-address-set', function () use ($node) {
358
-                $addresses = $node->getAlternateUriSet();
359
-                $emails = [];
360
-                foreach ($addresses as $address) {
361
-                    if ('mailto:' === substr($address, 0, 7)) {
362
-                        $emails[] = substr($address, 7);
363
-                    }
364
-                }
365
-
366
-                return new Xml\Property\EmailAddressSet($emails);
367
-            });
368
-
369
-            // These two properties are shortcuts for ical to easily find
370
-            // other principals this principal has access to.
371
-            $propRead = '{'.self::NS_CALENDARSERVER.'}calendar-proxy-read-for';
372
-            $propWrite = '{'.self::NS_CALENDARSERVER.'}calendar-proxy-write-for';
373
-
374
-            if (404 === $propFind->getStatus($propRead) || 404 === $propFind->getStatus($propWrite)) {
375
-                $aclPlugin = $this->server->getPlugin('acl');
376
-                $membership = $aclPlugin->getPrincipalMembership($propFind->getPath());
377
-                $readList = [];
378
-                $writeList = [];
379
-
380
-                foreach ($membership as $group) {
381
-                    $groupNode = $this->server->tree->getNodeForPath($group);
382
-
383
-                    $listItem = Uri\split($group)[0].'/';
384
-
385
-                    // If the node is either ap proxy-read or proxy-write
386
-                    // group, we grab the parent principal and add it to the
387
-                    // list.
388
-                    if ($groupNode instanceof Principal\IProxyRead) {
389
-                        $readList[] = $listItem;
390
-                    }
391
-                    if ($groupNode instanceof Principal\IProxyWrite) {
392
-                        $writeList[] = $listItem;
393
-                    }
394
-                }
395
-
396
-                $propFind->set($propRead, new LocalHref($readList));
397
-                $propFind->set($propWrite, new LocalHref($writeList));
398
-            }
399
-        } // instanceof IPrincipal
400
-
401
-        if ($node instanceof ICalendarObject) {
402
-            // The calendar-data property is not supposed to be a 'real'
403
-            // property, but in large chunks of the spec it does act as such.
404
-            // Therefore we simply expose it as a property.
405
-            $propFind->handle('{'.self::NS_CALDAV.'}calendar-data', function () use ($node) {
406
-                $val = $node->get();
407
-                if (is_resource($val)) {
408
-                    $val = stream_get_contents($val);
409
-                }
410
-
411
-                // Taking out \r to not screw up the xml output
412
-                return str_replace("\r", '', $val);
413
-            });
414
-        }
415
-    }
416
-
417
-    /**
418
-     * This function handles the calendar-multiget REPORT.
419
-     *
420
-     * This report is used by the client to fetch the content of a series
421
-     * of urls. Effectively avoiding a lot of redundant requests.
422
-     *
423
-     * @param CalendarMultiGetReport $report
424
-     */
425
-    public function calendarMultiGetReport($report)
426
-    {
427
-        $needsJson = 'application/calendar+json' === $report->contentType;
428
-
429
-        $timeZones = [];
430
-        $propertyList = [];
431
-
432
-        $paths = array_map(
433
-            [$this->server, 'calculateUri'],
434
-            $report->hrefs
435
-        );
436
-
437
-        foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) {
438
-            if (($needsJson || $report->expand) && isset($objProps[200]['{'.self::NS_CALDAV.'}calendar-data'])) {
439
-                $vObject = VObject\Reader::read($objProps[200]['{'.self::NS_CALDAV.'}calendar-data']);
440
-
441
-                if ($report->expand) {
442
-                    // We're expanding, and for that we need to figure out the
443
-                    // calendar's timezone.
444
-                    list($calendarPath) = Uri\split($uri);
445
-                    if (!isset($timeZones[$calendarPath])) {
446
-                        // Checking the calendar-timezone property.
447
-                        $tzProp = '{'.self::NS_CALDAV.'}calendar-timezone';
448
-                        $tzResult = $this->server->getProperties($calendarPath, [$tzProp]);
449
-                        if (isset($tzResult[$tzProp])) {
450
-                            // This property contains a VCALENDAR with a single
451
-                            // VTIMEZONE.
452
-                            $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
453
-                            $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
454
-                        } else {
455
-                            // Defaulting to UTC.
456
-                            $timeZone = new DateTimeZone('UTC');
457
-                        }
458
-                        $timeZones[$calendarPath] = $timeZone;
459
-                    }
460
-
461
-                    $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]);
462
-                }
463
-                if ($needsJson) {
464
-                    $objProps[200]['{'.self::NS_CALDAV.'}calendar-data'] = json_encode($vObject->jsonSerialize());
465
-                } else {
466
-                    $objProps[200]['{'.self::NS_CALDAV.'}calendar-data'] = $vObject->serialize();
467
-                }
468
-                // Destroy circular references so PHP will garbage collect the
469
-                // object.
470
-                $vObject->destroy();
471
-            }
472
-
473
-            $propertyList[] = $objProps;
474
-        }
475
-
476
-        $prefer = $this->server->getHTTPPrefer();
477
-
478
-        $this->server->httpResponse->setStatus(207);
479
-        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
480
-        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
481
-        $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, 'minimal' === $prefer['return']));
482
-    }
483
-
484
-    /**
485
-     * This function handles the calendar-query REPORT.
486
-     *
487
-     * This report is used by clients to request calendar objects based on
488
-     * complex conditions.
489
-     *
490
-     * @param Xml\Request\CalendarQueryReport $report
491
-     */
492
-    public function calendarQueryReport($report)
493
-    {
494
-        $path = $this->server->getRequestUri();
495
-
496
-        $needsJson = 'application/calendar+json' === $report->contentType;
497
-
498
-        $node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
499
-        $depth = $this->server->getHTTPDepth(0);
500
-
501
-        // The default result is an empty array
502
-        $result = [];
503
-
504
-        $calendarTimeZone = null;
505
-        if ($report->expand) {
506
-            // We're expanding, and for that we need to figure out the
507
-            // calendar's timezone.
508
-            $tzProp = '{'.self::NS_CALDAV.'}calendar-timezone';
509
-            $tzResult = $this->server->getProperties($path, [$tzProp]);
510
-            if (isset($tzResult[$tzProp])) {
511
-                // This property contains a VCALENDAR with a single
512
-                // VTIMEZONE.
513
-                $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
514
-                $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
515
-
516
-                // Destroy circular references so PHP will garbage collect the
517
-                // object.
518
-                $vtimezoneObj->destroy();
519
-            } else {
520
-                // Defaulting to UTC.
521
-                $calendarTimeZone = new DateTimeZone('UTC');
522
-            }
523
-        }
524
-
525
-        // The calendarobject was requested directly. In this case we handle
526
-        // this locally.
527
-        if (0 == $depth && $node instanceof ICalendarObject) {
528
-            $requestedCalendarData = true;
529
-            $requestedProperties = $report->properties;
530
-
531
-            if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
532
-                // We always retrieve calendar-data, as we need it for filtering.
533
-                $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
534
-
535
-                // If calendar-data wasn't explicitly requested, we need to remove
536
-                // it after processing.
537
-                $requestedCalendarData = false;
538
-            }
539
-
540
-            $properties = $this->server->getPropertiesForPath(
541
-                $path,
542
-                $requestedProperties,
543
-                0
544
-            );
545
-
546
-            // This array should have only 1 element, the first calendar
547
-            // object.
548
-            $properties = current($properties);
549
-
550
-            // If there wasn't any calendar-data returned somehow, we ignore
551
-            // this.
552
-            if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
553
-                $validator = new CalendarQueryValidator();
554
-
555
-                $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
556
-                if ($validator->validate($vObject, $report->filters)) {
557
-                    // If the client didn't require the calendar-data property,
558
-                    // we won't give it back.
559
-                    if (!$requestedCalendarData) {
560
-                        unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
561
-                    } else {
562
-                        if ($report->expand) {
563
-                            $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
564
-                        }
565
-                        if ($needsJson) {
566
-                            $properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = json_encode($vObject->jsonSerialize());
567
-                        } elseif ($report->expand) {
568
-                            $properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = $vObject->serialize();
569
-                        }
570
-                    }
571
-
572
-                    $result = [$properties];
573
-                }
574
-                // Destroy circular references so PHP will garbage collect the
575
-                // object.
576
-                $vObject->destroy();
577
-            }
578
-        }
579
-
580
-        if ($node instanceof ICalendarObjectContainer && 0 === $depth) {
581
-            if (0 === strpos((string) $this->server->httpRequest->getHeader('User-Agent'), 'MSFT-')) {
582
-                // Microsoft clients incorrectly supplied depth as 0, when it actually
583
-                // should have set depth to 1. We're implementing a workaround here
584
-                // to deal with this.
585
-                //
586
-                // This targets at least the following clients:
587
-                //   Windows 10
588
-                //   Windows Phone 8, 10
589
-                $depth = 1;
590
-            } else {
591
-                throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
592
-            }
593
-        }
594
-
595
-        // If we're dealing with a calendar, the calendar itself is responsible
596
-        // for the calendar-query.
597
-        if ($node instanceof ICalendarObjectContainer && 1 == $depth) {
598
-            $nodePaths = $node->calendarQuery($report->filters);
599
-
600
-            foreach ($nodePaths as $path) {
601
-                list($properties) =
602
-                    $this->server->getPropertiesForPath($this->server->getRequestUri().'/'.$path, $report->properties);
603
-
604
-                if (($needsJson || $report->expand)) {
605
-                    $vObject = VObject\Reader::read($properties[200]['{'.self::NS_CALDAV.'}calendar-data']);
606
-
607
-                    if ($report->expand) {
608
-                        $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
609
-                    }
610
-
611
-                    if ($needsJson) {
612
-                        $properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = json_encode($vObject->jsonSerialize());
613
-                    } else {
614
-                        $properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = $vObject->serialize();
615
-                    }
616
-
617
-                    // Destroy circular references so PHP will garbage collect the
618
-                    // object.
619
-                    $vObject->destroy();
620
-                }
621
-                $result[] = $properties;
622
-            }
623
-        }
624
-
625
-        $prefer = $this->server->getHTTPPrefer();
626
-
627
-        $this->server->httpResponse->setStatus(207);
628
-        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
629
-        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
630
-        $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, 'minimal' === $prefer['return']));
631
-    }
632
-
633
-    /**
634
-     * This method is responsible for parsing the request and generating the
635
-     * response for the CALDAV:free-busy-query REPORT.
636
-     */
637
-    protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report)
638
-    {
639
-        $uri = $this->server->getRequestUri();
640
-
641
-        $acl = $this->server->getPlugin('acl');
642
-        if ($acl) {
643
-            $acl->checkPrivileges($uri, '{'.self::NS_CALDAV.'}read-free-busy');
644
-        }
645
-
646
-        $calendar = $this->server->tree->getNodeForPath($uri);
647
-        if (!$calendar instanceof ICalendar) {
648
-            throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars');
649
-        }
650
-
651
-        $tzProp = '{'.self::NS_CALDAV.'}calendar-timezone';
652
-
653
-        // Figuring out the default timezone for the calendar, for floating
654
-        // times.
655
-        $calendarProps = $this->server->getProperties($uri, [$tzProp]);
656
-
657
-        if (isset($calendarProps[$tzProp])) {
658
-            $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]);
659
-            $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
660
-            // Destroy circular references so PHP will garbage collect the object.
661
-            $vtimezoneObj->destroy();
662
-        } else {
663
-            $calendarTimeZone = new DateTimeZone('UTC');
664
-        }
665
-
666
-        // Doing a calendar-query first, to make sure we get the most
667
-        // performance.
668
-        $urls = $calendar->calendarQuery([
669
-            'name' => 'VCALENDAR',
670
-            'comp-filters' => [
671
-                [
672
-                    'name' => 'VEVENT',
673
-                    'comp-filters' => [],
674
-                    'prop-filters' => [],
675
-                    'is-not-defined' => false,
676
-                    'time-range' => [
677
-                        'start' => $report->start,
678
-                        'end' => $report->end,
679
-                    ],
680
-                ],
681
-            ],
682
-            'prop-filters' => [],
683
-            'is-not-defined' => false,
684
-            'time-range' => null,
685
-        ]);
686
-
687
-        $objects = array_map(function ($url) use ($calendar) {
688
-            $obj = $calendar->getChild($url)->get();
689
-
690
-            return $obj;
691
-        }, $urls);
692
-
693
-        $generator = new VObject\FreeBusyGenerator();
694
-        $generator->setObjects($objects);
695
-        $generator->setTimeRange($report->start, $report->end);
696
-        $generator->setTimeZone($calendarTimeZone);
697
-        $result = $generator->getResult();
698
-        $result = $result->serialize();
699
-
700
-        $this->server->httpResponse->setStatus(200);
701
-        $this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
702
-        $this->server->httpResponse->setHeader('Content-Length', strlen($result));
703
-        $this->server->httpResponse->setBody($result);
704
-    }
705
-
706
-    /**
707
-     * This method is triggered before a file gets updated with new content.
708
-     *
709
-     * This plugin uses this method to ensure that CalDAV objects receive
710
-     * valid calendar data.
711
-     *
712
-     * @param string   $path
713
-     * @param resource $data
714
-     * @param bool     $modified should be set to true, if this event handler
715
-     *                           changed &$data
716
-     */
717
-    public function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified)
718
-    {
719
-        if (!$node instanceof ICalendarObject) {
720
-            return;
721
-        }
722
-
723
-        // We're onyl interested in ICalendarObject nodes that are inside of a
724
-        // real calendar. This is to avoid triggering validation and scheduling
725
-        // for non-calendars (such as an inbox).
726
-        list($parent) = Uri\split($path);
727
-        $parentNode = $this->server->tree->getNodeForPath($parent);
728
-
729
-        if (!$parentNode instanceof ICalendar) {
730
-            return;
731
-        }
732
-
733
-        $this->validateICalendar(
734
-            $data,
735
-            $path,
736
-            $modified,
737
-            $this->server->httpRequest,
738
-            $this->server->httpResponse,
739
-            false
740
-        );
741
-    }
742
-
743
-    /**
744
-     * This method is triggered before a new file is created.
745
-     *
746
-     * This plugin uses this method to ensure that newly created calendar
747
-     * objects contain valid calendar data.
748
-     *
749
-     * @param string   $path
750
-     * @param resource $data
751
-     * @param bool     $modified should be set to true, if this event handler
752
-     *                           changed &$data
753
-     */
754
-    public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified)
755
-    {
756
-        if (!$parentNode instanceof ICalendar) {
757
-            return;
758
-        }
759
-
760
-        $this->validateICalendar(
761
-            $data,
762
-            $path,
763
-            $modified,
764
-            $this->server->httpRequest,
765
-            $this->server->httpResponse,
766
-            true
767
-        );
768
-    }
769
-
770
-    /**
771
-     * Checks if the submitted iCalendar data is in fact, valid.
772
-     *
773
-     * An exception is thrown if it's not.
774
-     *
775
-     * @param resource|string   $data
776
-     * @param string            $path
777
-     * @param bool              $modified should be set to true, if this event handler
778
-     *                                    changed &$data
779
-     * @param RequestInterface  $request  the http request
780
-     * @param ResponseInterface $response the http response
781
-     * @param bool              $isNew    is the item a new one, or an update
782
-     */
783
-    protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew)
784
-    {
785
-        // If it's a stream, we convert it to a string first.
786
-        if (is_resource($data)) {
787
-            $data = stream_get_contents($data);
788
-        }
789
-
790
-        $before = $data;
791
-
792
-        try {
793
-            // If the data starts with a [, we can reasonably assume we're dealing
794
-            // with a jCal object.
795
-            if ('[' === substr($data, 0, 1)) {
796
-                $vobj = VObject\Reader::readJson($data);
797
-
798
-                // Converting $data back to iCalendar, as that's what we
799
-                // technically support everywhere.
800
-                $data = $vobj->serialize();
801
-                $modified = true;
802
-            } else {
803
-                $vobj = VObject\Reader::read($data);
804
-            }
805
-        } catch (VObject\ParseException $e) {
806
-            throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: '.$e->getMessage());
807
-        }
808
-
809
-        if ('VCALENDAR' !== $vobj->name) {
810
-            throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
811
-        }
812
-
813
-        $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
814
-
815
-        // Get the Supported Components for the target calendar
816
-        list($parentPath) = Uri\split($path);
817
-        $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
818
-
819
-        if (isset($calendarProperties[$sCCS])) {
820
-            $supportedComponents = $calendarProperties[$sCCS]->getValue();
821
-        } else {
822
-            $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
823
-        }
824
-
825
-        $foundType = null;
826
-
827
-        foreach ($vobj->getComponents() as $component) {
828
-            switch ($component->name) {
829
-                case 'VTIMEZONE':
830
-                    continue 2;
831
-                case 'VEVENT':
832
-                case 'VTODO':
833
-                case 'VJOURNAL':
834
-                    $foundType = $component->name;
835
-                    break;
836
-            }
837
-        }
838
-
839
-        if (!$foundType || !in_array($foundType, $supportedComponents)) {
840
-            throw new Exception\InvalidComponentType('iCalendar objects must at least have a component of type '.implode(', ', $supportedComponents));
841
-        }
842
-
843
-        $options = VObject\Node::PROFILE_CALDAV;
844
-        $prefer = $this->server->getHTTPPrefer();
845
-
846
-        if ('strict' !== $prefer['handling']) {
847
-            $options |= VObject\Node::REPAIR;
848
-        }
849
-
850
-        $messages = $vobj->validate($options);
851
-
852
-        $highestLevel = 0;
853
-        $warningMessage = null;
854
-
855
-        // $messages contains a list of problems with the vcard, along with
856
-        // their severity.
857
-        foreach ($messages as $message) {
858
-            if ($message['level'] > $highestLevel) {
859
-                // Recording the highest reported error level.
860
-                $highestLevel = $message['level'];
861
-                $warningMessage = $message['message'];
862
-            }
863
-            switch ($message['level']) {
864
-                case 1:
865
-                    // Level 1 means that there was a problem, but it was repaired.
866
-                    $modified = true;
867
-                    break;
868
-                case 2:
869
-                    // Level 2 means a warning, but not critical
870
-                    break;
871
-                case 3:
872
-                    // Level 3 means a critical error
873
-                    throw new DAV\Exception\UnsupportedMediaType('Validation error in iCalendar: '.$message['message']);
874
-            }
875
-        }
876
-        if ($warningMessage) {
877
-            $response->setHeader(
878
-                'X-Sabre-Ew-Gross',
879
-                'iCalendar validation warning: '.$warningMessage
880
-            );
881
-        }
882
-
883
-        // We use an extra variable to allow event handles to tell us whether
884
-        // the object was modified or not.
885
-        //
886
-        // This helps us determine if we need to re-serialize the object.
887
-        $subModified = false;
888
-
889
-        $this->server->emit(
890
-            'calendarObjectChange',
891
-            [
892
-                $request,
893
-                $response,
894
-                $vobj,
895
-                $parentPath,
896
-                &$subModified,
897
-                $isNew,
898
-            ]
899
-        );
900
-
901
-        if ($modified || $subModified) {
902
-            // An event handler told us that it modified the object.
903
-            $data = $vobj->serialize();
904
-
905
-            // Using md5 to figure out if there was an *actual* change.
906
-            if (!$modified && 0 !== strcmp($data, $before)) {
907
-                $modified = true;
908
-            }
909
-        }
910
-
911
-        // Destroy circular references so PHP will garbage collect the object.
912
-        $vobj->destroy();
913
-    }
914
-
915
-    /**
916
-     * This method is triggered whenever a subsystem reqeuests the privileges
917
-     * that are supported on a particular node.
918
-     */
919
-    public function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
920
-    {
921
-        if ($node instanceof ICalendar) {
922
-            $supportedPrivilegeSet['{DAV:}read']['aggregates']['{'.self::NS_CALDAV.'}read-free-busy'] = [
923
-                'abstract' => false,
924
-                'aggregates' => [],
925
-            ];
926
-        }
927
-    }
928
-
929
-    /**
930
-     * This method is used to generate HTML output for the
931
-     * DAV\Browser\Plugin. This allows us to generate an interface users
932
-     * can use to create new calendars.
933
-     *
934
-     * @param string $output
935
-     *
936
-     * @return bool
937
-     */
938
-    public function htmlActionsPanel(DAV\INode $node, &$output)
939
-    {
940
-        if (!$node instanceof CalendarHome) {
941
-            return;
942
-        }
943
-
944
-        $output .= '<tr><td colspan="2"><form method="post" action="">
33
+	/**
34
+	 * This is the official CalDAV namespace.
35
+	 */
36
+	const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
37
+
38
+	/**
39
+	 * This is the namespace for the proprietary calendarserver extensions.
40
+	 */
41
+	const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
42
+
43
+	/**
44
+	 * The hardcoded root for calendar objects. It is unfortunate
45
+	 * that we're stuck with it, but it will have to do for now.
46
+	 */
47
+	const CALENDAR_ROOT = 'calendars';
48
+
49
+	/**
50
+	 * Reference to server object.
51
+	 *
52
+	 * @var DAV\Server
53
+	 */
54
+	protected $server;
55
+
56
+	/**
57
+	 * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
58
+	 * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
59
+	 * capping it to 10M here.
60
+	 */
61
+	protected $maxResourceSize = 10000000;
62
+
63
+	/**
64
+	 * Use this method to tell the server this plugin defines additional
65
+	 * HTTP methods.
66
+	 *
67
+	 * This method is passed a uri. It should only return HTTP methods that are
68
+	 * available for the specified uri.
69
+	 *
70
+	 * @param string $uri
71
+	 *
72
+	 * @return array
73
+	 */
74
+	public function getHTTPMethods($uri)
75
+	{
76
+		// The MKCALENDAR is only available on unmapped uri's, whose
77
+		// parents extend IExtendedCollection
78
+		list($parent, $name) = Uri\split($uri);
79
+
80
+		if ('' === $uri) {
81
+			$parent = '';
82
+		}
83
+
84
+		$node = $this->server->tree->getNodeForPath($parent);
85
+
86
+		if ($node instanceof DAV\IExtendedCollection) {
87
+			try {
88
+				$node->getChild($name);
89
+			} catch (DAV\Exception\NotFound $e) {
90
+				return ['MKCALENDAR'];
91
+			}
92
+		}
93
+
94
+		return [];
95
+	}
96
+
97
+	/**
98
+	 * Returns the path to a principal's calendar home.
99
+	 *
100
+	 * The return url must not end with a slash.
101
+	 * This function should return null in case a principal did not have
102
+	 * a calendar home.
103
+	 *
104
+	 * @param string $principalUrl
105
+	 *
106
+	 * @return string
107
+	 */
108
+	public function getCalendarHomeForPrincipal($principalUrl)
109
+	{
110
+		// The default behavior for most sabre/dav servers is that there is a
111
+		// principals root node, which contains users directly under it.
112
+		//
113
+		// This function assumes that there are two components in a principal
114
+		// path. If there's more, we don't return a calendar home. This
115
+		// excludes things like the calendar-proxy-read principal (which it
116
+		// should).
117
+		$parts = explode('/', trim($principalUrl, '/'));
118
+		if (2 !== count($parts)) {
119
+			return;
120
+		}
121
+		if ('principals' !== $parts[0]) {
122
+			return;
123
+		}
124
+
125
+		return self::CALENDAR_ROOT.'/'.$parts[1];
126
+	}
127
+
128
+	/**
129
+	 * Returns a list of features for the DAV: HTTP header.
130
+	 *
131
+	 * @return array
132
+	 */
133
+	public function getFeatures()
134
+	{
135
+		return ['calendar-access', 'calendar-proxy'];
136
+	}
137
+
138
+	/**
139
+	 * Returns a plugin name.
140
+	 *
141
+	 * Using this name other plugins will be able to access other plugins
142
+	 * using DAV\Server::getPlugin
143
+	 *
144
+	 * @return string
145
+	 */
146
+	public function getPluginName()
147
+	{
148
+		return 'caldav';
149
+	}
150
+
151
+	/**
152
+	 * Returns a list of reports this plugin supports.
153
+	 *
154
+	 * This will be used in the {DAV:}supported-report-set property.
155
+	 * Note that you still need to subscribe to the 'report' event to actually
156
+	 * implement them
157
+	 *
158
+	 * @param string $uri
159
+	 *
160
+	 * @return array
161
+	 */
162
+	public function getSupportedReportSet($uri)
163
+	{
164
+		$node = $this->server->tree->getNodeForPath($uri);
165
+
166
+		$reports = [];
167
+		if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) {
168
+			$reports[] = '{'.self::NS_CALDAV.'}calendar-multiget';
169
+			$reports[] = '{'.self::NS_CALDAV.'}calendar-query';
170
+		}
171
+		if ($node instanceof ICalendar) {
172
+			$reports[] = '{'.self::NS_CALDAV.'}free-busy-query';
173
+		}
174
+		// iCal has a bug where it assumes that sync support is enabled, only
175
+		// if we say we support it on the calendar-home, even though this is
176
+		// not actually the case.
177
+		if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) {
178
+			$reports[] = '{DAV:}sync-collection';
179
+		}
180
+
181
+		return $reports;
182
+	}
183
+
184
+	/**
185
+	 * Initializes the plugin.
186
+	 */
187
+	public function initialize(DAV\Server $server)
188
+	{
189
+		$this->server = $server;
190
+
191
+		$server->on('method:MKCALENDAR', [$this, 'httpMkCalendar']);
192
+		$server->on('report', [$this, 'report']);
193
+		$server->on('propFind', [$this, 'propFind']);
194
+		$server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
195
+		$server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
196
+		$server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
197
+		$server->on('afterMethod:GET', [$this, 'httpAfterGET']);
198
+		$server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
199
+
200
+		$server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
201
+		$server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
202
+
203
+		$server->xml->elementMap['{'.self::NS_CALDAV.'}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
204
+		$server->xml->elementMap['{'.self::NS_CALDAV.'}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport';
205
+		$server->xml->elementMap['{'.self::NS_CALDAV.'}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport';
206
+		$server->xml->elementMap['{'.self::NS_CALDAV.'}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport';
207
+		$server->xml->elementMap['{'.self::NS_CALDAV.'}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar';
208
+		$server->xml->elementMap['{'.self::NS_CALDAV.'}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp';
209
+		$server->xml->elementMap['{'.self::NS_CALDAV.'}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
210
+
211
+		$server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';
212
+
213
+		$server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
214
+		$server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
215
+
216
+		array_push($server->protectedProperties,
217
+			'{'.self::NS_CALDAV.'}supported-calendar-component-set',
218
+			'{'.self::NS_CALDAV.'}supported-calendar-data',
219
+			'{'.self::NS_CALDAV.'}max-resource-size',
220
+			'{'.self::NS_CALDAV.'}min-date-time',
221
+			'{'.self::NS_CALDAV.'}max-date-time',
222
+			'{'.self::NS_CALDAV.'}max-instances',
223
+			'{'.self::NS_CALDAV.'}max-attendees-per-instance',
224
+			'{'.self::NS_CALDAV.'}calendar-home-set',
225
+			'{'.self::NS_CALDAV.'}supported-collation-set',
226
+			'{'.self::NS_CALDAV.'}calendar-data',
227
+
228
+			// CalendarServer extensions
229
+			'{'.self::NS_CALENDARSERVER.'}getctag',
230
+			'{'.self::NS_CALENDARSERVER.'}calendar-proxy-read-for',
231
+			'{'.self::NS_CALENDARSERVER.'}calendar-proxy-write-for'
232
+		);
233
+
234
+		if ($aclPlugin = $server->getPlugin('acl')) {
235
+			$aclPlugin->principalSearchPropertySet['{'.self::NS_CALDAV.'}calendar-user-address-set'] = 'Calendar address';
236
+		}
237
+	}
238
+
239
+	/**
240
+	 * This functions handles REPORT requests specific to CalDAV.
241
+	 *
242
+	 * @param string $reportName
243
+	 * @param mixed  $report
244
+	 * @param mixed  $path
245
+	 *
246
+	 * @return bool|null
247
+	 */
248
+	public function report($reportName, $report, $path)
249
+	{
250
+		switch ($reportName) {
251
+			case '{'.self::NS_CALDAV.'}calendar-multiget':
252
+				$this->server->transactionType = 'report-calendar-multiget';
253
+				$this->calendarMultiGetReport($report);
254
+
255
+				return false;
256
+			case '{'.self::NS_CALDAV.'}calendar-query':
257
+				$this->server->transactionType = 'report-calendar-query';
258
+				$this->calendarQueryReport($report);
259
+
260
+				return false;
261
+			case '{'.self::NS_CALDAV.'}free-busy-query':
262
+				$this->server->transactionType = 'report-free-busy-query';
263
+				$this->freeBusyQueryReport($report);
264
+
265
+				return false;
266
+		}
267
+	}
268
+
269
+	/**
270
+	 * This function handles the MKCALENDAR HTTP method, which creates
271
+	 * a new calendar.
272
+	 *
273
+	 * @return bool
274
+	 */
275
+	public function httpMkCalendar(RequestInterface $request, ResponseInterface $response)
276
+	{
277
+		$body = $request->getBodyAsString();
278
+		$path = $request->getPath();
279
+
280
+		$properties = [];
281
+
282
+		if ($body) {
283
+			try {
284
+				$mkcalendar = $this->server->xml->expect(
285
+					'{urn:ietf:params:xml:ns:caldav}mkcalendar',
286
+					$body
287
+				);
288
+			} catch (\Sabre\Xml\ParseException $e) {
289
+				throw new BadRequest($e->getMessage(), 0, $e);
290
+			}
291
+			$properties = $mkcalendar->getProperties();
292
+		}
293
+
294
+		// iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
295
+		// subscriptions. Before that it used MKCOL which was the correct way
296
+		// to do this.
297
+		//
298
+		// If the body had a {DAV:}resourcetype, it means we stumbled upon this
299
+		// request, and we simply use it instead of the pre-defined list.
300
+		if (isset($properties['{DAV:}resourcetype'])) {
301
+			$resourceType = $properties['{DAV:}resourcetype']->getValue();
302
+		} else {
303
+			$resourceType = ['{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar'];
304
+		}
305
+
306
+		$this->server->createCollection($path, new MkCol($resourceType, $properties));
307
+
308
+		$response->setStatus(201);
309
+		$response->setHeader('Content-Length', 0);
310
+
311
+		// This breaks the method chain.
312
+		return false;
313
+	}
314
+
315
+	/**
316
+	 * PropFind.
317
+	 *
318
+	 * This method handler is invoked before any after properties for a
319
+	 * resource are fetched. This allows us to add in any CalDAV specific
320
+	 * properties.
321
+	 */
322
+	public function propFind(DAV\PropFind $propFind, DAV\INode $node)
323
+	{
324
+		$ns = '{'.self::NS_CALDAV.'}';
325
+
326
+		if ($node instanceof ICalendarObjectContainer) {
327
+			$propFind->handle($ns.'max-resource-size', $this->maxResourceSize);
328
+			$propFind->handle($ns.'supported-calendar-data', function () {
329
+				return new Xml\Property\SupportedCalendarData();
330
+			});
331
+			$propFind->handle($ns.'supported-collation-set', function () {
332
+				return new Xml\Property\SupportedCollationSet();
333
+			});
334
+		}
335
+
336
+		if ($node instanceof DAVACL\IPrincipal) {
337
+			$principalUrl = $node->getPrincipalUrl();
338
+
339
+			$propFind->handle('{'.self::NS_CALDAV.'}calendar-home-set', function () use ($principalUrl) {
340
+				$calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
341
+				if (is_null($calendarHomePath)) {
342
+					return null;
343
+				}
344
+
345
+				return new LocalHref($calendarHomePath.'/');
346
+			});
347
+			// The calendar-user-address-set property is basically mapped to
348
+			// the {DAV:}alternate-URI-set property.
349
+			$propFind->handle('{'.self::NS_CALDAV.'}calendar-user-address-set', function () use ($node) {
350
+				$addresses = $node->getAlternateUriSet();
351
+				$addresses[] = $this->server->getBaseUri().$node->getPrincipalUrl().'/';
352
+
353
+				return new LocalHref($addresses);
354
+			});
355
+			// For some reason somebody thought it was a good idea to add
356
+			// another one of these properties. We're supporting it too.
357
+			$propFind->handle('{'.self::NS_CALENDARSERVER.'}email-address-set', function () use ($node) {
358
+				$addresses = $node->getAlternateUriSet();
359
+				$emails = [];
360
+				foreach ($addresses as $address) {
361
+					if ('mailto:' === substr($address, 0, 7)) {
362
+						$emails[] = substr($address, 7);
363
+					}
364
+				}
365
+
366
+				return new Xml\Property\EmailAddressSet($emails);
367
+			});
368
+
369
+			// These two properties are shortcuts for ical to easily find
370
+			// other principals this principal has access to.
371
+			$propRead = '{'.self::NS_CALENDARSERVER.'}calendar-proxy-read-for';
372
+			$propWrite = '{'.self::NS_CALENDARSERVER.'}calendar-proxy-write-for';
373
+
374
+			if (404 === $propFind->getStatus($propRead) || 404 === $propFind->getStatus($propWrite)) {
375
+				$aclPlugin = $this->server->getPlugin('acl');
376
+				$membership = $aclPlugin->getPrincipalMembership($propFind->getPath());
377
+				$readList = [];
378
+				$writeList = [];
379
+
380
+				foreach ($membership as $group) {
381
+					$groupNode = $this->server->tree->getNodeForPath($group);
382
+
383
+					$listItem = Uri\split($group)[0].'/';
384
+
385
+					// If the node is either ap proxy-read or proxy-write
386
+					// group, we grab the parent principal and add it to the
387
+					// list.
388
+					if ($groupNode instanceof Principal\IProxyRead) {
389
+						$readList[] = $listItem;
390
+					}
391
+					if ($groupNode instanceof Principal\IProxyWrite) {
392
+						$writeList[] = $listItem;
393
+					}
394
+				}
395
+
396
+				$propFind->set($propRead, new LocalHref($readList));
397
+				$propFind->set($propWrite, new LocalHref($writeList));
398
+			}
399
+		} // instanceof IPrincipal
400
+
401
+		if ($node instanceof ICalendarObject) {
402
+			// The calendar-data property is not supposed to be a 'real'
403
+			// property, but in large chunks of the spec it does act as such.
404
+			// Therefore we simply expose it as a property.
405
+			$propFind->handle('{'.self::NS_CALDAV.'}calendar-data', function () use ($node) {
406
+				$val = $node->get();
407
+				if (is_resource($val)) {
408
+					$val = stream_get_contents($val);
409
+				}
410
+
411
+				// Taking out \r to not screw up the xml output
412
+				return str_replace("\r", '', $val);
413
+			});
414
+		}
415
+	}
416
+
417
+	/**
418
+	 * This function handles the calendar-multiget REPORT.
419
+	 *
420
+	 * This report is used by the client to fetch the content of a series
421
+	 * of urls. Effectively avoiding a lot of redundant requests.
422
+	 *
423
+	 * @param CalendarMultiGetReport $report
424
+	 */
425
+	public function calendarMultiGetReport($report)
426
+	{
427
+		$needsJson = 'application/calendar+json' === $report->contentType;
428
+
429
+		$timeZones = [];
430
+		$propertyList = [];
431
+
432
+		$paths = array_map(
433
+			[$this->server, 'calculateUri'],
434
+			$report->hrefs
435
+		);
436
+
437
+		foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) {
438
+			if (($needsJson || $report->expand) && isset($objProps[200]['{'.self::NS_CALDAV.'}calendar-data'])) {
439
+				$vObject = VObject\Reader::read($objProps[200]['{'.self::NS_CALDAV.'}calendar-data']);
440
+
441
+				if ($report->expand) {
442
+					// We're expanding, and for that we need to figure out the
443
+					// calendar's timezone.
444
+					list($calendarPath) = Uri\split($uri);
445
+					if (!isset($timeZones[$calendarPath])) {
446
+						// Checking the calendar-timezone property.
447
+						$tzProp = '{'.self::NS_CALDAV.'}calendar-timezone';
448
+						$tzResult = $this->server->getProperties($calendarPath, [$tzProp]);
449
+						if (isset($tzResult[$tzProp])) {
450
+							// This property contains a VCALENDAR with a single
451
+							// VTIMEZONE.
452
+							$vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
453
+							$timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
454
+						} else {
455
+							// Defaulting to UTC.
456
+							$timeZone = new DateTimeZone('UTC');
457
+						}
458
+						$timeZones[$calendarPath] = $timeZone;
459
+					}
460
+
461
+					$vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]);
462
+				}
463
+				if ($needsJson) {
464
+					$objProps[200]['{'.self::NS_CALDAV.'}calendar-data'] = json_encode($vObject->jsonSerialize());
465
+				} else {
466
+					$objProps[200]['{'.self::NS_CALDAV.'}calendar-data'] = $vObject->serialize();
467
+				}
468
+				// Destroy circular references so PHP will garbage collect the
469
+				// object.
470
+				$vObject->destroy();
471
+			}
472
+
473
+			$propertyList[] = $objProps;
474
+		}
475
+
476
+		$prefer = $this->server->getHTTPPrefer();
477
+
478
+		$this->server->httpResponse->setStatus(207);
479
+		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
480
+		$this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
481
+		$this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, 'minimal' === $prefer['return']));
482
+	}
483
+
484
+	/**
485
+	 * This function handles the calendar-query REPORT.
486
+	 *
487
+	 * This report is used by clients to request calendar objects based on
488
+	 * complex conditions.
489
+	 *
490
+	 * @param Xml\Request\CalendarQueryReport $report
491
+	 */
492
+	public function calendarQueryReport($report)
493
+	{
494
+		$path = $this->server->getRequestUri();
495
+
496
+		$needsJson = 'application/calendar+json' === $report->contentType;
497
+
498
+		$node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
499
+		$depth = $this->server->getHTTPDepth(0);
500
+
501
+		// The default result is an empty array
502
+		$result = [];
503
+
504
+		$calendarTimeZone = null;
505
+		if ($report->expand) {
506
+			// We're expanding, and for that we need to figure out the
507
+			// calendar's timezone.
508
+			$tzProp = '{'.self::NS_CALDAV.'}calendar-timezone';
509
+			$tzResult = $this->server->getProperties($path, [$tzProp]);
510
+			if (isset($tzResult[$tzProp])) {
511
+				// This property contains a VCALENDAR with a single
512
+				// VTIMEZONE.
513
+				$vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
514
+				$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
515
+
516
+				// Destroy circular references so PHP will garbage collect the
517
+				// object.
518
+				$vtimezoneObj->destroy();
519
+			} else {
520
+				// Defaulting to UTC.
521
+				$calendarTimeZone = new DateTimeZone('UTC');
522
+			}
523
+		}
524
+
525
+		// The calendarobject was requested directly. In this case we handle
526
+		// this locally.
527
+		if (0 == $depth && $node instanceof ICalendarObject) {
528
+			$requestedCalendarData = true;
529
+			$requestedProperties = $report->properties;
530
+
531
+			if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
532
+				// We always retrieve calendar-data, as we need it for filtering.
533
+				$requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
534
+
535
+				// If calendar-data wasn't explicitly requested, we need to remove
536
+				// it after processing.
537
+				$requestedCalendarData = false;
538
+			}
539
+
540
+			$properties = $this->server->getPropertiesForPath(
541
+				$path,
542
+				$requestedProperties,
543
+				0
544
+			);
545
+
546
+			// This array should have only 1 element, the first calendar
547
+			// object.
548
+			$properties = current($properties);
549
+
550
+			// If there wasn't any calendar-data returned somehow, we ignore
551
+			// this.
552
+			if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
553
+				$validator = new CalendarQueryValidator();
554
+
555
+				$vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
556
+				if ($validator->validate($vObject, $report->filters)) {
557
+					// If the client didn't require the calendar-data property,
558
+					// we won't give it back.
559
+					if (!$requestedCalendarData) {
560
+						unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
561
+					} else {
562
+						if ($report->expand) {
563
+							$vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
564
+						}
565
+						if ($needsJson) {
566
+							$properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = json_encode($vObject->jsonSerialize());
567
+						} elseif ($report->expand) {
568
+							$properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = $vObject->serialize();
569
+						}
570
+					}
571
+
572
+					$result = [$properties];
573
+				}
574
+				// Destroy circular references so PHP will garbage collect the
575
+				// object.
576
+				$vObject->destroy();
577
+			}
578
+		}
579
+
580
+		if ($node instanceof ICalendarObjectContainer && 0 === $depth) {
581
+			if (0 === strpos((string) $this->server->httpRequest->getHeader('User-Agent'), 'MSFT-')) {
582
+				// Microsoft clients incorrectly supplied depth as 0, when it actually
583
+				// should have set depth to 1. We're implementing a workaround here
584
+				// to deal with this.
585
+				//
586
+				// This targets at least the following clients:
587
+				//   Windows 10
588
+				//   Windows Phone 8, 10
589
+				$depth = 1;
590
+			} else {
591
+				throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
592
+			}
593
+		}
594
+
595
+		// If we're dealing with a calendar, the calendar itself is responsible
596
+		// for the calendar-query.
597
+		if ($node instanceof ICalendarObjectContainer && 1 == $depth) {
598
+			$nodePaths = $node->calendarQuery($report->filters);
599
+
600
+			foreach ($nodePaths as $path) {
601
+				list($properties) =
602
+					$this->server->getPropertiesForPath($this->server->getRequestUri().'/'.$path, $report->properties);
603
+
604
+				if (($needsJson || $report->expand)) {
605
+					$vObject = VObject\Reader::read($properties[200]['{'.self::NS_CALDAV.'}calendar-data']);
606
+
607
+					if ($report->expand) {
608
+						$vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
609
+					}
610
+
611
+					if ($needsJson) {
612
+						$properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = json_encode($vObject->jsonSerialize());
613
+					} else {
614
+						$properties[200]['{'.self::NS_CALDAV.'}calendar-data'] = $vObject->serialize();
615
+					}
616
+
617
+					// Destroy circular references so PHP will garbage collect the
618
+					// object.
619
+					$vObject->destroy();
620
+				}
621
+				$result[] = $properties;
622
+			}
623
+		}
624
+
625
+		$prefer = $this->server->getHTTPPrefer();
626
+
627
+		$this->server->httpResponse->setStatus(207);
628
+		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
629
+		$this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
630
+		$this->server->httpResponse->setBody($this->server->generateMultiStatus($result, 'minimal' === $prefer['return']));
631
+	}
632
+
633
+	/**
634
+	 * This method is responsible for parsing the request and generating the
635
+	 * response for the CALDAV:free-busy-query REPORT.
636
+	 */
637
+	protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report)
638
+	{
639
+		$uri = $this->server->getRequestUri();
640
+
641
+		$acl = $this->server->getPlugin('acl');
642
+		if ($acl) {
643
+			$acl->checkPrivileges($uri, '{'.self::NS_CALDAV.'}read-free-busy');
644
+		}
645
+
646
+		$calendar = $this->server->tree->getNodeForPath($uri);
647
+		if (!$calendar instanceof ICalendar) {
648
+			throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars');
649
+		}
650
+
651
+		$tzProp = '{'.self::NS_CALDAV.'}calendar-timezone';
652
+
653
+		// Figuring out the default timezone for the calendar, for floating
654
+		// times.
655
+		$calendarProps = $this->server->getProperties($uri, [$tzProp]);
656
+
657
+		if (isset($calendarProps[$tzProp])) {
658
+			$vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]);
659
+			$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
660
+			// Destroy circular references so PHP will garbage collect the object.
661
+			$vtimezoneObj->destroy();
662
+		} else {
663
+			$calendarTimeZone = new DateTimeZone('UTC');
664
+		}
665
+
666
+		// Doing a calendar-query first, to make sure we get the most
667
+		// performance.
668
+		$urls = $calendar->calendarQuery([
669
+			'name' => 'VCALENDAR',
670
+			'comp-filters' => [
671
+				[
672
+					'name' => 'VEVENT',
673
+					'comp-filters' => [],
674
+					'prop-filters' => [],
675
+					'is-not-defined' => false,
676
+					'time-range' => [
677
+						'start' => $report->start,
678
+						'end' => $report->end,
679
+					],
680
+				],
681
+			],
682
+			'prop-filters' => [],
683
+			'is-not-defined' => false,
684
+			'time-range' => null,
685
+		]);
686
+
687
+		$objects = array_map(function ($url) use ($calendar) {
688
+			$obj = $calendar->getChild($url)->get();
689
+
690
+			return $obj;
691
+		}, $urls);
692
+
693
+		$generator = new VObject\FreeBusyGenerator();
694
+		$generator->setObjects($objects);
695
+		$generator->setTimeRange($report->start, $report->end);
696
+		$generator->setTimeZone($calendarTimeZone);
697
+		$result = $generator->getResult();
698
+		$result = $result->serialize();
699
+
700
+		$this->server->httpResponse->setStatus(200);
701
+		$this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
702
+		$this->server->httpResponse->setHeader('Content-Length', strlen($result));
703
+		$this->server->httpResponse->setBody($result);
704
+	}
705
+
706
+	/**
707
+	 * This method is triggered before a file gets updated with new content.
708
+	 *
709
+	 * This plugin uses this method to ensure that CalDAV objects receive
710
+	 * valid calendar data.
711
+	 *
712
+	 * @param string   $path
713
+	 * @param resource $data
714
+	 * @param bool     $modified should be set to true, if this event handler
715
+	 *                           changed &$data
716
+	 */
717
+	public function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified)
718
+	{
719
+		if (!$node instanceof ICalendarObject) {
720
+			return;
721
+		}
722
+
723
+		// We're onyl interested in ICalendarObject nodes that are inside of a
724
+		// real calendar. This is to avoid triggering validation and scheduling
725
+		// for non-calendars (such as an inbox).
726
+		list($parent) = Uri\split($path);
727
+		$parentNode = $this->server->tree->getNodeForPath($parent);
728
+
729
+		if (!$parentNode instanceof ICalendar) {
730
+			return;
731
+		}
732
+
733
+		$this->validateICalendar(
734
+			$data,
735
+			$path,
736
+			$modified,
737
+			$this->server->httpRequest,
738
+			$this->server->httpResponse,
739
+			false
740
+		);
741
+	}
742
+
743
+	/**
744
+	 * This method is triggered before a new file is created.
745
+	 *
746
+	 * This plugin uses this method to ensure that newly created calendar
747
+	 * objects contain valid calendar data.
748
+	 *
749
+	 * @param string   $path
750
+	 * @param resource $data
751
+	 * @param bool     $modified should be set to true, if this event handler
752
+	 *                           changed &$data
753
+	 */
754
+	public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified)
755
+	{
756
+		if (!$parentNode instanceof ICalendar) {
757
+			return;
758
+		}
759
+
760
+		$this->validateICalendar(
761
+			$data,
762
+			$path,
763
+			$modified,
764
+			$this->server->httpRequest,
765
+			$this->server->httpResponse,
766
+			true
767
+		);
768
+	}
769
+
770
+	/**
771
+	 * Checks if the submitted iCalendar data is in fact, valid.
772
+	 *
773
+	 * An exception is thrown if it's not.
774
+	 *
775
+	 * @param resource|string   $data
776
+	 * @param string            $path
777
+	 * @param bool              $modified should be set to true, if this event handler
778
+	 *                                    changed &$data
779
+	 * @param RequestInterface  $request  the http request
780
+	 * @param ResponseInterface $response the http response
781
+	 * @param bool              $isNew    is the item a new one, or an update
782
+	 */
783
+	protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew)
784
+	{
785
+		// If it's a stream, we convert it to a string first.
786
+		if (is_resource($data)) {
787
+			$data = stream_get_contents($data);
788
+		}
789
+
790
+		$before = $data;
791
+
792
+		try {
793
+			// If the data starts with a [, we can reasonably assume we're dealing
794
+			// with a jCal object.
795
+			if ('[' === substr($data, 0, 1)) {
796
+				$vobj = VObject\Reader::readJson($data);
797
+
798
+				// Converting $data back to iCalendar, as that's what we
799
+				// technically support everywhere.
800
+				$data = $vobj->serialize();
801
+				$modified = true;
802
+			} else {
803
+				$vobj = VObject\Reader::read($data);
804
+			}
805
+		} catch (VObject\ParseException $e) {
806
+			throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: '.$e->getMessage());
807
+		}
808
+
809
+		if ('VCALENDAR' !== $vobj->name) {
810
+			throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
811
+		}
812
+
813
+		$sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
814
+
815
+		// Get the Supported Components for the target calendar
816
+		list($parentPath) = Uri\split($path);
817
+		$calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
818
+
819
+		if (isset($calendarProperties[$sCCS])) {
820
+			$supportedComponents = $calendarProperties[$sCCS]->getValue();
821
+		} else {
822
+			$supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
823
+		}
824
+
825
+		$foundType = null;
826
+
827
+		foreach ($vobj->getComponents() as $component) {
828
+			switch ($component->name) {
829
+				case 'VTIMEZONE':
830
+					continue 2;
831
+				case 'VEVENT':
832
+				case 'VTODO':
833
+				case 'VJOURNAL':
834
+					$foundType = $component->name;
835
+					break;
836
+			}
837
+		}
838
+
839
+		if (!$foundType || !in_array($foundType, $supportedComponents)) {
840
+			throw new Exception\InvalidComponentType('iCalendar objects must at least have a component of type '.implode(', ', $supportedComponents));
841
+		}
842
+
843
+		$options = VObject\Node::PROFILE_CALDAV;
844
+		$prefer = $this->server->getHTTPPrefer();
845
+
846
+		if ('strict' !== $prefer['handling']) {
847
+			$options |= VObject\Node::REPAIR;
848
+		}
849
+
850
+		$messages = $vobj->validate($options);
851
+
852
+		$highestLevel = 0;
853
+		$warningMessage = null;
854
+
855
+		// $messages contains a list of problems with the vcard, along with
856
+		// their severity.
857
+		foreach ($messages as $message) {
858
+			if ($message['level'] > $highestLevel) {
859
+				// Recording the highest reported error level.
860
+				$highestLevel = $message['level'];
861
+				$warningMessage = $message['message'];
862
+			}
863
+			switch ($message['level']) {
864
+				case 1:
865
+					// Level 1 means that there was a problem, but it was repaired.
866
+					$modified = true;
867
+					break;
868
+				case 2:
869
+					// Level 2 means a warning, but not critical
870
+					break;
871
+				case 3:
872
+					// Level 3 means a critical error
873
+					throw new DAV\Exception\UnsupportedMediaType('Validation error in iCalendar: '.$message['message']);
874
+			}
875
+		}
876
+		if ($warningMessage) {
877
+			$response->setHeader(
878
+				'X-Sabre-Ew-Gross',
879
+				'iCalendar validation warning: '.$warningMessage
880
+			);
881
+		}
882
+
883
+		// We use an extra variable to allow event handles to tell us whether
884
+		// the object was modified or not.
885
+		//
886
+		// This helps us determine if we need to re-serialize the object.
887
+		$subModified = false;
888
+
889
+		$this->server->emit(
890
+			'calendarObjectChange',
891
+			[
892
+				$request,
893
+				$response,
894
+				$vobj,
895
+				$parentPath,
896
+				&$subModified,
897
+				$isNew,
898
+			]
899
+		);
900
+
901
+		if ($modified || $subModified) {
902
+			// An event handler told us that it modified the object.
903
+			$data = $vobj->serialize();
904
+
905
+			// Using md5 to figure out if there was an *actual* change.
906
+			if (!$modified && 0 !== strcmp($data, $before)) {
907
+				$modified = true;
908
+			}
909
+		}
910
+
911
+		// Destroy circular references so PHP will garbage collect the object.
912
+		$vobj->destroy();
913
+	}
914
+
915
+	/**
916
+	 * This method is triggered whenever a subsystem reqeuests the privileges
917
+	 * that are supported on a particular node.
918
+	 */
919
+	public function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
920
+	{
921
+		if ($node instanceof ICalendar) {
922
+			$supportedPrivilegeSet['{DAV:}read']['aggregates']['{'.self::NS_CALDAV.'}read-free-busy'] = [
923
+				'abstract' => false,
924
+				'aggregates' => [],
925
+			];
926
+		}
927
+	}
928
+
929
+	/**
930
+	 * This method is used to generate HTML output for the
931
+	 * DAV\Browser\Plugin. This allows us to generate an interface users
932
+	 * can use to create new calendars.
933
+	 *
934
+	 * @param string $output
935
+	 *
936
+	 * @return bool
937
+	 */
938
+	public function htmlActionsPanel(DAV\INode $node, &$output)
939
+	{
940
+		if (!$node instanceof CalendarHome) {
941
+			return;
942
+		}
943
+
944
+		$output .= '<tr><td colspan="2"><form method="post" action="">
945 945
             <h3>Create new calendar</h3>
946 946
             <input type="hidden" name="sabreAction" value="mkcol" />
947 947
             <input type="hidden" name="resourceType" value="{DAV:}collection,{'.self::NS_CALDAV.'}calendar" />
@@ -951,61 +951,61 @@  discard block
 block discarded – undo
951 951
             </form>
952 952
             </td></tr>';
953 953
 
954
-        return false;
955
-    }
956
-
957
-    /**
958
-     * This event is triggered after GET requests.
959
-     *
960
-     * This is used to transform data into jCal, if this was requested.
961
-     */
962
-    public function httpAfterGet(RequestInterface $request, ResponseInterface $response)
963
-    {
964
-        $contentType = $response->getHeader('Content-Type');
965
-        if (null === $contentType || false === strpos($contentType, 'text/calendar')) {
966
-            return;
967
-        }
968
-
969
-        $result = HTTP\negotiateContentType(
970
-            $request->getHeader('Accept'),
971
-            ['text/calendar', 'application/calendar+json']
972
-        );
973
-
974
-        if ('application/calendar+json' !== $result) {
975
-            // Do nothing
976
-            return;
977
-        }
978
-
979
-        // Transforming.
980
-        $vobj = VObject\Reader::read($response->getBody());
981
-
982
-        $jsonBody = json_encode($vobj->jsonSerialize());
983
-        $response->setBody($jsonBody);
984
-
985
-        // Destroy circular references so PHP will garbage collect the object.
986
-        $vobj->destroy();
987
-
988
-        $response->setHeader('Content-Type', 'application/calendar+json');
989
-        $response->setHeader('Content-Length', strlen($jsonBody));
990
-    }
991
-
992
-    /**
993
-     * Returns a bunch of meta-data about the plugin.
994
-     *
995
-     * Providing this information is optional, and is mainly displayed by the
996
-     * Browser plugin.
997
-     *
998
-     * The description key in the returned array may contain html and will not
999
-     * be sanitized.
1000
-     *
1001
-     * @return array
1002
-     */
1003
-    public function getPluginInfo()
1004
-    {
1005
-        return [
1006
-            'name' => $this->getPluginName(),
1007
-            'description' => 'Adds support for CalDAV (rfc4791)',
1008
-            'link' => 'http://sabre.io/dav/caldav/',
1009
-        ];
1010
-    }
954
+		return false;
955
+	}
956
+
957
+	/**
958
+	 * This event is triggered after GET requests.
959
+	 *
960
+	 * This is used to transform data into jCal, if this was requested.
961
+	 */
962
+	public function httpAfterGet(RequestInterface $request, ResponseInterface $response)
963
+	{
964
+		$contentType = $response->getHeader('Content-Type');
965
+		if (null === $contentType || false === strpos($contentType, 'text/calendar')) {
966
+			return;
967
+		}
968
+
969
+		$result = HTTP\negotiateContentType(
970
+			$request->getHeader('Accept'),
971
+			['text/calendar', 'application/calendar+json']
972
+		);
973
+
974
+		if ('application/calendar+json' !== $result) {
975
+			// Do nothing
976
+			return;
977
+		}
978
+
979
+		// Transforming.
980
+		$vobj = VObject\Reader::read($response->getBody());
981
+
982
+		$jsonBody = json_encode($vobj->jsonSerialize());
983
+		$response->setBody($jsonBody);
984
+
985
+		// Destroy circular references so PHP will garbage collect the object.
986
+		$vobj->destroy();
987
+
988
+		$response->setHeader('Content-Type', 'application/calendar+json');
989
+		$response->setHeader('Content-Length', strlen($jsonBody));
990
+	}
991
+
992
+	/**
993
+	 * Returns a bunch of meta-data about the plugin.
994
+	 *
995
+	 * Providing this information is optional, and is mainly displayed by the
996
+	 * Browser plugin.
997
+	 *
998
+	 * The description key in the returned array may contain html and will not
999
+	 * be sanitized.
1000
+	 *
1001
+	 * @return array
1002
+	 */
1003
+	public function getPluginInfo()
1004
+	{
1005
+		return [
1006
+			'name' => $this->getPluginName(),
1007
+			'description' => 'Adds support for CalDAV (rfc4791)',
1008
+			'link' => 'http://sabre.io/dav/caldav/',
1009
+		];
1010
+	}
1011 1011
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -325,10 +325,10 @@  discard block
 block discarded – undo
325 325
 
326 326
         if ($node instanceof ICalendarObjectContainer) {
327 327
             $propFind->handle($ns.'max-resource-size', $this->maxResourceSize);
328
-            $propFind->handle($ns.'supported-calendar-data', function () {
328
+            $propFind->handle($ns.'supported-calendar-data', function() {
329 329
                 return new Xml\Property\SupportedCalendarData();
330 330
             });
331
-            $propFind->handle($ns.'supported-collation-set', function () {
331
+            $propFind->handle($ns.'supported-collation-set', function() {
332 332
                 return new Xml\Property\SupportedCollationSet();
333 333
             });
334 334
         }
@@ -336,7 +336,7 @@  discard block
 block discarded – undo
336 336
         if ($node instanceof DAVACL\IPrincipal) {
337 337
             $principalUrl = $node->getPrincipalUrl();
338 338
 
339
-            $propFind->handle('{'.self::NS_CALDAV.'}calendar-home-set', function () use ($principalUrl) {
339
+            $propFind->handle('{'.self::NS_CALDAV.'}calendar-home-set', function() use ($principalUrl) {
340 340
                 $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
341 341
                 if (is_null($calendarHomePath)) {
342 342
                     return null;
@@ -346,7 +346,7 @@  discard block
 block discarded – undo
346 346
             });
347 347
             // The calendar-user-address-set property is basically mapped to
348 348
             // the {DAV:}alternate-URI-set property.
349
-            $propFind->handle('{'.self::NS_CALDAV.'}calendar-user-address-set', function () use ($node) {
349
+            $propFind->handle('{'.self::NS_CALDAV.'}calendar-user-address-set', function() use ($node) {
350 350
                 $addresses = $node->getAlternateUriSet();
351 351
                 $addresses[] = $this->server->getBaseUri().$node->getPrincipalUrl().'/';
352 352
 
@@ -354,7 +354,7 @@  discard block
 block discarded – undo
354 354
             });
355 355
             // For some reason somebody thought it was a good idea to add
356 356
             // another one of these properties. We're supporting it too.
357
-            $propFind->handle('{'.self::NS_CALENDARSERVER.'}email-address-set', function () use ($node) {
357
+            $propFind->handle('{'.self::NS_CALENDARSERVER.'}email-address-set', function() use ($node) {
358 358
                 $addresses = $node->getAlternateUriSet();
359 359
                 $emails = [];
360 360
                 foreach ($addresses as $address) {
@@ -402,7 +402,7 @@  discard block
 block discarded – undo
402 402
             // The calendar-data property is not supposed to be a 'real'
403 403
             // property, but in large chunks of the spec it does act as such.
404 404
             // Therefore we simply expose it as a property.
405
-            $propFind->handle('{'.self::NS_CALDAV.'}calendar-data', function () use ($node) {
405
+            $propFind->handle('{'.self::NS_CALDAV.'}calendar-data', function() use ($node) {
406 406
                 $val = $node->get();
407 407
                 if (is_resource($val)) {
408 408
                     $val = stream_get_contents($val);
@@ -684,7 +684,7 @@  discard block
 block discarded – undo
684 684
             'time-range' => null,
685 685
         ]);
686 686
 
687
-        $objects = array_map(function ($url) use ($calendar) {
687
+        $objects = array_map(function($url) use ($calendar) {
688 688
             $obj = $calendar->getChild($url)->get();
689 689
 
690 690
             return $obj;
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/Principal/ProxyRead.php 1 patch
Indentation   +138 added lines, -138 removed lines patch added patch discarded remove patch
@@ -20,142 +20,142 @@
 block discarded – undo
20 20
  */
21 21
 class ProxyRead implements IProxyRead
22 22
 {
23
-    /**
24
-     * Principal information from the parent principal.
25
-     *
26
-     * @var array
27
-     */
28
-    protected $principalInfo;
29
-
30
-    /**
31
-     * Principal backend.
32
-     *
33
-     * @var DAVACL\PrincipalBackend\BackendInterface
34
-     */
35
-    protected $principalBackend;
36
-
37
-    /**
38
-     * Creates the object.
39
-     *
40
-     * Note that you MUST supply the parent principal information.
41
-     */
42
-    public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo)
43
-    {
44
-        $this->principalInfo = $principalInfo;
45
-        $this->principalBackend = $principalBackend;
46
-    }
47
-
48
-    /**
49
-     * Returns this principals name.
50
-     *
51
-     * @return string
52
-     */
53
-    public function getName()
54
-    {
55
-        return 'calendar-proxy-read';
56
-    }
57
-
58
-    /**
59
-     * Returns the last modification time.
60
-     */
61
-    public function getLastModified()
62
-    {
63
-        return null;
64
-    }
65
-
66
-    /**
67
-     * Deletes the current node.
68
-     *
69
-     * @throws DAV\Exception\Forbidden
70
-     */
71
-    public function delete()
72
-    {
73
-        throw new DAV\Exception\Forbidden('Permission denied to delete node');
74
-    }
75
-
76
-    /**
77
-     * Renames the node.
78
-     *
79
-     * @param string $name The new name
80
-     *
81
-     * @throws DAV\Exception\Forbidden
82
-     */
83
-    public function setName($name)
84
-    {
85
-        throw new DAV\Exception\Forbidden('Permission denied to rename file');
86
-    }
87
-
88
-    /**
89
-     * Returns a list of alternative urls for a principal.
90
-     *
91
-     * This can for example be an email address, or ldap url.
92
-     *
93
-     * @return array
94
-     */
95
-    public function getAlternateUriSet()
96
-    {
97
-        return [];
98
-    }
99
-
100
-    /**
101
-     * Returns the full principal url.
102
-     *
103
-     * @return string
104
-     */
105
-    public function getPrincipalUrl()
106
-    {
107
-        return $this->principalInfo['uri'].'/'.$this->getName();
108
-    }
109
-
110
-    /**
111
-     * Returns the list of group members.
112
-     *
113
-     * If this principal is a group, this function should return
114
-     * all member principal uri's for the group.
115
-     *
116
-     * @return array
117
-     */
118
-    public function getGroupMemberSet()
119
-    {
120
-        return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
121
-    }
122
-
123
-    /**
124
-     * Returns the list of groups this principal is member of.
125
-     *
126
-     * If this principal is a member of a (list of) groups, this function
127
-     * should return a list of principal uri's for it's members.
128
-     *
129
-     * @return array
130
-     */
131
-    public function getGroupMembership()
132
-    {
133
-        return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
134
-    }
135
-
136
-    /**
137
-     * Sets a list of group members.
138
-     *
139
-     * If this principal is a group, this method sets all the group members.
140
-     * The list of members is always overwritten, never appended to.
141
-     *
142
-     * This method should throw an exception if the members could not be set.
143
-     */
144
-    public function setGroupMemberSet(array $principals)
145
-    {
146
-        $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
147
-    }
148
-
149
-    /**
150
-     * Returns the displayname.
151
-     *
152
-     * This should be a human readable name for the principal.
153
-     * If none is available, return the nodename.
154
-     *
155
-     * @return string
156
-     */
157
-    public function getDisplayName()
158
-    {
159
-        return $this->getName();
160
-    }
23
+	/**
24
+	 * Principal information from the parent principal.
25
+	 *
26
+	 * @var array
27
+	 */
28
+	protected $principalInfo;
29
+
30
+	/**
31
+	 * Principal backend.
32
+	 *
33
+	 * @var DAVACL\PrincipalBackend\BackendInterface
34
+	 */
35
+	protected $principalBackend;
36
+
37
+	/**
38
+	 * Creates the object.
39
+	 *
40
+	 * Note that you MUST supply the parent principal information.
41
+	 */
42
+	public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo)
43
+	{
44
+		$this->principalInfo = $principalInfo;
45
+		$this->principalBackend = $principalBackend;
46
+	}
47
+
48
+	/**
49
+	 * Returns this principals name.
50
+	 *
51
+	 * @return string
52
+	 */
53
+	public function getName()
54
+	{
55
+		return 'calendar-proxy-read';
56
+	}
57
+
58
+	/**
59
+	 * Returns the last modification time.
60
+	 */
61
+	public function getLastModified()
62
+	{
63
+		return null;
64
+	}
65
+
66
+	/**
67
+	 * Deletes the current node.
68
+	 *
69
+	 * @throws DAV\Exception\Forbidden
70
+	 */
71
+	public function delete()
72
+	{
73
+		throw new DAV\Exception\Forbidden('Permission denied to delete node');
74
+	}
75
+
76
+	/**
77
+	 * Renames the node.
78
+	 *
79
+	 * @param string $name The new name
80
+	 *
81
+	 * @throws DAV\Exception\Forbidden
82
+	 */
83
+	public function setName($name)
84
+	{
85
+		throw new DAV\Exception\Forbidden('Permission denied to rename file');
86
+	}
87
+
88
+	/**
89
+	 * Returns a list of alternative urls for a principal.
90
+	 *
91
+	 * This can for example be an email address, or ldap url.
92
+	 *
93
+	 * @return array
94
+	 */
95
+	public function getAlternateUriSet()
96
+	{
97
+		return [];
98
+	}
99
+
100
+	/**
101
+	 * Returns the full principal url.
102
+	 *
103
+	 * @return string
104
+	 */
105
+	public function getPrincipalUrl()
106
+	{
107
+		return $this->principalInfo['uri'].'/'.$this->getName();
108
+	}
109
+
110
+	/**
111
+	 * Returns the list of group members.
112
+	 *
113
+	 * If this principal is a group, this function should return
114
+	 * all member principal uri's for the group.
115
+	 *
116
+	 * @return array
117
+	 */
118
+	public function getGroupMemberSet()
119
+	{
120
+		return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
121
+	}
122
+
123
+	/**
124
+	 * Returns the list of groups this principal is member of.
125
+	 *
126
+	 * If this principal is a member of a (list of) groups, this function
127
+	 * should return a list of principal uri's for it's members.
128
+	 *
129
+	 * @return array
130
+	 */
131
+	public function getGroupMembership()
132
+	{
133
+		return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
134
+	}
135
+
136
+	/**
137
+	 * Sets a list of group members.
138
+	 *
139
+	 * If this principal is a group, this method sets all the group members.
140
+	 * The list of members is always overwritten, never appended to.
141
+	 *
142
+	 * This method should throw an exception if the members could not be set.
143
+	 */
144
+	public function setGroupMemberSet(array $principals)
145
+	{
146
+		$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
147
+	}
148
+
149
+	/**
150
+	 * Returns the displayname.
151
+	 *
152
+	 * This should be a human readable name for the principal.
153
+	 * If none is available, return the nodename.
154
+	 *
155
+	 * @return string
156
+	 */
157
+	public function getDisplayName()
158
+	{
159
+		return $this->getName();
160
+	}
161 161
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/Principal/Collection.php 1 patch
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -20,13 +20,13 @@
 block discarded – undo
20 20
  */
21 21
 class Collection extends DAVACL\PrincipalCollection
22 22
 {
23
-    /**
24
-     * Returns a child object based on principal information.
25
-     *
26
-     * @return User
27
-     */
28
-    public function getChildForPrincipal(array $principalInfo)
29
-    {
30
-        return new User($this->principalBackend, $principalInfo);
31
-    }
23
+	/**
24
+	 * Returns a child object based on principal information.
25
+	 *
26
+	 * @return User
27
+	 */
28
+	public function getChildForPrincipal(array $principalInfo)
29
+	{
30
+		return new User($this->principalBackend, $principalInfo);
31
+	}
32 32
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/Principal/User.php 1 patch
Indentation   +103 added lines, -103 removed lines patch added patch discarded remove patch
@@ -20,117 +20,117 @@
 block discarded – undo
20 20
  */
21 21
 class User extends DAVACL\Principal implements DAV\ICollection
22 22
 {
23
-    /**
24
-     * Creates a new file in the directory.
25
-     *
26
-     * @param string   $name Name of the file
27
-     * @param resource $data initial payload, passed as a readable stream resource
28
-     *
29
-     * @throws DAV\Exception\Forbidden
30
-     */
31
-    public function createFile($name, $data = null)
32
-    {
33
-        throw new DAV\Exception\Forbidden('Permission denied to create file (filename '.$name.')');
34
-    }
23
+	/**
24
+	 * Creates a new file in the directory.
25
+	 *
26
+	 * @param string   $name Name of the file
27
+	 * @param resource $data initial payload, passed as a readable stream resource
28
+	 *
29
+	 * @throws DAV\Exception\Forbidden
30
+	 */
31
+	public function createFile($name, $data = null)
32
+	{
33
+		throw new DAV\Exception\Forbidden('Permission denied to create file (filename '.$name.')');
34
+	}
35 35
 
36
-    /**
37
-     * Creates a new subdirectory.
38
-     *
39
-     * @param string $name
40
-     *
41
-     * @throws DAV\Exception\Forbidden
42
-     */
43
-    public function createDirectory($name)
44
-    {
45
-        throw new DAV\Exception\Forbidden('Permission denied to create directory');
46
-    }
36
+	/**
37
+	 * Creates a new subdirectory.
38
+	 *
39
+	 * @param string $name
40
+	 *
41
+	 * @throws DAV\Exception\Forbidden
42
+	 */
43
+	public function createDirectory($name)
44
+	{
45
+		throw new DAV\Exception\Forbidden('Permission denied to create directory');
46
+	}
47 47
 
48
-    /**
49
-     * Returns a specific child node, referenced by its name.
50
-     *
51
-     * @param string $name
52
-     *
53
-     * @return DAV\INode
54
-     */
55
-    public function getChild($name)
56
-    {
57
-        $principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL().'/'.$name);
58
-        if (!$principal) {
59
-            throw new DAV\Exception\NotFound('Node with name '.$name.' was not found');
60
-        }
61
-        if ('calendar-proxy-read' === $name) {
62
-            return new ProxyRead($this->principalBackend, $this->principalProperties);
63
-        }
48
+	/**
49
+	 * Returns a specific child node, referenced by its name.
50
+	 *
51
+	 * @param string $name
52
+	 *
53
+	 * @return DAV\INode
54
+	 */
55
+	public function getChild($name)
56
+	{
57
+		$principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL().'/'.$name);
58
+		if (!$principal) {
59
+			throw new DAV\Exception\NotFound('Node with name '.$name.' was not found');
60
+		}
61
+		if ('calendar-proxy-read' === $name) {
62
+			return new ProxyRead($this->principalBackend, $this->principalProperties);
63
+		}
64 64
 
65
-        if ('calendar-proxy-write' === $name) {
66
-            return new ProxyWrite($this->principalBackend, $this->principalProperties);
67
-        }
65
+		if ('calendar-proxy-write' === $name) {
66
+			return new ProxyWrite($this->principalBackend, $this->principalProperties);
67
+		}
68 68
 
69
-        throw new DAV\Exception\NotFound('Node with name '.$name.' was not found');
70
-    }
69
+		throw new DAV\Exception\NotFound('Node with name '.$name.' was not found');
70
+	}
71 71
 
72
-    /**
73
-     * Returns an array with all the child nodes.
74
-     *
75
-     * @return DAV\INode[]
76
-     */
77
-    public function getChildren()
78
-    {
79
-        $r = [];
80
-        if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL().'/calendar-proxy-read')) {
81
-            $r[] = new ProxyRead($this->principalBackend, $this->principalProperties);
82
-        }
83
-        if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL().'/calendar-proxy-write')) {
84
-            $r[] = new ProxyWrite($this->principalBackend, $this->principalProperties);
85
-        }
72
+	/**
73
+	 * Returns an array with all the child nodes.
74
+	 *
75
+	 * @return DAV\INode[]
76
+	 */
77
+	public function getChildren()
78
+	{
79
+		$r = [];
80
+		if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL().'/calendar-proxy-read')) {
81
+			$r[] = new ProxyRead($this->principalBackend, $this->principalProperties);
82
+		}
83
+		if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL().'/calendar-proxy-write')) {
84
+			$r[] = new ProxyWrite($this->principalBackend, $this->principalProperties);
85
+		}
86 86
 
87
-        return $r;
88
-    }
87
+		return $r;
88
+	}
89 89
 
90
-    /**
91
-     * Returns whether or not the child node exists.
92
-     *
93
-     * @param string $name
94
-     *
95
-     * @return bool
96
-     */
97
-    public function childExists($name)
98
-    {
99
-        try {
100
-            $this->getChild($name);
90
+	/**
91
+	 * Returns whether or not the child node exists.
92
+	 *
93
+	 * @param string $name
94
+	 *
95
+	 * @return bool
96
+	 */
97
+	public function childExists($name)
98
+	{
99
+		try {
100
+			$this->getChild($name);
101 101
 
102
-            return true;
103
-        } catch (DAV\Exception\NotFound $e) {
104
-            return false;
105
-        }
106
-    }
102
+			return true;
103
+		} catch (DAV\Exception\NotFound $e) {
104
+			return false;
105
+		}
106
+	}
107 107
 
108
-    /**
109
-     * Returns a list of ACE's for this node.
110
-     *
111
-     * Each ACE has the following properties:
112
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
113
-     *     currently the only supported privileges
114
-     *   * 'principal', a url to the principal who owns the node
115
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
116
-     *      be updated.
117
-     *
118
-     * @return array
119
-     */
120
-    public function getACL()
121
-    {
122
-        $acl = parent::getACL();
123
-        $acl[] = [
124
-            'privilege' => '{DAV:}read',
125
-            'principal' => $this->principalProperties['uri'].'/calendar-proxy-read',
126
-            'protected' => true,
127
-        ];
128
-        $acl[] = [
129
-            'privilege' => '{DAV:}read',
130
-            'principal' => $this->principalProperties['uri'].'/calendar-proxy-write',
131
-            'protected' => true,
132
-        ];
108
+	/**
109
+	 * Returns a list of ACE's for this node.
110
+	 *
111
+	 * Each ACE has the following properties:
112
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
113
+	 *     currently the only supported privileges
114
+	 *   * 'principal', a url to the principal who owns the node
115
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
116
+	 *      be updated.
117
+	 *
118
+	 * @return array
119
+	 */
120
+	public function getACL()
121
+	{
122
+		$acl = parent::getACL();
123
+		$acl[] = [
124
+			'privilege' => '{DAV:}read',
125
+			'principal' => $this->principalProperties['uri'].'/calendar-proxy-read',
126
+			'protected' => true,
127
+		];
128
+		$acl[] = [
129
+			'privilege' => '{DAV:}read',
130
+			'principal' => $this->principalProperties['uri'].'/calendar-proxy-write',
131
+			'protected' => true,
132
+		];
133 133
 
134
-        return $acl;
135
-    }
134
+		return $acl;
135
+	}
136 136
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php 1 patch
Indentation   +138 added lines, -138 removed lines patch added patch discarded remove patch
@@ -20,142 +20,142 @@
 block discarded – undo
20 20
  */
21 21
 class ProxyWrite implements IProxyWrite
22 22
 {
23
-    /**
24
-     * Parent principal information.
25
-     *
26
-     * @var array
27
-     */
28
-    protected $principalInfo;
29
-
30
-    /**
31
-     * Principal Backend.
32
-     *
33
-     * @var DAVACL\PrincipalBackend\BackendInterface
34
-     */
35
-    protected $principalBackend;
36
-
37
-    /**
38
-     * Creates the object.
39
-     *
40
-     * Note that you MUST supply the parent principal information.
41
-     */
42
-    public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo)
43
-    {
44
-        $this->principalInfo = $principalInfo;
45
-        $this->principalBackend = $principalBackend;
46
-    }
47
-
48
-    /**
49
-     * Returns this principals name.
50
-     *
51
-     * @return string
52
-     */
53
-    public function getName()
54
-    {
55
-        return 'calendar-proxy-write';
56
-    }
57
-
58
-    /**
59
-     * Returns the last modification time.
60
-     */
61
-    public function getLastModified()
62
-    {
63
-        return null;
64
-    }
65
-
66
-    /**
67
-     * Deletes the current node.
68
-     *
69
-     * @throws DAV\Exception\Forbidden
70
-     */
71
-    public function delete()
72
-    {
73
-        throw new DAV\Exception\Forbidden('Permission denied to delete node');
74
-    }
75
-
76
-    /**
77
-     * Renames the node.
78
-     *
79
-     * @param string $name The new name
80
-     *
81
-     * @throws DAV\Exception\Forbidden
82
-     */
83
-    public function setName($name)
84
-    {
85
-        throw new DAV\Exception\Forbidden('Permission denied to rename file');
86
-    }
87
-
88
-    /**
89
-     * Returns a list of alternative urls for a principal.
90
-     *
91
-     * This can for example be an email address, or ldap url.
92
-     *
93
-     * @return array
94
-     */
95
-    public function getAlternateUriSet()
96
-    {
97
-        return [];
98
-    }
99
-
100
-    /**
101
-     * Returns the full principal url.
102
-     *
103
-     * @return string
104
-     */
105
-    public function getPrincipalUrl()
106
-    {
107
-        return $this->principalInfo['uri'].'/'.$this->getName();
108
-    }
109
-
110
-    /**
111
-     * Returns the list of group members.
112
-     *
113
-     * If this principal is a group, this function should return
114
-     * all member principal uri's for the group.
115
-     *
116
-     * @return array
117
-     */
118
-    public function getGroupMemberSet()
119
-    {
120
-        return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
121
-    }
122
-
123
-    /**
124
-     * Returns the list of groups this principal is member of.
125
-     *
126
-     * If this principal is a member of a (list of) groups, this function
127
-     * should return a list of principal uri's for it's members.
128
-     *
129
-     * @return array
130
-     */
131
-    public function getGroupMembership()
132
-    {
133
-        return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
134
-    }
135
-
136
-    /**
137
-     * Sets a list of group members.
138
-     *
139
-     * If this principal is a group, this method sets all the group members.
140
-     * The list of members is always overwritten, never appended to.
141
-     *
142
-     * This method should throw an exception if the members could not be set.
143
-     */
144
-    public function setGroupMemberSet(array $principals)
145
-    {
146
-        $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
147
-    }
148
-
149
-    /**
150
-     * Returns the displayname.
151
-     *
152
-     * This should be a human readable name for the principal.
153
-     * If none is available, return the nodename.
154
-     *
155
-     * @return string
156
-     */
157
-    public function getDisplayName()
158
-    {
159
-        return $this->getName();
160
-    }
23
+	/**
24
+	 * Parent principal information.
25
+	 *
26
+	 * @var array
27
+	 */
28
+	protected $principalInfo;
29
+
30
+	/**
31
+	 * Principal Backend.
32
+	 *
33
+	 * @var DAVACL\PrincipalBackend\BackendInterface
34
+	 */
35
+	protected $principalBackend;
36
+
37
+	/**
38
+	 * Creates the object.
39
+	 *
40
+	 * Note that you MUST supply the parent principal information.
41
+	 */
42
+	public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo)
43
+	{
44
+		$this->principalInfo = $principalInfo;
45
+		$this->principalBackend = $principalBackend;
46
+	}
47
+
48
+	/**
49
+	 * Returns this principals name.
50
+	 *
51
+	 * @return string
52
+	 */
53
+	public function getName()
54
+	{
55
+		return 'calendar-proxy-write';
56
+	}
57
+
58
+	/**
59
+	 * Returns the last modification time.
60
+	 */
61
+	public function getLastModified()
62
+	{
63
+		return null;
64
+	}
65
+
66
+	/**
67
+	 * Deletes the current node.
68
+	 *
69
+	 * @throws DAV\Exception\Forbidden
70
+	 */
71
+	public function delete()
72
+	{
73
+		throw new DAV\Exception\Forbidden('Permission denied to delete node');
74
+	}
75
+
76
+	/**
77
+	 * Renames the node.
78
+	 *
79
+	 * @param string $name The new name
80
+	 *
81
+	 * @throws DAV\Exception\Forbidden
82
+	 */
83
+	public function setName($name)
84
+	{
85
+		throw new DAV\Exception\Forbidden('Permission denied to rename file');
86
+	}
87
+
88
+	/**
89
+	 * Returns a list of alternative urls for a principal.
90
+	 *
91
+	 * This can for example be an email address, or ldap url.
92
+	 *
93
+	 * @return array
94
+	 */
95
+	public function getAlternateUriSet()
96
+	{
97
+		return [];
98
+	}
99
+
100
+	/**
101
+	 * Returns the full principal url.
102
+	 *
103
+	 * @return string
104
+	 */
105
+	public function getPrincipalUrl()
106
+	{
107
+		return $this->principalInfo['uri'].'/'.$this->getName();
108
+	}
109
+
110
+	/**
111
+	 * Returns the list of group members.
112
+	 *
113
+	 * If this principal is a group, this function should return
114
+	 * all member principal uri's for the group.
115
+	 *
116
+	 * @return array
117
+	 */
118
+	public function getGroupMemberSet()
119
+	{
120
+		return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
121
+	}
122
+
123
+	/**
124
+	 * Returns the list of groups this principal is member of.
125
+	 *
126
+	 * If this principal is a member of a (list of) groups, this function
127
+	 * should return a list of principal uri's for it's members.
128
+	 *
129
+	 * @return array
130
+	 */
131
+	public function getGroupMembership()
132
+	{
133
+		return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
134
+	}
135
+
136
+	/**
137
+	 * Sets a list of group members.
138
+	 *
139
+	 * If this principal is a group, this method sets all the group members.
140
+	 * The list of members is always overwritten, never appended to.
141
+	 *
142
+	 * This method should throw an exception if the members could not be set.
143
+	 */
144
+	public function setGroupMemberSet(array $principals)
145
+	{
146
+		$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
147
+	}
148
+
149
+	/**
150
+	 * Returns the displayname.
151
+	 *
152
+	 * This should be a human readable name for the principal.
153
+	 * If none is available, return the nodename.
154
+	 *
155
+	 * @return string
156
+	 */
157
+	public function getDisplayName()
158
+	{
159
+		return $this->getName();
160
+	}
161 161
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/ICSExportPlugin.php 2 patches
Indentation   +326 added lines, -326 removed lines patch added patch discarded remove patch
@@ -48,330 +48,330 @@
 block discarded – undo
48 48
  */
49 49
 class ICSExportPlugin extends DAV\ServerPlugin
50 50
 {
51
-    /**
52
-     * Reference to Server class.
53
-     *
54
-     * @var \Sabre\DAV\Server
55
-     */
56
-    protected $server;
57
-
58
-    /**
59
-     * Initializes the plugin and registers event handlers.
60
-     *
61
-     * @param \Sabre\DAV\Server $server
62
-     */
63
-    public function initialize(DAV\Server $server)
64
-    {
65
-        $this->server = $server;
66
-        $server->on('method:GET', [$this, 'httpGet'], 90);
67
-        $server->on('browserButtonActions', function ($path, $node, &$actions) {
68
-            if ($node instanceof ICalendar) {
69
-                $actions .= '<a href="'.htmlspecialchars($path, ENT_QUOTES, 'UTF-8').'?export"><span class="oi" data-glyph="calendar"></span></a>';
70
-            }
71
-        });
72
-    }
73
-
74
-    /**
75
-     * Intercepts GET requests on calendar urls ending with ?export.
76
-     *
77
-     * @throws BadRequest
78
-     * @throws DAV\Exception\NotFound
79
-     * @throws VObject\InvalidDataException
80
-     *
81
-     * @return bool
82
-     */
83
-    public function httpGet(RequestInterface $request, ResponseInterface $response)
84
-    {
85
-        $queryParams = $request->getQueryParameters();
86
-        if (!array_key_exists('export', $queryParams)) {
87
-            return;
88
-        }
89
-
90
-        $path = $request->getPath();
91
-
92
-        $node = $this->server->getProperties($path, [
93
-            '{DAV:}resourcetype',
94
-            '{DAV:}displayname',
95
-            '{http://sabredav.org/ns}sync-token',
96
-            '{DAV:}sync-token',
97
-            '{http://apple.com/ns/ical/}calendar-color',
98
-        ]);
99
-
100
-        if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{'.Plugin::NS_CALDAV.'}calendar')) {
101
-            return;
102
-        }
103
-        // Marking the transactionType, for logging purposes.
104
-        $this->server->transactionType = 'get-calendar-export';
105
-
106
-        $properties = $node;
107
-
108
-        $start = null;
109
-        $end = null;
110
-        $expand = false;
111
-        $componentType = false;
112
-        if (isset($queryParams['start'])) {
113
-            if (!ctype_digit($queryParams['start'])) {
114
-                throw new BadRequest('The start= parameter must contain a unix timestamp');
115
-            }
116
-            $start = DateTime::createFromFormat('U', $queryParams['start']);
117
-        }
118
-        if (isset($queryParams['end'])) {
119
-            if (!ctype_digit($queryParams['end'])) {
120
-                throw new BadRequest('The end= parameter must contain a unix timestamp');
121
-            }
122
-            $end = DateTime::createFromFormat('U', $queryParams['end']);
123
-        }
124
-        if (isset($queryParams['expand']) && (bool) $queryParams['expand']) {
125
-            if (!$start || !$end) {
126
-                throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
127
-            }
128
-            $expand = true;
129
-            $componentType = 'VEVENT';
130
-        }
131
-        if (isset($queryParams['componentType'])) {
132
-            if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
133
-                throw new BadRequest('You are not allowed to search for components of type: '.$queryParams['componentType'].' here');
134
-            }
135
-            $componentType = $queryParams['componentType'];
136
-        }
137
-
138
-        $format = \Sabre\HTTP\negotiateContentType(
139
-            $request->getHeader('Accept'),
140
-            [
141
-                'text/calendar',
142
-                'application/calendar+json',
143
-            ]
144
-        );
145
-
146
-        if (isset($queryParams['accept'])) {
147
-            if ('application/calendar+json' === $queryParams['accept'] || 'jcal' === $queryParams['accept']) {
148
-                $format = 'application/calendar+json';
149
-            }
150
-        }
151
-        if (!$format) {
152
-            $format = 'text/calendar';
153
-        }
154
-
155
-        $this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);
156
-
157
-        // Returning false to break the event chain
158
-        return false;
159
-    }
160
-
161
-    /**
162
-     * This method is responsible for generating the actual, full response.
163
-     *
164
-     * @param string        $path
165
-     * @param DateTime|null $start
166
-     * @param DateTime|null $end
167
-     * @param bool          $expand
168
-     * @param string        $componentType
169
-     * @param string        $format
170
-     * @param array         $properties
171
-     *
172
-     * @throws DAV\Exception\NotFound
173
-     * @throws VObject\InvalidDataException
174
-     */
175
-    protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response)
176
-    {
177
-        $calDataProp = '{'.Plugin::NS_CALDAV.'}calendar-data';
178
-        $calendarNode = $this->server->tree->getNodeForPath($path);
179
-
180
-        $blobs = [];
181
-        if ($start || $end || $componentType) {
182
-            // If there was a start or end filter, we need to enlist
183
-            // calendarQuery for speed.
184
-            $queryResult = $calendarNode->calendarQuery([
185
-                'name' => 'VCALENDAR',
186
-                'comp-filters' => [
187
-                    [
188
-                        'name' => $componentType,
189
-                        'comp-filters' => [],
190
-                        'prop-filters' => [],
191
-                        'is-not-defined' => false,
192
-                        'time-range' => [
193
-                            'start' => $start,
194
-                            'end' => $end,
195
-                        ],
196
-                    ],
197
-                ],
198
-                'prop-filters' => [],
199
-                'is-not-defined' => false,
200
-                'time-range' => null,
201
-            ]);
202
-
203
-            // queryResult is just a list of base urls. We need to prefix the
204
-            // calendar path.
205
-            $queryResult = array_map(
206
-                function ($item) use ($path) {
207
-                    return $path.'/'.$item;
208
-                },
209
-                $queryResult
210
-            );
211
-            $nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
212
-            unset($queryResult);
213
-        } else {
214
-            $nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1);
215
-        }
216
-
217
-        // Flattening the arrays
218
-        foreach ($nodes as $node) {
219
-            if (isset($node[200][$calDataProp])) {
220
-                $blobs[$node['href']] = $node[200][$calDataProp];
221
-            }
222
-        }
223
-        unset($nodes);
224
-
225
-        $mergedCalendar = $this->mergeObjects(
226
-            $properties,
227
-            $blobs
228
-        );
229
-
230
-        if ($expand) {
231
-            $calendarTimeZone = null;
232
-            // We're expanding, and for that we need to figure out the
233
-            // calendar's timezone.
234
-            $tzProp = '{'.Plugin::NS_CALDAV.'}calendar-timezone';
235
-            $tzResult = $this->server->getProperties($path, [$tzProp]);
236
-            if (isset($tzResult[$tzProp])) {
237
-                // This property contains a VCALENDAR with a single
238
-                // VTIMEZONE.
239
-                $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
240
-                $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
241
-                // Destroy circular references to PHP will GC the object.
242
-                $vtimezoneObj->destroy();
243
-                unset($vtimezoneObj);
244
-            } else {
245
-                // Defaulting to UTC.
246
-                $calendarTimeZone = new DateTimeZone('UTC');
247
-            }
248
-
249
-            $mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
250
-        }
251
-
252
-        $filenameExtension = '.ics';
253
-
254
-        switch ($format) {
255
-            case 'text/calendar':
256
-                $mergedCalendar = $mergedCalendar->serialize();
257
-                $filenameExtension = '.ics';
258
-                break;
259
-            case 'application/calendar+json':
260
-                $mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
261
-                $filenameExtension = '.json';
262
-                break;
263
-        }
264
-
265
-        $filename = preg_replace(
266
-            '/[^a-zA-Z0-9-_ ]/um',
267
-            '',
268
-            $calendarNode->getName()
269
-        );
270
-        $filename .= '-'.date('Y-m-d').$filenameExtension;
271
-
272
-        $response->setHeader('Content-Disposition', 'attachment; filename="'.$filename.'"');
273
-        $response->setHeader('Content-Type', $format);
274
-
275
-        $response->setStatus(200);
276
-        $response->setBody($mergedCalendar);
277
-    }
278
-
279
-    /**
280
-     * Merges all calendar objects, and builds one big iCalendar blob.
281
-     *
282
-     * @param array $properties Some CalDAV properties
283
-     *
284
-     * @return VObject\Component\VCalendar
285
-     */
286
-    public function mergeObjects(array $properties, array $inputObjects)
287
-    {
288
-        $calendar = new VObject\Component\VCalendar();
289
-        $calendar->VERSION = '2.0';
290
-        if (DAV\Server::$exposeVersion) {
291
-            $calendar->PRODID = '-//SabreDAV//SabreDAV '.DAV\Version::VERSION.'//EN';
292
-        } else {
293
-            $calendar->PRODID = '-//SabreDAV//SabreDAV//EN';
294
-        }
295
-        if (isset($properties['{DAV:}displayname'])) {
296
-            $calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
297
-        }
298
-        if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) {
299
-            $calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color'];
300
-        }
301
-
302
-        $collectedTimezones = [];
303
-
304
-        $timezones = [];
305
-        $objects = [];
306
-
307
-        foreach ($inputObjects as $href => $inputObject) {
308
-            $nodeComp = VObject\Reader::read($inputObject);
309
-
310
-            foreach ($nodeComp->children() as $child) {
311
-                switch ($child->name) {
312
-                    case 'VEVENT':
313
-                    case 'VTODO':
314
-                    case 'VJOURNAL':
315
-                        $objects[] = clone $child;
316
-                        break;
317
-
318
-                    // VTIMEZONE is special, because we need to filter out the duplicates
319
-                    case 'VTIMEZONE':
320
-                        // Naively just checking tzid.
321
-                        if (in_array((string) $child->TZID, $collectedTimezones)) {
322
-                            break;
323
-                        }
324
-
325
-                        $timezones[] = clone $child;
326
-                        $collectedTimezones[] = $child->TZID;
327
-                        break;
328
-                }
329
-            }
330
-            // Destroy circular references to PHP will GC the object.
331
-            $nodeComp->destroy();
332
-            unset($nodeComp);
333
-        }
334
-
335
-        foreach ($timezones as $tz) {
336
-            $calendar->add($tz);
337
-        }
338
-        foreach ($objects as $obj) {
339
-            $calendar->add($obj);
340
-        }
341
-
342
-        return $calendar;
343
-    }
344
-
345
-    /**
346
-     * Returns a plugin name.
347
-     *
348
-     * Using this name other plugins will be able to access other plugins
349
-     * using \Sabre\DAV\Server::getPlugin
350
-     *
351
-     * @return string
352
-     */
353
-    public function getPluginName()
354
-    {
355
-        return 'ics-export';
356
-    }
357
-
358
-    /**
359
-     * Returns a bunch of meta-data about the plugin.
360
-     *
361
-     * Providing this information is optional, and is mainly displayed by the
362
-     * Browser plugin.
363
-     *
364
-     * The description key in the returned array may contain html and will not
365
-     * be sanitized.
366
-     *
367
-     * @return array
368
-     */
369
-    public function getPluginInfo()
370
-    {
371
-        return [
372
-            'name' => $this->getPluginName(),
373
-            'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.',
374
-            'link' => 'http://sabre.io/dav/ics-export-plugin/',
375
-        ];
376
-    }
51
+	/**
52
+	 * Reference to Server class.
53
+	 *
54
+	 * @var \Sabre\DAV\Server
55
+	 */
56
+	protected $server;
57
+
58
+	/**
59
+	 * Initializes the plugin and registers event handlers.
60
+	 *
61
+	 * @param \Sabre\DAV\Server $server
62
+	 */
63
+	public function initialize(DAV\Server $server)
64
+	{
65
+		$this->server = $server;
66
+		$server->on('method:GET', [$this, 'httpGet'], 90);
67
+		$server->on('browserButtonActions', function ($path, $node, &$actions) {
68
+			if ($node instanceof ICalendar) {
69
+				$actions .= '<a href="'.htmlspecialchars($path, ENT_QUOTES, 'UTF-8').'?export"><span class="oi" data-glyph="calendar"></span></a>';
70
+			}
71
+		});
72
+	}
73
+
74
+	/**
75
+	 * Intercepts GET requests on calendar urls ending with ?export.
76
+	 *
77
+	 * @throws BadRequest
78
+	 * @throws DAV\Exception\NotFound
79
+	 * @throws VObject\InvalidDataException
80
+	 *
81
+	 * @return bool
82
+	 */
83
+	public function httpGet(RequestInterface $request, ResponseInterface $response)
84
+	{
85
+		$queryParams = $request->getQueryParameters();
86
+		if (!array_key_exists('export', $queryParams)) {
87
+			return;
88
+		}
89
+
90
+		$path = $request->getPath();
91
+
92
+		$node = $this->server->getProperties($path, [
93
+			'{DAV:}resourcetype',
94
+			'{DAV:}displayname',
95
+			'{http://sabredav.org/ns}sync-token',
96
+			'{DAV:}sync-token',
97
+			'{http://apple.com/ns/ical/}calendar-color',
98
+		]);
99
+
100
+		if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{'.Plugin::NS_CALDAV.'}calendar')) {
101
+			return;
102
+		}
103
+		// Marking the transactionType, for logging purposes.
104
+		$this->server->transactionType = 'get-calendar-export';
105
+
106
+		$properties = $node;
107
+
108
+		$start = null;
109
+		$end = null;
110
+		$expand = false;
111
+		$componentType = false;
112
+		if (isset($queryParams['start'])) {
113
+			if (!ctype_digit($queryParams['start'])) {
114
+				throw new BadRequest('The start= parameter must contain a unix timestamp');
115
+			}
116
+			$start = DateTime::createFromFormat('U', $queryParams['start']);
117
+		}
118
+		if (isset($queryParams['end'])) {
119
+			if (!ctype_digit($queryParams['end'])) {
120
+				throw new BadRequest('The end= parameter must contain a unix timestamp');
121
+			}
122
+			$end = DateTime::createFromFormat('U', $queryParams['end']);
123
+		}
124
+		if (isset($queryParams['expand']) && (bool) $queryParams['expand']) {
125
+			if (!$start || !$end) {
126
+				throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
127
+			}
128
+			$expand = true;
129
+			$componentType = 'VEVENT';
130
+		}
131
+		if (isset($queryParams['componentType'])) {
132
+			if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
133
+				throw new BadRequest('You are not allowed to search for components of type: '.$queryParams['componentType'].' here');
134
+			}
135
+			$componentType = $queryParams['componentType'];
136
+		}
137
+
138
+		$format = \Sabre\HTTP\negotiateContentType(
139
+			$request->getHeader('Accept'),
140
+			[
141
+				'text/calendar',
142
+				'application/calendar+json',
143
+			]
144
+		);
145
+
146
+		if (isset($queryParams['accept'])) {
147
+			if ('application/calendar+json' === $queryParams['accept'] || 'jcal' === $queryParams['accept']) {
148
+				$format = 'application/calendar+json';
149
+			}
150
+		}
151
+		if (!$format) {
152
+			$format = 'text/calendar';
153
+		}
154
+
155
+		$this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);
156
+
157
+		// Returning false to break the event chain
158
+		return false;
159
+	}
160
+
161
+	/**
162
+	 * This method is responsible for generating the actual, full response.
163
+	 *
164
+	 * @param string        $path
165
+	 * @param DateTime|null $start
166
+	 * @param DateTime|null $end
167
+	 * @param bool          $expand
168
+	 * @param string        $componentType
169
+	 * @param string        $format
170
+	 * @param array         $properties
171
+	 *
172
+	 * @throws DAV\Exception\NotFound
173
+	 * @throws VObject\InvalidDataException
174
+	 */
175
+	protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response)
176
+	{
177
+		$calDataProp = '{'.Plugin::NS_CALDAV.'}calendar-data';
178
+		$calendarNode = $this->server->tree->getNodeForPath($path);
179
+
180
+		$blobs = [];
181
+		if ($start || $end || $componentType) {
182
+			// If there was a start or end filter, we need to enlist
183
+			// calendarQuery for speed.
184
+			$queryResult = $calendarNode->calendarQuery([
185
+				'name' => 'VCALENDAR',
186
+				'comp-filters' => [
187
+					[
188
+						'name' => $componentType,
189
+						'comp-filters' => [],
190
+						'prop-filters' => [],
191
+						'is-not-defined' => false,
192
+						'time-range' => [
193
+							'start' => $start,
194
+							'end' => $end,
195
+						],
196
+					],
197
+				],
198
+				'prop-filters' => [],
199
+				'is-not-defined' => false,
200
+				'time-range' => null,
201
+			]);
202
+
203
+			// queryResult is just a list of base urls. We need to prefix the
204
+			// calendar path.
205
+			$queryResult = array_map(
206
+				function ($item) use ($path) {
207
+					return $path.'/'.$item;
208
+				},
209
+				$queryResult
210
+			);
211
+			$nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
212
+			unset($queryResult);
213
+		} else {
214
+			$nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1);
215
+		}
216
+
217
+		// Flattening the arrays
218
+		foreach ($nodes as $node) {
219
+			if (isset($node[200][$calDataProp])) {
220
+				$blobs[$node['href']] = $node[200][$calDataProp];
221
+			}
222
+		}
223
+		unset($nodes);
224
+
225
+		$mergedCalendar = $this->mergeObjects(
226
+			$properties,
227
+			$blobs
228
+		);
229
+
230
+		if ($expand) {
231
+			$calendarTimeZone = null;
232
+			// We're expanding, and for that we need to figure out the
233
+			// calendar's timezone.
234
+			$tzProp = '{'.Plugin::NS_CALDAV.'}calendar-timezone';
235
+			$tzResult = $this->server->getProperties($path, [$tzProp]);
236
+			if (isset($tzResult[$tzProp])) {
237
+				// This property contains a VCALENDAR with a single
238
+				// VTIMEZONE.
239
+				$vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
240
+				$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
241
+				// Destroy circular references to PHP will GC the object.
242
+				$vtimezoneObj->destroy();
243
+				unset($vtimezoneObj);
244
+			} else {
245
+				// Defaulting to UTC.
246
+				$calendarTimeZone = new DateTimeZone('UTC');
247
+			}
248
+
249
+			$mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
250
+		}
251
+
252
+		$filenameExtension = '.ics';
253
+
254
+		switch ($format) {
255
+			case 'text/calendar':
256
+				$mergedCalendar = $mergedCalendar->serialize();
257
+				$filenameExtension = '.ics';
258
+				break;
259
+			case 'application/calendar+json':
260
+				$mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
261
+				$filenameExtension = '.json';
262
+				break;
263
+		}
264
+
265
+		$filename = preg_replace(
266
+			'/[^a-zA-Z0-9-_ ]/um',
267
+			'',
268
+			$calendarNode->getName()
269
+		);
270
+		$filename .= '-'.date('Y-m-d').$filenameExtension;
271
+
272
+		$response->setHeader('Content-Disposition', 'attachment; filename="'.$filename.'"');
273
+		$response->setHeader('Content-Type', $format);
274
+
275
+		$response->setStatus(200);
276
+		$response->setBody($mergedCalendar);
277
+	}
278
+
279
+	/**
280
+	 * Merges all calendar objects, and builds one big iCalendar blob.
281
+	 *
282
+	 * @param array $properties Some CalDAV properties
283
+	 *
284
+	 * @return VObject\Component\VCalendar
285
+	 */
286
+	public function mergeObjects(array $properties, array $inputObjects)
287
+	{
288
+		$calendar = new VObject\Component\VCalendar();
289
+		$calendar->VERSION = '2.0';
290
+		if (DAV\Server::$exposeVersion) {
291
+			$calendar->PRODID = '-//SabreDAV//SabreDAV '.DAV\Version::VERSION.'//EN';
292
+		} else {
293
+			$calendar->PRODID = '-//SabreDAV//SabreDAV//EN';
294
+		}
295
+		if (isset($properties['{DAV:}displayname'])) {
296
+			$calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
297
+		}
298
+		if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) {
299
+			$calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color'];
300
+		}
301
+
302
+		$collectedTimezones = [];
303
+
304
+		$timezones = [];
305
+		$objects = [];
306
+
307
+		foreach ($inputObjects as $href => $inputObject) {
308
+			$nodeComp = VObject\Reader::read($inputObject);
309
+
310
+			foreach ($nodeComp->children() as $child) {
311
+				switch ($child->name) {
312
+					case 'VEVENT':
313
+					case 'VTODO':
314
+					case 'VJOURNAL':
315
+						$objects[] = clone $child;
316
+						break;
317
+
318
+					// VTIMEZONE is special, because we need to filter out the duplicates
319
+					case 'VTIMEZONE':
320
+						// Naively just checking tzid.
321
+						if (in_array((string) $child->TZID, $collectedTimezones)) {
322
+							break;
323
+						}
324
+
325
+						$timezones[] = clone $child;
326
+						$collectedTimezones[] = $child->TZID;
327
+						break;
328
+				}
329
+			}
330
+			// Destroy circular references to PHP will GC the object.
331
+			$nodeComp->destroy();
332
+			unset($nodeComp);
333
+		}
334
+
335
+		foreach ($timezones as $tz) {
336
+			$calendar->add($tz);
337
+		}
338
+		foreach ($objects as $obj) {
339
+			$calendar->add($obj);
340
+		}
341
+
342
+		return $calendar;
343
+	}
344
+
345
+	/**
346
+	 * Returns a plugin name.
347
+	 *
348
+	 * Using this name other plugins will be able to access other plugins
349
+	 * using \Sabre\DAV\Server::getPlugin
350
+	 *
351
+	 * @return string
352
+	 */
353
+	public function getPluginName()
354
+	{
355
+		return 'ics-export';
356
+	}
357
+
358
+	/**
359
+	 * Returns a bunch of meta-data about the plugin.
360
+	 *
361
+	 * Providing this information is optional, and is mainly displayed by the
362
+	 * Browser plugin.
363
+	 *
364
+	 * The description key in the returned array may contain html and will not
365
+	 * be sanitized.
366
+	 *
367
+	 * @return array
368
+	 */
369
+	public function getPluginInfo()
370
+	{
371
+		return [
372
+			'name' => $this->getPluginName(),
373
+			'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.',
374
+			'link' => 'http://sabre.io/dav/ics-export-plugin/',
375
+		];
376
+	}
377 377
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -64,7 +64,7 @@  discard block
 block discarded – undo
64 64
     {
65 65
         $this->server = $server;
66 66
         $server->on('method:GET', [$this, 'httpGet'], 90);
67
-        $server->on('browserButtonActions', function ($path, $node, &$actions) {
67
+        $server->on('browserButtonActions', function($path, $node, &$actions) {
68 68
             if ($node instanceof ICalendar) {
69 69
                 $actions .= '<a href="'.htmlspecialchars($path, ENT_QUOTES, 'UTF-8').'?export"><span class="oi" data-glyph="calendar"></span></a>';
70 70
             }
@@ -203,7 +203,7 @@  discard block
 block discarded – undo
203 203
             // queryResult is just a list of base urls. We need to prefix the
204 204
             // calendar path.
205 205
             $queryResult = array_map(
206
-                function ($item) use ($path) {
206
+                function($item) use ($path) {
207 207
                     return $path.'/'.$item;
208 208
                 },
209 209
                 $queryResult
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/Backend/BackendInterface.php 1 patch
Indentation   +246 added lines, -246 removed lines patch added patch discarded remove patch
@@ -13,261 +13,261 @@
 block discarded – undo
13 13
  */
14 14
 interface BackendInterface
15 15
 {
16
-    /**
17
-     * Returns a list of calendars for a principal.
18
-     *
19
-     * Every project is an array with the following keys:
20
-     *  * id, a unique id that will be used by other functions to modify the
21
-     *    calendar. This can be the same as the uri or a database key.
22
-     *  * uri, which is the basename of the uri with which the calendar is
23
-     *    accessed.
24
-     *  * principaluri. The owner of the calendar. Almost always the same as
25
-     *    principalUri passed to this method.
26
-     *
27
-     * Furthermore it can contain webdav properties in clark notation. A very
28
-     * common one is '{DAV:}displayname'.
29
-     *
30
-     * Many clients also require:
31
-     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
32
-     * For this property, you can just return an instance of
33
-     * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
34
-     *
35
-     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
36
-     * ACL will automatically be put in read-only mode.
37
-     *
38
-     * @param string $principalUri
39
-     *
40
-     * @return array
41
-     */
42
-    public function getCalendarsForUser($principalUri);
16
+	/**
17
+	 * Returns a list of calendars for a principal.
18
+	 *
19
+	 * Every project is an array with the following keys:
20
+	 *  * id, a unique id that will be used by other functions to modify the
21
+	 *    calendar. This can be the same as the uri or a database key.
22
+	 *  * uri, which is the basename of the uri with which the calendar is
23
+	 *    accessed.
24
+	 *  * principaluri. The owner of the calendar. Almost always the same as
25
+	 *    principalUri passed to this method.
26
+	 *
27
+	 * Furthermore it can contain webdav properties in clark notation. A very
28
+	 * common one is '{DAV:}displayname'.
29
+	 *
30
+	 * Many clients also require:
31
+	 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
32
+	 * For this property, you can just return an instance of
33
+	 * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
34
+	 *
35
+	 * If you return {http://sabredav.org/ns}read-only and set the value to 1,
36
+	 * ACL will automatically be put in read-only mode.
37
+	 *
38
+	 * @param string $principalUri
39
+	 *
40
+	 * @return array
41
+	 */
42
+	public function getCalendarsForUser($principalUri);
43 43
 
44
-    /**
45
-     * Creates a new calendar for a principal.
46
-     *
47
-     * If the creation was a success, an id must be returned that can be used to
48
-     * reference this calendar in other methods, such as updateCalendar.
49
-     *
50
-     * The id can be any type, including ints, strings, objects or array.
51
-     *
52
-     * @param string $principalUri
53
-     * @param string $calendarUri
54
-     *
55
-     * @return mixed
56
-     */
57
-    public function createCalendar($principalUri, $calendarUri, array $properties);
44
+	/**
45
+	 * Creates a new calendar for a principal.
46
+	 *
47
+	 * If the creation was a success, an id must be returned that can be used to
48
+	 * reference this calendar in other methods, such as updateCalendar.
49
+	 *
50
+	 * The id can be any type, including ints, strings, objects or array.
51
+	 *
52
+	 * @param string $principalUri
53
+	 * @param string $calendarUri
54
+	 *
55
+	 * @return mixed
56
+	 */
57
+	public function createCalendar($principalUri, $calendarUri, array $properties);
58 58
 
59
-    /**
60
-     * Updates properties for a calendar.
61
-     *
62
-     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
63
-     * To do the actual updates, you must tell this object which properties
64
-     * you're going to process with the handle() method.
65
-     *
66
-     * Calling the handle method is like telling the PropPatch object "I
67
-     * promise I can handle updating this property".
68
-     *
69
-     * Read the PropPatch documentation for more info and examples.
70
-     *
71
-     * @param mixed $calendarId
72
-     */
73
-    public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch);
59
+	/**
60
+	 * Updates properties for a calendar.
61
+	 *
62
+	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
63
+	 * To do the actual updates, you must tell this object which properties
64
+	 * you're going to process with the handle() method.
65
+	 *
66
+	 * Calling the handle method is like telling the PropPatch object "I
67
+	 * promise I can handle updating this property".
68
+	 *
69
+	 * Read the PropPatch documentation for more info and examples.
70
+	 *
71
+	 * @param mixed $calendarId
72
+	 */
73
+	public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch);
74 74
 
75
-    /**
76
-     * Delete a calendar and all its objects.
77
-     *
78
-     * @param mixed $calendarId
79
-     */
80
-    public function deleteCalendar($calendarId);
75
+	/**
76
+	 * Delete a calendar and all its objects.
77
+	 *
78
+	 * @param mixed $calendarId
79
+	 */
80
+	public function deleteCalendar($calendarId);
81 81
 
82
-    /**
83
-     * Returns all calendar objects within a calendar.
84
-     *
85
-     * Every item contains an array with the following keys:
86
-     *   * calendardata - The iCalendar-compatible calendar data
87
-     *   * uri - a unique key which will be used to construct the uri. This can
88
-     *     be any arbitrary string, but making sure it ends with '.ics' is a
89
-     *     good idea. This is only the basename, or filename, not the full
90
-     *     path.
91
-     *   * lastmodified - a timestamp of the last modification time
92
-     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
93
-     *   '"abcdef"')
94
-     *   * size - The size of the calendar objects, in bytes.
95
-     *   * component - optional, a string containing the type of object, such
96
-     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
97
-     *     the Content-Type header.
98
-     *
99
-     * Note that the etag is optional, but it's highly encouraged to return for
100
-     * speed reasons.
101
-     *
102
-     * The calendardata is also optional. If it's not returned
103
-     * 'getCalendarObject' will be called later, which *is* expected to return
104
-     * calendardata.
105
-     *
106
-     * If neither etag or size are specified, the calendardata will be
107
-     * used/fetched to determine these numbers. If both are specified the
108
-     * amount of times this is needed is reduced by a great degree.
109
-     *
110
-     * @param mixed $calendarId
111
-     *
112
-     * @return array
113
-     */
114
-    public function getCalendarObjects($calendarId);
82
+	/**
83
+	 * Returns all calendar objects within a calendar.
84
+	 *
85
+	 * Every item contains an array with the following keys:
86
+	 *   * calendardata - The iCalendar-compatible calendar data
87
+	 *   * uri - a unique key which will be used to construct the uri. This can
88
+	 *     be any arbitrary string, but making sure it ends with '.ics' is a
89
+	 *     good idea. This is only the basename, or filename, not the full
90
+	 *     path.
91
+	 *   * lastmodified - a timestamp of the last modification time
92
+	 *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
93
+	 *   '"abcdef"')
94
+	 *   * size - The size of the calendar objects, in bytes.
95
+	 *   * component - optional, a string containing the type of object, such
96
+	 *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
97
+	 *     the Content-Type header.
98
+	 *
99
+	 * Note that the etag is optional, but it's highly encouraged to return for
100
+	 * speed reasons.
101
+	 *
102
+	 * The calendardata is also optional. If it's not returned
103
+	 * 'getCalendarObject' will be called later, which *is* expected to return
104
+	 * calendardata.
105
+	 *
106
+	 * If neither etag or size are specified, the calendardata will be
107
+	 * used/fetched to determine these numbers. If both are specified the
108
+	 * amount of times this is needed is reduced by a great degree.
109
+	 *
110
+	 * @param mixed $calendarId
111
+	 *
112
+	 * @return array
113
+	 */
114
+	public function getCalendarObjects($calendarId);
115 115
 
116
-    /**
117
-     * Returns information from a single calendar object, based on it's object
118
-     * uri.
119
-     *
120
-     * The object uri is only the basename, or filename and not a full path.
121
-     *
122
-     * The returned array must have the same keys as getCalendarObjects. The
123
-     * 'calendardata' object is required here though, while it's not required
124
-     * for getCalendarObjects.
125
-     *
126
-     * This method must return null if the object did not exist.
127
-     *
128
-     * @param mixed  $calendarId
129
-     * @param string $objectUri
130
-     *
131
-     * @return array|null
132
-     */
133
-    public function getCalendarObject($calendarId, $objectUri);
116
+	/**
117
+	 * Returns information from a single calendar object, based on it's object
118
+	 * uri.
119
+	 *
120
+	 * The object uri is only the basename, or filename and not a full path.
121
+	 *
122
+	 * The returned array must have the same keys as getCalendarObjects. The
123
+	 * 'calendardata' object is required here though, while it's not required
124
+	 * for getCalendarObjects.
125
+	 *
126
+	 * This method must return null if the object did not exist.
127
+	 *
128
+	 * @param mixed  $calendarId
129
+	 * @param string $objectUri
130
+	 *
131
+	 * @return array|null
132
+	 */
133
+	public function getCalendarObject($calendarId, $objectUri);
134 134
 
135
-    /**
136
-     * Returns a list of calendar objects.
137
-     *
138
-     * This method should work identical to getCalendarObject, but instead
139
-     * return all the calendar objects in the list as an array.
140
-     *
141
-     * If the backend supports this, it may allow for some speed-ups.
142
-     *
143
-     * @param mixed $calendarId
144
-     *
145
-     * @return array
146
-     */
147
-    public function getMultipleCalendarObjects($calendarId, array $uris);
135
+	/**
136
+	 * Returns a list of calendar objects.
137
+	 *
138
+	 * This method should work identical to getCalendarObject, but instead
139
+	 * return all the calendar objects in the list as an array.
140
+	 *
141
+	 * If the backend supports this, it may allow for some speed-ups.
142
+	 *
143
+	 * @param mixed $calendarId
144
+	 *
145
+	 * @return array
146
+	 */
147
+	public function getMultipleCalendarObjects($calendarId, array $uris);
148 148
 
149
-    /**
150
-     * Creates a new calendar object.
151
-     *
152
-     * The object uri is only the basename, or filename and not a full path.
153
-     *
154
-     * It is possible to return an etag from this function, which will be used
155
-     * in the response to this PUT request. Note that the ETag must be
156
-     * surrounded by double-quotes.
157
-     *
158
-     * However, you should only really return this ETag if you don't mangle the
159
-     * calendar-data. If the result of a subsequent GET to this object is not
160
-     * the exact same as this request body, you should omit the ETag.
161
-     *
162
-     * @param mixed  $calendarId
163
-     * @param string $objectUri
164
-     * @param string $calendarData
165
-     *
166
-     * @return string|null
167
-     */
168
-    public function createCalendarObject($calendarId, $objectUri, $calendarData);
149
+	/**
150
+	 * Creates a new calendar object.
151
+	 *
152
+	 * The object uri is only the basename, or filename and not a full path.
153
+	 *
154
+	 * It is possible to return an etag from this function, which will be used
155
+	 * in the response to this PUT request. Note that the ETag must be
156
+	 * surrounded by double-quotes.
157
+	 *
158
+	 * However, you should only really return this ETag if you don't mangle the
159
+	 * calendar-data. If the result of a subsequent GET to this object is not
160
+	 * the exact same as this request body, you should omit the ETag.
161
+	 *
162
+	 * @param mixed  $calendarId
163
+	 * @param string $objectUri
164
+	 * @param string $calendarData
165
+	 *
166
+	 * @return string|null
167
+	 */
168
+	public function createCalendarObject($calendarId, $objectUri, $calendarData);
169 169
 
170
-    /**
171
-     * Updates an existing calendarobject, based on it's uri.
172
-     *
173
-     * The object uri is only the basename, or filename and not a full path.
174
-     *
175
-     * It is possible return an etag from this function, which will be used in
176
-     * the response to this PUT request. Note that the ETag must be surrounded
177
-     * by double-quotes.
178
-     *
179
-     * However, you should only really return this ETag if you don't mangle the
180
-     * calendar-data. If the result of a subsequent GET to this object is not
181
-     * the exact same as this request body, you should omit the ETag.
182
-     *
183
-     * @param mixed  $calendarId
184
-     * @param string $objectUri
185
-     * @param string $calendarData
186
-     *
187
-     * @return string|null
188
-     */
189
-    public function updateCalendarObject($calendarId, $objectUri, $calendarData);
170
+	/**
171
+	 * Updates an existing calendarobject, based on it's uri.
172
+	 *
173
+	 * The object uri is only the basename, or filename and not a full path.
174
+	 *
175
+	 * It is possible return an etag from this function, which will be used in
176
+	 * the response to this PUT request. Note that the ETag must be surrounded
177
+	 * by double-quotes.
178
+	 *
179
+	 * However, you should only really return this ETag if you don't mangle the
180
+	 * calendar-data. If the result of a subsequent GET to this object is not
181
+	 * the exact same as this request body, you should omit the ETag.
182
+	 *
183
+	 * @param mixed  $calendarId
184
+	 * @param string $objectUri
185
+	 * @param string $calendarData
186
+	 *
187
+	 * @return string|null
188
+	 */
189
+	public function updateCalendarObject($calendarId, $objectUri, $calendarData);
190 190
 
191
-    /**
192
-     * Deletes an existing calendar object.
193
-     *
194
-     * The object uri is only the basename, or filename and not a full path.
195
-     *
196
-     * @param mixed  $calendarId
197
-     * @param string $objectUri
198
-     */
199
-    public function deleteCalendarObject($calendarId, $objectUri);
191
+	/**
192
+	 * Deletes an existing calendar object.
193
+	 *
194
+	 * The object uri is only the basename, or filename and not a full path.
195
+	 *
196
+	 * @param mixed  $calendarId
197
+	 * @param string $objectUri
198
+	 */
199
+	public function deleteCalendarObject($calendarId, $objectUri);
200 200
 
201
-    /**
202
-     * Performs a calendar-query on the contents of this calendar.
203
-     *
204
-     * The calendar-query is defined in RFC4791 : CalDAV. Using the
205
-     * calendar-query it is possible for a client to request a specific set of
206
-     * object, based on contents of iCalendar properties, date-ranges and
207
-     * iCalendar component types (VTODO, VEVENT).
208
-     *
209
-     * This method should just return a list of (relative) urls that match this
210
-     * query.
211
-     *
212
-     * The list of filters are specified as an array. The exact array is
213
-     * documented by Sabre\CalDAV\CalendarQueryParser.
214
-     *
215
-     * Note that it is extremely likely that getCalendarObject for every path
216
-     * returned from this method will be called almost immediately after. You
217
-     * may want to anticipate this to speed up these requests.
218
-     *
219
-     * This method provides a default implementation, which parses *all* the
220
-     * iCalendar objects in the specified calendar.
221
-     *
222
-     * This default may well be good enough for personal use, and calendars
223
-     * that aren't very large. But if you anticipate high usage, big calendars
224
-     * or high loads, you are strongly adviced to optimize certain paths.
225
-     *
226
-     * The best way to do so is override this method and to optimize
227
-     * specifically for 'common filters'.
228
-     *
229
-     * Requests that are extremely common are:
230
-     *   * requests for just VEVENTS
231
-     *   * requests for just VTODO
232
-     *   * requests with a time-range-filter on either VEVENT or VTODO.
233
-     *
234
-     * ..and combinations of these requests. It may not be worth it to try to
235
-     * handle every possible situation and just rely on the (relatively
236
-     * easy to use) CalendarQueryValidator to handle the rest.
237
-     *
238
-     * Note that especially time-range-filters may be difficult to parse. A
239
-     * time-range filter specified on a VEVENT must for instance also handle
240
-     * recurrence rules correctly.
241
-     * A good example of how to interprete all these filters can also simply
242
-     * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
243
-     * as possible, so it gives you a good idea on what type of stuff you need
244
-     * to think of.
245
-     *
246
-     * @param mixed $calendarId
247
-     *
248
-     * @return array
249
-     */
250
-    public function calendarQuery($calendarId, array $filters);
201
+	/**
202
+	 * Performs a calendar-query on the contents of this calendar.
203
+	 *
204
+	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
205
+	 * calendar-query it is possible for a client to request a specific set of
206
+	 * object, based on contents of iCalendar properties, date-ranges and
207
+	 * iCalendar component types (VTODO, VEVENT).
208
+	 *
209
+	 * This method should just return a list of (relative) urls that match this
210
+	 * query.
211
+	 *
212
+	 * The list of filters are specified as an array. The exact array is
213
+	 * documented by Sabre\CalDAV\CalendarQueryParser.
214
+	 *
215
+	 * Note that it is extremely likely that getCalendarObject for every path
216
+	 * returned from this method will be called almost immediately after. You
217
+	 * may want to anticipate this to speed up these requests.
218
+	 *
219
+	 * This method provides a default implementation, which parses *all* the
220
+	 * iCalendar objects in the specified calendar.
221
+	 *
222
+	 * This default may well be good enough for personal use, and calendars
223
+	 * that aren't very large. But if you anticipate high usage, big calendars
224
+	 * or high loads, you are strongly adviced to optimize certain paths.
225
+	 *
226
+	 * The best way to do so is override this method and to optimize
227
+	 * specifically for 'common filters'.
228
+	 *
229
+	 * Requests that are extremely common are:
230
+	 *   * requests for just VEVENTS
231
+	 *   * requests for just VTODO
232
+	 *   * requests with a time-range-filter on either VEVENT or VTODO.
233
+	 *
234
+	 * ..and combinations of these requests. It may not be worth it to try to
235
+	 * handle every possible situation and just rely on the (relatively
236
+	 * easy to use) CalendarQueryValidator to handle the rest.
237
+	 *
238
+	 * Note that especially time-range-filters may be difficult to parse. A
239
+	 * time-range filter specified on a VEVENT must for instance also handle
240
+	 * recurrence rules correctly.
241
+	 * A good example of how to interprete all these filters can also simply
242
+	 * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
243
+	 * as possible, so it gives you a good idea on what type of stuff you need
244
+	 * to think of.
245
+	 *
246
+	 * @param mixed $calendarId
247
+	 *
248
+	 * @return array
249
+	 */
250
+	public function calendarQuery($calendarId, array $filters);
251 251
 
252
-    /**
253
-     * Searches through all of a users calendars and calendar objects to find
254
-     * an object with a specific UID.
255
-     *
256
-     * This method should return the path to this object, relative to the
257
-     * calendar home, so this path usually only contains two parts:
258
-     *
259
-     * calendarpath/objectpath.ics
260
-     *
261
-     * If the uid is not found, return null.
262
-     *
263
-     * This method should only consider * objects that the principal owns, so
264
-     * any calendars owned by other principals that also appear in this
265
-     * collection should be ignored.
266
-     *
267
-     * @param string $principalUri
268
-     * @param string $uid
269
-     *
270
-     * @return string|null
271
-     */
272
-    public function getCalendarObjectByUID($principalUri, $uid);
252
+	/**
253
+	 * Searches through all of a users calendars and calendar objects to find
254
+	 * an object with a specific UID.
255
+	 *
256
+	 * This method should return the path to this object, relative to the
257
+	 * calendar home, so this path usually only contains two parts:
258
+	 *
259
+	 * calendarpath/objectpath.ics
260
+	 *
261
+	 * If the uid is not found, return null.
262
+	 *
263
+	 * This method should only consider * objects that the principal owns, so
264
+	 * any calendars owned by other principals that also appear in this
265
+	 * collection should be ignored.
266
+	 *
267
+	 * @param string $principalUri
268
+	 * @param string $uid
269
+	 *
270
+	 * @return string|null
271
+	 */
272
+	public function getCalendarObjectByUID($principalUri, $uid);
273 273
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php 1 patch
Indentation   +31 added lines, -31 removed lines patch added patch discarded remove patch
@@ -26,37 +26,37 @@
 block discarded – undo
26 26
  */
27 27
 interface NotificationSupport extends BackendInterface
28 28
 {
29
-    /**
30
-     * Returns a list of notifications for a given principal url.
31
-     *
32
-     * @param string $principalUri
33
-     *
34
-     * @return NotificationInterface[]
35
-     */
36
-    public function getNotificationsForPrincipal($principalUri);
29
+	/**
30
+	 * Returns a list of notifications for a given principal url.
31
+	 *
32
+	 * @param string $principalUri
33
+	 *
34
+	 * @return NotificationInterface[]
35
+	 */
36
+	public function getNotificationsForPrincipal($principalUri);
37 37
 
38
-    /**
39
-     * This deletes a specific notifcation.
40
-     *
41
-     * This may be called by a client once it deems a notification handled.
42
-     *
43
-     * @param string $principalUri
44
-     */
45
-    public function deleteNotification($principalUri, NotificationInterface $notification);
38
+	/**
39
+	 * This deletes a specific notifcation.
40
+	 *
41
+	 * This may be called by a client once it deems a notification handled.
42
+	 *
43
+	 * @param string $principalUri
44
+	 */
45
+	public function deleteNotification($principalUri, NotificationInterface $notification);
46 46
 
47
-    /**
48
-     * This method is called when a user replied to a request to share.
49
-     *
50
-     * If the user chose to accept the share, this method should return the
51
-     * newly created calendar url.
52
-     *
53
-     * @param string $href        The sharee who is replying (often a mailto: address)
54
-     * @param int    $status      One of the SharingPlugin::STATUS_* constants
55
-     * @param string $calendarUri The url to the calendar thats being shared
56
-     * @param string $inReplyTo   The unique id this message is a response to
57
-     * @param string $summary     A description of the reply
58
-     *
59
-     * @return string|null
60
-     */
61
-    public function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
47
+	/**
48
+	 * This method is called when a user replied to a request to share.
49
+	 *
50
+	 * If the user chose to accept the share, this method should return the
51
+	 * newly created calendar url.
52
+	 *
53
+	 * @param string $href        The sharee who is replying (often a mailto: address)
54
+	 * @param int    $status      One of the SharingPlugin::STATUS_* constants
55
+	 * @param string $calendarUri The url to the calendar thats being shared
56
+	 * @param string $inReplyTo   The unique id this message is a response to
57
+	 * @param string $summary     A description of the reply
58
+	 *
59
+	 * @return string|null
60
+	 */
61
+	public function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
62 62
 }
Please login to merge, or discard this patch.