diff options
author | Jakob Sack <kde@jakobsack.de> | 2011-07-20 15:53:34 +0200 |
---|---|---|
committer | Jakob Sack <kde@jakobsack.de> | 2011-07-20 15:53:34 +0200 |
commit | bf1ca75710a99a96ba39790e9db79bb0a0f950b4 (patch) | |
tree | a1905c91590944e4558a6d2c5bd856be70a70f0e /3dparty/Sabre/CardDAV | |
parent | 6230001a3c80c081001c46197cc95403cc73622f (diff) | |
download | nextcloud-server-bf1ca75710a99a96ba39790e9db79bb0a0f950b4.tar.gz nextcloud-server-bf1ca75710a99a96ba39790e9db79bb0a0f950b4.zip |
Integration of SabreDAV
Diffstat (limited to '3dparty/Sabre/CardDAV')
-rw-r--r-- | 3dparty/Sabre/CardDAV/AddressBook.php | 225 | ||||
-rw-r--r-- | 3dparty/Sabre/CardDAV/AddressBookQueryParser.php | 211 | ||||
-rw-r--r-- | 3dparty/Sabre/CardDAV/AddressBookRoot.php | 72 | ||||
-rw-r--r-- | 3dparty/Sabre/CardDAV/Backend/Abstract.php | 123 | ||||
-rw-r--r-- | 3dparty/Sabre/CardDAV/Backend/PDO.php | 265 | ||||
-rw-r--r-- | 3dparty/Sabre/CardDAV/Card.php | 151 | ||||
-rw-r--r-- | 3dparty/Sabre/CardDAV/IAddressBook.php | 18 | ||||
-rw-r--r-- | 3dparty/Sabre/CardDAV/ICard.php | 20 | ||||
-rw-r--r-- | 3dparty/Sabre/CardDAV/IDirectory.php | 21 | ||||
-rw-r--r-- | 3dparty/Sabre/CardDAV/Plugin.php | 465 | ||||
-rw-r--r-- | 3dparty/Sabre/CardDAV/UserAddressBooks.php | 171 | ||||
-rw-r--r-- | 3dparty/Sabre/CardDAV/Version.php | 28 |
12 files changed, 1770 insertions, 0 deletions
diff --git a/3dparty/Sabre/CardDAV/AddressBook.php b/3dparty/Sabre/CardDAV/AddressBook.php new file mode 100644 index 00000000000..a4d2d0c4783 --- /dev/null +++ b/3dparty/Sabre/CardDAV/AddressBook.php @@ -0,0 +1,225 @@ +<?php + +/** + * UserAddressBook class + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ + +/** + * The AddressBook class represents a CardDAV addressbook, owned by a specific user + * + * The AddressBook can contain multiple vcards + */ +class Sabre_CardDAV_AddressBook extends Sabre_DAV_Directory implements Sabre_CardDAV_IAddressBook, Sabre_DAV_IProperties { + + /** + * This is an array with addressbook information + * + * @var array + */ + private $addressBookInfo; + + /** + * CardDAV backend + * + * @var Sabre_CardDAV_Backend_Abstract + */ + private $carddavBackend; + + /** + * Constructor + * + * @param Sabre_CardDAV_Backend_Abstract $carddavBackend + * @param array $addressBookInfo + * @return void + */ + public function __construct(Sabre_CardDAV_Backend_Abstract $carddavBackend,array $addressBookInfo) { + + $this->carddavBackend = $carddavBackend; + $this->addressBookInfo = $addressBookInfo; + + } + + /** + * Returns the name of the addressbook + * + * @return string + */ + public function getName() { + + return $this->addressBookInfo['uri']; + + } + + /** + * Returns a card + * + * @param string $name + * @return Sabre_DAV_Card + */ + public function getChild($name) { + + $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'],$name); + if (!$obj) throw new Sabre_DAV_Exception_FileNotFound('Card not found'); + return new Sabre_CardDAV_Card($this->carddavBackend,$this->addressBookInfo,$obj); + + } + + /** + * Returns the full list of cards + * + * @return array + */ + public function getChildren() { + + $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']); + $children = array(); + foreach($objs as $obj) { + $children[] = new Sabre_CardDAV_Card($this->carddavBackend,$this->addressBookInfo,$obj); + } + return $children; + + } + + /** + * Creates a new directory + * + * We actually block this, as subdirectories are not allowed in addressbooks. + * + * @param string $name + * @return void + */ + public function createDirectory($name) { + + throw new Sabre_DAV_Exception_MethodNotAllowed('Creating collections in addressbooks is not allowed'); + + } + + /** + * Creates a new file + * + * The contents of the new file must be a valid VCARD + * + * @param string $name + * @param resource $vcardData + * @return void + */ + public function createFile($name,$vcardData = null) { + + $vcardData = stream_get_contents($vcardData); + + $this->carddavBackend->createCard($this->addressBookInfo['id'],$name,$vcardData); + + } + + /** + * Deletes the entire addressbook. + * + * @return void + */ + public function delete() { + + $this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']); + + } + + /** + * Renames the addressbook. Note that most calendars use the + * {DAV:}displayname to display a name to display a name. + * + * @param string $newName + * @return void + */ + public function setName($newName) { + + throw new Sabre_DAV_Exception_MethodNotAllowed('Renaming addressbooks is not yet supported'); + + } + + /** + * Returns the last modification date as a unix timestamp. + * + * @return void + */ + public function getLastModified() { + + return null; + + } + + /** + * Updates properties on this node, + * + * The properties array uses the propertyName in clark-notation as key, + * and the array value for the property value. In the case a property + * should be deleted, the property value will be null. + * + * This method must be atomic. If one property cannot be changed, the + * entire operation must fail. + * + * If the operation was successful, true can be returned. + * If the operation failed, false can be returned. + * + * Deletion of a non-existant property is always succesful. + * + * Lastly, it is optional to return detailed information about any + * failures. In this case an array should be returned with the following + * structure: + * + * array( + * 403 => array( + * '{DAV:}displayname' => null, + * ), + * 424 => array( + * '{DAV:}owner' => null, + * ) + * ) + * + * In this example it was forbidden to update {DAV:}displayname. + * (403 Forbidden), which in turn also caused {DAV:}owner to fail + * (424 Failed Dependency) because the request needs to be atomic. + * + * @param array $mutations + * @return bool|array + */ + public function updateProperties($mutations) { + + return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $mutations); + + } + + /** + * Returns a list of properties for this nodes. + * + * The properties list is a list of propertynames the client requested, + * encoded in clark-notation {xmlnamespace}tagname + * + * If the array is empty, it means 'all properties' were requested. + * + * @param array $properties + * @return void + */ + public function getProperties($properties) { + + $response = array(); + foreach($properties as $propertyName) { + + if (isset($this->addressBookInfo[$propertyName])) { + + $response[$propertyName] = $this->addressBookInfo[$propertyName]; + + } + + } + + return $response; + + } + + +} diff --git a/3dparty/Sabre/CardDAV/AddressBookQueryParser.php b/3dparty/Sabre/CardDAV/AddressBookQueryParser.php new file mode 100644 index 00000000000..08adc3b8157 --- /dev/null +++ b/3dparty/Sabre/CardDAV/AddressBookQueryParser.php @@ -0,0 +1,211 @@ +<?php + +/** + * Parses the addressbook-query report request body. + * + * Whoever designed this format, and the CalDAV equavalent even more so, + * has no feel for design. + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Sabre_CardDAV_AddressBookQueryParser { + + const TEST_ANYOF = 'anyof'; + const TEST_ALLOF = 'allof'; + + /** + * List of requested properties the client wanted + * + * @var array + */ + public $requestedProperties; + + /** + * The number of results the client wants + * + * null means it wasn't specified, which in most cases means 'all results'. + * + * @var int|null + */ + public $limit; + + /** + * List of property filters. + * + * @var array + */ + public $filters; + + /** + * Either TEST_ANYOF or TEST_ALLOF + * + * @var string + */ + public $test; + + /** + * DOM Document + * + * @var DOMDocument + */ + protected $dom; + + /** + * DOM XPath object + * + * @var DOMXPath + */ + protected $xpath; + + /** + * Creates the parser + * + * @param DOMNode $dom + * @return void + */ + public function __construct(DOMDocument $dom) { + + $this->dom = $dom; + + $this->xpath = new DOMXPath($dom); + $this->xpath->registerNameSpace('card',Sabre_CardDAV_Plugin::NS_CARDDAV); + + } + + /** + * Parses the request. + * + * @param DOMNode $dom + * @return void + */ + public function parse() { + + $filterNode = null; + + $limit = $this->xpath->evaluate('number(/card:addressbook-query/card:limit/card:nresults)'); + if (is_nan($limit)) $limit = null; + + $filter = $this->xpath->query('/card:addressbook-query/card:filter'); + if ($filter->length !== 1) { + throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed'); + } + + $filter = $filter->item(0); + $test = $this->xpath->evaluate('string(@test)', $filter); + if (!$test) $test = self::TEST_ANYOF; + if ($test !== self::TEST_ANYOF && $test !== self::TEST_ALLOF) { + throw new Sabre_DAV_Exception_BadRequest('The test attribute must either hold "anyof" or "allof"'); + } + + $propFilters = array(); + + $propFilterNodes = $this->xpath->query('card:prop-filter', $filter); + for($ii=0; $ii < $propFilterNodes->length; $ii++) { + + $propFilters[] = $this->parsePropFilterNode($propFilterNodes->item($ii)); + + + } + + $this->filters = $propFilters; + $this->limit = $limit; + $this->requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild)); + $this->test = $test; + + } + + /** + * Parses the prop-filter xml element + * + * @param DOMElement $propFilterNode + * @return array + */ + protected function parsePropFilterNode(DOMElement $propFilterNode) { + + $propFilter = array(); + $propFilter['name'] = $propFilterNode->getAttribute('name'); + $propFilter['test'] = $propFilterNode->getAttribute('test'); + if (!$propFilter['test']) $propFilter['test'] = 'anyof'; + + $propFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $propFilterNode)->length>0; + + $paramFilterNodes = $this->xpath->query('card:param-filter', $propFilterNode); + + $propFilter['param-filters'] = array(); + + + for($ii=0;$ii<$paramFilterNodes->length;$ii++) { + + $propFilter['param-filters'][] = $this->parseParamFilterNode($paramFilterNodes->item($ii)); + + } + $propFilter['text-matches'] = array(); + $textMatchNodes = $this->xpath->query('card:text-match', $propFilterNode); + + for($ii=0;$ii<$textMatchNodes->length;$ii++) { + + $propFilter['text-matches'][] = $this->parseTextMatchNode($textMatchNodes->item($ii)); + + } + + return $propFilter; + + } + + /** + * Parses the param-filter element + * + * @param DOMElement $paramFilterNode + * @return array + */ + public function parseParamFilterNode(DOMElement $paramFilterNode) { + + $paramFilter = array(); + $paramFilter['name'] = $paramFilterNode->getAttribute('name'); + $paramFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $paramFilterNode)->length>0; + $paramFilter['text-match'] = null; + + $textMatch = $this->xpath->query('card:text-match', $paramFilterNode); + if ($textMatch->length>0) { + $paramFilter['text-match'] = $this->parseTextMatchNode($textMatch->item(0)); + } + + return $paramFilter; + + } + + /** + * Text match + * + * @param DOMElement $textMatchNode + * @return void + */ + public function parseTextMatchNode(DOMElement $textMatchNode) { + + $matchType = $textMatchNode->getAttribute('match-type'); + if (!$matchType) $matchType = 'contains'; + + if (!in_array($matchType, array('contains', 'equals', 'starts-with', 'ends-with'))) { + throw new Sabre_DAV_Exception_BadRequest('Unknown match-type: ' . $matchType); + } + + $negateCondition = $textMatchNode->getAttribute('negate-condition'); + $negateCondition = $negateCondition==='yes'; + $collation = $textMatchNode->getAttribute('collation'); + if (!$collation) $collation = 'i;unicode-casemap'; + + return array( + 'negate-condition' => $negateCondition, + 'collation' => $collation, + 'match-type' => $matchType, + 'value' => $textMatchNode->nodeValue + ); + + + } + +} diff --git a/3dparty/Sabre/CardDAV/AddressBookRoot.php b/3dparty/Sabre/CardDAV/AddressBookRoot.php new file mode 100644 index 00000000000..88c8ed2e061 --- /dev/null +++ b/3dparty/Sabre/CardDAV/AddressBookRoot.php @@ -0,0 +1,72 @@ +<?php + +/** + * AddressBook rootnode + * + * This object lists a collection of users, which can contain addressbooks. + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Sabre_CardDAV_AddressBookRoot extends Sabre_DAVACL_AbstractPrincipalCollection { + + /** + * Principal Backend + * + * @var Sabre_DAVACL_IPrincipalBackend + */ + protected $principalBackend; + + /** + * CardDAV backend + * + * @var Sabre_CardDAV_Backend_Abstract + */ + protected $carddavBackend; + + /** + * Constructor + * + * This constructor needs both a principal and a carddav backend. + * + * @param Sabre_DAVACL_IPrincipalBackend $principalBackend + * @param Sabre_CardDAV_Backend_Abstract $carddavBackend + */ + public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CardDAV_Backend_Abstract $carddavBackend) { + + $this->carddavBackend = $carddavBackend; + parent::__construct($principalBackend); + + } + + /** + * Returns the name of the node + * + * @return string + */ + public function getName() { + + return Sabre_CardDAV_Plugin::ADDRESSBOOK_ROOT; + + } + + /** + * This method returns a node for a principal. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principal + * @return Sabre_DAV_INode + */ + public function getChildForPrincipal(array $principal) { + + return new Sabre_CardDAV_UserAddressBooks($this->carddavBackend, $principal['uri']); + + } + +} diff --git a/3dparty/Sabre/CardDAV/Backend/Abstract.php b/3dparty/Sabre/CardDAV/Backend/Abstract.php new file mode 100644 index 00000000000..f6d10291ca6 --- /dev/null +++ b/3dparty/Sabre/CardDAV/Backend/Abstract.php @@ -0,0 +1,123 @@ +<?php + +/** + * Abstract Backend class + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ + +/** + * This class serves as a base-class for addressbook backends + * + * Note that there are references to 'addressBookId' scattered throughout the + * class. The value of the addressBookId is completely up to you, it can be any + * arbitrary value you can use as an unique identifier. + */ +abstract class Sabre_CardDAV_Backend_Abstract { + + /** + * Returns the list of addressbooks for a specific user. + * + * Every addressbook should have the following properties: + * id - an arbitrary unique id + * uri - the 'basename' part of the url + * principaluri - Same as the passed parameter + * + * Any additional clark-notation property may be passed besides this. Some + * common ones are : + * {DAV:}displayname + * {urn:ietf:params:xml:ns:carddav}addressbook-description + * {http://calendarserver.org/ns/}getctag + * + * @param string $principalUri + * @return array + */ + public abstract function getAddressBooksForUser($principalUri); + + /** + * Updates an addressbook's properties + * + * See Sabre_DAV_IProperties for a description of the mutations array, as + * well as the return value. + * + * @param mixed $addressBookId + * @param array $mutations + * @see Sabre_DAV_IProperties::updateProperties + * @return bool|array + */ + public abstract function updateAddressBook($addressBookId, array $mutations); + + /** + * Creates a new address book + * + * @param string $principalUri + * @param string $url Just the 'basename' of the url. + * @param array $properties + * @return void + */ + abstract public function createAddressBook($principalUri, $url, array $properties); + + /** + * Deletes an entire addressbook and all its contents + * + * @param mixed $addressBookId + * @return void + */ + abstract public function deleteAddressBook($addressBookId); + + /** + * Returns all cards for a specific addressbook id. + * + * This method should return the following properties for each card: + * * carddata - raw vcard data + * * uri - Some unique url + * * lastmodified - A unix timestamp + + * @param mixed $addressbookId + * @return array + */ + public abstract function getCards($addressbookId); + + /** + * Returns a specfic card + * + * @param mixed $addressBookId + * @param string $cardUri + * @return void + */ + public abstract function getCard($addressBookId, $cardUri); + + /** + * Creates a new card + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return bool + */ + abstract public function createCard($addressBookId, $cardUri, $cardData); + + /** + * Updates a card + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return bool + */ + abstract public function updateCard($addressBookId, $cardUri, $cardData); + + /** + * Deletes a card + * + * @param mixed $addressBookId + * @param string $cardUri + * @return bool + */ + abstract public function deleteCard($addressBookId, $cardUri); + +} diff --git a/3dparty/Sabre/CardDAV/Backend/PDO.php b/3dparty/Sabre/CardDAV/Backend/PDO.php new file mode 100644 index 00000000000..e7cd4ecd4df --- /dev/null +++ b/3dparty/Sabre/CardDAV/Backend/PDO.php @@ -0,0 +1,265 @@ +<?php + +/** + * PDO CardDAV backend + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ + +/** + * This CardDAV backend uses PDO to store addressbooks + */ +class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract { + + /** + * PDO connection + * + * @var PDO + */ + protected $pdo; + + /** + * Sets up the object + * + * @param PDO $pdo + */ + public function __construct(PDO $pdo) { + + $this->pdo = $pdo; + + } + + /** + * Returns the list of addressbooks for a specific user. + * + * @param string $principalUri + * @return array + */ + public function getAddressBooksForUser($principalUri) { + + $stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, ctag FROM addressbooks WHERE principaluri = ?'); + $result = $stmt->execute(array($principalUri)); + + $addressBooks = array(); + + foreach($stmt->fetchAll() as $row) { + + $addressBooks[] = array( + 'id' => $row['id'], + 'uri' => $row['uri'], + 'principaluri' => $row['principaluri'], + '{DAV:}displayname' => $row['displayname'], + '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'], + '{http://calendarserver.org/ns/}getctag' => $row['ctag'], + ); + + } + + return $addressBooks; + + } + + + /** + * Updates an addressbook's properties + * + * See Sabre_DAV_IProperties for a description of the mutations array, as + * well as the return value. + * + * @param mixed $addressBookId + * @param array $mutations + * @see Sabre_DAV_IProperties::updateProperties + * @return bool|array + */ + public function updateAddressBook($addressBookId, array $mutations) { + + $updates = array(); + + foreach($mutations as $property=>$newValue) { + + switch($property) { + case '{DAV:}displayname' : + $updates['displayname'] = $newValue; + break; + case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' : + $updates['description'] = $newValue; + break; + default : + // If any unsupported values were being updated, we must + // let the entire request fail. + return false; + } + + } + + // No values are being updated? + if (!$updates) { + return false; + } + + $query = 'UPDATE addressbooks SET ctag = ctag + 1 '; + foreach($updates as $key=>$value) { + $query.=', `' . $key . '` = :' . $key . ' '; + } + $query.=' WHERE id = :addressbookid'; + + $stmt = $this->pdo->prepare($query); + $updates['addressbookid'] = $addressBookId; + + $stmt->execute($updates); + + return true; + + } + + /** + * Creates a new address book + * + * @param string $principalUri + * @param string $url Just the 'basename' of the url. + * @param array $properties + * @return void + */ + public function createAddressBook($principalUri, $url, array $properties) { + + $values = array( + 'displayname' => null, + 'description' => null, + 'principaluri' => $principalUri, + 'uri' => $url, + ); + + foreach($properties as $property=>$newValue) { + + switch($property) { + case '{DAV:}displayname' : + $values['displayname'] = $newValue; + break; + case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' : + $values['description'] = $newValue; + break; + default : + throw new Sabre_DAV_Exception_BadRequest('Unknown property: ' . $property); + } + + } + + $query = 'INSERT INTO addressbooks (uri, displayname, description, principaluri, ctag) VALUES (:uri, :displayname, :description, :principaluri, 1)'; + $stmt = $this->pdo->prepare($query); + $stmt->execute($values); + + } + + /** + * Deletes an entire addressbook and all its contents + * + * @param int $addressBookId + * @return void + */ + public function deleteAddressBook($addressBookId) { + + $stmt = $this->pdo->prepare('DELETE FROM cards WHERE addressbookid = ?'); + $stmt->execute(array($addressBookId)); + + $stmt = $this->pdo->prepare('DELETE FROM addressbooks WHERE id = ?'); + $stmt->execute(array($addressBookId)); + + } + + /** + * Returns all cards for a specific addressbook id. + * + * @param mixed $addressbookId + * @return array + */ + public function getCards($addressbookId) { + + $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM cards WHERE addressbookid = ?'); + $stmt->execute(array($addressbookId)); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); + + + } + /** + * Returns a specfic card + * + * @param mixed $addressBookId + * @param string $cardUri + * @return array + */ + public function getCard($addressBookId, $cardUri) { + + $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM cards WHERE addressbookid = ? AND uri = ? LIMIT 1'); + $stmt->execute(array($addressBookId, $cardUri)); + + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + + return (count($result)>0?$result[0]:false); + + } + + /** + * Creates a new card + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return bool + */ + public function createCard($addressBookId, $cardUri, $cardData) { + + $stmt = $this->pdo->prepare('INSERT INTO cards (carddata, uri, lastmodified, addressbookid) VALUES (?, ?, ?, ?)'); + + $result = $stmt->execute(array($cardData, $cardUri, time(), $addressBookId)); + + $stmt2 = $this->pdo->prepare('UPDATE addressbooks SET ctag = ctag + 1 WHERE id = ?'); + $stmt2->execute(array($addressBookId)); + + return $result; + + } + + /** + * Updates a card + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return bool + */ + public function updateCard($addressBookId, $cardUri, $cardData) { + + $stmt = $this->pdo->prepare('UPDATE cards SET carddata = ?, lastmodified = ? WHERE uri = ? AND addressbookid =?'); + $result = $stmt->execute(array($cardData, time(), $cardUri, $addressBookId)); + + $stmt2 = $this->pdo->prepare('UPDATE addressbooks SET ctag = ctag + 1 WHERE id = ?'); + $stmt2->execute(array($addressBookId)); + + return $stmt->rowCount()===1; + + } + + /** + * Deletes a card + * + * @param mixed $addressBookId + * @param string $cardUri + * @return bool + */ + public function deleteCard($addressBookId, $cardUri) { + + $stmt = $this->pdo->prepare('DELETE FROM cards WHERE addressbookid = ? AND uri = ?'); + $stmt->execute(array($addressBookId, $cardUri)); + + $stmt2 = $this->pdo->prepare('UPDATE addressbooks SET ctag = ctag + 1 WHERE id = ?'); + $stmt2->execute(array($addressBookId)); + + return $stmt->rowCount()===1; + + } +} diff --git a/3dparty/Sabre/CardDAV/Card.php b/3dparty/Sabre/CardDAV/Card.php new file mode 100644 index 00000000000..a12e6d3914b --- /dev/null +++ b/3dparty/Sabre/CardDAV/Card.php @@ -0,0 +1,151 @@ +<?php + +/** + * Card class + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + * + / +/** + * The Card object represents a single Card from an addressbook + */ +class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard { + + /** + * CardDAV backend + * + * @var Sabre_CardDAV_Backend_Abstract + */ + private $carddavBackend; + + /** + * Array with information about this Card + * + * @var array + */ + private $cardData; + + /** + * Array with information about the containing addressbook + * + * @var array + */ + private $addressBookInfo; + + /** + * Constructor + * + * @param Sabre_CardDAV_Backend_Abstract $carddavBackend + * @param array $addressBookInfo + * @param array $cardData + */ + public function __construct(Sabre_CardDAV_Backend_Abstract $carddavBackend,array $addressBookInfo,array $cardData) { + + $this->carddavBackend = $carddavBackend; + $this->addressBookInfo = $addressBookInfo; + $this->cardData = $cardData; + + } + + /** + * Returns the uri for this object + * + * @return string + */ + public function getName() { + + return $this->cardData['uri']; + + } + + /** + * Returns the VCard-formatted object + * + * @return string + */ + public function get() { + + $cardData = $this->cardData['carddata']; + $s = fopen('php://temp','r+'); + fwrite($s, $cardData); + rewind($s); + return $s; + + } + + /** + * Updates the VCard-formatted object + * + * @param string $cardData + * @return void + */ + public function put($cardData) { + + if (is_resource($cardData)) + $cardData = stream_get_contents($cardData); + + $this->carddavBackend->updateCard($this->addressBookInfo['id'],$this->cardData['uri'],$cardData); + $this->cardData['carddata'] = $cardData; + + } + + /** + * Deletes the card + * + * @return void + */ + public function delete() { + + $this->carddavBackend->deleteCard($this->addressBookInfo['id'],$this->cardData['uri']); + + } + + /** + * Returns the mime content-type + * + * @return string + */ + public function getContentType() { + + return 'text/x-vcard'; + + } + + /** + * Returns an ETag for this object + * + * @return string + */ + public function getETag() { + + return md5($this->cardData['carddata']); + + } + + /** + * Returns the last modification date as a unix timestamp + * + * @return time + */ + public function getLastModified() { + + return isset($this->cardData['lastmodified'])?$this->cardData['lastmodified']:null; + + } + + /** + * Returns the size of this object in bytes + * + * @return int + */ + public function getSize() { + + return strlen($this->cardData['carddata']); + + } +} + diff --git a/3dparty/Sabre/CardDAV/IAddressBook.php b/3dparty/Sabre/CardDAV/IAddressBook.php new file mode 100644 index 00000000000..a0dffb30aea --- /dev/null +++ b/3dparty/Sabre/CardDAV/IAddressBook.php @@ -0,0 +1,18 @@ +<?php + +/** + * AddressBook interface + * + * Implement this interface to allow a node to be recognized as an addressbook. + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +interface Sabre_CardDAV_IAddressBook extends Sabre_DAV_ICollection { + + + +} diff --git a/3dparty/Sabre/CardDAV/ICard.php b/3dparty/Sabre/CardDAV/ICard.php new file mode 100644 index 00000000000..8f9bb097b56 --- /dev/null +++ b/3dparty/Sabre/CardDAV/ICard.php @@ -0,0 +1,20 @@ +<?php + +/** + * Card interface + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + * + / +/** + * Extend the ICard interface to allow your custom nodes to be picked up as + * 'Cards'. + */ +interface Sabre_CardDAV_ICard extends Sabre_DAV_IFile { + +} + diff --git a/3dparty/Sabre/CardDAV/IDirectory.php b/3dparty/Sabre/CardDAV/IDirectory.php new file mode 100644 index 00000000000..e0d0797d285 --- /dev/null +++ b/3dparty/Sabre/CardDAV/IDirectory.php @@ -0,0 +1,21 @@ +<?php + +/** + * IDirectory interface + * + * Implement this interface to have an addressbook marked as a 'directory'. A + * directory is an (often) global addressbook. + * + * A full description can be found in the IETF draft: + * - draft-daboo-carddav-directory-gateway + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +interface Sabre_CardDAV_IDirectory extends Sabre_CardDAV_IAddressBook { + + +} diff --git a/3dparty/Sabre/CardDAV/Plugin.php b/3dparty/Sabre/CardDAV/Plugin.php new file mode 100644 index 00000000000..16fadc526e4 --- /dev/null +++ b/3dparty/Sabre/CardDAV/Plugin.php @@ -0,0 +1,465 @@ +<?php + +/** + * CardDAV plugin + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ + + +/** + * The CardDAV plugin adds CardDAV functionality to the WebDAV server + */ +class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { + + /** + * Url to the addressbooks + */ + const ADDRESSBOOK_ROOT = 'addressbooks'; + + /** + * xml namespace for CardDAV elements + */ + const NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav'; + + /** + * Add urls to this property to have them automatically exposed as + * 'directories' to the user. + * + * @var array + */ + public $directories = array(); + + /** + * Server class + * + * @var Sabre_DAV_Server + */ + protected $server; + + /** + * Initializes the plugin + * + * @param Sabre_DAV_Server $server + * @return void + */ + public function initialize(Sabre_DAV_Server $server) { + + /* Events */ + $server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties')); + $server->subscribeEvent('report', array($this,'report')); + + /* Namespaces */ + $server->xmlNamespaces[self::NS_CARDDAV] = 'card'; + + /* Mapping Interfaces to {DAV:}resourcetype values */ + $server->resourceTypeMapping['Sabre_CardDAV_IAddressBook'] = '{' . self::NS_CARDDAV . '}addressbook'; + $server->resourceTypeMapping['Sabre_CardDAV_IDirectory'] = '{' . self::NS_CARDDAV . '}directory'; + + /* Adding properties that may never be changed */ + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-address-data'; + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}max-resource-size'; + + + $this->server = $server; + + } + + /** + * Returns a list of supported features. + * + * This is used in the DAV: header in the OPTIONS and PROPFIND requests. + * + * @return array + */ + public function getFeatures() { + + return array('addressbook'); + + } + + /** + * Returns a list of reports this plugin supports. + * + * This will be used in the {DAV:}supported-report-set property. + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * + * @param string $uri + * @return array + */ + public function getSupportedReportSet($uri) { + + $node = $this->server->tree->getNodeForPath($uri); + if ($node instanceof Sabre_CardDAV_AddressBook || $node instanceof Sabre_CardDAV_ICard) { + return array( + '{' . self::NS_CARDDAV . '}addressbook-multiget', + ); + } + return array(); + + } + + + /** + * Adds all CardDAV-specific properties + * + * @param string $path + * @param Sabre_DAV_INode $node + * @param array $requestedProperties + * @param array $returnedProperties + * @return void + */ + public function beforeGetProperties($path, Sabre_DAV_INode $node, array &$requestedProperties, array &$returnedProperties) { + + if ($node instanceof Sabre_DAVACL_IPrincipal) { + + // calendar-home-set property + $addHome = '{' . self::NS_CARDDAV . '}addressbook-home-set'; + if (in_array($addHome,$requestedProperties)) { + $principalId = $node->getName(); + $addressbookHomePath = self::ADDRESSBOOK_ROOT . '/' . $principalId . '/'; + unset($requestedProperties[array_search($addHome, $requestedProperties)]); + $returnedProperties[200][$addHome] = new Sabre_DAV_Property_Href($addressbookHomePath); + } + + $directories = '{' . self::NS_CARDDAV . '}directory-gateway'; + if ($this->directories && in_array($directories, $requestedProperties)) { + unset($requestedProperties[array_search($directories, $requestedProperties)]); + $returnedProperties[200][$directories] = new Sabre_DAV_Property_HrefList($this->directories); + } + + } + + if ($node instanceof Sabre_CardDAV_ICard) { + + // The address-data property is not supposed to be a 'real' + // property, but in large chunks of the spec it does act as such. + // Therefore we simply expose it as a property. + $addressDataProp = '{' . self::NS_CARDDAV . '}address-data'; + if (in_array($addressDataProp, $requestedProperties)) { + unset($requestedProperties[$addressDataProp]); + $val = $node->get(); + if (is_resource($val)) + $val = stream_get_contents($val); + + // Taking out \r to not screw up the xml output + $returnedProperties[200][$addressDataProp] = str_replace("\r","", $val); + + } + } + + } + + /** + * This functions handles REPORT requests specific to CardDAV + * + * @param string $reportName + * @param DOMNode $dom + * @return bool + */ + public function report($reportName,$dom) { + + switch($reportName) { + case '{'.self::NS_CARDDAV.'}addressbook-multiget' : + $this->addressbookMultiGetReport($dom); + return false; + case '{'.self::NS_CARDDAV.'}addressbook-query' : + $this->addressBookQueryReport($dom); + return false; + default : + return; + + } + + + } + + /** + * This function handles the addressbook-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 + */ + public function addressbookMultiGetReport($dom) { + + $properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild)); + + $hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href'); + $propertyList = array(); + + foreach($hrefElems as $elem) { + + $uri = $this->server->calculateUri($elem->nodeValue); + list($propertyList[]) = $this->server->getPropertiesForPath($uri,$properties); + + } + + $this->server->httpResponse->sendStatus(207); + $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); + $this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList)); + + } + + /** + * This function handles the addressbook-query REPORT + * + * This report is used by the client to filter an addressbook based on a + * complex query. + * + * @param DOMNode $dom + * @return void + */ + protected function addressbookQueryReport($dom) { + + $query = new Sabre_CardDAV_AddressBookQueryParser($dom); + $query->parse(); + + $depth = $this->server->getHTTPDepth(0); + + if ($depth==0) { + $candidateNodes = array( + $this->server->tree->getNodeForPath($this->server->getRequestUri()) + ); + } else { + $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri()); + } + + $validNodes = array(); + foreach($candidateNodes as $node) { + + if (!$node instanceof Sabre_CardDAV_ICard) + continue; + + $blob = $node->get(); + if (is_resource($blob)) { + $blob = stream_get_contents($blob); + } + + if (!$this->validateFilters($blob, $query->filters, $query->test)) { + continue; + } + + $validNodes[] = $node; + + if ($query->limit && $query->limit <= count($validNodes)) { + // We hit the maximum number of items, we can stop now. + break; + } + + } + + $result = array(); + foreach($validNodes as $validNode) { + if ($depth==0) { + $href = $this->server->getRequestUri(); + } else { + $href = $this->server->getRequestUri() . '/' . $validNode->getName(); + } + + list($result[]) = $this->server->getPropertiesForPath($href, $query->requestedProperties, 0); + + } + + $this->server->httpResponse->sendStatus(207); + $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); + $this->server->httpResponse->sendBody($this->server->generateMultiStatus($result)); + + } + + /** + * Validates if a vcard makes it throught a list of filters. + * + * @param string $vcardData + * @param array $filters + * @param string $test anyof or allof (which means OR or AND) + * @return bool + */ + public function validateFilters($vcardData, array $filters, $test) { + + $vcard = Sabre_VObject_Reader::read($vcardData); + + $success = true; + + foreach($filters as $filter) { + + $isDefined = isset($vcard->{$filter['name']}); + if ($filter['is-not-defined']) { + if ($isDefined) { + $success = false; + } else { + $success = true; + } + } elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) { + + // We only need to check for existence + $success = $isDefined; + + } else { + + $vProperties = $vcard->select($filter['name']); + + $results = array(); + if ($filter['param-filters']) { + $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']); + } + if ($filter['text-matches']) { + $texts = array(); + foreach($vProperties as $vProperty) + $texts[] = $vProperty->value; + + $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']); + } + + if (count($results)===1) { + $success = $results[0]; + } else { + if ($filter['test'] === 'anyof') { + $success = $results[0] || $results[1]; + } else { + $success = $results[0] && $results[1]; + } + } + + } // else + + // There are two conditions where we can already determine wether + // or not this filter succeeds. + if ($test==='anyof' && $success) { + return true; + } + if ($test==='allof' && !$success) { + return false; + } + + } // foreach + + // If we got all the way here, it means we haven't been able to + // determine early if the test failed or not. + // + // This implies for 'anyof' that the test failed, and for 'allof' that + // we succeeded. Sounds weird, but makes sense. + return $test==='allof'; + + } + + /** + * Validates if a param-filter can be applied to a specific property. + * + * @todo currently we're only validating the first parameter of the passed + * property. Any subsequence parameters with the same name are + * ignored. + * @param Sabre_VObject_Property $vProperty + * @param array $filters + * @param string $test + * @return bool + */ + protected function validateParamFilters(array $vProperties, array $filters, $test) { + + $success = false; + foreach($filters as $filter) { + + $isDefined = false; + foreach($vProperties as $vProperty) { + $isDefined = isset($vProperty[$filter['name']]); + if ($isDefined) break; + } + + if ($filter['is-not-defined']) { + if ($isDefined) { + $success = false; + } else { + $success = true; + } + + // If there's no text-match, we can just check for existence + } elseif (!$filter['text-match'] || !$isDefined) { + + $success = $isDefined; + + } else { + + $texts = array(); + $success = false; + foreach($vProperties as $vProperty) { + // If we got all the way here, we'll need to validate the + // text-match filter. + $success = Sabre_DAV_StringUtil::textMatch($vProperty[$filter['name']]->value, $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['matchType']); + if ($success) break; + } + if ($filter['text-match']['negate-condition']) { + $success = !$success; + } + + } // else + + // There are two conditions where we can already determine wether + // or not this filter succeeds. + if ($test==='anyof' && $success) { + return true; + } + if ($test==='allof' && !$success) { + return false; + } + + } + + // If we got all the way here, it means we haven't been able to + // determine early if the test failed or not. + // + // This implies for 'anyof' that the test failed, and for 'allof' that + // we succeeded. Sounds weird, but makes sense. + return $test==='allof'; + + } + + /** + * Validates if a text-filter can be applied to a specific property. + * + * @param array $texts + * @param array $filters + * @param string $test + * @return bool + */ + protected function validateTextMatches(array $texts, array $filters, $test) { + + foreach($filters as $filter) { + + $success = false; + foreach($texts as $haystack) { + $success = Sabre_DAV_StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['matchType']); + + // Breaking on the first match + if ($success) break; + } + if ($filter['negate-condition']) { + $success = !$success; + } + + if ($success && $test==='anyof') + return true; + + if (!$success && $test=='allof') + return false; + + + } + + // If we got all the way here, it means we haven't been able to + // determine early if the test failed or not. + // + // This implies for 'anyof' that the test failed, and for 'allof' that + // we succeeded. Sounds weird, but makes sense. + return $test==='allof'; + + } + + +} diff --git a/3dparty/Sabre/CardDAV/UserAddressBooks.php b/3dparty/Sabre/CardDAV/UserAddressBooks.php new file mode 100644 index 00000000000..186bf016a1e --- /dev/null +++ b/3dparty/Sabre/CardDAV/UserAddressBooks.php @@ -0,0 +1,171 @@ +<?php + +/** + * UserAddressBooks class + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ + +/** + * The UserAddressBooks collection contains a list of addressbooks associated with a user + */ +class Sabre_CardDAV_UserAddressBooks extends Sabre_DAV_Directory implements Sabre_DAV_IExtendedCollection { + + /** + * Principal uri + * + * @var array + */ + protected $principalUri; + + /** + * carddavBackend + * + * @var Sabre_CardDAV_Backend_Abstract + */ + protected $carddavBackend; + + /** + * Constructor + * + * @param Sabre_CardDAV_Backend_Abstract $carddavBackend + * @param string $principalUri + */ + public function __construct(Sabre_CardDAV_Backend_Abstract $carddavBackend, $principalUri) { + + $this->carddavBackend = $carddavBackend; + $this->principalUri = $principalUri; + + } + + /** + * Returns the name of this object + * + * @return string + */ + public function getName() { + + list(,$name) = Sabre_DAV_URLUtil::splitPath($this->principalUri); + return $name; + + } + + /** + * Updates the name of this object + * + * @param string $name + * @return void + */ + public function setName($name) { + + throw new Sabre_DAV_Exception_MethodNotAllowed(); + + } + + /** + * Deletes this object + * + * @return void + */ + public function delete() { + + throw new Sabre_DAV_Exception_MethodNotAllowed(); + + } + + /** + * Returns the last modification date + * + * @return int + */ + public function getLastModified() { + + return null; + + } + + /** + * Creates a new file under this object. + * + * This is currently not allowed + * + * @param string $filename + * @param resource $data + * @return void + */ + public function createFile($filename, $data=null) { + + throw new Sabre_DAV_Exception_MethodNotAllowed('Creating new files in this collection is not supported'); + + } + + /** + * Creates a new directory under this object. + * + * This is currently not allowed. + * + * @param string $filename + * @return void + */ + public function createDirectory($filename) { + + throw new Sabre_DAV_Exception_MethodNotAllowed('Creating new collections in this collection is not supported'); + + } + + /** + * Returns a single calendar, by name + * + * @param string $name + * @todo needs optimizing + * @return Sabre_CardDAV_AddressBook + */ + public function getChild($name) { + + foreach($this->getChildren() as $child) { + if ($name==$child->getName()) + return $child; + + } + throw new Sabre_DAV_Exception_FileNotFound('Addressbook with name \'' . $name . '\' could not be found'); + + } + + /** + * Returns a list of addressbooks + * + * @return array + */ + public function getChildren() { + + $addressbooks = $this->carddavBackend->getAddressbooksForUser($this->principalUri); + $objs = array(); + foreach($addressbooks as $addressbook) { + $objs[] = new Sabre_CardDAV_AddressBook($this->carddavBackend, $addressbook); + } + return $objs; + + } + + /** + * Creates a new addressbook + * + * @param string $name + * @param array $resourceType + * @param array $properties + * @return void + */ + public function createExtendedCollection($name, array $resourceType, array $properties) { + + if (!in_array('{'.Sabre_CardDAV_Plugin::NS_CARDDAV.'}addressbook',$resourceType) || count($resourceType)!==2) { + throw new Sabre_DAV_Exception_InvalidResourceType('Unknown resourceType for this collection'); + } + $this->carddavBackend->createAddressBook($this->principalUri, $name, $properties); + + } + +} diff --git a/3dparty/Sabre/CardDAV/Version.php b/3dparty/Sabre/CardDAV/Version.php new file mode 100644 index 00000000000..8961027fc89 --- /dev/null +++ b/3dparty/Sabre/CardDAV/Version.php @@ -0,0 +1,28 @@ +<?php + +/** + * Version Class + * + * @package Sabre + * @subpackage CardDAV + * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ + +/** + * This class contains the Sabre_CardDAV version information + */ +class Sabre_CardDAV_Version { + + /** + * Full version number + */ + const VERSION = '0.2'; + + /** + * Stability : alpha, beta, stable + */ + const STABILITY = 'alpha'; + +} |