/** * 'beforeMethod' event handles. This event handles intercepts GET requests ending * with ?export * * @param string $method * @param string $uri * @return bool */ public function beforeMethod($method, $uri) { if ($method != 'GET') { return; } if ($this->server->httpRequest->getQueryString() != 'export') { return; } // splitting uri list($uri) = explode('?', $uri, 2); $node = $this->server->tree->getNodeForPath($uri); if (!$node instanceof Calendar) { return; } // Checking ACL, if available. if ($aclPlugin = $this->server->getPlugin('acl')) { $aclPlugin->checkPrivileges($uri, '{DAV:}read'); } $this->server->httpResponse->setHeader('Content-Type', 'text/calendar'); $this->server->httpResponse->sendStatus(200); $nodes = $this->server->getPropertiesForPath($uri, array('{' . Plugin::NS_CALDAV . '}calendar-data'), 1); $this->server->httpResponse->sendBody($this->generateICS($nodes)); // Returning false to break the event chain return false; }
/** * WebDAV PROPFIND * * This WebDAV method requests information about an uri resource, or a list of resources * If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value * If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory) * * The request body contains an XML data structure that has a list of properties the client understands * The response body is also an xml document, containing information about every uri resource and the requested properties * * It has to return a HTTP 207 Multi-status status code * * @param RequestInterface $request * @param ResponseInterface $response * @return void */ function httpPropfind(RequestInterface $request, ResponseInterface $response) { $path = $request->getPath(); $requestedProperties = $this->server->parsePropFindRequest($request->getBodyAsString()); $depth = $this->server->getHTTPDepth(1); // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled if (!$this->server->enablePropfindDepthInfinity && $depth != 0) { $depth = 1; } $newProperties = $this->server->getPropertiesForPath($path, $requestedProperties, $depth); // This is a multi-status response $response->setStatus(207); $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); $response->setHeader('Vary', 'Brief,Prefer'); // Normally this header is only needed for OPTIONS responses, however.. // iCal seems to also depend on these being set for PROPFIND. Since // this is not harmful, we'll add it. $features = ['1', '3', 'extended-mkcol']; foreach ($this->server->getPlugins() as $plugin) { $features = array_merge($features, $plugin->getFeatures()); } $response->setHeader('DAV', implode(', ', $features)); $prefer = $this->server->getHTTPPrefer(); $minimal = $prefer['return-minimal']; $data = $this->server->generateMultiStatus($newProperties, $minimal); $response->setBody($data); // Sending back false will interupt the event chain and tell the server // we've handled this method. return false; }
/** * REPORT operations to look for comments * * @param string $reportName * @param [] $report * @param string $uri * @return bool * @throws NotFound * @throws ReportNotSupported */ public function onReport($reportName, $report, $uri) { $node = $this->server->tree->getNodeForPath($uri); if (!$node instanceof EntityCollection || $reportName !== self::REPORT_NAME) { throw new ReportNotSupported(); } $args = ['limit' => 0, 'offset' => 0, 'datetime' => null]; $acceptableParameters = [$this::REPORT_PARAM_LIMIT, $this::REPORT_PARAM_OFFSET, $this::REPORT_PARAM_TIMESTAMP]; $ns = '{' . $this::NS_OWNCLOUD . '}'; foreach ($report as $parameter) { if (!in_array($parameter['name'], $acceptableParameters) || empty($parameter['value'])) { continue; } $args[str_replace($ns, '', $parameter['name'])] = $parameter['value']; } if (!is_null($args['datetime'])) { $args['datetime'] = new \DateTime($args['datetime']); } $results = $node->findChildren($args['limit'], $args['offset'], $args['datetime']); $responses = []; foreach ($results as $node) { $nodePath = $this->server->getRequestUri() . '/' . $node->comment->getId(); $resultSet = $this->server->getPropertiesForPath($nodePath, CommentNode::getPropertyNames()); if (isset($resultSet[0]) && isset($resultSet[0][200])) { $responses[] = new Response($this->server->getBaseUri() . $nodePath, [200 => $resultSet[0][200]], 200); } } $xml = $this->server->xml->write('{DAV:}multistatus', new MultiStatus($responses)); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setBody($xml); return false; }
/** * This method handler is invoked during fetching of properties. * * We use this event to add calendar-auto-schedule-specific properties. * * @param PropFind $propFind * @param INode $node * @return void */ function propFind(PropFind $propFind, INode $node) { if ($node instanceof DAVACL\IPrincipal) { $caldavPlugin = $this->server->getPlugin('caldav'); $principalUrl = $node->getPrincipalUrl(); // schedule-outbox-URL property $propFind->handle('{' . self::NS_CALDAV . '}schedule-outbox-URL', function () use($principalUrl, $caldavPlugin) { $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); if (!$calendarHomePath) { return null; } $outboxPath = $calendarHomePath . '/outbox/'; return new Href($outboxPath); }); // schedule-inbox-URL property $propFind->handle('{' . self::NS_CALDAV . '}schedule-inbox-URL', function () use($principalUrl, $caldavPlugin) { $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); if (!$calendarHomePath) { return null; } $inboxPath = $calendarHomePath . '/inbox/'; return new Href($inboxPath); }); $propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function () use($principalUrl, $caldavPlugin) { // We don't support customizing this property yet, so in the // meantime we just grab the first calendar in the home-set. $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); if (!$calendarHomePath) { return null; } $sccs = '{' . self::NS_CALDAV . '}supported-calendar-component-set'; $result = $this->server->getPropertiesForPath($calendarHomePath, ['{DAV:}resourcetype', $sccs], 1); foreach ($result as $child) { if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{' . self::NS_CALDAV . '}calendar') || $child[200]['{DAV:}resourcetype']->is('{http://calendarserver.org/ns/}shared')) { // Node is either not a calendar or a shared instance. continue; } if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) { // Either there is no supported-calendar-component-set // (which is fine) or we found one that supports VEVENT. return new Href($child['href']); } } }); // The server currently reports every principal to be of type // 'INDIVIDUAL' $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function () { return 'INDIVIDUAL'; }); } // Mapping the old property to the new property. $propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function () use($propFind, $node) { // In case it wasn't clear, the only difference is that we map the // old property to a different namespace. $availProp = '{' . self::NS_CALDAV . '}calendar-availability'; $subPropFind = new PropFind($propFind->getPath(), [$availProp]); $this->server->getPropertiesByNode($subPropFind, $node); $propFind->set('{http://calendarserver.org/ns/}calendar-availability', $subPropFind->get($availProp), $subPropFind->getStatus($availProp)); }); }
/** * 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); }
function testSupportedReportSetUserCalendars() { $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin()); $props = $this->server->getPropertiesForPath('/calendars/user1', ['{DAV:}supported-report-set']); $this->assertArrayHasKey(0, $props); $this->assertArrayHasKey(200, $props[0]); $this->assertArrayHasKey('{DAV:}supported-report-set', $props[0][200]); $prop = $props[0][200]['{DAV:}supported-report-set']; $this->assertInstanceOf('\\Sabre\\DAV\\Xml\\Property\\SupportedReportSet', $prop); $value = ['{DAV:}sync-collection', '{DAV:}expand-property', '{DAV:}principal-property-search', '{DAV:}principal-search-property-set']; $this->assertEquals($value, $prop->getValue()); }
/** * 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); }
/** * 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) { $resolvedProperties = array(); foreach (array_merge($added, $modified) as $item) { $fullPath = $collectionUrl . '/' . $item; try { $resolvedProperties[$fullPath] = $this->server->getPropertiesForPath($fullPath, $properties); // in case the user doesnt have access to this } catch (Sabre\DAV\Exception\NotFound $e) { unset($resolvedProperties[$fullPath]); } } foreach ($deleted as $item) { $fullPath = $collectionUrl . '/' . $item; $resolvedProperties[$fullPath] = array(); } $data = $this->generateMultiStatus($resolvedProperties, $syncToken); $this->server->httpResponse->sendStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->sendBody($data); }
/** * This function handles the calendar-query REPORT * * This report is used by clients to request calendar objects based on * complex conditions. * * @param Xml\Request\CalendarQueryReport $report * @return void */ function calendarQueryReport($report) { $path = $this->server->getRequestUri(); $needsJson = $report->contentType === 'application/calendar+json'; $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); $depth = $this->server->getHTTPDepth(0); // The default result is an empty array $result = []; $calendarTimeZone = null; if ($report->expand) { // We're expanding, and for that we need to figure out the // calendar's timezone. $tzProp = '{' . self::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(); unset($vtimezoneObj); } else { // Defaulting to UTC. $calendarTimeZone = new DateTimeZone('UTC'); } } // The calendarobject was requested directly. In this case we handle // this locally. if ($depth == 0 && $node instanceof ICalendarObject) { $requestedCalendarData = true; $requestedProperties = $report->properties; if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) { // We always retrieve calendar-data, as we need it for filtering. $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data'; // If calendar-data wasn't explicitly requested, we need to remove // it after processing. $requestedCalendarData = false; } $properties = $this->server->getPropertiesForPath($path, $requestedProperties, 0); // This array should have only 1 element, the first calendar // object. $properties = current($properties); // If there wasn't any calendar-data returned somehow, we ignore // this. if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) { $validator = new CalendarQueryValidator(); $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); if ($validator->validate($vObject, $report->filters)) { // If the client didn't require the calendar-data property, // we won't give it back. if (!$requestedCalendarData) { unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); } else { if ($report->expand) { $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone); } if ($needsJson) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); } elseif ($report->expand) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $result = [$properties]; } } } if ($node instanceof ICalendarObjectContainer && $depth === 0) { if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-WP/') === 0) { // Windows phone incorrectly supplied depth as 0, when it actually // should have set depth to 1. We're implementing a workaround here // to deal with this. $depth = 1; } else { throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1'); } } // If we're dealing with a calendar, the calendar itself is responsible // for the calendar-query. if ($node instanceof ICalendarObjectContainer && $depth == 1) { $nodePaths = $node->calendarQuery($report->filters); foreach ($nodePaths as $path) { list($properties) = $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $report->properties); if ($needsJson || $report->expand) { $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']); if ($report->expand) { $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone); } if ($needsJson) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); } else { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $result[] = $properties; } } $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($result, $prefer['return'] === 'minimal')); }
function testGetDisplayName() { $tree = [new DAV\SimpleCollection('principals', [$principal = new MockPrincipal('user', 'principals/user')])]; $fakeServer = new DAV\Server($tree); $plugin = new Plugin(); $fakeServer->addPlugin($plugin); $requestedProperties = ['{DAV:}displayname']; $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); $result = $result[0]; $this->assertTrue(isset($result[200])); $this->assertTrue(isset($result[200]['{DAV:}displayname'])); $this->assertEquals('user', $result[200]['{DAV:}displayname']); }
/** * This function handles the calendar-query REPORT * * This report is used by clients to request calendar objects based on * complex conditions. * * @param \DOMNode $dom * @return void */ function calendarQueryReport($dom) { $parser = new CalendarQueryParser($dom); $parser->parse(); $path = $this->server->getRequestUri(); // TODO: move this into CalendarQueryParser $xpath = new \DOMXPath($dom); $xpath->registerNameSpace('cal', Plugin::NS_CALDAV); $xpath->registerNameSpace('dav', 'urn:DAV'); $needsJson = $xpath->evaluate("boolean(/cal:calendar-query/dav:prop/cal:calendar-data[@content-type='application/calendar+json'])"); $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); $depth = $this->server->getHTTPDepth(0); // The default result is an empty array $result = []; $calendarTimeZone = null; if ($parser->expand) { // We're expanding, and for that we need to figure out the // calendar's timezone. $tzProp = '{' . self::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(); unset($vtimezoneObj); } else { // Defaulting to UTC. $calendarTimeZone = new DateTimeZone('UTC'); } } // The calendarobject was requested directly. In this case we handle // this locally. if ($depth == 0 && $node instanceof ICalendarObject) { $requestedCalendarData = true; $requestedProperties = $parser->requestedProperties; if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) { // We always retrieve calendar-data, as we need it for filtering. $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data'; // If calendar-data wasn't explicitly requested, we need to remove // it after processing. $requestedCalendarData = false; } $properties = $this->server->getPropertiesForPath($path, $requestedProperties, 0); // This array should have only 1 element, the first calendar // object. $properties = current($properties); // If there wasn't any calendar-data returned somehow, we ignore // this. if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) { $validator = new CalendarQueryValidator(); $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); if ($validator->validate($vObject, $parser->filters)) { // If the client didn't require the calendar-data property, // we won't give it back. if (!$requestedCalendarData) { unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); } else { if ($parser->expand) { $vObject->expand($parser->expand['start'], $parser->expand['end'], $calendarTimeZone); } if ($needsJson) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); } elseif ($parser->expand) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $result = [$properties]; } } } // If we're dealing with a calendar, the calendar itself is responsible // for the calendar-query. if ($node instanceof ICalendarObjectContainer && $depth == 1) { $nodePaths = $node->calendarQuery($parser->filters); $timeZones = []; foreach ($nodePaths as $path) { list($properties) = $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $parser->requestedProperties); if ($needsJson || $parser->expand) { $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']); if ($parser->expand) { $vObject->expand($parser->expand['start'], $parser->expand['end'], $calendarTimeZone); } if ($needsJson) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); } else { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $result[] = $properties; } } $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($result, $prefer['return-minimal'])); }
/** * This function handles the calendar-query REPORT * * This report is used by clients to request calendar objects based on * complex conditions. * * @param \DOMNode $dom * @return void */ public function calendarQueryReport($dom) { $parser = new CalendarQueryParser($dom); $parser->parse(); $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); $depth = $this->server->getHTTPDepth(0); // The default result is an empty array $result = array(); // The calendarobject was requested directly. In this case we handle // this locally. if ($depth == 0 && $node instanceof ICalendarObject) { $requestedCalendarData = true; $requestedProperties = $parser->requestedProperties; if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) { // We always retrieve calendar-data, as we need it for filtering. $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data'; // If calendar-data wasn't explicitly requested, we need to remove // it after processing. $requestedCalendarData = false; } $properties = $this->server->getPropertiesForPath($this->server->getRequestUri(), $requestedProperties, 0); // This array should have only 1 element, the first calendar // object. $properties = current($properties); // If there wasn't any calendar-data returned somehow, we ignore // this. if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) { $validator = new CalendarQueryValidator(); $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); if ($validator->validate($vObject, $parser->filters)) { // If the client didn't require the calendar-data property, // we won't give it back. if (!$requestedCalendarData) { unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); } else { if ($parser->expand) { $vObject->expand($parser->expand['start'], $parser->expand['end']); $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $result = array($properties); } } } // If we're dealing with a calendar, the calendar itself is responsible // for the calendar-query. if ($node instanceof ICalendar && ($depth = 1)) { $nodePaths = $node->calendarQuery($parser->filters); foreach ($nodePaths as $path) { list($properties) = $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $parser->requestedProperties); if ($parser->expand) { // We need to do some post-processing $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); $vObject->expand($parser->expand['start'], $parser->expand['end']); $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } $result[] = $properties; } } $prefer = $this->server->getHTTPPRefer(); $this->server->httpResponse->sendStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); $this->server->httpResponse->sendBody($this->server->generateMultiStatus($result, $prefer['return-minimal'])); }
function testGetDisplayName() { $tree = [new DAV\SimpleCollection('principals', [$principal = new MockPrincipal('user', 'principals/user')])]; $fakeServer = new DAV\Server($tree); $plugin = new Plugin(); $plugin->allowUnauthenticatedAccess = false; $fakeServer->addPlugin($plugin); $plugin->setDefaultACL([['principal' => '{DAV:}all', 'privilege' => '{DAV:}all']]); $requestedProperties = ['{DAV:}displayname']; $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); $result = $result[0]; $this->assertTrue(isset($result[200])); $this->assertTrue(isset($result[200]['{DAV:}displayname'])); $this->assertEquals('user', $result[200]['{DAV:}displayname']); }