Initial Release of the calendar plugin

This commit is contained in:
Tobias Hößl 2012-06-03 18:19:28 +00:00
parent 45cc9885fc
commit 7115197a33
561 changed files with 189494 additions and 0 deletions

View file

@ -0,0 +1,312 @@
<?php
/**
* The AddressBook class represents a CardDAV addressbook, owned by a specific user
*
* The AddressBook can contain multiple vcards
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 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_AddressBook extends Sabre_DAV_Collection implements Sabre_CardDAV_IAddressBook, Sabre_DAV_IProperties, Sabre_DAVACL_IACL {
/**
* 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
*/
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_CardDAV_ICard
*/
public function getChild($name) {
$obj = $this->carddavBackend->getCard($this->addressBookInfo['id'],$name);
if (!$obj) throw new Sabre_DAV_Exception_NotFound('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.
*
* This method may return an ETag.
*
* @param string $name
* @param resource $vcardData
* @return string|null
*/
public function createFile($name,$vcardData = null) {
if (is_resource($vcardData)) {
$vcardData = stream_get_contents($vcardData);
}
// Converting to UTF-8, if needed
$vcardData = Sabre_DAV_StringUtil::ensureUTF8($vcardData);
return $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
*
* @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-existent property is always successful.
*
* 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 array
*/
public function getProperties($properties) {
$response = array();
foreach($properties as $propertyName) {
if (isset($this->addressBookInfo[$propertyName])) {
$response[$propertyName] = $this->addressBookInfo[$propertyName];
}
}
return $response;
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->addressBookInfo['principaluri'];
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->addressBookInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->addressBookInfo['principaluri'],
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -0,0 +1,219 @@
<?php
/**
* Parses the addressbook-query report request body.
*
* Whoever designed this format, and the CalDAV equivalent even more so,
* has no feel for design.
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 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 DOMDocument $dom
*/
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.
*
* @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');
// According to the CardDAV spec there needs to be exactly 1 filter
// element. However, KDE 4.8.2 contains a bug that will encode 0 filter
// elements, so this is a workaround for that.
//
// See: https://bugs.kde.org/show_bug.cgi?id=300047
if ($filter->length === 0) {
$test = null;
$filter = null;
} elseif ($filter->length === 1) {
$filter = $filter->item(0);
$test = $this->xpath->evaluate('string(@test)', $filter);
} else {
throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed');
}
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 array
*/
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
);
}
}

View file

@ -0,0 +1,78 @@
<?php
/**
* AddressBook rootnode
*
* This object lists a collection of users, which can contain addressbooks.
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 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.
*
* By default this class will show a list of addressbook collections for
* principals in the 'principals' collection. If your main principals are
* actually located in a different path, use the $principalPrefix argument
* to override this.
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param Sabre_CardDAV_Backend_Abstract $carddavBackend
* @param string $principalPrefix
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CardDAV_Backend_Abstract $carddavBackend, $principalPrefix = 'principals') {
$this->carddavBackend = $carddavBackend;
parent::__construct($principalBackend, $principalPrefix);
}
/**
* 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']);
}
}

View file

@ -0,0 +1,166 @@
<?php
/**
* Abstract Backend class
*
* 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.
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
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
*
* It's recommended to also return the following properties:
* * etag - A unique etag. This must change every time the card changes.
* * size - The size of the card in bytes.
*
* If these last two properties are provided, less time will be spent
* calculating them. If they are specified, you can also ommit carddata.
* This may speed up certain requests, especially with large cards.
*
* @param mixed $addressbookId
* @return array
*/
public abstract function getCards($addressbookId);
/**
* Returns a specfic card.
*
* The same set of properties must be returned as with getCards. The only
* exception is that 'carddata' is absolutely required.
*
* @param mixed $addressBookId
* @param string $cardUri
* @return array
*/
public abstract function getCard($addressBookId, $cardUri);
/**
* Creates a new card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag is for the
* newly created resource, and must be enclosed with double quotes (that
* is, the string itself must contain the double quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
abstract public function createCard($addressBookId, $cardUri, $cardData);
/**
* Updates a card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag should
* match that of the updated resource, and must be enclosed with double
* quotes (that is: the string itself must contain the actual quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
abstract public function updateCard($addressBookId, $cardUri, $cardData);
/**
* Deletes a card
*
* @param mixed $addressBookId
* @param string $cardUri
* @return bool
*/
abstract public function deleteCard($addressBookId, $cardUri);
}

View file

@ -0,0 +1,330 @@
<?php
/**
* PDO CardDAV backend
*
* This CardDAV backend uses PDO to store addressbooks
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 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_Backend_PDO extends Sabre_CardDAV_Backend_Abstract {
/**
* PDO connection
*
* @var PDO
*/
protected $pdo;
/**
* The PDO table name used to store addressbooks
*/
protected $addressBooksTableName;
/**
* The PDO table name used to store cards
*/
protected $cardsTableName;
/**
* Sets up the object
*
* @param PDO $pdo
* @param string $addressBooksTableName
* @param string $cardsTableName
*/
public function __construct(PDO $pdo, $addressBooksTableName = 'addressbooks', $cardsTableName = 'cards') {
$this->pdo = $pdo;
$this->addressBooksTableName = $addressBooksTableName;
$this->cardsTableName = $cardsTableName;
}
/**
* 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 '.$this->addressBooksTableName.' WHERE principaluri = ?');
$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'],
'{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}supported-address-data' =>
new Sabre_CardDAV_Property_SupportedAddressData(),
);
}
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 ' . $this->addressBooksTableName . ' 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 ' . $this->addressBooksTableName . ' (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 ' . $this->cardsTableName . ' WHERE addressbookid = ?');
$stmt->execute(array($addressBookId));
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
$stmt->execute(array($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
*
* It's recommended to also return the following properties:
* * etag - A unique etag. This must change every time the card changes.
* * size - The size of the card in bytes.
*
* If these last two properties are provided, less time will be spent
* calculating them. If they are specified, you can also ommit carddata.
* This may speed up certain requests, especially with large cards.
*
* @param mixed $addressbookId
* @return array
*/
public function getCards($addressbookId) {
$stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
$stmt->execute(array($addressbookId));
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Returns a specfic card.
*
* The same set of properties must be returned as with getCards. The only
* exception is that 'carddata' is absolutely required.
*
* @param mixed $addressBookId
* @param string $cardUri
* @return array
*/
public function getCard($addressBookId, $cardUri) {
$stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' 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.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getCards method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag is for the
* newly created resource, and must be enclosed with double quotes (that
* is, the string itself must contain the double quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
public function createCard($addressBookId, $cardUri, $cardData) {
$stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid) VALUES (?, ?, ?, ?)');
$result = $stmt->execute(array($cardData, $cardUri, time(), $addressBookId));
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
$stmt2->execute(array($addressBookId));
return '"' . md5($cardData) . '"';
}
/**
* Updates a card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag should
* match that of the updated resource, and must be enclosed with double
* quotes (that is: the string itself must contain the actual quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
public function updateCard($addressBookId, $cardUri, $cardData) {
$stmt = $this->pdo->prepare('UPDATE ' . $this->cardsTableName . ' SET carddata = ?, lastmodified = ? WHERE uri = ? AND addressbookid =?');
$stmt->execute(array($cardData, time(), $cardUri, $addressBookId));
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
$stmt2->execute(array($addressBookId));
return '"' . md5($cardData) . '"';
}
/**
* Deletes a card
*
* @param mixed $addressBookId
* @param string $cardUri
* @return bool
*/
public function deleteCard($addressBookId, $cardUri) {
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ?');
$stmt->execute(array($addressBookId, $cardUri));
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
$stmt2->execute(array($addressBookId));
return $stmt->rowCount()===1;
}
}

View file

@ -0,0 +1,250 @@
<?php
/**
* The Card object represents a single Card from an addressbook
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 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_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, Sabre_DAVACL_IACL {
/**
* 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() {
// Pre-populating 'carddata' is optional. If we don't yet have it
// already, we fetch it from the backend.
if (!isset($this->cardData['carddata'])) {
$this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']);
}
return $this->cardData['carddata'];
}
/**
* Updates the VCard-formatted object
*
* @param string $cardData
* @return string|null
*/
public function put($cardData) {
if (is_resource($cardData))
$cardData = stream_get_contents($cardData);
// Converting to UTF-8, if needed
$cardData = Sabre_DAV_StringUtil::ensureUTF8($cardData);
$etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'],$this->cardData['uri'],$cardData);
$this->cardData['carddata'] = $cardData;
$this->cardData['etag'] = $etag;
return $etag;
}
/**
* 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; charset=utf-8';
}
/**
* Returns an ETag for this object
*
* @return string
*/
public function getETag() {
if (isset($this->cardData['etag'])) {
return $this->cardData['etag'];
} else {
return '"' . md5($this->get()) . '"';
}
}
/**
* Returns the last modification date as a unix timestamp
*
* @return int
*/
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() {
if (array_key_exists('size', $this->cardData)) {
return $this->cardData['size'];
} else {
return strlen($this->get());
}
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->addressBookInfo['principaluri'];
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->addressBookInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->addressBookInfo['principaluri'],
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -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-2012 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 {
}

View file

@ -0,0 +1,18 @@
<?php
/**
* Card interface
*
* Extend the ICard interface to allow your custom nodes to be picked up as
* 'Cards'.
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 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_ICard extends Sabre_DAV_IFile {
}

View file

@ -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-2012 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 {
}

View file

@ -0,0 +1,689 @@
<?php
/**
* CardDAV plugin
*
* The CardDAV plugin adds CardDAV functionality to the WebDAV server
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 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_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('afterGetProperties', array($this, 'afterGetProperties'));
$server->subscribeEvent('updateProperties', array($this, 'updateProperties'));
$server->subscribeEvent('report', array($this,'report'));
$server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel'));
$server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction'));
$server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent'));
$server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile'));
/* 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';
$server->protectedProperties[] = '{' . self::NS_CARDDAV . '}addressbook-home-set';
$server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-collation-set';
$server->propertyMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre_DAV_Property_Href';
$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_IAddressBook || $node instanceof Sabre_CardDAV_ICard) {
return array(
'{' . self::NS_CARDDAV . '}addressbook-multiget',
'{' . self::NS_CARDDAV . '}addressbook-query',
);
}
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);
}
}
if ($node instanceof Sabre_CardDAV_UserAddressBooks) {
$meCardProp = '{http://calendarserver.org/ns/}me-card';
if (in_array($meCardProp, $requestedProperties)) {
$props = $this->server->getProperties($node->getOwner(), array('{http://sabredav.org/ns}vcard-url'));
if (isset($props['{http://sabredav.org/ns}vcard-url'])) {
$returnedProperties[200][$meCardProp] = new Sabre_DAV_Property_Href(
$props['{http://sabredav.org/ns}vcard-url']
);
$pos = array_search($meCardProp, $requestedProperties);
unset($requestedProperties[$pos]);
}
}
}
}
/**
* This event is triggered when a PROPPATCH method is executed
*
* @param array $mutations
* @param array $result
* @param Sabre_DAV_INode $node
* @return bool
*/
public function updateProperties(&$mutations, &$result, $node) {
if (!$node instanceof Sabre_CardDAV_UserAddressBooks) {
return true;
}
$meCard = '{http://calendarserver.org/ns/}me-card';
// The only property we care about
if (!isset($mutations[$meCard]))
return true;
$value = $mutations[$meCard];
unset($mutations[$meCard]);
if ($value instanceof Sabre_DAV_Property_IHref) {
$value = $value->getHref();
$value = $this->server->calculateUri($value);
} elseif (!is_null($value)) {
$result[400][$meCard] = null;
return false;
}
$innerResult = $this->server->updateProperties(
$node->getOwner(),
array(
'{http://sabredav.org/ns}vcard-url' => $value,
)
);
$closureResult = false;
foreach($innerResult as $status => $props) {
if (is_array($props) && array_key_exists('{http://sabredav.org/ns}vcard-url', $props)) {
$result[$status][$meCard] = null;
$closureResult = ($status>=200 && $status<300);
}
}
return $result;
}
/**
* 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 method is triggered before a file gets updated with new content.
*
* This plugin uses this method to ensure that Card nodes receive valid
* vcard data.
*
* @param string $path
* @param Sabre_DAV_IFile $node
* @param resource $data
* @return void
*/
public function beforeWriteContent($path, Sabre_DAV_IFile $node, &$data) {
if (!$node instanceof Sabre_CardDAV_ICard)
return;
$this->validateVCard($data);
}
/**
* This method is triggered before a new file is created.
*
* This plugin uses this method to ensure that Card nodes receive valid
* vcard data.
*
* @param string $path
* @param resource $data
* @param Sabre_DAV_ICollection $parentNode
* @return void
*/
public function beforeCreateFile($path, &$data, Sabre_DAV_ICollection $parentNode) {
if (!$parentNode instanceof Sabre_CardDAV_IAddressBook)
return;
$this->validateVCard($data);
}
/**
* Checks if the submitted iCalendar data is in fact, valid.
*
* An exception is thrown if it's not.
*
* @param resource|string $data
* @return void
*/
protected function validateVCard(&$data) {
// If it's a stream, we convert it to a string first.
if (is_resource($data)) {
$data = stream_get_contents($data);
}
// Converting the data to unicode, if needed.
$data = Sabre_DAV_StringUtil::ensureUTF8($data);
try {
$vobj = Sabre_VObject_Reader::read($data);
} catch (Sabre_VObject_ParseException $e) {
throw new Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage());
}
if ($vobj->name !== 'VCARD') {
throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support vcard objects.');
}
}
/**
* 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);
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 whether
// 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 array $vProperties
* @param array $filters
* @param string $test
* @return bool
*/
protected function validateParamFilters(array $vProperties, array $filters, $test) {
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 {
$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']['match-type']);
if ($success) break;
}
if ($filter['text-match']['negate-condition']) {
$success = !$success;
}
} // else
// There are two conditions where we can already determine whether
// 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['match-type']);
// 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';
}
/**
* This event is triggered after webdav-properties have been retrieved.
*
* @return bool
*/
public function afterGetProperties($uri, &$properties) {
// If the request was made using the SOGO connector, we must rewrite
// the content-type property. By default SabreDAV will send back
// text/x-vcard; charset=utf-8, but for SOGO we must strip that last
// part.
if (!isset($properties[200]['{DAV:}getcontenttype']))
return;
if (strpos($this->server->httpRequest->getHeader('User-Agent'),'Thunderbird')===false) {
return;
}
if (strpos($properties[200]['{DAV:}getcontenttype'],'text/x-vcard')===0) {
$properties[200]['{DAV:}getcontenttype'] = 'text/x-vcard';
}
}
/**
* This method is used to generate HTML output for the
* Sabre_DAV_Browser_Plugin. This allows us to generate an interface users
* can use to create new calendars.
*
* @param Sabre_DAV_INode $node
* @param string $output
* @return bool
*/
public function htmlActionsPanel(Sabre_DAV_INode $node, &$output) {
if (!$node instanceof Sabre_CardDAV_UserAddressBooks)
return;
$output.= '<tr><td colspan="2"><form method="post" action="">
<h3>Create new address book</h3>
<input type="hidden" name="sabreAction" value="mkaddressbook" />
<label>Name (uri):</label> <input type="text" name="name" /><br />
<label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
<input type="submit" value="create" />
</form>
</td></tr>';
return false;
}
/**
* This method allows us to intercept the 'mkcalendar' sabreAction. This
* action enables the user to create new calendars from the browser plugin.
*
* @param string $uri
* @param string $action
* @param array $postVars
* @return bool
*/
public function browserPostAction($uri, $action, array $postVars) {
if ($action!=='mkaddressbook')
return;
$resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:carddav}addressbook');
$properties = array();
if (isset($postVars['{DAV:}displayname'])) {
$properties['{DAV:}displayname'] = $postVars['{DAV:}displayname'];
}
$this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties);
return false;
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* Supported-address-data property
*
* This property is a representation of the supported-address-data property
* in the CardDAV namespace.
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 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_Property_SupportedAddressData extends Sabre_DAV_Property {
/**
* supported versions
*
* @var array
*/
protected $supportedData = array();
/**
* Creates the property
*
* @param array|null $supportedData
*/
public function __construct(array $supportedData = null) {
if (is_null($supportedData)) {
$supportedData = array(
array('contentType' => 'text/vcard', 'version' => '3.0'),
array('contentType' => 'text/vcard', 'version' => '4.0'),
);
}
$this->supportedData = $supportedData;
}
/**
* Serializes the property in a DOMDocument
*
* @param Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $node) {
$doc = $node->ownerDocument;
$prefix =
isset($server->xmlNamespaces[Sabre_CardDAV_Plugin::NS_CARDDAV]) ?
$server->xmlNamespaces[Sabre_CardDAV_Plugin::NS_CARDDAV] :
'card';
foreach($this->supportedData as $supported) {
$caldata = $doc->createElementNS(Sabre_CardDAV_Plugin::NS_CARDDAV, $prefix . ':address-data-type');
$caldata->setAttribute('content-type',$supported['contentType']);
$caldata->setAttribute('version',$supported['version']);
$node->appendChild($caldata);
}
}
}

View file

@ -0,0 +1,257 @@
<?php
/**
* UserAddressBooks class
*
* The UserAddressBooks collection contains a list of addressbooks associated with a user
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 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_UserAddressBooks extends Sabre_DAV_Collection implements Sabre_DAV_IExtendedCollection, Sabre_DAVACL_IACL {
/**
* 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_NotFound('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);
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->principalUri;
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->principalUri,
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->principalUri,
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* Version Class
*
* This class contains the Sabre_CardDAV version information
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 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_Version {
/**
* Full version number
*/
const VERSION = '1.7.0';
/**
* Stability : alpha, beta, stable
*/
const STABILITY = 'alpha';
}

View file

@ -0,0 +1,32 @@
<?php
/**
* Sabre_CardDAV includes file
*
* Including this file will automatically include all files from the
* Sabre_CardDAV package.
*
* This often allows faster loadtimes, as autoload-speed is often quite slow.
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
// Begin includes
include __DIR__ . '/AddressBookQueryParser.php';
include __DIR__ . '/AddressBookRoot.php';
include __DIR__ . '/Backend/Abstract.php';
include __DIR__ . '/Backend/PDO.php';
include __DIR__ . '/IAddressBook.php';
include __DIR__ . '/ICard.php';
include __DIR__ . '/IDirectory.php';
include __DIR__ . '/Plugin.php';
include __DIR__ . '/Property/SupportedAddressData.php';
include __DIR__ . '/UserAddressBooks.php';
include __DIR__ . '/Version.php';
include __DIR__ . '/AddressBook.php';
include __DIR__ . '/Card.php';
// End includes