/** * Sends the response to a sync-collection request. * * @param string $syncToken * @param string $collectionUrl * @param array $added * @param array $modified * @param array $deleted * @param array $properties * @return void */ protected function sendSyncCollectionResponse($syncToken, $collectionUrl, array $added, array $modified, array $deleted, array $properties) { $fullPaths = []; // Pre-fetching children, if this is possible. foreach (array_merge($added, $modified) as $item) { $fullPath = $collectionUrl . '/' . $item; $fullPaths[] = $fullPath; } $responses = []; foreach ($this->server->getPropertiesForMultiplePaths($fullPaths, $properties) as $fullPath => $props) { // The 'Property_Response' class is responsible for generating a // single {DAV:}response xml element. $responses[] = new DAV\Xml\Element\Response($fullPath, $props); } // Deleted items also show up as 'responses'. They have no properties, // and a single {DAV:}status element set as 'HTTP/1.1 404 Not Found'. foreach ($deleted as $item) { $fullPath = $collectionUrl . '/' . $item; $responses[] = new DAV\Xml\Element\Response($fullPath, [], 404); } $multiStatus = new DAV\Xml\Response\MultiStatus($responses, self::SYNCTOKEN_PREFIX . $syncToken); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setBody($this->server->xml->write('{DAV:}multistatus', $multiStatus, $this->server->getBaseUri())); }
/** * Sends the response to a sync-collection request. * * @param string $syncToken * @param string $collectionUrl * @param array $added * @param array $modified * @param array $deleted * @param array $properties * @return void */ protected function sendSyncCollectionResponse($syncToken, $collectionUrl, array $added, array $modified, array $deleted, array $properties) { $dom = new \DOMDocument('1.0', 'utf-8'); $dom->formatOutput = true; $multiStatus = $dom->createElement('d:multistatus'); $dom->appendChild($multiStatus); // Adding in default namespaces foreach ($this->server->xmlNamespaces as $namespace => $prefix) { $multiStatus->setAttribute('xmlns:' . $prefix, $namespace); } $fullPaths = []; // Pre-fetching children, if this is possible. foreach (array_merge($added, $modified) as $item) { $fullPath = $collectionUrl . '/' . $item; $fullPaths[] = $fullPath; } foreach ($this->server->getPropertiesForMultiplePaths($fullPaths, $properties) as $fullPath => $props) { // The 'Property_Response' class is responsible for generating a // single {DAV:}response xml element. $response = new DAV\Property\Response($fullPath, $props); $response->serialize($this->server, $multiStatus); } // Deleted items also show up as 'responses'. They have no properties, // and a single {DAV:}status element set as 'HTTP/1.1 404 Not Found'. foreach ($deleted as $item) { $fullPath = $collectionUrl . '/' . $item; $response = new DAV\Property\Response($fullPath, [], 404); $response->serialize($this->server, $multiStatus); } $syncToken = $dom->createElement('d:sync-token', self::SYNCTOKEN_PREFIX . $syncToken); $multiStatus->appendChild($syncToken); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setBody($dom->saveXML()); }
/** * This method is responsible for generating the actual, full response. * * @param string $path * @param DateTime|null $start * @param DateTime|null $end * @param bool $expand * @param string $componentType * @param string $format * @param array $properties * @param ResponseInterface $response */ protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) { $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data'; $blobs = []; if ($start || $end || $componentType) { // If there was a start or end filter, we need to enlist // calendarQuery for speed. $calendarNode = $this->server->tree->getNodeForPath($path); $queryResult = $calendarNode->calendarQuery(['name' => 'VCALENDAR', 'comp-filters' => [['name' => $componentType, 'comp-filters' => [], 'prop-filters' => [], 'is-not-defined' => false, 'time-range' => ['start' => $start, 'end' => $end]]], 'prop-filters' => [], 'is-not-defined' => false, 'time-range' => null]); // queryResult is just a list of base urls. We need to prefix the // calendar path. $queryResult = array_map(function ($item) use($path) { return $path . '/' . $item; }, $queryResult); $nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]); unset($queryResult); } else { $nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1); } // Flattening the arrays foreach ($nodes as $node) { if (isset($node[200][$calDataProp])) { $blobs[$node['href']] = $node[200][$calDataProp]; } } unset($nodes); $mergedCalendar = $this->mergeObjects($properties, $blobs); if ($expand) { $calendarTimeZone = null; // We're expanding, and for that we need to figure out the // calendar's timezone. $tzProp = '{' . Plugin::NS_CALDAV . '}calendar-timezone'; $tzResult = $this->server->getProperties($path, [$tzProp]); if (isset($tzResult[$tzProp])) { // This property contains a VCALENDAR with a single // VTIMEZONE. $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]); $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); // Destroy circular references to PHP will GC the object. $vtimezoneObj->destroy(); unset($vtimezoneObj); } else { // Defaulting to UTC. $calendarTimeZone = new DateTimeZone('UTC'); } $mergedCalendar->expand($start, $end, $calendarTimeZone); } $response->setHeader('Content-Type', $format); switch ($format) { case 'text/calendar': $mergedCalendar = $mergedCalendar->serialize(); break; case 'application/calendar+json': $mergedCalendar = json_encode($mergedCalendar->jsonSerialize()); break; } $response->setStatus(200); $response->setBody($mergedCalendar); }
/** * This function handles the calendar-multiget REPORT. * * This report is used by the client to fetch the content of a series * of urls. Effectively avoiding a lot of redundant requests. * * @param CalendarMultiGetReport $report * @return void */ function calendarMultiGetReport($report) { $needsJson = $report->contentType === 'application/calendar+json'; $timeZones = []; $propertyList = []; $paths = array_map( [$this->server, 'calculateUri'], $report->hrefs ); foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) { if (($needsJson || $report->expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) { $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']); if ($report->expand) { // We're expanding, and for that we need to figure out the // calendar's timezone. list($calendarPath) = Uri\split($uri); if (!isset($timeZones[$calendarPath])) { // Checking the calendar-timezone property. $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; $tzResult = $this->server->getProperties($calendarPath, [$tzProp]); if (isset($tzResult[$tzProp])) { // This property contains a VCALENDAR with a single // VTIMEZONE. $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]); $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); } else { // Defaulting to UTC. $timeZone = new DateTimeZone('UTC'); } $timeZones[$calendarPath] = $timeZone; } $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]); } if ($needsJson) { $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); } else { $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $propertyList[] = $objProps; } $prefer = $this->server->getHTTPPrefer(); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal')); }
/** * This function handles the calendar-multiget REPORT. * * This report is used by the client to fetch the content of a series * of urls. Effectively avoiding a lot of redundant requests. * * @param \DOMNode $dom * @return void */ function calendarMultiGetReport($dom) { $properties = array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)); $hrefElems = $dom->getElementsByTagNameNS('urn:DAV', 'href'); $xpath = new \DOMXPath($dom); $xpath->registerNameSpace('cal', Plugin::NS_CALDAV); $xpath->registerNameSpace('dav', 'urn:DAV'); $expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand'); if ($expand->length > 0) { $expandElem = $expand->item(0); $start = $expandElem->getAttribute('start'); $end = $expandElem->getAttribute('end'); if (!$start || !$end) { throw new DAV\Exception\BadRequest('The "start" and "end" attributes are required for the CALDAV:expand element'); } $start = VObject\DateTimeParser::parseDateTime($start); $end = VObject\DateTimeParser::parseDateTime($end); if ($end <= $start) { throw new DAV\Exception\BadRequest('The end-date must be larger than the start-date in the expand element.'); } $expand = true; } else { $expand = false; } $needsJson = $xpath->evaluate("boolean(/cal:calendar-multiget/dav:prop/cal:calendar-data[@content-type='application/calendar+json'])"); $uris = []; foreach ($hrefElems as $elem) { $uris[] = $this->server->calculateUri($elem->nodeValue); } foreach ($this->server->getPropertiesForMultiplePaths($uris, $properties) as $uri => $objProps) { if (($needsJson || $expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) { $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']); if ($expand) { $vObject->expand($start, $end); } if ($needsJson) { $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); } else { $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $propertyList[] = $objProps; } $prefer = $this->server->getHTTPPRefer(); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return-minimal'])); }
/** * This method is responsible for generating the actual, full response. * * @param string $path * @param DateTime|null $start * @param DateTime|null $end * @param bool $expand * @param string $format * @param array $properties * @param ResponseInterface $response */ protected function generateResponse($path, $start, $end, $expand, $format, $properties, ResponseInterface $response) { $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data'; $blobs = []; if ($start || $end) { // If there was a start or end filter, we need to enlist // calendarQuery for speed. $calendarNode = $this->server->tree->getNodeForPath($path); $queryResult = $calendarNode->calendarQuery(['name' => 'VCALENDAR', 'comp-filters' => [['name' => 'VEVENT', 'comp-filters' => [], 'prop-filters' => [], 'is-not-defined' => false, 'time-range' => ['start' => $start, 'end' => $end]]], 'prop-filters' => [], 'is-not-defined' => false, 'time-range' => null]); // queryResult is just a list of base urls. We need to prefix the // calendar path. $queryResult = array_map(function ($item) use($path) { return $path . '/' . $item; }, $queryResult); $nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]); unset($queryResult); } else { $nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1); } // Flattening the arrays foreach ($nodes as $node) { if (isset($node[200][$calDataProp])) { $blobs[] = $node[200][$calDataProp]; } } unset($nodes); $mergedCalendar = $this->mergeObjects($properties, $blobs); if ($expand) { $mergedCalendar->expand($start, $end); } $response->setHeader('Content-Type', $format); switch ($format) { case 'text/calendar': $mergedCalendar = $mergedCalendar->serialize(); break; case 'application/calendar+json': $mergedCalendar = json_encode($mergedCalendar->jsonSerialize()); break; } $response->setStatus(200); $response->setBody($mergedCalendar); }