ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Sabre\CalDAV\Schedule\Plugin Class Reference
+ Inheritance diagram for Sabre\CalDAV\Schedule\Plugin:
+ Collaboration diagram for Sabre\CalDAV\Schedule\Plugin:

Public Member Functions

 getFeatures ()
 Returns a list of features for the DAV: HTTP header. More...
 
 getPluginName ()
 Returns the name of the plugin. More...
 
 initialize (Server $server)
 Initializes the plugin. More...
 
 getHTTPMethods ($uri)
 Use this method to tell the server this plugin defines additional HTTP methods. More...
 
 httpPost (RequestInterface $request, ResponseInterface $response)
 This method handles POST request for the outbox. More...
 
 propFind (PropFind $propFind, INode $node)
 This method handler is invoked during fetching of properties. More...
 
 propPatch ($path, PropPatch $propPatch)
 This method is called during property updates. More...
 
 calendarObjectChange (RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew)
 This method is triggered whenever there was a calendar object gets created or updated. More...
 
 deliver (ITip\Message $iTipMessage)
 This method is responsible for delivering the ITip message. More...
 
 beforeUnbind ($path)
 This method is triggered before a file gets deleted. More...
 
 scheduleLocalDelivery (ITip\Message $iTipMessage)
 Event handler for the 'schedule' event. More...
 
 getSupportedPrivilegeSet (INode $node, array &$supportedPrivilegeSet)
 This method is triggered whenever a subsystem requests the privileges that are supported on a particular node. More...
 
 outboxRequest (IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response)
 This method handles POST requests to the schedule-outbox. More...
 
 getPluginInfo ()
 Returns a bunch of meta-data about the plugin. More...
 
- Public Member Functions inherited from Sabre\DAV\ServerPlugin
 initialize (Server $server)
 This initializes the plugin. More...
 
 getFeatures ()
 This method should return a list of server-features. More...
 
 getHTTPMethods ($path)
 Use this method to tell the server this plugin defines additional HTTP methods. More...
 
 getPluginName ()
 Returns a plugin name. More...
 
 getSupportedReportSet ($uri)
 Returns a list of reports this plugin supports. More...
 
 getPluginInfo ()
 Returns a bunch of meta-data about the plugin. More...
 

Data Fields

const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav'
 This is the official CalDAV namespace. More...
 

Protected Member Functions

 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 messages based on the changes. More...
 
 getAddressesForPrincipal ($principal)
 Returns a list of addresses that are associated with a principal. More...
 
 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. More...
 
 getFreeBusyForEmail ($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request)
 Returns free-busy information for a specific address. More...
 

Protected Attributes

 $server
 

Private Member Functions

 scheduleReply (RequestInterface $request)
 This method checks the 'Schedule-Reply' header and returns false if it's 'F', otherwise true. More...
 

Detailed Description

CalDAV scheduling plugin.

This plugin provides the functionality added by the "Scheduling Extensions to CalDAV" standard, as defined in RFC6638.

calendar-auto-schedule largely works by intercepting a users request to update their local calendar. If a user creates a new event with attendees, this plugin is supposed to grab the information from that event, and notify the attendees of this.

There's 3 possible transports for this:

  • local delivery
  • delivery through email (iMip)
  • server-to-server delivery (iSchedule)

iMip is simply, because we just need to add the iTip message as an email attachment. Local delivery is harder, because we both need to add this same message to a local DAV inbox, as well as live-update the relevant events.

iSchedule is something for later.

Author
Evert Pot (http://evertpot.com/) @license http://sabre.io/license/ Modified BSD License

Definition at line 56 of file Plugin.php.

Member Function Documentation

◆ beforeUnbind()

Sabre\CalDAV\Schedule\Plugin::beforeUnbind (   $path)

This method is triggered before a file gets deleted.

We use this event to make sure that when this happens, attendees get cancellations, and organizers get 'DECLINED' statuses.

Parameters
string$path
Returns
void

Definition at line 406 of file Plugin.php.

406 {
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 }
$path
Definition: aliased.php:25
deliver(ITip\Message $iTipMessage)
This method is responsible for delivering the ITip message.
Definition: Plugin.php:382
scheduleReply(RequestInterface $request)
This method checks the 'Schedule-Reply' header and returns false if it's 'F', otherwise true.
Definition: Plugin.php:1039
getAddressesForPrincipal($principal)
Returns a list of addresses that are associated with a principal.
Definition: Plugin.php:699
$messages
Definition: en.php:5
catch(Exception $e) $message

References $message, $messages, $path, Sabre\CalDAV\Schedule\Plugin\deliver(), Sabre\CalDAV\Schedule\Plugin\getAddressesForPrincipal(), and Sabre\CalDAV\Schedule\Plugin\scheduleReply().

+ Here is the call graph for this function:

◆ calendarObjectChange()

Sabre\CalDAV\Schedule\Plugin::calendarObjectChange ( RequestInterface  $request,
ResponseInterface  $response,
VCalendar  $vCal,
  $calendarPath,
$modified,
  $isNew 
)

This method is triggered whenever there was a calendar object gets created or updated.

Parameters
RequestInterface$requestHTTP request
ResponseInterface$responseHTTP Response
VCalendar$vCalParsed iCalendar object
mixed$calendarPathPath to calendar collection
mixed$modifiedThe iCalendar object has been touched.
mixed$isNewWhether this was a new item or we're updating one
Returns
void

Definition at line 348 of file Plugin.php.

348 {
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 }
foreach($paths as $path) $request
Definition: asyncclient.php:32
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
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
Definition: Reader.php:42

References $request, Sabre\CalDAV\Schedule\Plugin\getAddressesForPrincipal(), Sabre\CalDAV\Schedule\Plugin\processICalendarChange(), Sabre\VObject\Reader\read(), and Sabre\CalDAV\Schedule\Plugin\scheduleReply().

+ Here is the call graph for this function:

◆ deliver()

Sabre\CalDAV\Schedule\Plugin::deliver ( ITip\Message  $iTipMessage)

This method is responsible for delivering the ITip message.

Parameters
ITip\Message$iTipMessage
Returns
void

Definition at line 382 of file Plugin.php.

382 {
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 }

Referenced by Sabre\CalDAV\Schedule\Plugin\beforeUnbind(), and Sabre\CalDAV\Schedule\Plugin\processICalendarChange().

+ Here is the caller graph for this function:

◆ getAddressesForPrincipal()

Sabre\CalDAV\Schedule\Plugin::getAddressesForPrincipal (   $principal)
protected

Returns a list of addresses that are associated with a principal.

Parameters
string$principal
Returns
array

Definition at line 699 of file Plugin.php.

699 {
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 }

Referenced by Sabre\CalDAV\Schedule\Plugin\beforeUnbind(), and Sabre\CalDAV\Schedule\Plugin\calendarObjectChange().

+ Here is the caller graph for this function:

◆ getFeatures()

Sabre\CalDAV\Schedule\Plugin::getFeatures ( )

Returns a list of features for the DAV: HTTP header.

Returns
array

Reimplemented from Sabre\DAV\ServerPlugin.

Definition at line 75 of file Plugin.php.

75 {
76
77 return ['calendar-auto-schedule', 'calendar-availability'];
78
79 }

◆ getFreeBusyForEmail()

Sabre\CalDAV\Schedule\Plugin::getFreeBusyForEmail (   $email,
\DateTimeInterface  $start,
\DateTimeInterface  $end,
VObject\Component  $request 
)
protected

Returns free-busy information for a specific address.

The returned data is an array containing the following properties:

calendar-data : A VFREEBUSY VObject request-status : an iTip status code. href: The principal's email address, as requested

The following request status codes may be returned:

  • 2.0;description
  • 3.7;description
Parameters
string$emailaddress
\DateTimeInterface$start
\DateTimeInterface$end
VObject\Component$request
Returns
array

Definition at line 897 of file Plugin.php.

897 {
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 }
$result
$aclPlugin
$urls
Definition: croninfo.php:28
if( $orgName !==null) if($spconfig->hasValue('contacts')) $email
Definition: metadata.php:201
$url
$start
Definition: bench.php:8

References $aclPlugin, $email, $end, $request, $result, $start, $url, $urls, Sabre\VObject\Reader\read(), and Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp\TRANSPARENT.

Referenced by Sabre\CalDAV\Schedule\Plugin\handleFreeBusyRequest().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ getHTTPMethods()

Sabre\CalDAV\Schedule\Plugin::getHTTPMethods (   $uri)

Use this method to tell the server this plugin defines additional HTTP methods.

This method is passed a uri. It should only return HTTP methods that are available for the specified uri.

Parameters
string$uri
Returns
array

Reimplemented from Sabre\DAV\ServerPlugin.

Definition at line 144 of file Plugin.php.

144 {
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 }

◆ getPluginInfo()

Sabre\CalDAV\Schedule\Plugin::getPluginInfo ( )

Returns a bunch of meta-data about the plugin.

Providing this information is optional, and is mainly displayed by the Browser plugin.

The description key in the returned array may contain html and will not be sanitized.

Returns
array

Reimplemented from Sabre\DAV\ServerPlugin.

Definition at line 1057 of file Plugin.php.

1057 {
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 }
getPluginName()
Returns a plugin name.
Definition: Plugin.php:135

References Sabre\CalDAV\Plugin\getPluginName().

+ Here is the call graph for this function:

◆ getPluginName()

Sabre\CalDAV\Schedule\Plugin::getPluginName ( )

Returns the name of the plugin.

Using this name other plugins will be able to access other plugins using Server::getPlugin

Returns
string

Reimplemented from Sabre\DAV\ServerPlugin.

Definition at line 89 of file Plugin.php.

89 {
90
91 return 'caldav-schedule';
92
93 }

◆ getSupportedPrivilegeSet()

Sabre\CalDAV\Schedule\Plugin::getSupportedPrivilegeSet ( INode  $node,
array &  $supportedPrivilegeSet 
)

This method is triggered whenever a subsystem requests the privileges that are supported on a particular node.

We need to add a number of privileges for scheduling purposes.

Parameters
INode$node
array$supportedPrivilegeSet

Definition at line 584 of file Plugin.php.

584 {
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 }

◆ handleFreeBusyRequest()

Sabre\CalDAV\Schedule\Plugin::handleFreeBusyRequest ( IOutbox  $outbox,
VObject\Component  $vObject,
RequestInterface  $request,
ResponseInterface  $response 
)
protected

This method is responsible for parsing a free-busy query request and returning it's result.

Parameters
IOutbox$outbox
VObject\Component$vObject
RequestInterface$request
ResponseInterface$response
Returns
string

Definition at line 798 of file Plugin.php.

798 {
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 }
getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request)
Returns free-busy information for a specific address.
Definition: Plugin.php:897
if($err=$client->getError()) $namespace
$response
$results
Definition: svg-scanner.php:47

References $namespace, $response, $result, $results, Sabre\CalDAV\Schedule\Plugin\getFreeBusyForEmail(), and Sabre\DAVACL\IACL\getOwner().

Referenced by Sabre\CalDAV\Schedule\Plugin\outboxRequest().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ httpPost()

Sabre\CalDAV\Schedule\Plugin::httpPost ( RequestInterface  $request,
ResponseInterface  $response 
)

This method handles POST request for the outbox.

Parameters
RequestInterface$request
ResponseInterface$response
Returns
bool

Definition at line 167 of file Plugin.php.

167 {
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 }
outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response)
This method handles POST requests to the schedule-outbox.
Definition: Plugin.php:734
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

References $contentType, $path, $request, $response, and Sabre\CalDAV\Schedule\Plugin\outboxRequest().

+ Here is the call graph for this function:

◆ initialize()

Sabre\CalDAV\Schedule\Plugin::initialize ( Server  $server)

Initializes the plugin.

Parameters
Server$server
Returns
void

This information ensures that the {DAV:}resourcetype property has the correct values.

Properties we protect are made read-only by the server.

Reimplemented from Sabre\DAV\ServerPlugin.

Definition at line 101 of file Plugin.php.

101 {
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 }

References Sabre\CalDAV\Plugin\$server.

◆ outboxRequest()

Sabre\CalDAV\Schedule\Plugin::outboxRequest ( IOutbox  $outboxNode,
RequestInterface  $request,
ResponseInterface  $response 
)

This method handles POST requests to the schedule-outbox.

Currently, two types of requests are supported:

  • FREEBUSY requests from RFC 6638
  • Simple iTIP messages from draft-desruisseaux-caldav-sched-04

The latter is from an expired early draft of the CalDAV scheduling extensions, but iCal depends on a feature from that spec, so we implement it.

Parameters
IOutbox$outboxNode
RequestInterface$request
ResponseInterface$response
Returns
void

Definition at line 734 of file Plugin.php.

734 {
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 }
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

References $request, $response, Sabre\CalDAV\Schedule\Plugin\handleFreeBusyRequest(), and Sabre\VObject\Reader\read().

Referenced by Sabre\CalDAV\Schedule\Plugin\httpPost().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ processICalendarChange()

Sabre\CalDAV\Schedule\Plugin::processICalendarChange (   $oldObject = null,
VCalendar  $newObject,
array  $addresses,
array  $ignore = [],
$modified = false 
)
protected

This method looks at an old iCalendar object, a new iCalendar object and starts sending scheduling messages based on the changes.

A list of addresses needs to be specified, so the system knows who made the update, because the behavior may be different based on if it's an attendee or an organizer.

This method may update $newObject to add any status changes.

Parameters
VCalendar | string$oldObject
VCalendar$newObject
array$addresses
array$ignoreAny addresses to not send messages to.
bool$modifiedA marker to indicate that the original object modified by this process.
Returns
void

Definition at line 652 of file Plugin.php.

652 {
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 }
while(false !==( $line=fgets( $in))) if(! $columns) $ignore
Definition: Utf8Test.php:63

References $ignore, $message, $messages, and Sabre\CalDAV\Schedule\Plugin\deliver().

Referenced by Sabre\CalDAV\Schedule\Plugin\calendarObjectChange(), and Sabre\CalDAV\Schedule\Plugin\scheduleLocalDelivery().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ propFind()

Sabre\CalDAV\Schedule\Plugin::propFind ( PropFind  $propFind,
INode  $node 
)

This method handler is invoked during fetching of properties.

We use this event to add calendar-auto-schedule-specific properties.

Parameters
PropFind$propFind
INode$node
Returns
void

Definition at line 204 of file Plugin.php.

204 {
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 }
$caldavPlugin

References $caldavPlugin, $result, Sabre\DAV\PropFind\getPath(), Sabre\DAV\PropFind\handle(), and Sabre\DAV\PropFind\set().

+ Here is the call graph for this function:

◆ propPatch()

Sabre\CalDAV\Schedule\Plugin::propPatch (   $path,
PropPatch  $propPatch 
)

This method is called during property updates.

Parameters
string$path
PropPatch$propPatch
Returns
void

Definition at line 320 of file Plugin.php.

320 {
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 }

References $path, and Sabre\DAV\PropPatch\handle().

+ Here is the call graph for this function:

◆ scheduleLocalDelivery()

Sabre\CalDAV\Schedule\Plugin::scheduleLocalDelivery ( ITip\Message  $iTipMessage)

Event handler for the 'schedule' event.

This handler attempts to look at local accounts to deliver the scheduling object.

Parameters
ITip\Message$iTipMessage
Returns
void

Definition at line 444 of file Plugin.php.

444 {
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 }
static getUUID()
Returns a pseudo-random v4 UUID.
Definition: UUIDUtil.php:26

References $aclPlugin, $calendar, $result, Sabre\DAV\UUIDUtil\getUUID(), Sabre\CalDAV\Schedule\Plugin\processICalendarChange(), and Sabre\VObject\Reader\read().

+ Here is the call graph for this function:

◆ scheduleReply()

Sabre\CalDAV\Schedule\Plugin::scheduleReply ( RequestInterface  $request)
private

This method checks the 'Schedule-Reply' header and returns false if it's 'F', otherwise true.

Parameters
RequestInterface$request
Returns
bool

Definition at line 1039 of file Plugin.php.

1039 {
1040
1041 $scheduleReply = $request->getHeader('Schedule-Reply');
1042 return $scheduleReply !== 'F';
1043
1044 }

References $request.

Referenced by Sabre\CalDAV\Schedule\Plugin\beforeUnbind(), and Sabre\CalDAV\Schedule\Plugin\calendarObjectChange().

+ Here is the caller graph for this function:

Field Documentation

◆ $server

Sabre\CalDAV\Schedule\Plugin::$server
protected

Definition at line 68 of file Plugin.php.

◆ NS_CALDAV

const Sabre\CalDAV\Schedule\Plugin::NS_CALDAV = 'urn:ietf:params:xml:ns:caldav'

This is the official CalDAV namespace.

Definition at line 61 of file Plugin.php.


The documentation for this class was generated from the following file: