77        return [
'calendar-auto-schedule', 
'calendar-availability'];
 
   91        return 'caldav-schedule';
 
  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']);
 
  112        $ns = 
'{' . self::NS_CALDAV . 
'}';
 
  118        $server->resourceTypeMapping[
'\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns . 
'schedule-outbox';
 
  119        $server->resourceTypeMapping[
'\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns . 
'schedule-inbox';
 
  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' 
  147            $node = $this->server->tree->getNodeForPath($uri);
 
  152        if ($node instanceof 
IOutbox) {
 
  179            $node = $this->server->tree->getNodeForPath(
$path);
 
  186        $this->server->transactionType = 
'post-caldav-outbox';
 
  209            $principalUrl = $node->getPrincipalUrl();
 
  212            $propFind->
handle(
'{' . self::NS_CALDAV . 
'}schedule-outbox-URL', 
function() use ($principalUrl, 
$caldavPlugin) {
 
  214                $calendarHomePath = 
$caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
 
  215                if (!$calendarHomePath) {
 
  218                $outboxPath = $calendarHomePath . 
'/outbox/';
 
  224            $propFind->
handle(
'{' . self::NS_CALDAV . 
'}schedule-inbox-URL', 
function() use ($principalUrl, 
$caldavPlugin) {
 
  226                $calendarHomePath = 
$caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
 
  227                if (!$calendarHomePath) {
 
  230                $inboxPath = $calendarHomePath . 
'/inbox/';
 
  236            $propFind->
handle(
'{' . self::NS_CALDAV . 
'}schedule-default-calendar-URL', 
function() use ($principalUrl, 
$caldavPlugin) {
 
  240                $calendarHomePath = 
$caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
 
  242                if (!$calendarHomePath) {
 
  246                $sccs = 
'{' . self::NS_CALDAV . 
'}supported-calendar-component-set';
 
  248                $result = $this->server->getPropertiesForPath($calendarHomePath, [
 
  249                    '{DAV:}resourcetype',
 
  250                    '{DAV:}share-access',
 
  255                    if (!isset($child[200][
'{DAV:}resourcetype']) || !$child[200][
'{DAV:}resourcetype']->is(
'{' . self::NS_CALDAV . 
'}calendar')) {
 
  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) {
 
  268                    if (!isset($child[200][$sccs]) || in_array(
'VEVENT', $child[200][$sccs]->getValue())) {
 
  279            $propFind->
handle(
'{' . self::NS_CALDAV . 
'}calendar-user-type', 
function() {
 
  288        $propFind->
handle(
'{http://calendarserver.org/ns/}calendar-availability', 
function() use ($propFind, $node) {
 
  292             $availProp = 
'{' . self::NS_CALDAV . 
'}calendar-availability';
 
  298             $this->server->getPropertiesByNode(
 
  304                 '{http://calendarserver.org/ns/}calendar-availability',
 
  305                 $subPropFind->get($availProp),
 
  306                 $subPropFind->getStatus($availProp)
 
  323        $propPatch->
handle(
'{http://calendarserver.org/ns/}calendar-availability', 
function($value) use (
$path) {
 
  325            $availProp = 
'{' . self::NS_CALDAV . 
'}calendar-availability';
 
  326            $subPropPatch = 
new PropPatch([$availProp => $value]);
 
  327            $this->server->emit(
'propPatch', [
$path, $subPropPatch]);
 
  328            $subPropPatch->commit();
 
  330            return $subPropPatch->getResult()[$availProp];
 
  354        $calendarNode = $this->server->tree->getNodeForPath($calendarPath);
 
  357            $calendarNode->getOwner()
 
  361            $node = $this->server->tree->getNodeForPath(
$request->getPath());
 
  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';
 
  390        list($baseCode) = explode(
'.', $iTipMessage->scheduleStatus);
 
  391        if (!$iTipMessage->significantChange && in_array($baseCode, [
'3', 
'5'])) {
 
  392            $iTipMessage->scheduleStatus = 
null;
 
  410        if ($this->server->httpRequest->getMethod() === 
'MOVE') 
return;
 
  412        $node = $this->server->tree->getNodeForPath(
$path);
 
  427        $messages = $broker->parseEvent(
null, $addresses, $node->get());
 
  453        $caldavNS = 
'{' . self::NS_CALDAV . 
'}';
 
  455        $principalUri = 
$aclPlugin->getPrincipalByUri($iTipMessage->recipient);
 
  456        if (!$principalUri) {
 
  457            $iTipMessage->scheduleStatus = 
'3.7;Could not find principal.';
 
  467        $this->server->removeListener(
'propFind', [
$aclPlugin, 
'propFind']);
 
  469        $result = $this->server->getProperties(
 
  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',
 
  481        $this->server->on(
'propFind', [
$aclPlugin, 
'propFind'], 20);
 
  483        if (!isset(
$result[$caldavNS . 
'schedule-inbox-URL'])) {
 
  484            $iTipMessage->scheduleStatus = 
'5.2;Could not find local inbox';
 
  487        if (!isset(
$result[$caldavNS . 
'calendar-home-set'])) {
 
  488            $iTipMessage->scheduleStatus = 
'5.2;Could not locate a calendar-home-set';
 
  491        if (!isset(
$result[$caldavNS . 
'schedule-default-calendar-URL'])) {
 
  492            $iTipMessage->scheduleStatus = 
'5.2;Could not find a schedule-default-calendar-URL property';
 
  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();
 
  500        if ($iTipMessage->method === 
'REPLY') {
 
  501            $privilege = 
'schedule-deliver-reply';
 
  503            $privilege = 
'schedule-deliver-invite';
 
  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.';
 
  513        $uid = $iTipMessage->uid;
 
  517        $home = $this->server->tree->getNodeForPath($homePath);
 
  518        $inbox = $this->server->tree->getNodeForPath($inboxPath);
 
  520        $currentObject = 
null;
 
  524        $result = $home->getCalendarObjectByUID($uid);
 
  527            $objectPath = $homePath . 
'/' . 
$result;
 
  528            $objectNode = $this->server->tree->getNodeForPath($objectPath);
 
  529            $oldICalendarData = $objectNode->get();
 
  536        $newObject = $broker->processMessage($iTipMessage, $currentObject);
 
  538        $inbox->createFile($newFileName, $iTipMessage->message->serialize());
 
  547            $iTipMessage->scheduleStatus = 
'5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';
 
  555            $calendar = $this->server->tree->getNodeForPath($calendarPath);
 
  556            $calendar->createFile($newFileName, $newObject->serialize());
 
  561            if ($iTipMessage->method === 
'REPLY') {
 
  565                    [$iTipMessage->recipient],
 
  566                    [$iTipMessage->sender]
 
  569            $objectNode->put($newObject->serialize());
 
  571        $iTipMessage->scheduleStatus = 
'1.2;Message delivered locally';
 
  586        $ns = 
'{' . self::NS_CALDAV . 
'}';
 
  587        if ($node instanceof 
IOutbox) {
 
  588            $supportedPrivilegeSet[$ns . 
'schedule-send'] = [
 
  591                    $ns . 
'schedule-send-invite' => [
 
  595                    $ns . 
'schedule-send-reply' => [
 
  599                    $ns . 
'schedule-send-freebusy' => [
 
  605                    $ns . 
'schedule-post-vevent' => [
 
  612        if ($node instanceof 
IInbox) {
 
  613            $supportedPrivilegeSet[$ns . 
'schedule-deliver'] = [
 
  616                    $ns . 
'schedule-deliver-invite' => [
 
  620                    $ns . 
'schedule-deliver-reply' => [
 
  624                    $ns . 
'schedule-query-freebusy' => [
 
  655        $messages = $broker->parseEvent($newObject, $addresses, $oldObject);
 
  667            if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === 
$message->recipient)) {
 
  669                    $newObject->VEVENT->ORGANIZER[
'SCHEDULE-STATUS'] = 
$message->getScheduleStatus();
 
  671                unset($newObject->VEVENT->ORGANIZER[
'SCHEDULE-FORCE-SEND']);
 
  675                if (isset($newObject->VEVENT->ATTENDEE)) 
foreach ($newObject->VEVENT->ATTENDEE as $attendee) {
 
  677                    if ($attendee->getNormalizedValue() === 
$message->recipient) {
 
  679                            $attendee[
'SCHEDULE-STATUS'] = 
$message->getScheduleStatus();
 
  681                        unset($attendee[
'SCHEDULE-FORCE-SEND']);
 
  701        $CUAS = 
'{' . self::NS_CALDAV . 
'}calendar-user-address-set';
 
  703        $properties = $this->server->getProperties(
 
  709        if (!isset($properties[$CUAS])) {
 
  713        $addresses = $properties[$CUAS]->getHrefs();
 
  742            throw new BadRequest(
'The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
 
  748        $componentType = 
null;
 
  749        foreach ($vObject->getComponents() as $component) {
 
  750            if ($component->name !== 
'VTIMEZONE') {
 
  751                $componentType = $component->name;
 
  755        if (is_null($componentType)) {
 
  756            throw new BadRequest(
'We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
 
  760        $method = strtoupper((
string)$vObject->METHOD);
 
  762            throw new BadRequest(
'A METHOD property must be specified in iTIP messages');
 
  769        $acl = $this->server->getPlugin(
'acl');
 
  771        if ($componentType === 
'VFREEBUSY' && $method === 
'REQUEST') {
 
  773            $acl && $acl->checkPrivileges($outboxPath, 
'{' . self::NS_CALDAV . 
'}schedule-send-freebusy');
 
  782            throw new NotImplemented(
'We only support VFREEBUSY (REQUEST) on this endpoint');
 
  800        $vFreeBusy = $vObject->VFREEBUSY;
 
  801        $organizer = $vFreeBusy->ORGANIZER;
 
  803        $organizer = (string)$organizer;
 
  808        $caldavNS = 
'{' . self::NS_CALDAV . 
'}';
 
  810        $uas = $caldavNS . 
'calendar-user-address-set';
 
  811        $props = $this->server->getProperties($owner, [$uas]);
 
  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');
 
  817        if (!isset($vFreeBusy->ATTENDEE)) {
 
  818            throw new BadRequest(
'You must at least specify 1 attendee');
 
  822        foreach ($vFreeBusy->ATTENDEE as $attendee) {
 
  823            $attendees[] = (string)$attendee;
 
  827        if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) {
 
  828            throw new BadRequest(
'DTSTART and DTEND must both be specified');
 
  831        $startRange = $vFreeBusy->DTSTART->getDateTime();
 
  832        $endRange = $vFreeBusy->DTEND->getDateTime();
 
  835        foreach ($attendees as $attendee) {
 
  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) {
 
  844            $scheduleResponse->setAttribute(
'xmlns:' . $prefix, 
$namespace);
 
  847        $dom->appendChild($scheduleResponse);
 
  850            $xresponse = $dom->createElement(
'cal:response');
 
  852            $recipient = $dom->createElement(
'cal:recipient');
 
  853            $recipientHref = $dom->createElement(
'd:href');
 
  855            $recipientHref->appendChild($dom->createTextNode(
$result[
'href']));
 
  856            $recipient->appendChild($recipientHref);
 
  857            $xresponse->appendChild($recipient);
 
  859            $reqStatus = $dom->createElement(
'cal:request-status');
 
  860            $reqStatus->appendChild($dom->createTextNode(
$result[
'request-status']));
 
  861            $xresponse->appendChild($reqStatus);
 
  863            if (isset(
$result[
'calendar-data'])) {
 
  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);
 
  870            $scheduleResponse->appendChild($xresponse);
 
  874        $response->setHeader(
'Content-Type', 
'application/xml');
 
  899        $caldavNS = 
'{' . self::NS_CALDAV . 
'}';
 
  905            [
'{http://sabredav.org/ns}email-address' => 
$email],
 
  907                '{DAV:}principal-URL',
 
  908                $caldavNS . 
'calendar-home-set',
 
  909                $caldavNS . 
'schedule-inbox-URL',
 
  910                '{http://sabredav.org/ns}email-address',
 
  917                'request-status' => 
'3.7;Could not find principal',
 
  918                'href'           => 
'mailto:' . 
$email,
 
  922        if (!isset(
$result[0][200][$caldavNS . 
'calendar-home-set'])) {
 
  924                'request-status' => 
'3.7;No calendar-home-set property found',
 
  925                'href'           => 
'mailto:' . 
$email,
 
  928        if (!isset(
$result[0][200][$caldavNS . 
'schedule-inbox-URL'])) {
 
  930                'request-status' => 
'3.7;No schedule-inbox-URL property found',
 
  931                'href'           => 
'mailto:' . 
$email,
 
  934        $homeSet = 
$result[0][200][$caldavNS . 
'calendar-home-set']->getHref();
 
  935        $inboxUrl = 
$result[0][200][$caldavNS . 
'schedule-inbox-URL']->getHref();
 
  938        $aclPlugin->checkPrivileges($inboxUrl, $caldavNS . 
'schedule-query-freebusy');
 
  942        $calendarTimeZone = 
new DateTimeZone(
'UTC');
 
  944        foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) {
 
  949            $sct = $caldavNS . 
'schedule-calendar-transp';
 
  950            $ctz = $caldavNS . 
'calendar-timezone';
 
  951            $props = $node->getProperties([$sct, $ctz]);
 
  959            if (isset($props[$ctz])) {
 
  961                $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
 
  964                $vtimezoneObj->destroy();
 
  969            $urls = $node->calendarQuery([
 
  970                'name'         => 
'VCALENDAR',
 
  974                        'comp-filters'   => [],
 
  975                        'prop-filters'   => [],
 
  976                        'is-not-defined' => 
false,
 
  983                'prop-filters'   => [],
 
  984                'is-not-defined' => 
false,
 
  985                'time-range'     => 
null,
 
  988            $calObjects = array_map(
function(
$url) use ($node) {
 
  989                $obj = $node->getChild(
$url)->
get();
 
  993            $objects = array_merge($objects, $calObjects);
 
  997        $inboxProps = $this->server->getProperties(
 
  999            $caldavNS . 
'calendar-availability' 
 1003        $vcalendar->METHOD = 
'REPLY';
 
 1006        $generator->setObjects($objects);
 
 1008        $generator->setBaseObject($vcalendar);
 
 1009        $generator->setTimeZone($calendarTimeZone);
 
 1012            $generator->setVAvailability(
 
 1014                    $inboxProps[$caldavNS . 
'calendar-availability']
 
 1019        $result = $generator->getResult();
 
 1021        $vcalendar->VFREEBUSY->ATTENDEE = 
'mailto:' . 
$email;
 
 1022        $vcalendar->VFREEBUSY->UID = (string)
$request->VFREEBUSY->UID;
 
 1023        $vcalendar->VFREEBUSY->ORGANIZER = clone 
$request->VFREEBUSY->ORGANIZER;
 
 1027            'request-status' => 
'2.0;Success',
 
 1028            'href'           => 
'mailto:' . 
$email,
 
 1041        $scheduleReply = 
$request->getHeader(
'Schedule-Reply');
 
 1042        return $scheduleReply !== 
'F';
 
 1061            'description' => 
'Adds calendar-auto-schedule, as defined in rfc6638',
 
 1062            'link'        => 
'http://sabre.io/dav/scheduling/',
 
while(false !==( $line=fgets( $in))) if(! $columns) $ignore
foreach($paths as $path) $request
An exception for terminatinating execution or to throw for unit testing.
const NS_CALDAV
This is the official CalDAV namespace.
getPluginName()
Returns a plugin name.
initialize(Server $server)
Initializes the plugin.
deliver(ITip\Message $iTipMessage)
This method is responsible for delivering the ITip message.
getPluginName()
Returns the name of the plugin.
beforeUnbind($path)
This method is triggered before a file gets deleted.
scheduleReply(RequestInterface $request)
This method checks the 'Schedule-Reply' header and returns false if it's 'F', otherwise true.
getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
This method is triggered whenever a subsystem requests the privileges that are supported on a particu...
calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew)
This method is triggered whenever there was a calendar object gets created or updated.
scheduleLocalDelivery(ITip\Message $iTipMessage)
Event handler for the 'schedule' event.
getAddressesForPrincipal($principal)
Returns a list of addresses that are associated with a principal.
httpPost(RequestInterface $request, ResponseInterface $response)
This method handles POST request for the outbox.
getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request)
Returns free-busy information for a specific address.
propPatch($path, PropPatch $propPatch)
This method is called during property updates.
getPluginInfo()
Returns a bunch of meta-data about the plugin.
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...
propFind(PropFind $propFind, INode $node)
This method handler is invoked during fetching of properties.
outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response)
This method handles POST requests to the schedule-outbox.
getFeatures()
Returns a list of features for the DAV: HTTP header.
getHTTPMethods($uri)
Use this method to tell the server this plugin defines additional HTTP methods.
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.
schedule-calendar-transp property.
This class holds all the information about a PROPFIND request.
set($propertyName, $value, $status=null)
Sets the value of the property.
getPath()
Returns the path this PROPFIND request is for.
handle($propertyName, $valueOrCallBack)
Handles a specific property.
This class represents a set of properties that are going to be updated.
handle($properties, callable $callback)
Call this function if you wish to handle updating certain properties.
The baseclass for all server plugins.
static getUUID()
Returns a pseudo-random v4 UUID.
This is the abstract base class for both the Request and Response objects.
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.
This class represents an iTip message.
Exception thrown by Reader if an invalid object was attempted to be parsed.
iCalendar/vCard/jCal/jCard/xCal/xCard reader object.
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
if($err=$client->getError()) $namespace
CalendarObject interface.
Implement this interface to have a node be recognized as a CalDAV scheduling inbox.
Implement this interface to have a node be recognized as a CalDAV scheduling outbox.
The SchedulingObject represents a scheduling object in the Inbox collection.
getOwner()
Returns the owner principal.
The INode interface is the base interface, and the parent class of both ICollection and IFile.
The RequestInterface represents a HTTP request.
This interface represents a HTTP response.
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