ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Plugin.php
Go to the documentation of this file.
1<?php
2
3namespace Sabre\CalDAV;
4
5use DateTimeZone;
6use Sabre\DAV;
11use Sabre\DAVACL;
12use Sabre\HTTP;
15use Sabre\Uri;
17
28class Plugin extends DAV\ServerPlugin {
29
33 const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
34
38 const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
39
44 const CALENDAR_ROOT = 'calendars';
45
51 protected $server;
52
58 protected $maxResourceSize = 10000000;
59
70 function getHTTPMethods($uri) {
71
72 // The MKCALENDAR is only available on unmapped uri's, whose
73 // parents extend IExtendedCollection
74 list($parent, $name) = Uri\split($uri);
75
76 $node = $this->server->tree->getNodeForPath($parent);
77
78 if ($node instanceof DAV\IExtendedCollection) {
79 try {
80 $node->getChild($name);
81 } catch (DAV\Exception\NotFound $e) {
82 return ['MKCALENDAR'];
83 }
84 }
85 return [];
86
87 }
88
99 function getCalendarHomeForPrincipal($principalUrl) {
100
101 // The default behavior for most sabre/dav servers is that there is a
102 // principals root node, which contains users directly under it.
103 //
104 // This function assumes that there are two components in a principal
105 // path. If there's more, we don't return a calendar home. This
106 // excludes things like the calendar-proxy-read principal (which it
107 // should).
108 $parts = explode('/', trim($principalUrl, '/'));
109 if (count($parts) !== 2) return;
110 if ($parts[0] !== 'principals') return;
111
112 return self::CALENDAR_ROOT . '/' . $parts[1];
113
114 }
115
121 function getFeatures() {
122
123 return ['calendar-access', 'calendar-proxy'];
124
125 }
126
135 function getPluginName() {
136
137 return 'caldav';
138
139 }
140
151 function getSupportedReportSet($uri) {
152
153 $node = $this->server->tree->getNodeForPath($uri);
154
155 $reports = [];
156 if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) {
157 $reports[] = '{' . self::NS_CALDAV . '}calendar-multiget';
158 $reports[] = '{' . self::NS_CALDAV . '}calendar-query';
159 }
160 if ($node instanceof ICalendar) {
161 $reports[] = '{' . self::NS_CALDAV . '}free-busy-query';
162 }
163 // iCal has a bug where it assumes that sync support is enabled, only
164 // if we say we support it on the calendar-home, even though this is
165 // not actually the case.
166 if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) {
167 $reports[] = '{DAV:}sync-collection';
168 }
169 return $reports;
170
171 }
172
179 function initialize(DAV\Server $server) {
180
181 $this->server = $server;
182
183 $server->on('method:MKCALENDAR', [$this, 'httpMkCalendar']);
184 $server->on('report', [$this, 'report']);
185 $server->on('propFind', [$this, 'propFind']);
186 $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
187 $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
188 $server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
189 $server->on('afterMethod:GET', [$this, 'httpAfterGET']);
190 $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
191
192 $server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
193 $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
194
195 $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
196 $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport';
197 $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport';
198 $server->xml->elementMap['{' . self::NS_CALDAV . '}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport';
199 $server->xml->elementMap['{' . self::NS_CALDAV . '}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar';
200 $server->xml->elementMap['{' . self::NS_CALDAV . '}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp';
201 $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
202
203 $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';
204
205 $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
206 $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
207
208 array_push($server->protectedProperties,
209
210 '{' . self::NS_CALDAV . '}supported-calendar-component-set',
211 '{' . self::NS_CALDAV . '}supported-calendar-data',
212 '{' . self::NS_CALDAV . '}max-resource-size',
213 '{' . self::NS_CALDAV . '}min-date-time',
214 '{' . self::NS_CALDAV . '}max-date-time',
215 '{' . self::NS_CALDAV . '}max-instances',
216 '{' . self::NS_CALDAV . '}max-attendees-per-instance',
217 '{' . self::NS_CALDAV . '}calendar-home-set',
218 '{' . self::NS_CALDAV . '}supported-collation-set',
219 '{' . self::NS_CALDAV . '}calendar-data',
220
221 // CalendarServer extensions
222 '{' . self::NS_CALENDARSERVER . '}getctag',
223 '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for',
224 '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'
225
226 );
227
228 if ($aclPlugin = $server->getPlugin('acl')) {
229 $aclPlugin->principalSearchPropertySet['{' . self::NS_CALDAV . '}calendar-user-address-set'] = 'Calendar address';
230 }
231 }
232
241 function report($reportName, $report, $path) {
242
243 switch ($reportName) {
244 case '{' . self::NS_CALDAV . '}calendar-multiget' :
245 $this->server->transactionType = 'report-calendar-multiget';
246 $this->calendarMultiGetReport($report);
247 return false;
248 case '{' . self::NS_CALDAV . '}calendar-query' :
249 $this->server->transactionType = 'report-calendar-query';
250 $this->calendarQueryReport($report);
251 return false;
252 case '{' . self::NS_CALDAV . '}free-busy-query' :
253 $this->server->transactionType = 'report-free-busy-query';
254 $this->freeBusyQueryReport($report);
255 return false;
256
257 }
258
259
260 }
261
271
272 $body = $request->getBodyAsString();
273 $path = $request->getPath();
274
275 $properties = [];
276
277 if ($body) {
278
279 try {
280 $mkcalendar = $this->server->xml->expect(
281 '{urn:ietf:params:xml:ns:caldav}mkcalendar',
282 $body
283 );
284 } catch (\Sabre\Xml\ParseException $e) {
285 throw new BadRequest($e->getMessage(), null, $e);
286 }
287 $properties = $mkcalendar->getProperties();
288
289 }
290
291 // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
292 // subscriptions. Before that it used MKCOL which was the correct way
293 // to do this.
294 //
295 // If the body had a {DAV:}resourcetype, it means we stumbled upon this
296 // request, and we simply use it instead of the pre-defined list.
297 if (isset($properties['{DAV:}resourcetype'])) {
298 $resourceType = $properties['{DAV:}resourcetype']->getValue();
299 } else {
300 $resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'];
301 }
302
303 $this->server->createCollection($path, new MkCol($resourceType, $properties));
304
305 $response->setStatus(201);
306 $response->setHeader('Content-Length', 0);
307
308 // This breaks the method chain.
309 return false;
310 }
311
323 function propFind(DAV\PropFind $propFind, DAV\INode $node) {
324
325 $ns = '{' . self::NS_CALDAV . '}';
326
327 if ($node instanceof ICalendarObjectContainer) {
328
329 $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize);
330 $propFind->handle($ns . 'supported-calendar-data', function() {
332 });
333 $propFind->handle($ns . 'supported-collation-set', function() {
335 });
336
337 }
338
339 if ($node instanceof DAVACL\IPrincipal) {
340
341 $principalUrl = $node->getPrincipalUrl();
342
343 $propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() use ($principalUrl) {
344
345 $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
346 if (is_null($calendarHomePath)) return null;
347 return new LocalHref($calendarHomePath . '/');
348
349 });
350 // The calendar-user-address-set property is basically mapped to
351 // the {DAV:}alternate-URI-set property.
352 $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-address-set', function() use ($node) {
353 $addresses = $node->getAlternateUriSet();
354 $addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/';
355 return new LocalHref($addresses);
356 });
357 // For some reason somebody thought it was a good idea to add
358 // another one of these properties. We're supporting it too.
359 $propFind->handle('{' . self::NS_CALENDARSERVER . '}email-address-set', function() use ($node) {
360 $addresses = $node->getAlternateUriSet();
361 $emails = [];
362 foreach ($addresses as $address) {
363 if (substr($address, 0, 7) === 'mailto:') {
364 $emails[] = substr($address, 7);
365 }
366 }
367 return new Xml\Property\EmailAddressSet($emails);
368 });
369
370 // These two properties are shortcuts for ical to easily find
371 // other principals this principal has access to.
372 $propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for';
373 $propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for';
374
375 if ($propFind->getStatus($propRead) === 404 || $propFind->getStatus($propWrite) === 404) {
376
377 $aclPlugin = $this->server->getPlugin('acl');
378 $membership = $aclPlugin->getPrincipalMembership($propFind->getPath());
379 $readList = [];
380 $writeList = [];
381
382 foreach ($membership as $group) {
383
384 $groupNode = $this->server->tree->getNodeForPath($group);
385
386 $listItem = Uri\split($group)[0] . '/';
387
388 // If the node is either ap proxy-read or proxy-write
389 // group, we grab the parent principal and add it to the
390 // list.
391 if ($groupNode instanceof Principal\IProxyRead) {
392 $readList[] = $listItem;
393 }
394 if ($groupNode instanceof Principal\IProxyWrite) {
395 $writeList[] = $listItem;
396 }
397
398 }
399
400 $propFind->set($propRead, new LocalHref($readList));
401 $propFind->set($propWrite, new LocalHref($writeList));
402
403 }
404
405 } // instanceof IPrincipal
406
407 if ($node instanceof ICalendarObject) {
408
409 // The calendar-data property is not supposed to be a 'real'
410 // property, but in large chunks of the spec it does act as such.
411 // Therefore we simply expose it as a property.
412 $propFind->handle('{' . self::NS_CALDAV . '}calendar-data', function() use ($node) {
413 $val = $node->get();
414 if (is_resource($val))
415 $val = stream_get_contents($val);
416
417 // Taking out \r to not screw up the xml output
418 return str_replace("\r", "", $val);
419
420 });
421
422 }
423
424 }
425
435 function calendarMultiGetReport($report) {
436
437 $needsJson = $report->contentType === 'application/calendar+json';
438
439 $timeZones = [];
440 $propertyList = [];
441
442 $paths = array_map(
443 [$this->server, 'calculateUri'],
444 $report->hrefs
445 );
446
447 foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) {
448
449 if (($needsJson || $report->expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
450 $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);
451
452 if ($report->expand) {
453 // We're expanding, and for that we need to figure out the
454 // calendar's timezone.
455 list($calendarPath) = Uri\split($uri);
456 if (!isset($timeZones[$calendarPath])) {
457 // Checking the calendar-timezone property.
458 $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
459 $tzResult = $this->server->getProperties($calendarPath, [$tzProp]);
460 if (isset($tzResult[$tzProp])) {
461 // This property contains a VCALENDAR with a single
462 // VTIMEZONE.
463 $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
464 $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
465 } else {
466 // Defaulting to UTC.
467 $timeZone = new DateTimeZone('UTC');
468 }
469 $timeZones[$calendarPath] = $timeZone;
470 }
471
472 $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]);
473 }
474 if ($needsJson) {
475 $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
476 } else {
477 $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
478 }
479 // Destroy circular references so PHP will garbage collect the
480 // object.
481 $vObject->destroy();
482 }
483
484 $propertyList[] = $objProps;
485
486 }
487
488 $prefer = $this->server->getHTTPPrefer();
489
490 $this->server->httpResponse->setStatus(207);
491 $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
492 $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
493 $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal'));
494
495 }
496
506 function calendarQueryReport($report) {
507
508 $path = $this->server->getRequestUri();
509
510 $needsJson = $report->contentType === 'application/calendar+json';
511
512 $node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
513 $depth = $this->server->getHTTPDepth(0);
514
515 // The default result is an empty array
516 $result = [];
517
518 $calendarTimeZone = null;
519 if ($report->expand) {
520 // We're expanding, and for that we need to figure out the
521 // calendar's timezone.
522 $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
523 $tzResult = $this->server->getProperties($path, [$tzProp]);
524 if (isset($tzResult[$tzProp])) {
525 // This property contains a VCALENDAR with a single
526 // VTIMEZONE.
527 $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
528 $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
529
530 // Destroy circular references so PHP will garbage collect the
531 // object.
532 $vtimezoneObj->destroy();
533 } else {
534 // Defaulting to UTC.
535 $calendarTimeZone = new DateTimeZone('UTC');
536 }
537 }
538
539 // The calendarobject was requested directly. In this case we handle
540 // this locally.
541 if ($depth == 0 && $node instanceof ICalendarObject) {
542
543 $requestedCalendarData = true;
544 $requestedProperties = $report->properties;
545
546 if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
547
548 // We always retrieve calendar-data, as we need it for filtering.
549 $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
550
551 // If calendar-data wasn't explicitly requested, we need to remove
552 // it after processing.
553 $requestedCalendarData = false;
554 }
555
556 $properties = $this->server->getPropertiesForPath(
557 $path,
558 $requestedProperties,
559 0
560 );
561
562 // This array should have only 1 element, the first calendar
563 // object.
564 $properties = current($properties);
565
566 // If there wasn't any calendar-data returned somehow, we ignore
567 // this.
568 if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
569
570 $validator = new CalendarQueryValidator();
571
572 $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
573 if ($validator->validate($vObject, $report->filters)) {
574
575 // If the client didn't require the calendar-data property,
576 // we won't give it back.
577 if (!$requestedCalendarData) {
578 unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
579 } else {
580
581
582 if ($report->expand) {
583 $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
584 }
585 if ($needsJson) {
586 $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
587 } elseif ($report->expand) {
588 $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
589 }
590 }
591
592 $result = [$properties];
593
594 }
595 // Destroy circular references so PHP will garbage collect the
596 // object.
597 $vObject->destroy();
598
599 }
600
601 }
602
603 if ($node instanceof ICalendarObjectContainer && $depth === 0) {
604
605 if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) {
606 // Microsoft clients incorrectly supplied depth as 0, when it actually
607 // should have set depth to 1. We're implementing a workaround here
608 // to deal with this.
609 //
610 // This targets at least the following clients:
611 // Windows 10
612 // Windows Phone 8, 10
613 $depth = 1;
614 } else {
615 throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
616 }
617
618 }
619
620 // If we're dealing with a calendar, the calendar itself is responsible
621 // for the calendar-query.
622 if ($node instanceof ICalendarObjectContainer && $depth == 1) {
623
624 $nodePaths = $node->calendarQuery($report->filters);
625
626 foreach ($nodePaths as $path) {
627
628 list($properties) =
629 $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $report->properties);
630
631 if (($needsJson || $report->expand)) {
632 $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']);
633
634 if ($report->expand) {
635 $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
636 }
637
638 if ($needsJson) {
639 $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
640 } else {
641 $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
642 }
643
644 // Destroy circular references so PHP will garbage collect the
645 // object.
646 $vObject->destroy();
647 }
648 $result[] = $properties;
649
650 }
651
652 }
653
654 $prefer = $this->server->getHTTPPrefer();
655
656 $this->server->httpResponse->setStatus(207);
657 $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
658 $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
659 $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));
660
661 }
662
670 protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report) {
671
672 $uri = $this->server->getRequestUri();
673
674 $acl = $this->server->getPlugin('acl');
675 if ($acl) {
676 $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy');
677 }
678
679 $calendar = $this->server->tree->getNodeForPath($uri);
680 if (!$calendar instanceof ICalendar) {
681 throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars');
682 }
683
684 $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
685
686 // Figuring out the default timezone for the calendar, for floating
687 // times.
688 $calendarProps = $this->server->getProperties($uri, [$tzProp]);
689
690 if (isset($calendarProps[$tzProp])) {
691 $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]);
692 $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
693 // Destroy circular references so PHP will garbage collect the object.
694 $vtimezoneObj->destroy();
695 } else {
696 $calendarTimeZone = new DateTimeZone('UTC');
697 }
698
699 // Doing a calendar-query first, to make sure we get the most
700 // performance.
701 $urls = $calendar->calendarQuery([
702 'name' => 'VCALENDAR',
703 'comp-filters' => [
704 [
705 'name' => 'VEVENT',
706 'comp-filters' => [],
707 'prop-filters' => [],
708 'is-not-defined' => false,
709 'time-range' => [
710 'start' => $report->start,
711 'end' => $report->end,
712 ],
713 ],
714 ],
715 'prop-filters' => [],
716 'is-not-defined' => false,
717 'time-range' => null,
718 ]);
719
720 $objects = array_map(function($url) use ($calendar) {
721 $obj = $calendar->getChild($url)->get();
722 return $obj;
723 }, $urls);
724
725 $generator = new VObject\FreeBusyGenerator();
726 $generator->setObjects($objects);
727 $generator->setTimeRange($report->start, $report->end);
728 $generator->setTimeZone($calendarTimeZone);
729 $result = $generator->getResult();
730 $result = $result->serialize();
731
732 $this->server->httpResponse->setStatus(200);
733 $this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
734 $this->server->httpResponse->setHeader('Content-Length', strlen($result));
735 $this->server->httpResponse->setBody($result);
736
737 }
738
752 function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {
753
754 if (!$node instanceof ICalendarObject)
755 return;
756
757 // We're onyl interested in ICalendarObject nodes that are inside of a
758 // real calendar. This is to avoid triggering validation and scheduling
759 // for non-calendars (such as an inbox).
760 list($parent) = Uri\split($path);
761 $parentNode = $this->server->tree->getNodeForPath($parent);
762
763 if (!$parentNode instanceof ICalendar)
764 return;
765
766 $this->validateICalendar(
767 $data,
768 $path,
769 $modified,
770 $this->server->httpRequest,
771 $this->server->httpResponse,
772 false
773 );
774
775 }
776
790 function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {
791
792 if (!$parentNode instanceof ICalendar)
793 return;
794
795 $this->validateICalendar(
796 $data,
797 $path,
798 $modified,
799 $this->server->httpRequest,
800 $this->server->httpResponse,
801 true
802 );
803
804 }
805
821
822 // If it's a stream, we convert it to a string first.
823 if (is_resource($data)) {
824 $data = stream_get_contents($data);
825 }
826
827 $before = $data;
828
829 try {
830
831 // If the data starts with a [, we can reasonably assume we're dealing
832 // with a jCal object.
833 if (substr($data, 0, 1) === '[') {
835
836 // Converting $data back to iCalendar, as that's what we
837 // technically support everywhere.
838 $data = $vobj->serialize();
839 $modified = true;
840 } else {
842 }
843
844 } catch (VObject\ParseException $e) {
845
846 throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
847
848 }
849
850 if ($vobj->name !== 'VCALENDAR') {
851 throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
852 }
853
854 $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
855
856 // Get the Supported Components for the target calendar
857 list($parentPath) = Uri\split($path);
858 $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
859
860 if (isset($calendarProperties[$sCCS])) {
861 $supportedComponents = $calendarProperties[$sCCS]->getValue();
862 } else {
863 $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
864 }
865
866 $foundType = null;
867
868 foreach ($vobj->getComponents() as $component) {
869 switch ($component->name) {
870 case 'VTIMEZONE' :
871 continue 2;
872 case 'VEVENT' :
873 case 'VTODO' :
874 case 'VJOURNAL' :
875 $foundType = $component->name;
876 break;
877 }
878
879 }
880
881 if (!$foundType || !in_array($foundType, $supportedComponents)) {
882 throw new Exception\InvalidComponentType('iCalendar objects must at least have a component of type ' . implode(', ', $supportedComponents));
883 }
884
886 $prefer = $this->server->getHTTPPrefer();
887
888 if ($prefer['handling'] !== 'strict') {
890 }
891
892 $messages = $vobj->validate($options);
893
894 $highestLevel = 0;
895 $warningMessage = null;
896
897 // $messages contains a list of problems with the vcard, along with
898 // their severity.
899 foreach ($messages as $message) {
900
901 if ($message['level'] > $highestLevel) {
902 // Recording the highest reported error level.
903 $highestLevel = $message['level'];
904 $warningMessage = $message['message'];
905 }
906 switch ($message['level']) {
907
908 case 1 :
909 // Level 1 means that there was a problem, but it was repaired.
910 $modified = true;
911 break;
912 case 2 :
913 // Level 2 means a warning, but not critical
914 break;
915 case 3 :
916 // Level 3 means a critical error
917 throw new DAV\Exception\UnsupportedMediaType('Validation error in iCalendar: ' . $message['message']);
918
919 }
920
921 }
922 if ($warningMessage) {
923 $response->setHeader(
924 'X-Sabre-Ew-Gross',
925 'iCalendar validation warning: ' . $warningMessage
926 );
927 }
928
929 // We use an extra variable to allow event handles to tell us whether
930 // the object was modified or not.
931 //
932 // This helps us determine if we need to re-serialize the object.
933 $subModified = false;
934
935 $this->server->emit(
936 'calendarObjectChange',
937 [
938 $request,
939 $response,
940 $vobj,
941 $parentPath,
942 &$subModified,
943 $isNew
944 ]
945 );
946
947 if ($modified || $subModified) {
948 // An event handler told us that it modified the object.
949 $data = $vobj->serialize();
950
951 // Using md5 to figure out if there was an *actual* change.
952 if (!$modified && strcmp($data, $before) !== 0) {
953 $modified = true;
954 }
955
956 }
957
958 // Destroy circular references so PHP will garbage collect the object.
959 $vobj->destroy();
960
961 }
962
970 function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {
971
972 if ($node instanceof ICalendar) {
973 $supportedPrivilegeSet['{DAV:}read']['aggregates']['{' . self::NS_CALDAV . '}read-free-busy'] = [
974 'abstract' => false,
975 'aggregates' => [],
976 ];
977 }
978 }
979
989 function htmlActionsPanel(DAV\INode $node, &$output) {
990
991 if (!$node instanceof CalendarHome)
992 return;
993
994 $output .= '<tr><td colspan="2"><form method="post" action="">
995 <h3>Create new calendar</h3>
996 <input type="hidden" name="sabreAction" value="mkcol" />
997 <input type="hidden" name="resourceType" value="{DAV:}collection,{' . self::NS_CALDAV . '}calendar" />
998 <label>Name (uri):</label> <input type="text" name="name" /><br />
999 <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
1000 <input type="submit" value="create" />
1001 </form>
1002 </td></tr>';
1003
1004 return false;
1005
1006 }
1007
1018
1019 if (strpos($response->getHeader('Content-Type'), 'text/calendar') === false) {
1020 return;
1021 }
1022
1024 $request->getHeader('Accept'),
1025 ['text/calendar', 'application/calendar+json']
1026 );
1027
1028 if ($result !== 'application/calendar+json') {
1029 // Do nothing
1030 return;
1031 }
1032
1033 // Transforming.
1034 $vobj = VObject\Reader::read($response->getBody());
1035
1036 $jsonBody = json_encode($vobj->jsonSerialize());
1037 $response->setBody($jsonBody);
1038
1039 // Destroy circular references so PHP will garbage collect the object.
1040 $vobj->destroy();
1041
1042 $response->setHeader('Content-Type', 'application/calendar+json');
1043 $response->setHeader('Content-Length', strlen($jsonBody));
1044
1045 }
1046
1058 function getPluginInfo() {
1059
1060 return [
1061 'name' => $this->getPluginName(),
1062 'description' => 'Adds support for CalDAV (rfc4791)',
1063 'link' => 'http://sabre.io/dav/caldav/',
1064 ];
1065
1066 }
1067
1068}
$result
$path
Definition: aliased.php:25
foreach($paths as $path) $request
Definition: asyncclient.php:32
$aclPlugin
An exception for terminatinating execution or to throw for unit testing.
The CalendarHome represents a node that is usually in a users' calendar-homeset.
CalDAV plugin.
Definition: Plugin.php:28
report($reportName, $report, $path)
This functions handles REPORT requests specific to CalDAV.
Definition: Plugin.php:241
freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report)
This method is responsible for parsing the request and generating the response for the CALDAV:free-bu...
Definition: Plugin.php:670
calendarMultiGetReport($report)
This function handles the calendar-multiget REPORT.
Definition: Plugin.php:435
htmlActionsPanel(DAV\INode $node, &$output)
This method is used to generate HTML output for the DAV\Browser\Plugin.
Definition: Plugin.php:989
getCalendarHomeForPrincipal($principalUrl)
Returns the path to a principal's calendar home.
Definition: Plugin.php:99
getHTTPMethods($uri)
Use this method to tell the server this plugin defines additional HTTP methods.
Definition: Plugin.php:70
$maxResourceSize
The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data, which can hold up to 2^24 = 16777...
Definition: Plugin.php:58
initialize(DAV\Server $server)
Initializes the plugin.
Definition: Plugin.php:179
beforeWriteContent($path, DAV\IFile $node, &$data, &$modified)
This method is triggered before a file gets updated with new content.
Definition: Plugin.php:752
propFind(DAV\PropFind $propFind, DAV\INode $node)
PropFind.
Definition: Plugin.php:323
const NS_CALDAV
This is the official CalDAV namespace.
Definition: Plugin.php:33
getPluginInfo()
Returns a bunch of meta-data about the plugin.
Definition: Plugin.php:1058
httpAfterGet(RequestInterface $request, ResponseInterface $response)
This event is triggered after GET requests.
Definition: Plugin.php:1017
getSupportedReportSet($uri)
Returns a list of reports this plugin supports.
Definition: Plugin.php:151
getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
This method is triggered whenever a subsystem reqeuests the privileges that are supported on a partic...
Definition: Plugin.php:970
httpMkCalendar(RequestInterface $request, ResponseInterface $response)
This function handles the MKCALENDAR HTTP method, which creates a new calendar.
Definition: Plugin.php:270
const NS_CALENDARSERVER
This is the namespace for the proprietary calendarserver extensions.
Definition: Plugin.php:38
getPluginName()
Returns a plugin name.
Definition: Plugin.php:135
getFeatures()
Returns a list of features for the DAV: HTTP header.
Definition: Plugin.php:121
beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified)
This method is triggered before a new file is created.
Definition: Plugin.php:790
calendarQueryReport($report)
This function handles the calendar-query REPORT.
Definition: Plugin.php:506
const CALENDAR_ROOT
The hardcoded root for calendar objects.
Definition: Plugin.php:44
validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew)
Checks if the submitted iCalendar data is in fact, valid.
Definition: Plugin.php:820
Principal class.
Definition: Principal.php:23
Main Exception class.
Definition: Exception.php:18
This class represents a MKCOL operation.
Definition: MkCol.php:23
This class holds all the information about a PROPFIND request.
Definition: PropFind.php:11
The baseclass for all server plugins.
Main DAV server class.
Definition: Server.php:23
The Request class represents a single HTTP request.
Definition: Request.php:18
static negotiate($acceptHeaderValue, array $availableOptions)
Deprecated! Use negotiateContentType.
Definition: Util.php:38
This class helps with generating FREEBUSY reports based on existing sets of objects.
const PROFILE_CALDAV
If this option is set, the validator will operate on iCalendar objects on the assumption that the vca...
Definition: Node.php:45
const REPAIR
The following constants are used by the validate() method.
Definition: Node.php:27
Exception thrown by Reader if an invalid object was attempted to be parsed.
static readJson($data, $options=0)
Parses a jCard or jCal object, and returns the top component.
Definition: Reader.php:67
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
Definition: Reader.php:42
$urls
Definition: croninfo.php:28
$messages
Definition: en.php:5
This interface represents a node that may contain calendar objects.
CalendarObject interface.
Calendar interface.
Definition: ICalendar.php:16
IPrincipal interface.
Definition: IPrincipal.php:16
The ICollection Interface.
Definition: ICollection.php:14
The IExtendedCollection interface.
This interface represents a file in the directory tree.
Definition: IFile.php:16
The INode interface is the base interface, and the parent class of both ICollection and IFile.
Definition: INode.php:12
The RequestInterface represents a HTTP request.
This interface represents a HTTP response.
catch(Exception $e) $message
if($argc< 2) $paths
Definition: migrateto20.php:44
split($path)
Returns the 'dirname' and 'basename' for a path.
Definition: functions.php:279
$url
$response
$vobj
Definition: rrulebench.php:21
$data
Definition: bench.php:6