ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Sabre\CalDAV\Schedule\Plugin Class Reference

CalDAV scheduling plugin. More...

+ 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/) 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.

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

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
$messages
Definition: en.php:5
catch(Exception $e) $message
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
+ 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.

References Sabre\CalDAV\Schedule\Plugin\getAddressesForPrincipal(), Sabre\HTTP\RequestInterface\getPath(), Sabre\CalDAV\Schedule\Plugin\processICalendarChange(), Sabre\VObject\Reader\read(), and Sabre\CalDAV\Schedule\Plugin\scheduleReply().

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

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

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  }
+ 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.

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

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  }
+ 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

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.

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

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

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  }
if($orgName !==null) if($spconfig->hasValue('contacts')) $email
Definition: metadata.php:201
$result
foreach($paths as $path) $request
Definition: asyncclient.php:32
$start
Definition: bench.php:8
$urls
Definition: croninfo.php:28
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
Definition: Reader.php:42
$url
$aclPlugin
+ 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

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

Definition at line 1057 of file Plugin.php.

References Sabre\CalDAV\Plugin\getPluginName().

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
+ 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

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.

References $namespace, $result, $results, Sabre\CalDAV\Schedule\Plugin\getFreeBusyForEmail(), Sabre\DAVACL\IACL\getOwner(), Sabre\HTTP\MessageInterface\setBody(), Sabre\HTTP\MessageInterface\setHeader(), and Sabre\HTTP\ResponseInterface\setStatus().

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

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

References $contentType, $path, Sabre\HTTP\MessageInterface\getHeader(), Sabre\HTTP\RequestInterface\getPath(), and Sabre\CalDAV\Schedule\Plugin\outboxRequest().

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  }
$path
Definition: aliased.php:25
foreach($paths as $path) $request
Definition: asyncclient.php:32
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
$response
+ 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.

Definition at line 101 of file Plugin.php.

References Sabre\CalDAV\Plugin\$server, and Sabre\Event\EventEmitterInterface\on().

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  }
+ Here is the call graph for this function:

◆ 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.

References Sabre\HTTP\MessageInterface\getBody(), Sabre\HTTP\RequestInterface\getPath(), Sabre\CalDAV\Schedule\Plugin\handleFreeBusyRequest(), and Sabre\VObject\Reader\read().

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

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

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

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

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  }
deliver(ITip\Message $iTipMessage)
This method is responsible for delivering the ITip message.
Definition: Plugin.php:382
$messages
Definition: en.php:5
catch(Exception $e) $message
while(false !==($line=fgets($in))) if(! $columns) $ignore
Definition: Utf8Test.php:63
+ 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.

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

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  }
$result
$caldavPlugin
+ 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.

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

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  }
$path
Definition: aliased.php:25
+ 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.

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

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') {
562  $this->processICalendarChange(
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  }
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
$result
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
Definition: Reader.php:42
static getUUID()
Returns a pseudo-random v4 UUID.
Definition: UUIDUtil.php:26
$aclPlugin
+ 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.

References Sabre\HTTP\MessageInterface\getHeader().

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

1039  {
1040 
1041  $scheduleReply = $request->getHeader('Schedule-Reply');
1042  return $scheduleReply !== 'F';
1043 
1044  }
foreach($paths as $path) $request
Definition: asyncclient.php:32
+ Here is the call graph for this function:
+ 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: