/** * Unserializes the property. * * This static method should return a an instance of this object. * * @param \DOMElement $prop * @param array $propertyMap * @return DAV\IProperty */ static function unserialize(\DOMElement $prop, array $propertyMap) { $xpath = new \DOMXPath($prop->ownerDocument); $xpath->registerNamespace('d', 'urn:DAV'); // Finding the 'response' element $xResponses = $xpath->evaluate('d:response', $prop); $result = []; for ($jj = 0; $jj < $xResponses->length; $jj++) { $xResponse = $xResponses->item($jj); // Parsing 'href' $href = Href::unserialize($xResponse, $propertyMap); $properties = []; // Parsing 'status' in 'd:response' $responseStatus = $xpath->evaluate('string(d:status)', $xResponse); if ($responseStatus) { list(, $responseStatus, ) = explode(' ', $responseStatus, 3); } // Parsing 'propstat' $xPropstat = $xpath->query('d:propstat', $xResponse); for ($ii = 0; $ii < $xPropstat->length; $ii++) { // Parsing 'status' $status = $xpath->evaluate('string(d:status)', $xPropstat->item($ii)); list(, $statusCode, ) = explode(' ', $status, 3); $usedPropertyMap = $statusCode == '200' ? $propertyMap : []; // Parsing 'prop' $properties[$statusCode] = DAV\XMLUtil::parseProperties($xPropstat->item($ii), $usedPropertyMap); } $result[] = new Response($href->getHref(), $properties, $responseStatus ? $responseStatus : null); } return new self($result); }
/** * This method tests if hrefs containing & are correctly encoded. */ function testSerializeEntity() { $href = new Href('http://example.org/?a&b', false); $this->assertEquals('http://example.org/?a&b', $href->getHref()); $doc = new \DOMDocument(); $root = $doc->createElement('d:anything'); $root->setAttribute('xmlns:d', 'DAV:'); $doc->appendChild($root); $server = new DAV\Server(); $server->setBaseUri('/bla/'); $href->serialize($server, $root); $xml = $doc->saveXML(); $this->assertEquals('<?xml version="1.0"?> <d:anything xmlns:d="DAV:"><d:href>http://example.org/?a&b</d:href></d:anything> ', $xml); }
/** * This method serializes the entire notification, as it is used in the * response body. * * @param DAV\Server $server * @param \DOMElement $node * @return void */ public function serializeBody(DAV\Server $server, \DOMElement $node) { $doc = $node->ownerDocument; $dt = $doc->createElement('cs:dtstamp'); $this->dtStamp->setTimezone(new \DateTimezone('GMT')); $dt->appendChild($doc->createTextNode($this->dtStamp->format('Ymd\\THis\\Z'))); $node->appendChild($dt); $prop = $doc->createElement('cs:invite-notification'); $node->appendChild($prop); $uid = $doc->createElement('cs:uid'); $uid->appendChild($doc->createTextNode($this->id)); $prop->appendChild($uid); $href = $doc->createElement('d:href'); $href->appendChild($doc->createTextNode($this->href)); $prop->appendChild($href); $nodeName = null; switch ($this->type) { case SharingPlugin::STATUS_ACCEPTED: $nodeName = 'cs:invite-accepted'; break; case SharingPlugin::STATUS_DECLINED: $nodeName = 'cs:invite-declined'; break; case SharingPlugin::STATUS_DELETED: $nodeName = 'cs:invite-deleted'; break; case SharingPlugin::STATUS_NORESPONSE: $nodeName = 'cs:invite-noresponse'; break; } $prop->appendChild($doc->createElement($nodeName)); $hostHref = $doc->createElement('d:href', $server->getBaseUri() . $this->hostUrl); $hostUrl = $doc->createElement('cs:hosturl'); $hostUrl->appendChild($hostHref); $prop->appendChild($hostUrl); $access = $doc->createElement('cs:access'); if ($this->readOnly) { $access->appendChild($doc->createElement('cs:read')); } else { $access->appendChild($doc->createElement('cs:read-write')); } $prop->appendChild($access); $organizerUrl = $doc->createElement('cs:organizer'); // If the organizer contains a 'mailto:' part, it means it should be // treated as absolute. if (strtolower(substr($this->organizer, 0, 7)) === 'mailto:') { $organizerHref = new DAV\Property\Href($this->organizer, false); } else { $organizerHref = new DAV\Property\Href($this->organizer, true); } $organizerHref->serialize($server, $organizerUrl); if ($this->commonName) { $commonName = $doc->createElement('cs:common-name'); $commonName->appendChild($doc->createTextNode($this->commonName)); $organizerUrl->appendChild($commonName); $commonNameOld = $doc->createElement('cs:organizer-cn'); $commonNameOld->appendChild($doc->createTextNode($this->commonName)); $prop->appendChild($commonNameOld); } if ($this->firstName) { $firstName = $doc->createElement('cs:first-name'); $firstName->appendChild($doc->createTextNode($this->firstName)); $organizerUrl->appendChild($firstName); $firstNameOld = $doc->createElement('cs:organizer-first'); $firstNameOld->appendChild($doc->createTextNode($this->firstName)); $prop->appendChild($firstNameOld); } if ($this->lastName) { $lastName = $doc->createElement('cs:last-name'); $lastName->appendChild($doc->createTextNode($this->lastName)); $organizerUrl->appendChild($lastName); $lastNameOld = $doc->createElement('cs:organizer-last'); $lastNameOld->appendChild($doc->createTextNode($this->lastName)); $prop->appendChild($lastNameOld); } $prop->appendChild($organizerUrl); if ($this->summary) { $summary = $doc->createElement('cs:summary'); $summary->appendChild($doc->createTextNode($this->summary)); $prop->appendChild($summary); } if ($this->supportedComponents) { $xcomp = $doc->createElement('cal:supported-calendar-component-set'); $this->supportedComponents->serialize($server, $xcomp); $prop->appendChild($xcomp); } }
/** * This event is triggered when the server didn't know how to handle a * certain request. * * We intercept this to handle POST requests on calendars. * * @param string $method * @param string $uri * @return null|bool */ public function unknownMethod($method, $uri) { if ($method !== 'POST') { return; } // Only handling xml $contentType = $this->server->httpRequest->getHeader('Content-Type'); if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) { return; } // Making sure the node exists try { $node = $this->server->tree->getNodeForPath($uri); } catch (DAV\Exception\NotFound $e) { return; } $requestBody = $this->server->httpRequest->getBody(true); // If this request handler could not deal with this POST request, it // will return 'null' and other plugins get a chance to handle the // request. // // However, we already requested the full body. This is a problem, // because a body can only be read once. This is why we preemptively // re-populated the request body with the existing data. $this->server->httpRequest->setBody($requestBody); $dom = DAV\XMLUtil::loadDOMDocument($requestBody); $documentType = DAV\XMLUtil::toClarkNotation($dom->firstChild); switch ($documentType) { // Dealing with the 'share' document, which modified invitees on a // calendar. case '{' . Plugin::NS_CALENDARSERVER . '}share': // We can only deal with IShareableCalendar objects if (!$node instanceof IShareableCalendar) { return; } // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($uri, '{DAV:}write'); } $mutations = $this->parseShareRequest($dom); $node->updateShares($mutations[0], $mutations[1]); $this->server->httpResponse->sendStatus(200); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; // The invite-reply document is sent when the user replies to an // invitation of a calendar share. // The invite-reply document is sent when the user replies to an // invitation of a calendar share. case '{' . Plugin::NS_CALENDARSERVER . '}invite-reply': // This only works on the calendar-home-root node. if (!$node instanceof UserCalendars) { return; } // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($uri, '{DAV:}write'); } $message = $this->parseInviteReplyRequest($dom); $url = $node->shareReply($message['href'], $message['status'], $message['calendarUri'], $message['inReplyTo'], $message['summary']); $this->server->httpResponse->sendStatus(200); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); if ($url) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $root = $dom->createElement('cs:shared-as'); foreach ($this->server->xmlNamespaces as $namespace => $prefix) { $root->setAttribute('xmlns:' . $prefix, $namespace); } $dom->appendChild($root); $href = new DAV\Property\Href($url); $href->serialize($this->server, $root); $this->server->httpResponse->setHeader('Content-Type', 'application/xml'); $this->server->httpResponse->sendBody($dom->saveXML()); } // Breaking the event chain return false; case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar': // We can only deal with IShareableCalendar objects if (!$node instanceof IShareableCalendar) { return; } // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($uri, '{DAV:}write'); } $node->setPublishStatus(true); // iCloud sends back the 202, so we will too. $this->server->httpResponse->sendStatus(202); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar': // We can only deal with IShareableCalendar objects if (!$node instanceof IShareableCalendar) { return; } // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($uri, '{DAV:}write'); } $node->setPublishStatus(false); $this->server->httpResponse->sendStatus(200); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; } }