65 $server->on(
'method:GET', [$this,
'httpGet'], 90);
66 $server->on(
'browserButtonActions',
function(
$path, $node, &$actions) {
68 $actions .=
'<a href="' . htmlspecialchars(
$path, ENT_QUOTES,
'UTF-8') .
'?export"><span class="oi" data-glyph="calendar"></span></a>';
84 if (!array_key_exists(
'export', $queryParams))
return;
88 $node = $this->server->getProperties(
$path, [
91 '{http://sabredav.org/ns}sync-token',
93 '{http://apple.com/ns/ical/}calendar-color',
96 if (!isset($node[
'{DAV:}resourcetype']) || !$node[
'{DAV:}resourcetype']->
is(
'{' .
Plugin::NS_CALDAV .
'}calendar')) {
100 $this->server->transactionType =
'get-calendar-export';
107 $componentType =
false;
108 if (isset($queryParams[
'start'])) {
109 if (!ctype_digit($queryParams[
'start'])) {
110 throw new BadRequest(
'The start= parameter must contain a unix timestamp');
112 $start = DateTime::createFromFormat(
'U', $queryParams[
'start']);
114 if (isset($queryParams[
'end'])) {
115 if (!ctype_digit($queryParams[
'end'])) {
116 throw new BadRequest(
'The end= parameter must contain a unix timestamp');
118 $end = DateTime::createFromFormat(
'U', $queryParams[
'end']);
120 if (isset($queryParams[
'expand']) && !!$queryParams[
'expand']) {
122 throw new BadRequest(
'If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
125 $componentType =
'VEVENT';
127 if (isset($queryParams[
'componentType'])) {
128 if (!in_array($queryParams[
'componentType'], [
'VEVENT',
'VTODO',
'VJOURNAL'])) {
129 throw new BadRequest(
'You are not allowed to search for components of type: ' . $queryParams[
'componentType'] .
' here');
131 $componentType = $queryParams[
'componentType'];
134 $format = \Sabre\HTTP\Util::Negotiate(
138 'application/calendar+json',
142 if (isset($queryParams[
'accept'])) {
143 if ($queryParams[
'accept'] ===
'application/calendar+json' || $queryParams[
'accept'] ===
'jcal') {
144 $format =
'application/calendar+json';
173 $calendarNode = $this->server->tree->getNodeForPath(
$path);
180 $queryResult = $calendarNode->calendarQuery([
181 'name' =>
'VCALENDAR',
184 'name' => $componentType,
185 'comp-filters' => [],
186 'prop-filters' => [],
187 'is-not-defined' =>
false,
194 'prop-filters' => [],
195 'is-not-defined' =>
false,
196 'time-range' => null,
201 $queryResult = array_map(
202 function($item) use (
$path) {
203 return $path .
'/' . $item;
207 $nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
211 $nodes = $this->server->getPropertiesForPath(
$path, [$calDataProp], 1);
215 foreach (
$nodes as $node) {
216 if (isset($node[200][$calDataProp])) {
217 $blobs[$node[
'href']] = $node[200][$calDataProp];
228 $calendarTimeZone = null;
232 $tzResult = $this->server->getProperties(
$path, [$tzProp]);
233 if (isset($tzResult[$tzProp])) {
237 $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
239 $vtimezoneObj->destroy();
240 unset($vtimezoneObj);
246 $mergedCalendar = $mergedCalendar->expand(
$start,
$end, $calendarTimeZone);
249 $filenameExtension =
'.ics';
252 case 'text/calendar' :
253 $mergedCalendar = $mergedCalendar->serialize();
254 $filenameExtension =
'.ics';
256 case 'application/calendar+json' :
257 $mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
258 $filenameExtension =
'.json';
263 '/[^a-zA-Z0-9-_ ]/um',
265 $calendarNode->getName()
267 $filename .=
'-' . date(
'Y-m-d') . $filenameExtension;
269 $response->
setHeader(
'Content-Disposition',
'attachment; filename="' .
$filename .
'"');
273 $response->
setBody($mergedCalendar);
291 $calendar->PRODID =
'-//SabreDAV//SabreDAV//EN';
293 if (isset($properties[
'{DAV:}displayname'])) {
294 $calendar->{
'X-WR-CALNAME'} = $properties[
'{DAV:}displayname'];
296 if (isset($properties[
'{http://apple.com/ns/ical/}calendar-color'])) {
297 $calendar->{
'X-APPLE-CALENDAR-COLOR'} = $properties[
'{http://apple.com/ns/ical/}calendar-color'];
300 $collectedTimezones = [];
305 foreach ($inputObjects as $href => $inputObject) {
309 foreach ($nodeComp->children() as $child) {
311 switch ($child->name) {
315 $objects[] = clone $child;
321 if (in_array((
string)$child->TZID, $collectedTimezones))
continue;
323 $timezones[] = clone $child;
324 $collectedTimezones[] = $child->TZID;
331 $nodeComp->destroy();
337 foreach ($objects as $obj)
$calendar->add($obj);
372 'description' =>
'Adds the ability to export CalDAV calendars as a single iCalendar file.',
373 'link' =>
'http://sabre.io/dav/ics-export-plugin/',
This interface represents a HTTP response.
The RequestInterface represents a HTTP request.
The baseclass for all server plugins.
setBody($body)
Updates the body resource with a new stream.
foreach($paths as $path) $request
mergeObjects(array $properties, array $inputObjects)
Merges all calendar objects, and builds one big iCalendar blob.
getPluginInfo()
Returns a bunch of meta-data about the plugin.
const VERSION
Full version number.
getQueryParameters()
Returns the list of query parameters.
setStatus($status)
Sets the HTTP status code.
getHeader($name)
Returns a specific HTTP header, based on it's name.
initialize(DAV\Server $server)
Initializes the plugin and registers event handlers.
getPath()
Returns the relative path.
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
httpGet(RequestInterface $request, ResponseInterface $response)
Intercepts GET requests on calendar urls ending with ?export.
const NS_CALDAV
This is the official CalDAV namespace.
setHeader($name, $value)
Updates a HTTP header.
generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response)
This method is responsible for generating the actual, full response.
getPluginName()
Returns a plugin name.