76 $node = $this->server->tree->getNodeForPath($parent);
80 $node->getChild(
$name);
82 return [
'MKCALENDAR'];
108 $parts = explode(
'/', trim($principalUrl,
'/'));
109 if (count($parts) !== 2)
return;
110 if ($parts[0] !==
'principals')
return;
112 return self::CALENDAR_ROOT .
'/' . $parts[1];
123 return [
'calendar-access',
'calendar-proxy'];
153 $node = $this->server->tree->getNodeForPath($uri);
157 $reports[] =
'{' . self::NS_CALDAV .
'}calendar-multiget';
158 $reports[] =
'{' . self::NS_CALDAV .
'}calendar-query';
161 $reports[] =
'{' . self::NS_CALDAV .
'}free-busy-query';
166 if ($node instanceof
CalendarHome && $this->server->getPlugin(
'sync')) {
167 $reports[] =
'{DAV:}sync-collection';
183 $server->on(
'method:MKCALENDAR', [$this,
'httpMkCalendar']);
184 $server->on(
'report', [$this,
'report']);
185 $server->on(
'propFind', [$this,
'propFind']);
186 $server->on(
'onHTMLActionsPanel', [$this,
'htmlActionsPanel']);
187 $server->on(
'beforeCreateFile', [$this,
'beforeCreateFile']);
188 $server->on(
'beforeWriteContent', [$this,
'beforeWriteContent']);
189 $server->on(
'afterMethod:GET', [$this,
'httpAfterGET']);
190 $server->on(
'getSupportedPrivilegeSet', [$this,
'getSupportedPrivilegeSet']);
192 $server->xml->namespaceMap[self::NS_CALDAV] =
'cal';
193 $server->xml->namespaceMap[self::NS_CALENDARSERVER] =
'cs';
195 $server->xml->elementMap[
'{' . self::NS_CALDAV .
'}supported-calendar-component-set'] =
'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
196 $server->xml->elementMap[
'{' . self::NS_CALDAV .
'}calendar-query'] =
'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport';
197 $server->xml->elementMap[
'{' . self::NS_CALDAV .
'}calendar-multiget'] =
'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport';
198 $server->xml->elementMap[
'{' . self::NS_CALDAV .
'}free-busy-query'] =
'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport';
199 $server->xml->elementMap[
'{' . self::NS_CALDAV .
'}mkcalendar'] =
'Sabre\\CalDAV\\Xml\\Request\\MkCalendar';
200 $server->xml->elementMap[
'{' . self::NS_CALDAV .
'}schedule-calendar-transp'] =
'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp';
201 $server->xml->elementMap[
'{' . self::NS_CALDAV .
'}supported-calendar-component-set'] =
'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
203 $server->resourceTypeMapping[
'\\Sabre\\CalDAV\\ICalendar'] =
'{urn:ietf:params:xml:ns:caldav}calendar';
205 $server->resourceTypeMapping[
'\\Sabre\\CalDAV\\Principal\\IProxyRead'] =
'{http://calendarserver.org/ns/}calendar-proxy-read';
206 $server->resourceTypeMapping[
'\\Sabre\\CalDAV\\Principal\\IProxyWrite'] =
'{http://calendarserver.org/ns/}calendar-proxy-write';
208 array_push($server->protectedProperties,
210 '{' . self::NS_CALDAV .
'}supported-calendar-component-set',
211 '{' . self::NS_CALDAV .
'}supported-calendar-data',
212 '{' . self::NS_CALDAV .
'}max-resource-size',
213 '{' . self::NS_CALDAV .
'}min-date-time',
214 '{' . self::NS_CALDAV .
'}max-date-time',
215 '{' . self::NS_CALDAV .
'}max-instances',
216 '{' . self::NS_CALDAV .
'}max-attendees-per-instance',
217 '{' . self::NS_CALDAV .
'}calendar-home-set',
218 '{' . self::NS_CALDAV .
'}supported-collation-set',
219 '{' . self::NS_CALDAV .
'}calendar-data',
222 '{' . self::NS_CALENDARSERVER .
'}getctag',
223 '{' . self::NS_CALENDARSERVER .
'}calendar-proxy-read-for',
224 '{' . self::NS_CALENDARSERVER .
'}calendar-proxy-write-for' 229 $aclPlugin->principalSearchPropertySet[
'{' . self::NS_CALDAV .
'}calendar-user-address-set'] =
'Calendar address';
243 switch ($reportName) {
244 case '{' . self::NS_CALDAV .
'}calendar-multiget' :
245 $this->server->transactionType =
'report-calendar-multiget';
248 case '{' . self::NS_CALDAV .
'}calendar-query' :
249 $this->server->transactionType =
'report-calendar-query';
252 case '{' . self::NS_CALDAV .
'}free-busy-query' :
253 $this->server->transactionType =
'report-free-busy-query';
280 $mkcalendar = $this->server->xml->expect(
281 '{urn:ietf:params:xml:ns:caldav}mkcalendar',
285 throw new BadRequest($e->getMessage(), null, $e);
287 $properties = $mkcalendar->getProperties();
297 if (isset($properties[
'{DAV:}resourcetype'])) {
298 $resourceType = $properties[
'{DAV:}resourcetype']->getValue();
300 $resourceType = [
'{DAV:}collection',
'{urn:ietf:params:xml:ns:caldav}calendar'];
303 $this->server->createCollection(
$path,
new MkCol($resourceType, $properties));
306 $response->
setHeader(
'Content-Length', 0);
325 $ns =
'{' . self::NS_CALDAV .
'}';
329 $propFind->handle($ns .
'max-resource-size', $this->maxResourceSize);
330 $propFind->handle($ns .
'supported-calendar-data',
function() {
333 $propFind->handle($ns .
'supported-collation-set',
function() {
341 $principalUrl = $node->getPrincipalUrl();
343 $propFind->handle(
'{' . self::NS_CALDAV .
'}calendar-home-set',
function() use ($principalUrl) {
346 if (is_null($calendarHomePath))
return null;
347 return new LocalHref($calendarHomePath .
'/');
352 $propFind->handle(
'{' . self::NS_CALDAV .
'}calendar-user-address-set',
function() use ($node) {
353 $addresses = $node->getAlternateUriSet();
354 $addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() .
'/';
359 $propFind->handle(
'{' . self::NS_CALENDARSERVER .
'}email-address-set',
function() use ($node) {
360 $addresses = $node->getAlternateUriSet();
362 foreach ($addresses as $address) {
363 if (substr($address, 0, 7) ===
'mailto:') {
364 $emails[] = substr($address, 7);
372 $propRead =
'{' . self::NS_CALENDARSERVER .
'}calendar-proxy-read-for';
373 $propWrite =
'{' . self::NS_CALENDARSERVER .
'}calendar-proxy-write-for';
375 if ($propFind->getStatus($propRead) === 404 || $propFind->getStatus($propWrite) === 404) {
378 $membership =
$aclPlugin->getPrincipalMembership($propFind->getPath());
382 foreach ($membership as $group) {
384 $groupNode = $this->server->tree->getNodeForPath($group);
391 if ($groupNode instanceof
Principal\IProxyRead) {
392 $readList[] = $listItem;
394 if ($groupNode instanceof
Principal\IProxyWrite) {
395 $writeList[] = $listItem;
400 $propFind->set($propRead,
new LocalHref($readList));
401 $propFind->set($propWrite,
new LocalHref($writeList));
412 $propFind->handle(
'{' . self::NS_CALDAV .
'}calendar-data',
function() use ($node) {
414 if (is_resource($val))
415 $val = stream_get_contents($val);
418 return str_replace(
"\r",
"", $val);
437 $needsJson = $report->contentType ===
'application/calendar+json';
443 [$this->server,
'calculateUri'],
447 foreach ($this->server->getPropertiesForMultiplePaths(
$paths, $report->properties) as $uri => $objProps) {
449 if (($needsJson || $report->expand) && isset($objProps[200][
'{' . self::NS_CALDAV .
'}calendar-data'])) {
452 if ($report->expand) {
456 if (!isset($timeZones[$calendarPath])) {
458 $tzProp =
'{' . self::NS_CALDAV .
'}calendar-timezone';
459 $tzResult = $this->server->getProperties($calendarPath, [$tzProp]);
460 if (isset($tzResult[$tzProp])) {
464 $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
472 $vObject = $vObject->expand($report->expand[
'start'], $report->expand[
'end'], $timeZones[$calendarPath]);
475 $objProps[200][
'{' . self::NS_CALDAV .
'}calendar-data'] = json_encode($vObject->jsonSerialize());
477 $objProps[200][
'{' . self::NS_CALDAV .
'}calendar-data'] = $vObject->serialize();
484 $propertyList[] = $objProps;
488 $prefer = $this->server->getHTTPPrefer();
490 $this->server->httpResponse->setStatus(207);
491 $this->server->httpResponse->setHeader(
'Content-Type',
'application/xml; charset=utf-8');
492 $this->server->httpResponse->setHeader(
'Vary',
'Brief,Prefer');
493 $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer[
'return'] ===
'minimal'));
508 $path = $this->server->getRequestUri();
510 $needsJson = $report->contentType ===
'application/calendar+json';
512 $node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
513 $depth = $this->server->getHTTPDepth(0);
518 $calendarTimeZone = null;
519 if ($report->expand) {
522 $tzProp =
'{' . self::NS_CALDAV .
'}calendar-timezone';
523 $tzResult = $this->server->getProperties(
$path, [$tzProp]);
524 if (isset($tzResult[$tzProp])) {
528 $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
532 $vtimezoneObj->destroy();
543 $requestedCalendarData =
true;
544 $requestedProperties = $report->properties;
546 if (!in_array(
'{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
549 $requestedProperties[] =
'{urn:ietf:params:xml:ns:caldav}calendar-data';
553 $requestedCalendarData =
false;
556 $properties = $this->server->getPropertiesForPath(
558 $requestedProperties,
564 $properties = current($properties);
568 if (isset($properties[200][
'{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
572 $vObject =
VObject\Reader::read($properties[200][
'{urn:ietf:params:xml:ns:caldav}calendar-data']);
573 if ($validator->validate($vObject, $report->filters)) {
577 if (!$requestedCalendarData) {
578 unset($properties[200][
'{urn:ietf:params:xml:ns:caldav}calendar-data']);
582 if ($report->expand) {
583 $vObject = $vObject->expand($report->expand[
'start'], $report->expand[
'end'], $calendarTimeZone);
586 $properties[200][
'{' . self::NS_CALDAV .
'}calendar-data'] = json_encode($vObject->jsonSerialize());
587 } elseif ($report->expand) {
588 $properties[200][
'{' . self::NS_CALDAV .
'}calendar-data'] = $vObject->serialize();
605 if (strpos($this->server->httpRequest->getHeader(
'User-Agent'),
'MSFT-') === 0) {
615 throw new BadRequest(
'A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
624 $nodePaths = $node->calendarQuery($report->filters);
626 foreach ($nodePaths as
$path) {
629 $this->server->getPropertiesForPath($this->server->getRequestUri() .
'/' .
$path, $report->properties);
631 if (($needsJson || $report->expand)) {
634 if ($report->expand) {
635 $vObject = $vObject->expand($report->expand[
'start'], $report->expand[
'end'], $calendarTimeZone);
639 $properties[200][
'{' . self::NS_CALDAV .
'}calendar-data'] = json_encode($vObject->jsonSerialize());
641 $properties[200][
'{' . self::NS_CALDAV .
'}calendar-data'] = $vObject->serialize();
654 $prefer = $this->server->getHTTPPrefer();
656 $this->server->httpResponse->setStatus(207);
657 $this->server->httpResponse->setHeader(
'Content-Type',
'application/xml; charset=utf-8');
658 $this->server->httpResponse->setHeader(
'Vary',
'Brief,Prefer');
659 $this->server->httpResponse->setBody($this->server->generateMultiStatus(
$result, $prefer[
'return'] ===
'minimal'));
672 $uri = $this->server->getRequestUri();
674 $acl = $this->server->getPlugin(
'acl');
676 $acl->checkPrivileges($uri,
'{' . self::NS_CALDAV .
'}read-free-busy');
679 $calendar = $this->server->tree->getNodeForPath($uri);
681 throw new DAV\Exception\NotImplemented(
'The free-busy-query REPORT is only implemented on calendars');
684 $tzProp =
'{' . self::NS_CALDAV .
'}calendar-timezone';
688 $calendarProps = $this->server->getProperties($uri, [$tzProp]);
690 if (isset($calendarProps[$tzProp])) {
692 $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
694 $vtimezoneObj->destroy();
702 'name' =>
'VCALENDAR',
706 'comp-filters' => [],
707 'prop-filters' => [],
708 'is-not-defined' =>
false,
710 'start' => $report->start,
711 'end' => $report->end,
715 'prop-filters' => [],
716 'is-not-defined' =>
false,
717 'time-range' => null,
726 $generator->setObjects($objects);
727 $generator->setTimeRange($report->start, $report->end);
728 $generator->setTimeZone($calendarTimeZone);
729 $result = $generator->getResult();
732 $this->server->httpResponse->setStatus(200);
733 $this->server->httpResponse->setHeader(
'Content-Type',
'text/calendar');
734 $this->server->httpResponse->setHeader(
'Content-Length', strlen(
$result));
735 $this->server->httpResponse->setBody(
$result);
761 $parentNode = $this->server->tree->getNodeForPath($parent);
770 $this->server->httpRequest,
771 $this->server->httpResponse,
799 $this->server->httpRequest,
800 $this->server->httpResponse,
823 if (is_resource(
$data)) {
833 if (substr(
$data, 0, 1) ===
'[') {
846 throw new DAV\Exception\UnsupportedMediaType(
'This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
850 if (
$vobj->name !==
'VCALENDAR') {
851 throw new DAV\Exception\UnsupportedMediaType(
'This collection can only support iCalendar objects.');
854 $sCCS =
'{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
858 $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
860 if (isset($calendarProperties[$sCCS])) {
861 $supportedComponents = $calendarProperties[$sCCS]->getValue();
863 $supportedComponents = [
'VJOURNAL',
'VTODO',
'VEVENT'];
868 foreach (
$vobj->getComponents() as $component) {
869 switch ($component->name) {
875 $foundType = $component->name;
881 if (!$foundType || !in_array($foundType, $supportedComponents)) {
886 $prefer = $this->server->getHTTPPrefer();
888 if ($prefer[
'handling'] !==
'strict') {
895 $warningMessage = null;
901 if ($message[
'level'] > $highestLevel) {
903 $highestLevel = $message[
'level'];
904 $warningMessage = $message[
'message'];
906 switch ($message[
'level']) {
917 throw new DAV\Exception\UnsupportedMediaType(
'Validation error in iCalendar: ' . $message[
'message']);
922 if ($warningMessage) {
925 'iCalendar validation warning: ' . $warningMessage
933 $subModified =
false;
936 'calendarObjectChange',
947 if ($modified || $subModified) {
952 if (!$modified && strcmp(
$data, $before) !== 0) {
973 $supportedPrivilegeSet[
'{DAV:}read'][
'aggregates'][
'{' . self::NS_CALDAV .
'}read-free-busy'] = [
994 $output .=
'<tr><td colspan="2"><form method="post" action=""> 995 <h3>Create new calendar</h3> 996 <input type="hidden" name="sabreAction" value="mkcol" /> 997 <input type="hidden" name="resourceType" value="{DAV:}collection,{' . self::NS_CALDAV .
'}calendar" /> 998 <label>Name (uri):</label> <input type="text" name="name" /><br /> 999 <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br /> 1000 <input type="submit" value="create" /> 1019 if (strpos($response->
getHeader(
'Content-Type'),
'text/calendar') ===
false) {
1025 [
'text/calendar',
'application/calendar+json']
1028 if (
$result !==
'application/calendar+json') {
1036 $jsonBody = json_encode(
$vobj->jsonSerialize());
1037 $response->
setBody($jsonBody);
1042 $response->
setHeader(
'Content-Type',
'application/calendar+json');
1043 $response->
setHeader(
'Content-Length', strlen($jsonBody));
1062 'description' =>
'Adds support for CalDAV (rfc4791)',
1063 'link' =>
'http://sabre.io/dav/caldav/',
This interface represents a HTTP response.
getCalendarHomeForPrincipal($principalUrl)
Returns the path to a principal's calendar home.
The RequestInterface represents a HTTP request.
email-address-set property
getPluginInfo()
Returns a bunch of meta-data about the plugin.
htmlActionsPanel(DAV\INode $node, &$output)
This method is used to generate HTML output for the DAV.
The baseclass for all server plugins.
setBody($body)
Updates the body resource with a new stream.
const CALENDAR_ROOT
The hardcoded root for calendar objects.
foreach($paths as $path) $request
static negotiate($acceptHeaderValue, array $availableOptions)
Deprecated! Use negotiateContentType.
The Request class represents a single HTTP request.
This class helps with generating FREEBUSY reports based on existing sets of objects.
getBodyAsString()
Returns the body as a string.
This interface represents a node that may contain calendar objects.
split($path)
Returns the 'dirname' and 'basename' for a path.
The CalendarHome represents a node that is usually in a users' calendar-homeset.
This class holds all the information about a PROPFIND request.
initialize(DAV\Server $server)
Initializes the plugin.
beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified)
This method is triggered before a new file is created.
calendarMultiGetReport($report)
This function handles the calendar-multiget REPORT.
validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew)
Checks if the submitted iCalendar data is in fact, valid.
beforeWriteContent($path, DAV\IFile $node, &$data, &$modified)
This method is triggered before a file gets updated with new content.
The IExtendedCollection interface.
The ICollection Interface.
httpMkCalendar(RequestInterface $request, ResponseInterface $response)
This function handles the MKCALENDAR HTTP method, which creates a new calendar.
getPluginName()
Returns a plugin name.
calendarQueryReport($report)
This function handles the calendar-query REPORT.
catch(Exception $e) $message
report($reportName, $report, $path)
This functions handles REPORT requests specific to CalDAV.
const NS_CALENDARSERVER
This is the namespace for the proprietary calendarserver extensions.
setStatus($status)
Sets the HTTP status code.
supported-collation-set property
getHeader($name)
Returns a specific HTTP header, based on it's name.
This interface represents a file in the directory tree.
freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report)
This method is responsible for parsing the request and generating the response for the CALDAV:free-bu...
The INode interface is the base interface, and the parent class of both ICollection and IFile...
propFind(DAV\PropFind $propFind, DAV\INode $node)
PropFind.
static readJson($data, $options=0)
Parses a jCard or jCal object, and returns the top component.
CalendarObject interface.
const REPAIR
The following constants are used by the validate() method.
getPath()
Returns the relative path.
This class represents a MKCOL operation.
$maxResourceSize
The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data, which can hold up to 2^24 = 16777...
Supported-calendar-data property.
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
getSupportedReportSet($uri)
Returns a list of reports this plugin supports.
getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
This method is triggered whenever a subsystem reqeuests the privileges that are supported on a partic...
httpAfterGet(RequestInterface $request, ResponseInterface $response)
This event is triggered after GET requests.
Exception thrown by Reader if an invalid object was attempted to be parsed.
const PROFILE_CALDAV
If this option is set, the validator will operate on iCalendar objects on the assumption that the vca...
getHTTPMethods($uri)
Use this method to tell the server this plugin defines additional HTTP methods.
getFeatures()
Returns a list of features for the DAV: HTTP header.
const NS_CALDAV
This is the official CalDAV namespace.
setHeader($name, $value)
Updates a HTTP header.
getBody()
Returns the message body, as it's internal representation.