ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Plugin.php
Go to the documentation of this file.
1<?php
2
4
5use DateTimeZone;
20use Sabre\DAVACL;
28
56class Plugin extends ServerPlugin {
57
61 const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
62
68 protected $server;
69
75 function getFeatures() {
76
77 return ['calendar-auto-schedule', 'calendar-availability'];
78
79 }
80
89 function getPluginName() {
90
91 return 'caldav-schedule';
92
93 }
94
102
103 $this->server = $server;
104 $server->on('method:POST', [$this, 'httpPost']);
105 $server->on('propFind', [$this, 'propFind']);
106 $server->on('propPatch', [$this, 'propPatch']);
107 $server->on('calendarObjectChange', [$this, 'calendarObjectChange']);
108 $server->on('beforeUnbind', [$this, 'beforeUnbind']);
109 $server->on('schedule', [$this, 'scheduleLocalDelivery']);
110 $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
111
112 $ns = '{' . self::NS_CALDAV . '}';
113
118 $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns . 'schedule-outbox';
119 $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns . 'schedule-inbox';
120
124 array_push($server->protectedProperties,
125 $ns . 'schedule-inbox-URL',
126 $ns . 'schedule-outbox-URL',
127 $ns . 'calendar-user-address-set',
128 $ns . 'calendar-user-type',
129 $ns . 'schedule-default-calendar-URL'
130 );
131
132 }
133
144 function getHTTPMethods($uri) {
145
146 try {
147 $node = $this->server->tree->getNodeForPath($uri);
148 } catch (NotFound $e) {
149 return [];
150 }
151
152 if ($node instanceof IOutbox) {
153 return ['POST'];
154 }
155
156 return [];
157
158 }
159
168
169 // Checking if this is a text/calendar content type
170 $contentType = $request->getHeader('Content-Type');
171 if (strpos($contentType, 'text/calendar') !== 0) {
172 return;
173 }
174
175 $path = $request->getPath();
176
177 // Checking if we're talking to an outbox
178 try {
179 $node = $this->server->tree->getNodeForPath($path);
180 } catch (NotFound $e) {
181 return;
182 }
183 if (!$node instanceof IOutbox)
184 return;
185
186 $this->server->transactionType = 'post-caldav-outbox';
187 $this->outboxRequest($node, $request, $response);
188
189 // Returning false breaks the event chain and tells the server we've
190 // handled the request.
191 return false;
192
193 }
194
204 function propFind(PropFind $propFind, INode $node) {
205
206 if ($node instanceof DAVACL\IPrincipal) {
207
208 $caldavPlugin = $this->server->getPlugin('caldav');
209 $principalUrl = $node->getPrincipalUrl();
210
211 // schedule-outbox-URL property
212 $propFind->handle('{' . self::NS_CALDAV . '}schedule-outbox-URL', function() use ($principalUrl, $caldavPlugin) {
213
214 $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
215 if (!$calendarHomePath) {
216 return null;
217 }
218 $outboxPath = $calendarHomePath . '/outbox/';
219
220 return new LocalHref($outboxPath);
221
222 });
223 // schedule-inbox-URL property
224 $propFind->handle('{' . self::NS_CALDAV . '}schedule-inbox-URL', function() use ($principalUrl, $caldavPlugin) {
225
226 $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
227 if (!$calendarHomePath) {
228 return null;
229 }
230 $inboxPath = $calendarHomePath . '/inbox/';
231
232 return new LocalHref($inboxPath);
233
234 });
235
236 $propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function() use ($principalUrl, $caldavPlugin) {
237
238 // We don't support customizing this property yet, so in the
239 // meantime we just grab the first calendar in the home-set.
240 $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
241
242 if (!$calendarHomePath) {
243 return null;
244 }
245
246 $sccs = '{' . self::NS_CALDAV . '}supported-calendar-component-set';
247
248 $result = $this->server->getPropertiesForPath($calendarHomePath, [
249 '{DAV:}resourcetype',
250 '{DAV:}share-access',
251 $sccs,
252 ], 1);
253
254 foreach ($result as $child) {
255 if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{' . self::NS_CALDAV . '}calendar')) {
256 // Node is either not a calendar
257 continue;
258 }
259 if (isset($child[200]['{DAV:}share-access'])) {
260 $shareAccess = $child[200]['{DAV:}share-access']->getValue();
261 if ($shareAccess !== Sharing\Plugin::ACCESS_NOTSHARED && $shareAccess !== Sharing\Plugin::ACCESS_SHAREDOWNER) {
262 // Node is a shared node, not owned by the relevant
263 // user.
264 continue;
265 }
266
267 }
268 if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) {
269 // Either there is no supported-calendar-component-set
270 // (which is fine) or we found one that supports VEVENT.
271 return new LocalHref($child['href']);
272 }
273 }
274
275 });
276
277 // The server currently reports every principal to be of type
278 // 'INDIVIDUAL'
279 $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function() {
280
281 return 'INDIVIDUAL';
282
283 });
284
285 }
286
287 // Mapping the old property to the new property.
288 $propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function() use ($propFind, $node) {
289
290 // In case it wasn't clear, the only difference is that we map the
291 // old property to a different namespace.
292 $availProp = '{' . self::NS_CALDAV . '}calendar-availability';
293 $subPropFind = new PropFind(
294 $propFind->getPath(),
295 [$availProp]
296 );
297
298 $this->server->getPropertiesByNode(
299 $subPropFind,
300 $node
301 );
302
303 $propFind->set(
304 '{http://calendarserver.org/ns/}calendar-availability',
305 $subPropFind->get($availProp),
306 $subPropFind->getStatus($availProp)
307 );
308
309 });
310
311 }
312
320 function propPatch($path, PropPatch $propPatch) {
321
322 // Mapping the old property to the new property.
323 $propPatch->handle('{http://calendarserver.org/ns/}calendar-availability', function($value) use ($path) {
324
325 $availProp = '{' . self::NS_CALDAV . '}calendar-availability';
326 $subPropPatch = new PropPatch([$availProp => $value]);
327 $this->server->emit('propPatch', [$path, $subPropPatch]);
328 $subPropPatch->commit();
329
330 return $subPropPatch->getResult()[$availProp];
331
332 });
333
334 }
335
348 function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
349
350 if (!$this->scheduleReply($this->server->httpRequest)) {
351 return;
352 }
353
354 $calendarNode = $this->server->tree->getNodeForPath($calendarPath);
355
356 $addresses = $this->getAddressesForPrincipal(
357 $calendarNode->getOwner()
358 );
359
360 if (!$isNew) {
361 $node = $this->server->tree->getNodeForPath($request->getPath());
362 $oldObj = Reader::read($node->get());
363 } else {
364 $oldObj = null;
365 }
366
367 $this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified);
368
369 if ($oldObj) {
370 // Destroy circular references so PHP will GC the object.
371 $oldObj->destroy();
372 }
373
374 }
375
382 function deliver(ITip\Message $iTipMessage) {
383
384 $this->server->emit('schedule', [$iTipMessage]);
385 if (!$iTipMessage->scheduleStatus) {
386 $iTipMessage->scheduleStatus = '5.2;There was no system capable of delivering the scheduling message';
387 }
388 // In case the change was considered 'insignificant', we are going to
389 // remove any error statuses, if any. See ticket #525.
390 list($baseCode) = explode('.', $iTipMessage->scheduleStatus);
391 if (!$iTipMessage->significantChange && in_array($baseCode, ['3', '5'])) {
392 $iTipMessage->scheduleStatus = null;
393 }
394
395 }
396
406 function beforeUnbind($path) {
407
408 // FIXME: We shouldn't trigger this functionality when we're issuing a
409 // MOVE. This is a hack.
410 if ($this->server->httpRequest->getMethod() === 'MOVE') return;
411
412 $node = $this->server->tree->getNodeForPath($path);
413
414 if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) {
415 return;
416 }
417
418 if (!$this->scheduleReply($this->server->httpRequest)) {
419 return;
420 }
421
422 $addresses = $this->getAddressesForPrincipal(
423 $node->getOwner()
424 );
425
426 $broker = new ITip\Broker();
427 $messages = $broker->parseEvent(null, $addresses, $node->get());
428
429 foreach ($messages as $message) {
430 $this->deliver($message);
431 }
432
433 }
434
444 function scheduleLocalDelivery(ITip\Message $iTipMessage) {
445
446 $aclPlugin = $this->server->getPlugin('acl');
447
448 // Local delivery is not available if the ACL plugin is not loaded.
449 if (!$aclPlugin) {
450 return;
451 }
452
453 $caldavNS = '{' . self::NS_CALDAV . '}';
454
455 $principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
456 if (!$principalUri) {
457 $iTipMessage->scheduleStatus = '3.7;Could not find principal.';
458 return;
459 }
460
461 // We found a principal URL, now we need to find its inbox.
462 // Unfortunately we may not have sufficient privileges to find this, so
463 // we are temporarily turning off ACL to let this come through.
464 //
465 // Once we support PHP 5.5, this should be wrapped in a try..finally
466 // block so we can ensure that this privilege gets added again after.
467 $this->server->removeListener('propFind', [$aclPlugin, 'propFind']);
468
469 $result = $this->server->getProperties(
470 $principalUri,
471 [
472 '{DAV:}principal-URL',
473 $caldavNS . 'calendar-home-set',
474 $caldavNS . 'schedule-inbox-URL',
475 $caldavNS . 'schedule-default-calendar-URL',
476 '{http://sabredav.org/ns}email-address',
477 ]
478 );
479
480 // Re-registering the ACL event
481 $this->server->on('propFind', [$aclPlugin, 'propFind'], 20);
482
483 if (!isset($result[$caldavNS . 'schedule-inbox-URL'])) {
484 $iTipMessage->scheduleStatus = '5.2;Could not find local inbox';
485 return;
486 }
487 if (!isset($result[$caldavNS . 'calendar-home-set'])) {
488 $iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set';
489 return;
490 }
491 if (!isset($result[$caldavNS . 'schedule-default-calendar-URL'])) {
492 $iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property';
493 return;
494 }
495
496 $calendarPath = $result[$caldavNS . 'schedule-default-calendar-URL']->getHref();
497 $homePath = $result[$caldavNS . 'calendar-home-set']->getHref();
498 $inboxPath = $result[$caldavNS . 'schedule-inbox-URL']->getHref();
499
500 if ($iTipMessage->method === 'REPLY') {
501 $privilege = 'schedule-deliver-reply';
502 } else {
503 $privilege = 'schedule-deliver-invite';
504 }
505
506 if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS . $privilege, DAVACL\Plugin::R_PARENT, false)) {
507 $iTipMessage->scheduleStatus = '3.8;insufficient privileges: ' . $privilege . ' is required on the recipient schedule inbox.';
508 return;
509 }
510
511 // Next, we're going to find out if the item already exits in one of
512 // the users' calendars.
513 $uid = $iTipMessage->uid;
514
515 $newFileName = 'sabredav-' . \Sabre\DAV\UUIDUtil::getUUID() . '.ics';
516
517 $home = $this->server->tree->getNodeForPath($homePath);
518 $inbox = $this->server->tree->getNodeForPath($inboxPath);
519
520 $currentObject = null;
521 $objectNode = null;
522 $isNewNode = false;
523
524 $result = $home->getCalendarObjectByUID($uid);
525 if ($result) {
526 // There was an existing object, we need to update probably.
527 $objectPath = $homePath . '/' . $result;
528 $objectNode = $this->server->tree->getNodeForPath($objectPath);
529 $oldICalendarData = $objectNode->get();
530 $currentObject = Reader::read($oldICalendarData);
531 } else {
532 $isNewNode = true;
533 }
534
535 $broker = new ITip\Broker();
536 $newObject = $broker->processMessage($iTipMessage, $currentObject);
537
538 $inbox->createFile($newFileName, $iTipMessage->message->serialize());
539
540 if (!$newObject) {
541 // We received an iTip message referring to a UID that we don't
542 // have in any calendars yet, and processMessage did not give us a
543 // calendarobject back.
544 //
545 // The implication is that processMessage did not understand the
546 // iTip message.
547 $iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';
548 return;
549 }
550
551 // Note that we are bypassing ACL on purpose by calling this directly.
552 // We may need to look a bit deeper into this later. Supporting ACL
553 // here would be nice.
554 if ($isNewNode) {
555 $calendar = $this->server->tree->getNodeForPath($calendarPath);
556 $calendar->createFile($newFileName, $newObject->serialize());
557 } else {
558 // If the message was a reply, we may have to inform other
559 // attendees of this attendees status. Therefore we're shooting off
560 // another itipMessage.
561 if ($iTipMessage->method === 'REPLY') {
563 $oldICalendarData,
564 $newObject,
565 [$iTipMessage->recipient],
566 [$iTipMessage->sender]
567 );
568 }
569 $objectNode->put($newObject->serialize());
570 }
571 $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
572
573 }
574
584 function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {
585
586 $ns = '{' . self::NS_CALDAV . '}';
587 if ($node instanceof IOutbox) {
588 $supportedPrivilegeSet[$ns . 'schedule-send'] = [
589 'abstract' => false,
590 'aggregates' => [
591 $ns . 'schedule-send-invite' => [
592 'abstract' => false,
593 'aggregates' => [],
594 ],
595 $ns . 'schedule-send-reply' => [
596 'abstract' => false,
597 'aggregates' => [],
598 ],
599 $ns . 'schedule-send-freebusy' => [
600 'abstract' => false,
601 'aggregates' => [],
602 ],
603 // Privilege from an earlier scheduling draft, but still
604 // used by some clients.
605 $ns . 'schedule-post-vevent' => [
606 'abstract' => false,
607 'aggregates' => [],
608 ],
609 ]
610 ];
611 }
612 if ($node instanceof IInbox) {
613 $supportedPrivilegeSet[$ns . 'schedule-deliver'] = [
614 'abstract' => false,
615 'aggregates' => [
616 $ns . 'schedule-deliver-invite' => [
617 'abstract' => false,
618 'aggregates' => [],
619 ],
620 $ns . 'schedule-deliver-reply' => [
621 'abstract' => false,
622 'aggregates' => [],
623 ],
624 $ns . 'schedule-query-freebusy' => [
625 'abstract' => false,
626 'aggregates' => [],
627 ],
628 ]
629 ];
630 }
631
632 }
633
652 protected function processICalendarChange($oldObject = null, VCalendar $newObject, array $addresses, array $ignore = [], &$modified = false) {
653
654 $broker = new ITip\Broker();
655 $messages = $broker->parseEvent($newObject, $addresses, $oldObject);
656
657 if ($messages) $modified = true;
658
659 foreach ($messages as $message) {
660
661 if (in_array($message->recipient, $ignore)) {
662 continue;
663 }
664
665 $this->deliver($message);
666
667 if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === $message->recipient)) {
668 if ($message->scheduleStatus) {
669 $newObject->VEVENT->ORGANIZER['SCHEDULE-STATUS'] = $message->getScheduleStatus();
670 }
671 unset($newObject->VEVENT->ORGANIZER['SCHEDULE-FORCE-SEND']);
672
673 } else {
674
675 if (isset($newObject->VEVENT->ATTENDEE)) foreach ($newObject->VEVENT->ATTENDEE as $attendee) {
676
677 if ($attendee->getNormalizedValue() === $message->recipient) {
678 if ($message->scheduleStatus) {
679 $attendee['SCHEDULE-STATUS'] = $message->getScheduleStatus();
680 }
681 unset($attendee['SCHEDULE-FORCE-SEND']);
682 break;
683 }
684
685 }
686
687 }
688
689 }
690
691 }
692
699 protected function getAddressesForPrincipal($principal) {
700
701 $CUAS = '{' . self::NS_CALDAV . '}calendar-user-address-set';
702
703 $properties = $this->server->getProperties(
704 $principal,
705 [$CUAS]
706 );
707
708 // If we can't find this information, we'll stop processing
709 if (!isset($properties[$CUAS])) {
710 return;
711 }
712
713 $addresses = $properties[$CUAS]->getHrefs();
714 return $addresses;
715
716 }
717
735
736 $outboxPath = $request->getPath();
737
738 // Parsing the request body
739 try {
740 $vObject = VObject\Reader::read($request->getBody());
741 } catch (VObject\ParseException $e) {
742 throw new BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
743 }
744
745 // The incoming iCalendar object must have a METHOD property, and a
746 // component. The combination of both determines what type of request
747 // this is.
748 $componentType = null;
749 foreach ($vObject->getComponents() as $component) {
750 if ($component->name !== 'VTIMEZONE') {
751 $componentType = $component->name;
752 break;
753 }
754 }
755 if (is_null($componentType)) {
756 throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
757 }
758
759 // Validating the METHOD
760 $method = strtoupper((string)$vObject->METHOD);
761 if (!$method) {
762 throw new BadRequest('A METHOD property must be specified in iTIP messages');
763 }
764
765 // So we support one type of request:
766 //
767 // REQUEST with a VFREEBUSY component
768
769 $acl = $this->server->getPlugin('acl');
770
771 if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') {
772
773 $acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-send-freebusy');
774 $this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response);
775
776 // Destroy circular references so PHP can GC the object.
777 $vObject->destroy();
778 unset($vObject);
779
780 } else {
781
782 throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint');
783
784 }
785
786 }
787
799
800 $vFreeBusy = $vObject->VFREEBUSY;
801 $organizer = $vFreeBusy->ORGANIZER;
802
803 $organizer = (string)$organizer;
804
805 // Validating if the organizer matches the owner of the inbox.
806 $owner = $outbox->getOwner();
807
808 $caldavNS = '{' . self::NS_CALDAV . '}';
809
810 $uas = $caldavNS . 'calendar-user-address-set';
811 $props = $this->server->getProperties($owner, [$uas]);
812
813 if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) {
814 throw new Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox');
815 }
816
817 if (!isset($vFreeBusy->ATTENDEE)) {
818 throw new BadRequest('You must at least specify 1 attendee');
819 }
820
821 $attendees = [];
822 foreach ($vFreeBusy->ATTENDEE as $attendee) {
823 $attendees[] = (string)$attendee;
824 }
825
826
827 if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) {
828 throw new BadRequest('DTSTART and DTEND must both be specified');
829 }
830
831 $startRange = $vFreeBusy->DTSTART->getDateTime();
832 $endRange = $vFreeBusy->DTEND->getDateTime();
833
834 $results = [];
835 foreach ($attendees as $attendee) {
836 $results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject);
837 }
838
839 $dom = new \DOMDocument('1.0', 'utf-8');
840 $dom->formatOutput = true;
841 $scheduleResponse = $dom->createElement('cal:schedule-response');
842 foreach ($this->server->xml->namespaceMap as $namespace => $prefix) {
843
844 $scheduleResponse->setAttribute('xmlns:' . $prefix, $namespace);
845
846 }
847 $dom->appendChild($scheduleResponse);
848
849 foreach ($results as $result) {
850 $xresponse = $dom->createElement('cal:response');
851
852 $recipient = $dom->createElement('cal:recipient');
853 $recipientHref = $dom->createElement('d:href');
854
855 $recipientHref->appendChild($dom->createTextNode($result['href']));
856 $recipient->appendChild($recipientHref);
857 $xresponse->appendChild($recipient);
858
859 $reqStatus = $dom->createElement('cal:request-status');
860 $reqStatus->appendChild($dom->createTextNode($result['request-status']));
861 $xresponse->appendChild($reqStatus);
862
863 if (isset($result['calendar-data'])) {
864
865 $calendardata = $dom->createElement('cal:calendar-data');
866 $calendardata->appendChild($dom->createTextNode(str_replace("\r\n", "\n", $result['calendar-data']->serialize())));
867 $xresponse->appendChild($calendardata);
868
869 }
870 $scheduleResponse->appendChild($xresponse);
871 }
872
873 $response->setStatus(200);
874 $response->setHeader('Content-Type', 'application/xml');
875 $response->setBody($dom->saveXML());
876
877 }
878
897 protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request) {
898
899 $caldavNS = '{' . self::NS_CALDAV . '}';
900
901 $aclPlugin = $this->server->getPlugin('acl');
902 if (substr($email, 0, 7) === 'mailto:') $email = substr($email, 7);
903
904 $result = $aclPlugin->principalSearch(
905 ['{http://sabredav.org/ns}email-address' => $email],
906 [
907 '{DAV:}principal-URL',
908 $caldavNS . 'calendar-home-set',
909 $caldavNS . 'schedule-inbox-URL',
910 '{http://sabredav.org/ns}email-address',
911
912 ]
913 );
914
915 if (!count($result)) {
916 return [
917 'request-status' => '3.7;Could not find principal',
918 'href' => 'mailto:' . $email,
919 ];
920 }
921
922 if (!isset($result[0][200][$caldavNS . 'calendar-home-set'])) {
923 return [
924 'request-status' => '3.7;No calendar-home-set property found',
925 'href' => 'mailto:' . $email,
926 ];
927 }
928 if (!isset($result[0][200][$caldavNS . 'schedule-inbox-URL'])) {
929 return [
930 'request-status' => '3.7;No schedule-inbox-URL property found',
931 'href' => 'mailto:' . $email,
932 ];
933 }
934 $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref();
935 $inboxUrl = $result[0][200][$caldavNS . 'schedule-inbox-URL']->getHref();
936
937 // Do we have permission?
938 $aclPlugin->checkPrivileges($inboxUrl, $caldavNS . 'schedule-query-freebusy');
939
940 // Grabbing the calendar list
941 $objects = [];
942 $calendarTimeZone = new DateTimeZone('UTC');
943
944 foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) {
945 if (!$node instanceof ICalendar) {
946 continue;
947 }
948
949 $sct = $caldavNS . 'schedule-calendar-transp';
950 $ctz = $caldavNS . 'calendar-timezone';
951 $props = $node->getProperties([$sct, $ctz]);
952
953 if (isset($props[$sct]) && $props[$sct]->getValue() == ScheduleCalendarTransp::TRANSPARENT) {
954 // If a calendar is marked as 'transparent', it means we must
955 // ignore it for free-busy purposes.
956 continue;
957 }
958
959 if (isset($props[$ctz])) {
960 $vtimezoneObj = VObject\Reader::read($props[$ctz]);
961 $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
962
963 // Destroy circular references so PHP can garbage collect the object.
964 $vtimezoneObj->destroy();
965
966 }
967
968 // Getting the list of object uris within the time-range
969 $urls = $node->calendarQuery([
970 'name' => 'VCALENDAR',
971 'comp-filters' => [
972 [
973 'name' => 'VEVENT',
974 'comp-filters' => [],
975 'prop-filters' => [],
976 'is-not-defined' => false,
977 'time-range' => [
978 'start' => $start,
979 'end' => $end,
980 ],
981 ],
982 ],
983 'prop-filters' => [],
984 'is-not-defined' => false,
985 'time-range' => null,
986 ]);
987
988 $calObjects = array_map(function($url) use ($node) {
989 $obj = $node->getChild($url)->get();
990 return $obj;
991 }, $urls);
992
993 $objects = array_merge($objects, $calObjects);
994
995 }
996
997 $inboxProps = $this->server->getProperties(
998 $inboxUrl,
999 $caldavNS . 'calendar-availability'
1000 );
1001
1002 $vcalendar = new VObject\Component\VCalendar();
1003 $vcalendar->METHOD = 'REPLY';
1004
1005 $generator = new VObject\FreeBusyGenerator();
1006 $generator->setObjects($objects);
1007 $generator->setTimeRange($start, $end);
1008 $generator->setBaseObject($vcalendar);
1009 $generator->setTimeZone($calendarTimeZone);
1010
1011 if ($inboxProps) {
1012 $generator->setVAvailability(
1013 VObject\Reader::read(
1014 $inboxProps[$caldavNS . 'calendar-availability']
1015 )
1016 );
1017 }
1018
1019 $result = $generator->getResult();
1020
1021 $vcalendar->VFREEBUSY->ATTENDEE = 'mailto:' . $email;
1022 $vcalendar->VFREEBUSY->UID = (string)$request->VFREEBUSY->UID;
1023 $vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER;
1024
1025 return [
1026 'calendar-data' => $result,
1027 'request-status' => '2.0;Success',
1028 'href' => 'mailto:' . $email,
1029 ];
1030 }
1031
1040
1041 $scheduleReply = $request->getHeader('Schedule-Reply');
1042 return $scheduleReply !== 'F';
1043
1044 }
1045
1057 function getPluginInfo() {
1058
1059 return [
1060 'name' => $this->getPluginName(),
1061 'description' => 'Adds calendar-auto-schedule, as defined in rfc6638',
1062 'link' => 'http://sabre.io/dav/scheduling/',
1063 ];
1064
1065 }
1066}
$result
while(false !==( $line=fgets( $in))) if(! $columns) $ignore
Definition: Utf8Test.php:63
$path
Definition: aliased.php:25
foreach($paths as $path) $request
Definition: asyncclient.php:32
$caldavPlugin
$aclPlugin
An exception for terminatinating execution or to throw for unit testing.
CalDAV plugin.
Definition: Plugin.php:28
const NS_CALDAV
This is the official CalDAV namespace.
Definition: Plugin.php:33
getPluginName()
Returns a plugin name.
Definition: Plugin.php:135
initialize(Server $server)
Initializes the plugin.
Definition: Plugin.php:101
deliver(ITip\Message $iTipMessage)
This method is responsible for delivering the ITip message.
Definition: Plugin.php:382
getPluginName()
Returns the name of the plugin.
Definition: Plugin.php:89
beforeUnbind($path)
This method is triggered before a file gets deleted.
Definition: Plugin.php:406
scheduleReply(RequestInterface $request)
This method checks the 'Schedule-Reply' header and returns false if it's 'F', otherwise true.
Definition: Plugin.php:1039
getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
This method is triggered whenever a subsystem requests the privileges that are supported on a particu...
Definition: Plugin.php:584
calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew)
This method is triggered whenever there was a calendar object gets created or updated.
Definition: Plugin.php:348
scheduleLocalDelivery(ITip\Message $iTipMessage)
Event handler for the 'schedule' event.
Definition: Plugin.php:444
getAddressesForPrincipal($principal)
Returns a list of addresses that are associated with a principal.
Definition: Plugin.php:699
httpPost(RequestInterface $request, ResponseInterface $response)
This method handles POST request for the outbox.
Definition: Plugin.php:167
getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request)
Returns free-busy information for a specific address.
Definition: Plugin.php:897
propPatch($path, PropPatch $propPatch)
This method is called during property updates.
Definition: Plugin.php:320
getPluginInfo()
Returns a bunch of meta-data about the plugin.
Definition: Plugin.php:1057
processICalendarChange($oldObject=null, VCalendar $newObject, array $addresses, array $ignore=[], &$modified=false)
This method looks at an old iCalendar object, a new iCalendar object and starts sending scheduling me...
Definition: Plugin.php:652
propFind(PropFind $propFind, INode $node)
This method handler is invoked during fetching of properties.
Definition: Plugin.php:204
outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response)
This method handles POST requests to the schedule-outbox.
Definition: Plugin.php:734
getFeatures()
Returns a list of features for the DAV: HTTP header.
Definition: Plugin.php:75
getHTTPMethods($uri)
Use this method to tell the server this plugin defines additional HTTP methods.
Definition: Plugin.php:144
handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response)
This method is responsible for parsing a free-busy query request and returning it's result.
Definition: Plugin.php:798
This class holds all the information about a PROPFIND request.
Definition: PropFind.php:11
set($propertyName, $value, $status=null)
Sets the value of the property.
Definition: PropFind.php:121
getPath()
Returns the path this PROPFIND request is for.
Definition: PropFind.php:187
handle($propertyName, $valueOrCallBack)
Handles a specific property.
Definition: PropFind.php:94
This class represents a set of properties that are going to be updated.
Definition: PropPatch.php:20
handle($properties, callable $callback)
Call this function if you wish to handle updating certain properties.
Definition: PropPatch.php:87
The baseclass for all server plugins.
Main DAV server class.
Definition: Server.php:23
static getUUID()
Returns a pseudo-random v4 UUID.
Definition: UUIDUtil.php:26
This is the abstract base class for both the Request and Response objects.
Definition: Message.php:14
The VCalendar component.
Definition: VCalendar.php:23
This class helps with generating FREEBUSY reports based on existing sets of objects.
The ITip\Broker class is a utility class that helps with processing so-called iTip messages.
Definition: Broker.php:38
This class represents an iTip message.
Definition: Message.php:17
Exception thrown by Reader if an invalid object was attempted to be parsed.
iCalendar/vCard/jCal/jCard/xCal/xCard reader object.
Definition: Reader.php:15
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
if($err=$client->getError()) $namespace
$messages
Definition: en.php:5
CalendarObject interface.
Calendar interface.
Definition: ICalendar.php:16
Implement this interface to have a node be recognized as a CalDAV scheduling inbox.
Definition: IInbox.php:13
Implement this interface to have a node be recognized as a CalDAV scheduling outbox.
Definition: IOutbox.php:13
The SchedulingObject represents a scheduling object in the Inbox collection.
getOwner()
Returns the owner principal.
IPrincipal interface.
Definition: IPrincipal.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.
if( $orgName !==null) if($spconfig->hasValue('contacts')) $email
Definition: metadata.php:201
catch(Exception $e) $message
if( $path[strlen( $path) - 1]==='/') if(is_dir($path)) if(!file_exists( $path)) if(preg_match('#\.php$#D', mb_strtolower($path, 'UTF-8'))) $contentType
Definition: module.php:144
$url
$response
$results
Definition: svg-scanner.php:47
$start
Definition: bench.php:8