aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/sabre/dav/lib/CardDAV
diff options
context:
space:
mode:
authorredmatrix <git@macgirvin.com>2016-05-10 17:26:44 -0700
committerredmatrix <git@macgirvin.com>2016-05-10 17:26:44 -0700
commit0b02a6d123b2014705998c94ddf3d460948d3eac (patch)
tree78ff2cab9944a4f5ab3f80ec93cbe1120de90bb2 /vendor/sabre/dav/lib/CardDAV
parent40b5b6e9d2da7ab65c8b4d38cdceac83a4d78deb (diff)
downloadvolse-hubzilla-0b02a6d123b2014705998c94ddf3d460948d3eac.tar.gz
volse-hubzilla-0b02a6d123b2014705998c94ddf3d460948d3eac.tar.bz2
volse-hubzilla-0b02a6d123b2014705998c94ddf3d460948d3eac.zip
initial sabre upgrade (needs lots of work - to wit: authentication, redo the browser interface, and rework event export/import)
Diffstat (limited to 'vendor/sabre/dav/lib/CardDAV')
-rw-r--r--vendor/sabre/dav/lib/CardDAV/AddressBook.php433
-rw-r--r--vendor/sabre/dav/lib/CardDAV/AddressBookHome.php263
-rw-r--r--vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php80
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php38
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php187
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Backend/PDO.php545
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php81
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Card.php263
-rw-r--r--vendor/sabre/dav/lib/CardDAV/IAddressBook.php18
-rw-r--r--vendor/sabre/dav/lib/CardDAV/ICard.php19
-rw-r--r--vendor/sabre/dav/lib/CardDAV/IDirectory.php20
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Plugin.php871
-rw-r--r--vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php152
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php59
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php89
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php98
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php83
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php47
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php113
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php192
20 files changed, 3651 insertions, 0 deletions
diff --git a/vendor/sabre/dav/lib/CardDAV/AddressBook.php b/vendor/sabre/dav/lib/CardDAV/AddressBook.php
new file mode 100644
index 000000000..70bec8760
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/AddressBook.php
@@ -0,0 +1,433 @@
+<?php
+
+namespace Sabre\CardDAV;
+
+use Sabre\DAV;
+use Sabre\DAVACL;
+
+/**
+ * The AddressBook class represents a CardDAV addressbook, owned by a specific user
+ *
+ * The AddressBook can contain multiple vcards
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class AddressBook extends DAV\Collection implements IAddressBook, DAV\IProperties, DAVACL\IACL, DAV\Sync\ISyncCollection, DAV\IMultiGet {
+
+ /**
+ * This is an array with addressbook information
+ *
+ * @var array
+ */
+ protected $addressBookInfo;
+
+ /**
+ * CardDAV backend
+ *
+ * @var Backend\BackendInterface
+ */
+ protected $carddavBackend;
+
+ /**
+ * Constructor
+ *
+ * @param Backend\BackendInterface $carddavBackend
+ * @param array $addressBookInfo
+ */
+ function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo) {
+
+ $this->carddavBackend = $carddavBackend;
+ $this->addressBookInfo = $addressBookInfo;
+
+ }
+
+ /**
+ * Returns the name of the addressbook
+ *
+ * @return string
+ */
+ function getName() {
+
+ return $this->addressBookInfo['uri'];
+
+ }
+
+ /**
+ * Returns a card
+ *
+ * @param string $name
+ * @return \ICard
+ */
+ function getChild($name) {
+
+ $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name);
+ if (!$obj) throw new DAV\Exception\NotFound('Card not found');
+ return new Card($this->carddavBackend, $this->addressBookInfo, $obj);
+
+ }
+
+ /**
+ * Returns the full list of cards
+ *
+ * @return array
+ */
+ function getChildren() {
+
+ $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);
+ $children = [];
+ foreach ($objs as $obj) {
+ $obj['acl'] = $this->getChildACL();
+ $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
+ }
+ return $children;
+
+ }
+
+ /**
+ * This method receives a list of paths in it's first argument.
+ * It must return an array with Node objects.
+ *
+ * If any children are not found, you do not have to return them.
+ *
+ * @param string[] $paths
+ * @return array
+ */
+ function getMultipleChildren(array $paths) {
+
+ $objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths);
+ $children = [];
+ foreach ($objs as $obj) {
+ $obj['acl'] = $this->getChildACL();
+ $children[] = new 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
+ */
+ function createDirectory($name) {
+
+ throw new 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
+ */
+ function createFile($name, $vcardData = null) {
+
+ if (is_resource($vcardData)) {
+ $vcardData = stream_get_contents($vcardData);
+ }
+ // Converting to UTF-8, if needed
+ $vcardData = DAV\StringUtil::ensureUTF8($vcardData);
+
+ return $this->carddavBackend->createCard($this->addressBookInfo['id'], $name, $vcardData);
+
+ }
+
+ /**
+ * Deletes the entire addressbook.
+ *
+ * @return void
+ */
+ function delete() {
+
+ $this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']);
+
+ }
+
+ /**
+ * Renames the addressbook
+ *
+ * @param string $newName
+ * @return void
+ */
+ function setName($newName) {
+
+ throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported');
+
+ }
+
+ /**
+ * Returns the last modification date as a unix timestamp.
+ *
+ * @return void
+ */
+ function getLastModified() {
+
+ return null;
+
+ }
+
+ /**
+ * Updates properties on this node.
+ *
+ * This method received a PropPatch object, which contains all the
+ * information about the update.
+ *
+ * To update specific properties, call the 'handle' method on this object.
+ * Read the PropPatch documentation for more information.
+ *
+ * @param DAV\PropPatch $propPatch
+ * @return void
+ */
+ function propPatch(DAV\PropPatch $propPatch) {
+
+ return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $propPatch);
+
+ }
+
+ /**
+ * 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
+ */
+ function getProperties($properties) {
+
+ $response = [];
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ function getACL() {
+
+ return [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+
+ ];
+
+ }
+
+ /**
+ * This method returns the ACL's for card nodes in this address book.
+ * The result of this method automatically gets passed to the
+ * card nodes in this address book.
+ *
+ * @return array
+ */
+ function getChildACL() {
+
+ return [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ ];
+
+ }
+
+ /**
+ * Updates the ACL
+ *
+ * This method will receive a list of new ACE's.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function setACL(array $acl) {
+
+ throw new 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
+ */
+ function getSupportedPrivilegeSet() {
+
+ return null;
+
+ }
+
+ /**
+ * This method returns the current sync-token for this collection.
+ * This can be any string.
+ *
+ * If null is returned from this function, the plugin assumes there's no
+ * sync information available.
+ *
+ * @return string|null
+ */
+ function getSyncToken() {
+
+ if (
+ $this->carddavBackend instanceof Backend\SyncSupport &&
+ isset($this->addressBookInfo['{DAV:}sync-token'])
+ ) {
+ return $this->addressBookInfo['{DAV:}sync-token'];
+ }
+ if (
+ $this->carddavBackend instanceof Backend\SyncSupport &&
+ isset($this->addressBookInfo['{http://sabredav.org/ns}sync-token'])
+ ) {
+ return $this->addressBookInfo['{http://sabredav.org/ns}sync-token'];
+ }
+
+ }
+
+ /**
+ * The getChanges method returns all the changes that have happened, since
+ * the specified syncToken and the current collection.
+ *
+ * This function should return an array, such as the following:
+ *
+ * [
+ * 'syncToken' => 'The current synctoken',
+ * 'added' => [
+ * 'new.txt',
+ * ],
+ * 'modified' => [
+ * 'modified.txt',
+ * ],
+ * 'deleted' => [
+ * 'foo.php.bak',
+ * 'old.txt'
+ * ]
+ * ];
+ *
+ * The syncToken property should reflect the *current* syncToken of the
+ * collection, as reported getSyncToken(). This is needed here too, to
+ * ensure the operation is atomic.
+ *
+ * If the syncToken is specified as null, this is an initial sync, and all
+ * members should be reported.
+ *
+ * The modified property is an array of nodenames that have changed since
+ * the last token.
+ *
+ * The deleted property is an array with nodenames, that have been deleted
+ * from collection.
+ *
+ * The second argument is basically the 'depth' of the report. If it's 1,
+ * you only have to report changes that happened only directly in immediate
+ * descendants. If it's 2, it should also include changes from the nodes
+ * below the child collections. (grandchildren)
+ *
+ * The third (optional) argument allows a client to specify how many
+ * results should be returned at most. If the limit is not specified, it
+ * should be treated as infinite.
+ *
+ * If the limit (infinite or not) is higher than you're willing to return,
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
+ *
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
+ * return null.
+ *
+ * The limit is 'suggestive'. You are free to ignore it.
+ *
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ * @return array
+ */
+ function getChanges($syncToken, $syncLevel, $limit = null) {
+
+ if (!$this->carddavBackend instanceof Backend\SyncSupport) {
+ return null;
+ }
+
+ return $this->carddavBackend->getChangesForAddressBook(
+ $this->addressBookInfo['id'],
+ $syncToken,
+ $syncLevel,
+ $limit
+ );
+
+ }
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php b/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php
new file mode 100644
index 000000000..ebc251832
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php
@@ -0,0 +1,263 @@
+<?php
+
+namespace Sabre\CardDAV;
+
+use Sabre\DAV;
+use Sabre\DAV\MkCol;
+use Sabre\DAVACL;
+use Sabre\Uri;
+
+/**
+ * AddressBook Home class
+ *
+ * This collection contains a list of addressbooks associated with one user.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class AddressBookHome extends DAV\Collection implements DAV\IExtendedCollection, DAVACL\IACL {
+
+ /**
+ * Principal uri
+ *
+ * @var array
+ */
+ protected $principalUri;
+
+ /**
+ * carddavBackend
+ *
+ * @var Backend\BackendInterface
+ */
+ protected $carddavBackend;
+
+ /**
+ * Constructor
+ *
+ * @param Backend\BackendInterface $carddavBackend
+ * @param string $principalUri
+ */
+ function __construct(Backend\BackendInterface $carddavBackend, $principalUri) {
+
+ $this->carddavBackend = $carddavBackend;
+ $this->principalUri = $principalUri;
+
+ }
+
+ /**
+ * Returns the name of this object
+ *
+ * @return string
+ */
+ function getName() {
+
+ list(, $name) = Uri\split($this->principalUri);
+ return $name;
+
+ }
+
+ /**
+ * Updates the name of this object
+ *
+ * @param string $name
+ * @return void
+ */
+ function setName($name) {
+
+ throw new DAV\Exception\MethodNotAllowed();
+
+ }
+
+ /**
+ * Deletes this object
+ *
+ * @return void
+ */
+ function delete() {
+
+ throw new DAV\Exception\MethodNotAllowed();
+
+ }
+
+ /**
+ * Returns the last modification date
+ *
+ * @return int
+ */
+ function getLastModified() {
+
+ return null;
+
+ }
+
+ /**
+ * Creates a new file under this object.
+ *
+ * This is currently not allowed
+ *
+ * @param string $filename
+ * @param resource $data
+ * @return void
+ */
+ function createFile($filename, $data = null) {
+
+ throw new 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
+ */
+ function createDirectory($filename) {
+
+ throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
+
+ }
+
+ /**
+ * Returns a single addressbook, by name
+ *
+ * @param string $name
+ * @todo needs optimizing
+ * @return \AddressBook
+ */
+ function getChild($name) {
+
+ foreach ($this->getChildren() as $child) {
+ if ($name == $child->getName())
+ return $child;
+
+ }
+ throw new DAV\Exception\NotFound('Addressbook with name \'' . $name . '\' could not be found');
+
+ }
+
+ /**
+ * Returns a list of addressbooks
+ *
+ * @return array
+ */
+ function getChildren() {
+
+ $addressbooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
+ $objs = [];
+ foreach ($addressbooks as $addressbook) {
+ $objs[] = new AddressBook($this->carddavBackend, $addressbook);
+ }
+ return $objs;
+
+ }
+
+ /**
+ * Creates a new address book.
+ *
+ * @param string $name
+ * @param MkCol $mkCol
+ * @throws DAV\Exception\InvalidResourceType
+ * @return void
+ */
+ function createExtendedCollection($name, MkCol $mkCol) {
+
+ if (!$mkCol->hasResourceType('{' . Plugin::NS_CARDDAV . '}addressbook')) {
+ throw new DAV\Exception\InvalidResourceType('Unknown resourceType for this collection');
+ }
+ $properties = $mkCol->getRemainingValues();
+ $mkCol->setRemainingResultCode(201);
+ $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
+ */
+ 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
+ */
+ 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
+ */
+ function getACL() {
+
+ return [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->principalUri,
+ 'protected' => true,
+ ],
+ [
+ '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
+ */
+ function setACL(array $acl) {
+
+ throw new 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
+ */
+ function getSupportedPrivilegeSet() {
+
+ return null;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php b/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php
new file mode 100644
index 000000000..4a33df4ec
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Sabre\CardDAV;
+
+use Sabre\DAVACL;
+
+/**
+ * AddressBook rootnode
+ *
+ * This object lists a collection of users, which can contain addressbooks.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class AddressBookRoot extends DAVACL\AbstractPrincipalCollection {
+
+ /**
+ * Principal Backend
+ *
+ * @var Sabre\DAVACL\PrincipalBackend\BackendInteface
+ */
+ protected $principalBackend;
+
+ /**
+ * CardDAV backend
+ *
+ * @var Backend\BackendInterface
+ */
+ 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 DAVACL\PrincipalBackend\BackendInterface $principalBackend
+ * @param Backend\BackendInterface $carddavBackend
+ * @param string $principalPrefix
+ */
+ function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, Backend\BackendInterface $carddavBackend, $principalPrefix = 'principals') {
+
+ $this->carddavBackend = $carddavBackend;
+ parent::__construct($principalBackend, $principalPrefix);
+
+ }
+
+ /**
+ * Returns the name of the node
+ *
+ * @return string
+ */
+ function getName() {
+
+ return 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
+ */
+ function getChildForPrincipal(array $principal) {
+
+ return new AddressBookHome($this->carddavBackend, $principal['uri']);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php b/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php
new file mode 100644
index 000000000..03d2346da
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Sabre\CardDAV\Backend;
+
+/**
+ * CardDAV abstract Backend
+ *
+ * This class serves as a base-class for addressbook backends
+ *
+ * This class doesn't do much, but it was added for consistency.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+abstract class AbstractBackend implements BackendInterface {
+
+ /**
+ * Returns a list of cards.
+ *
+ * This method should work identical to getCard, but instead return all the
+ * cards in the list as an array.
+ *
+ * If the backend supports this, it may allow for some speed-ups.
+ *
+ * @param mixed $addressBookId
+ * @param array $uris
+ * @return array
+ */
+ function getMultipleCards($addressBookId, array $uris) {
+
+ return array_map(function($uri) use ($addressBookId) {
+ return $this->getCard($addressBookId, $uri);
+ }, $uris);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php b/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php
new file mode 100644
index 000000000..b9691b906
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php
@@ -0,0 +1,187 @@
+<?php
+
+namespace Sabre\CardDAV\Backend;
+
+/**
+ * CardDAV Backend Interface
+ *
+ * Any CardDAV backend must implement this interface.
+ *
+ * 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.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface BackendInterface {
+
+ /**
+ * 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
+ */
+ function getAddressBooksForUser($principalUri);
+
+ /**
+ * Updates properties for an address book.
+ *
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
+ * To do the actual updates, you must tell this object which properties
+ * you're going to process with the handle() method.
+ *
+ * Calling the handle method is like telling the PropPatch object "I
+ * promise I can handle updating this property".
+ *
+ * Read the PropPatch documenation for more info and examples.
+ *
+ * @param string $addressBookId
+ * @param \Sabre\DAV\PropPatch $propPatch
+ * @return void
+ */
+ function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch);
+
+ /**
+ * Creates a new address book
+ *
+ * @param string $principalUri
+ * @param string $url Just the 'basename' of the url.
+ * @param array $properties
+ * @return void
+ */
+ function createAddressBook($principalUri, $url, array $properties);
+
+ /**
+ * Deletes an entire addressbook and all its contents
+ *
+ * @param mixed $addressBookId
+ * @return void
+ */
+ 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
+ */
+ 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.
+ *
+ * If the card does not exist, you must return false.
+ *
+ * @param mixed $addressBookId
+ * @param string $cardUri
+ * @return array
+ */
+ function getCard($addressBookId, $cardUri);
+
+ /**
+ * Returns a list of cards.
+ *
+ * This method should work identical to getCard, but instead return all the
+ * cards in the list as an array.
+ *
+ * If the backend supports this, it may allow for some speed-ups.
+ *
+ * @param mixed $addressBookId
+ * @param array $uris
+ * @return array
+ */
+ function getMultipleCards($addressBookId, array $uris);
+
+ /**
+ * 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
+ */
+ 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
+ */
+ function updateCard($addressBookId, $cardUri, $cardData);
+
+ /**
+ * Deletes a card
+ *
+ * @param mixed $addressBookId
+ * @param string $cardUri
+ * @return bool
+ */
+ function deleteCard($addressBookId, $cardUri);
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php b/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php
new file mode 100644
index 000000000..5509ddc02
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php
@@ -0,0 +1,545 @@
+<?php
+
+namespace Sabre\CardDAV\Backend;
+
+use Sabre\CardDAV;
+use Sabre\DAV;
+
+/**
+ * PDO CardDAV backend
+ *
+ * This CardDAV backend uses PDO to store addressbooks
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class PDO extends AbstractBackend implements SyncSupport {
+
+ /**
+ * PDO connection
+ *
+ * @var PDO
+ */
+ protected $pdo;
+
+ /**
+ * The PDO table name used to store addressbooks
+ */
+ public $addressBooksTableName = 'addressbooks';
+
+ /**
+ * The PDO table name used to store cards
+ */
+ public $cardsTableName = 'cards';
+
+ /**
+ * The table name that will be used for tracking changes in address books.
+ *
+ * @var string
+ */
+ public $addressBookChangesTableName = 'addressbookchanges';
+
+ /**
+ * Sets up the object
+ *
+ * @param \PDO $pdo
+ */
+ function __construct(\PDO $pdo) {
+
+ $this->pdo = $pdo;
+
+ }
+
+ /**
+ * Returns the list of addressbooks for a specific user.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ function getAddressBooksForUser($principalUri) {
+
+ $stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, synctoken FROM ' . $this->addressBooksTableName . ' WHERE principaluri = ?');
+ $stmt->execute([$principalUri]);
+
+ $addressBooks = [];
+
+ foreach ($stmt->fetchAll() as $row) {
+
+ $addressBooks[] = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'principaluri' => $row['principaluri'],
+ '{DAV:}displayname' => $row['displayname'],
+ '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
+ '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
+ ];
+
+ }
+
+ return $addressBooks;
+
+ }
+
+
+ /**
+ * Updates properties for an address book.
+ *
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
+ * To do the actual updates, you must tell this object which properties
+ * you're going to process with the handle() method.
+ *
+ * Calling the handle method is like telling the PropPatch object "I
+ * promise I can handle updating this property".
+ *
+ * Read the PropPatch documenation for more info and examples.
+ *
+ * @param string $addressBookId
+ * @param \Sabre\DAV\PropPatch $propPatch
+ * @return void
+ */
+ function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
+
+ $supportedProperties = [
+ '{DAV:}displayname',
+ '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description',
+ ];
+
+ $propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
+
+ $updates = [];
+ foreach ($mutations as $property => $newValue) {
+
+ switch ($property) {
+ case '{DAV:}displayname' :
+ $updates['displayname'] = $newValue;
+ break;
+ case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
+ $updates['description'] = $newValue;
+ break;
+ }
+ }
+ $query = 'UPDATE ' . $this->addressBooksTableName . ' SET ';
+ $first = true;
+ foreach ($updates as $key => $value) {
+ if ($first) {
+ $first = false;
+ } else {
+ $query .= ', ';
+ }
+ $query .= ' `' . $key . '` = :' . $key . ' ';
+ }
+ $query .= ' WHERE id = :addressbookid';
+
+ $stmt = $this->pdo->prepare($query);
+ $updates['addressbookid'] = $addressBookId;
+
+ $stmt->execute($updates);
+
+ $this->addChange($addressBookId, "", 2);
+
+ return true;
+
+ });
+
+ }
+
+ /**
+ * Creates a new address book
+ *
+ * @param string $principalUri
+ * @param string $url Just the 'basename' of the url.
+ * @param array $properties
+ * @return int Last insert id
+ */
+ function createAddressBook($principalUri, $url, array $properties) {
+
+ $values = [
+ 'displayname' => null,
+ 'description' => null,
+ 'principaluri' => $principalUri,
+ 'uri' => $url,
+ ];
+
+ foreach ($properties as $property => $newValue) {
+
+ switch ($property) {
+ case '{DAV:}displayname' :
+ $values['displayname'] = $newValue;
+ break;
+ case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
+ $values['description'] = $newValue;
+ break;
+ default :
+ throw new DAV\Exception\BadRequest('Unknown property: ' . $property);
+ }
+
+ }
+
+ $query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, synctoken) VALUES (:uri, :displayname, :description, :principaluri, 1)';
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute($values);
+ return $this->pdo->lastInsertId();
+
+ }
+
+ /**
+ * Deletes an entire addressbook and all its contents
+ *
+ * @param int $addressBookId
+ * @return void
+ */
+ function deleteAddressBook($addressBookId) {
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
+ $stmt->execute([$addressBookId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
+ $stmt->execute([$addressBookId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBookChangesTableName . ' WHERE addressbookid = ?');
+ $stmt->execute([$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
+ */
+ function getCards($addressbookId) {
+
+ $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, size FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
+ $stmt->execute([$addressbookId]);
+
+ $result = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $row['etag'] = '"' . $row['etag'] . '"';
+ $result[] = $row;
+ }
+ return $result;
+
+ }
+
+ /**
+ * Returns a specfic card.
+ *
+ * The same set of properties must be returned as with getCards. The only
+ * exception is that 'carddata' is absolutely required.
+ *
+ * If the card does not exist, you must return false.
+ *
+ * @param mixed $addressBookId
+ * @param string $cardUri
+ * @return array
+ */
+ function getCard($addressBookId, $cardUri) {
+
+ $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified, etag, size FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ? LIMIT 1');
+ $stmt->execute([$addressBookId, $cardUri]);
+
+ $result = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if (!$result) return false;
+
+ $result['etag'] = '"' . $result['etag'] . '"';
+ return $result;
+
+ }
+
+ /**
+ * Returns a list of cards.
+ *
+ * This method should work identical to getCard, but instead return all the
+ * cards in the list as an array.
+ *
+ * If the backend supports this, it may allow for some speed-ups.
+ *
+ * @param mixed $addressBookId
+ * @param array $uris
+ * @return array
+ */
+ function getMultipleCards($addressBookId, array $uris) {
+
+ $query = 'SELECT id, uri, lastmodified, etag, size, carddata FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri IN (';
+ // Inserting a whole bunch of question marks
+ $query .= implode(',', array_fill(0, count($uris), '?'));
+ $query .= ')';
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute(array_merge([$addressBookId], $uris));
+ $result = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $row['etag'] = '"' . $row['etag'] . '"';
+ $result[] = $row;
+ }
+ return $result;
+
+ }
+
+ /**
+ * 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
+ */
+ function createCard($addressBookId, $cardUri, $cardData) {
+
+ $stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid, size, etag) VALUES (?, ?, ?, ?, ?, ?)');
+
+ $etag = md5($cardData);
+
+ $stmt->execute([
+ $cardData,
+ $cardUri,
+ time(),
+ $addressBookId,
+ strlen($cardData),
+ $etag,
+ ]);
+
+ $this->addChange($addressBookId, $cardUri, 1);
+
+ return '"' . $etag . '"';
+
+ }
+
+ /**
+ * 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
+ */
+ function updateCard($addressBookId, $cardUri, $cardData) {
+
+ $stmt = $this->pdo->prepare('UPDATE ' . $this->cardsTableName . ' SET carddata = ?, lastmodified = ?, size = ?, etag = ? WHERE uri = ? AND addressbookid =?');
+
+ $etag = md5($cardData);
+ $stmt->execute([
+ $cardData,
+ time(),
+ strlen($cardData),
+ $etag,
+ $cardUri,
+ $addressBookId
+ ]);
+
+ $this->addChange($addressBookId, $cardUri, 2);
+
+ return '"' . $etag . '"';
+
+ }
+
+ /**
+ * Deletes a card
+ *
+ * @param mixed $addressBookId
+ * @param string $cardUri
+ * @return bool
+ */
+ function deleteCard($addressBookId, $cardUri) {
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ?');
+ $stmt->execute([$addressBookId, $cardUri]);
+
+ $this->addChange($addressBookId, $cardUri, 3);
+
+ return $stmt->rowCount() === 1;
+
+ }
+
+ /**
+ * The getChanges method returns all the changes that have happened, since
+ * the specified syncToken in the specified address book.
+ *
+ * This function should return an array, such as the following:
+ *
+ * [
+ * 'syncToken' => 'The current synctoken',
+ * 'added' => [
+ * 'new.txt',
+ * ],
+ * 'modified' => [
+ * 'updated.txt',
+ * ],
+ * 'deleted' => [
+ * 'foo.php.bak',
+ * 'old.txt'
+ * ]
+ * ];
+ *
+ * The returned syncToken property should reflect the *current* syncToken
+ * of the addressbook, as reported in the {http://sabredav.org/ns}sync-token
+ * property. This is needed here too, to ensure the operation is atomic.
+ *
+ * If the $syncToken argument is specified as null, this is an initial
+ * sync, and all members should be reported.
+ *
+ * The modified property is an array of nodenames that have changed since
+ * the last token.
+ *
+ * The deleted property is an array with nodenames, that have been deleted
+ * from collection.
+ *
+ * The $syncLevel argument is basically the 'depth' of the report. If it's
+ * 1, you only have to report changes that happened only directly in
+ * immediate descendants. If it's 2, it should also include changes from
+ * the nodes below the child collections. (grandchildren)
+ *
+ * The $limit argument allows a client to specify how many results should
+ * be returned at most. If the limit is not specified, it should be treated
+ * as infinite.
+ *
+ * If the limit (infinite or not) is higher than you're willing to return,
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
+ *
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
+ * return null.
+ *
+ * The limit is 'suggestive'. You are free to ignore it.
+ *
+ * @param string $addressBookId
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ * @return array
+ */
+ function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
+
+ // Current synctoken
+ $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
+ $stmt->execute([ $addressBookId ]);
+ $currentToken = $stmt->fetchColumn(0);
+
+ if (is_null($currentToken)) return null;
+
+ $result = [
+ 'syncToken' => $currentToken,
+ 'added' => [],
+ 'modified' => [],
+ 'deleted' => [],
+ ];
+
+ if ($syncToken) {
+
+ $query = "SELECT uri, operation FROM " . $this->addressBookChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND addressbookid = ? ORDER BY synctoken";
+ if ($limit > 0) $query .= " LIMIT " . (int)$limit;
+
+ // Fetching all changes
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$syncToken, $currentToken, $addressBookId]);
+
+ $changes = [];
+
+ // This loop ensures that any duplicates are overwritten, only the
+ // last change on a node is relevant.
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+ $changes[$row['uri']] = $row['operation'];
+
+ }
+
+ foreach ($changes as $uri => $operation) {
+
+ switch ($operation) {
+ case 1:
+ $result['added'][] = $uri;
+ break;
+ case 2:
+ $result['modified'][] = $uri;
+ break;
+ case 3:
+ $result['deleted'][] = $uri;
+ break;
+ }
+
+ }
+ } else {
+ // No synctoken supplied, this is the initial sync.
+ $query = "SELECT uri FROM " . $this->cardsTableName . " WHERE addressbookid = ?";
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$addressBookId]);
+
+ $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
+ }
+ return $result;
+
+ }
+
+ /**
+ * Adds a change record to the addressbookchanges table.
+ *
+ * @param mixed $addressBookId
+ * @param string $objectUri
+ * @param int $operation 1 = add, 2 = modify, 3 = delete
+ * @return void
+ */
+ protected function addChange($addressBookId, $objectUri, $operation) {
+
+ $stmt = $this->pdo->prepare('INSERT INTO ' . $this->addressBookChangesTableName . ' (uri, synctoken, addressbookid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
+ $stmt->execute([
+ $objectUri,
+ $addressBookId,
+ $operation,
+ $addressBookId
+ ]);
+ $stmt = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET synctoken = synctoken + 1 WHERE id = ?');
+ $stmt->execute([
+ $addressBookId
+ ]);
+
+ }
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php b/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php
new file mode 100644
index 000000000..f80618a8e
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Sabre\CardDAV\Backend;
+
+/**
+ * WebDAV-sync support for CardDAV backends.
+ *
+ * In order for backends to advertise support for WebDAV-sync, this interface
+ * must be implemented.
+ *
+ * Implementing this can result in a significant reduction of bandwidth and CPU
+ * time.
+ *
+ * For this to work, you _must_ return a {http://sabredav.org/ns}sync-token
+ * property from getAddressBooksForUser.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface SyncSupport extends BackendInterface {
+
+ /**
+ * The getChanges method returns all the changes that have happened, since
+ * the specified syncToken in the specified address book.
+ *
+ * This function should return an array, such as the following:
+ *
+ * [
+ * 'syncToken' => 'The current synctoken',
+ * 'added' => [
+ * 'new.txt',
+ * ],
+ * 'modified' => [
+ * 'modified.txt',
+ * ],
+ * 'deleted' => [
+ * 'foo.php.bak',
+ * 'old.txt'
+ * ]
+ * ];
+ *
+ * The returned syncToken property should reflect the *current* syncToken
+ * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
+ * property. This is needed here too, to ensure the operation is atomic.
+ *
+ * If the $syncToken argument is specified as null, this is an initial
+ * sync, and all members should be reported.
+ *
+ * The modified property is an array of nodenames that have changed since
+ * the last token.
+ *
+ * The deleted property is an array with nodenames, that have been deleted
+ * from collection.
+ *
+ * The $syncLevel argument is basically the 'depth' of the report. If it's
+ * 1, you only have to report changes that happened only directly in
+ * immediate descendants. If it's 2, it should also include changes from
+ * the nodes below the child collections. (grandchildren)
+ *
+ * The $limit argument allows a client to specify how many results should
+ * be returned at most. If the limit is not specified, it should be treated
+ * as infinite.
+ *
+ * If the limit (infinite or not) is higher than you're willing to return,
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
+ *
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
+ * return null.
+ *
+ * The limit is 'suggestive'. You are free to ignore it.
+ *
+ * @param string $addressBookId
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ * @return array
+ */
+ function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null);
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Card.php b/vendor/sabre/dav/lib/CardDAV/Card.php
new file mode 100644
index 000000000..8da672502
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Card.php
@@ -0,0 +1,263 @@
+<?php
+
+namespace Sabre\CardDAV;
+
+use Sabre\DAVACL;
+use Sabre\DAV;
+
+/**
+ * The Card object represents a single Card from an addressbook
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Card extends DAV\File implements ICard, DAVACL\IACL {
+
+ /**
+ * CardDAV backend
+ *
+ * @var Backend\BackendInterface
+ */
+ protected $carddavBackend;
+
+ /**
+ * Array with information about this Card
+ *
+ * @var array
+ */
+ protected $cardData;
+
+ /**
+ * Array with information about the containing addressbook
+ *
+ * @var array
+ */
+ protected $addressBookInfo;
+
+ /**
+ * Constructor
+ *
+ * @param Backend\BackendInterface $carddavBackend
+ * @param array $addressBookInfo
+ * @param array $cardData
+ */
+ function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo, array $cardData) {
+
+ $this->carddavBackend = $carddavBackend;
+ $this->addressBookInfo = $addressBookInfo;
+ $this->cardData = $cardData;
+
+ }
+
+ /**
+ * Returns the uri for this object
+ *
+ * @return string
+ */
+ function getName() {
+
+ return $this->cardData['uri'];
+
+ }
+
+ /**
+ * Returns the VCard-formatted object
+ *
+ * @return string
+ */
+ 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
+ */
+ function put($cardData) {
+
+ if (is_resource($cardData))
+ $cardData = stream_get_contents($cardData);
+
+ // Converting to UTF-8, if needed
+ $cardData = 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
+ */
+ function delete() {
+
+ $this->carddavBackend->deleteCard($this->addressBookInfo['id'], $this->cardData['uri']);
+
+ }
+
+ /**
+ * Returns the mime content-type
+ *
+ * @return string
+ */
+ function getContentType() {
+
+ return 'text/vcard; charset=utf-8';
+
+ }
+
+ /**
+ * Returns an ETag for this object
+ *
+ * @return string
+ */
+ function getETag() {
+
+ if (isset($this->cardData['etag'])) {
+ return $this->cardData['etag'];
+ } else {
+ $data = $this->get();
+ if (is_string($data)) {
+ return '"' . md5($data) . '"';
+ } else {
+ // We refuse to calculate the md5 if it's a stream.
+ return null;
+ }
+ }
+
+ }
+
+ /**
+ * Returns the last modification date as a unix timestamp
+ *
+ * @return int
+ */
+ function getLastModified() {
+
+ return isset($this->cardData['lastmodified']) ? $this->cardData['lastmodified'] : null;
+
+ }
+
+ /**
+ * Returns the size of this object in bytes
+ *
+ * @return int
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ function getACL() {
+
+ // An alternative acl may be specified through the cardData array.
+ if (isset($this->cardData['acl'])) {
+ return $this->cardData['acl'];
+ }
+
+ return [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->addressBookInfo['principaluri'],
+ 'protected' => true,
+ ],
+ [
+ '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
+ */
+ function setACL(array $acl) {
+
+ throw new 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
+ */
+ function getSupportedPrivilegeSet() {
+
+ return null;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/IAddressBook.php b/vendor/sabre/dav/lib/CardDAV/IAddressBook.php
new file mode 100644
index 000000000..f80e05575
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/IAddressBook.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Sabre\CardDAV;
+
+use Sabre\DAV;
+
+/**
+ * AddressBook interface
+ *
+ * Implement this interface to allow a node to be recognized as an addressbook.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface IAddressBook extends DAV\ICollection {
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/ICard.php b/vendor/sabre/dav/lib/CardDAV/ICard.php
new file mode 100644
index 000000000..a974cbd8f
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/ICard.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Sabre\CardDAV;
+
+use Sabre\DAV;
+
+/**
+ * Card interface
+ *
+ * Extend the ICard interface to allow your custom nodes to be picked up as
+ * 'Cards'.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface ICard extends DAV\IFile {
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/IDirectory.php b/vendor/sabre/dav/lib/CardDAV/IDirectory.php
new file mode 100644
index 000000000..d991a1cc8
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/IDirectory.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Sabre\CardDAV;
+
+/**
+ * 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
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface IDirectory extends IAddressBook {
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Plugin.php b/vendor/sabre/dav/lib/CardDAV/Plugin.php
new file mode 100644
index 000000000..b8bded098
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Plugin.php
@@ -0,0 +1,871 @@
+<?php
+
+namespace Sabre\CardDAV;
+
+use Sabre\DAV;
+use Sabre\DAV\Exception\ReportNotSupported;
+use Sabre\DAV\Xml\Property\Href;
+use Sabre\DAVACL;
+use Sabre\HTTP;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+use Sabre\VObject;
+
+/**
+ * CardDAV plugin
+ *
+ * The CardDAV plugin adds CardDAV functionality to the WebDAV server
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Plugin extends 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 = [];
+
+ /**
+ * Server class
+ *
+ * @var Sabre\DAV\Server
+ */
+ protected $server;
+
+ /**
+ * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
+ * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
+ * capping it to 10M here.
+ */
+ protected $maxResourceSize = 10000000;
+
+ /**
+ * Initializes the plugin
+ *
+ * @param DAV\Server $server
+ * @return void
+ */
+ function initialize(DAV\Server $server) {
+
+ /* Events */
+ $server->on('propFind', [$this, 'propFindEarly']);
+ $server->on('propFind', [$this, 'propFindLate'], 150);
+ $server->on('report', [$this, 'report']);
+ $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
+ $server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
+ $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
+ $server->on('afterMethod:GET', [$this, 'httpAfterGet']);
+
+ $server->xml->namespaceMap[self::NS_CARDDAV] = 'card';
+
+ $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-query'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport';
+ $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-multiget'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport';
+
+ /* 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->xml->elementMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Xml\\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
+ */
+ function getFeatures() {
+
+ return ['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
+ */
+ function getSupportedReportSet($uri) {
+
+ $node = $this->server->tree->getNodeForPath($uri);
+ if ($node instanceof IAddressBook || $node instanceof ICard) {
+ return [
+ '{' . self::NS_CARDDAV . '}addressbook-multiget',
+ '{' . self::NS_CARDDAV . '}addressbook-query',
+ ];
+ }
+ return [];
+
+ }
+
+
+ /**
+ * Adds all CardDAV-specific properties
+ *
+ * @param DAV\PropFind $propFind
+ * @param DAV\INode $node
+ * @return void
+ */
+ function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) {
+
+ $ns = '{' . self::NS_CARDDAV . '}';
+
+ if ($node instanceof IAddressBook) {
+
+ $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize);
+ $propFind->handle($ns . 'supported-address-data', function() {
+ return new Xml\Property\SupportedAddressData();
+ });
+ $propFind->handle($ns . 'supported-collation-set', function() {
+ return new Xml\Property\SupportedCollationSet();
+ });
+
+ }
+ if ($node instanceof DAVACL\IPrincipal) {
+
+ $path = $propFind->getPath();
+
+ $propFind->handle('{' . self::NS_CARDDAV . '}addressbook-home-set', function() use ($path) {
+ return new Href($this->getAddressBookHomeForPrincipal($path) . '/');
+ });
+
+ if ($this->directories) $propFind->handle('{' . self::NS_CARDDAV . '}directory-gateway', function() {
+ return new Href($this->directories);
+ });
+
+ }
+
+ if ($node instanceof 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.
+ $propFind->handle('{' . self::NS_CARDDAV . '}address-data', function() use ($node) {
+ $val = $node->get();
+ if (is_resource($val))
+ $val = stream_get_contents($val);
+
+ return $val;
+
+ });
+
+ }
+
+ }
+
+ /**
+ * This functions handles REPORT requests specific to CardDAV
+ *
+ * @param string $reportName
+ * @param \DOMNode $dom
+ * @param mixed $path
+ * @return bool
+ */
+ function report($reportName, $dom, $path) {
+
+ switch ($reportName) {
+ case '{' . self::NS_CARDDAV . '}addressbook-multiget' :
+ $this->server->transactionType = 'report-addressbook-multiget';
+ $this->addressbookMultiGetReport($dom);
+ return false;
+ case '{' . self::NS_CARDDAV . '}addressbook-query' :
+ $this->server->transactionType = 'report-addressbook-query';
+ $this->addressBookQueryReport($dom);
+ return false;
+ default :
+ return;
+
+ }
+
+
+ }
+
+ /**
+ * Returns the addressbook home for a given principal
+ *
+ * @param string $principal
+ * @return string
+ */
+ protected function getAddressbookHomeForPrincipal($principal) {
+
+ list(, $principalId) = \Sabre\HTTP\URLUtil::splitPath($principal);
+ return self::ADDRESSBOOK_ROOT . '/' . $principalId;
+
+ }
+
+
+ /**
+ * 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 Xml\Request\AddressBookMultiGetReport $report
+ * @return void
+ */
+ function addressbookMultiGetReport($report) {
+
+ $contentType = $report->contentType;
+ $version = $report->version;
+ if ($version) {
+ $contentType .= '; version=' . $version;
+ }
+
+ $vcardType = $this->negotiateVCard(
+ $contentType
+ );
+
+ $propertyList = [];
+ $paths = array_map(
+ [$this->server, 'calculateUri'],
+ $report->hrefs
+ );
+ foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $props) {
+
+ if (isset($props['200']['{' . self::NS_CARDDAV . '}address-data'])) {
+
+ $props['200']['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard(
+ $props[200]['{' . self::NS_CARDDAV . '}address-data'],
+ $vcardType
+ );
+
+ }
+ $propertyList[] = $props;
+
+ }
+
+ $prefer = $this->server->getHTTPPrefer();
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal'));
+
+ }
+
+ /**
+ * This method is 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 DAV\IFile $node
+ * @param resource $data
+ * @param bool $modified Should be set to true, if this event handler
+ * changed &$data.
+ * @return void
+ */
+ function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {
+
+ if (!$node instanceof ICard)
+ return;
+
+ $this->validateVCard($data, $modified);
+
+ }
+
+ /**
+ * 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 DAV\ICollection $parentNode
+ * @param bool $modified Should be set to true, if this event handler
+ * changed &$data.
+ * @return void
+ */
+ function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {
+
+ if (!$parentNode instanceof IAddressBook)
+ return;
+
+ $this->validateVCard($data, $modified);
+
+ }
+
+ /**
+ * Checks if the submitted iCalendar data is in fact, valid.
+ *
+ * An exception is thrown if it's not.
+ *
+ * @param resource|string $data
+ * @param bool $modified Should be set to true, if this event handler
+ * changed &$data.
+ * @return void
+ */
+ protected function validateVCard(&$data, &$modified) {
+
+ // If it's a stream, we convert it to a string first.
+ if (is_resource($data)) {
+ $data = stream_get_contents($data);
+ }
+
+ $before = md5($data);
+
+ // Converting the data to unicode, if needed.
+ $data = DAV\StringUtil::ensureUTF8($data);
+
+ if (md5($data) !== $before) $modified = true;
+
+ try {
+
+ // If the data starts with a [, we can reasonably assume we're dealing
+ // with a jCal object.
+ if (substr($data, 0, 1) === '[') {
+ $vobj = VObject\Reader::readJson($data);
+
+ // Converting $data back to iCalendar, as that's what we
+ // technically support everywhere.
+ $data = $vobj->serialize();
+ $modified = true;
+ } else {
+ $vobj = VObject\Reader::read($data);
+ }
+
+ } catch (VObject\ParseException $e) {
+
+ throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vCard or jCard data. Parse error: ' . $e->getMessage());
+
+ }
+
+ if ($vobj->name !== 'VCARD') {
+ throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
+ }
+
+ if (!isset($vobj->UID)) {
+ // No UID in vcards is invalid, but we'll just add it in anyway.
+ $vobj->add('UID', DAV\UUIDUtil::getUUID());
+ $data = $vobj->serialize();
+ $modified = true;
+ }
+
+ // Destroy circular references to PHP will GC the object.
+ $vobj->destroy();
+ }
+
+
+ /**
+ * This function handles the addressbook-query REPORT
+ *
+ * This report is used by the client to filter an addressbook based on a
+ * complex query.
+ *
+ * @param Xml\Request\AddressBookQueryReport $report
+ * @return void
+ */
+ protected function addressbookQueryReport($report) {
+
+ $depth = $this->server->getHTTPDepth(0);
+
+ if ($depth == 0) {
+ $candidateNodes = [
+ $this->server->tree->getNodeForPath($this->server->getRequestUri())
+ ];
+ if (!$candidateNodes[0] instanceof ICard) {
+ throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0');
+ }
+ } else {
+ $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
+ }
+
+ $contentType = $report->contentType;
+ if ($report->version) {
+ $contentType .= '; version=' . $report->version;
+ }
+
+ $vcardType = $this->negotiateVCard(
+ $contentType
+ );
+
+ $validNodes = [];
+ foreach ($candidateNodes as $node) {
+
+ if (!$node instanceof ICard)
+ continue;
+
+ $blob = $node->get();
+ if (is_resource($blob)) {
+ $blob = stream_get_contents($blob);
+ }
+
+ if (!$this->validateFilters($blob, $report->filters, $report->test)) {
+ continue;
+ }
+
+ $validNodes[] = $node;
+
+ if ($report->limit && $report->limit <= count($validNodes)) {
+ // We hit the maximum number of items, we can stop now.
+ break;
+ }
+
+ }
+
+ $result = [];
+ foreach ($validNodes as $validNode) {
+
+ if ($depth == 0) {
+ $href = $this->server->getRequestUri();
+ } else {
+ $href = $this->server->getRequestUri() . '/' . $validNode->getName();
+ }
+
+ list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0);
+
+ if (isset($props[200]['{' . self::NS_CARDDAV . '}address-data'])) {
+
+ $props[200]['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard(
+ $props[200]['{' . self::NS_CARDDAV . '}address-data'],
+ $vcardType
+ );
+
+ }
+ $result[] = $props;
+
+ }
+
+ $prefer = $this->server->getHTTPPrefer();
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));
+
+ }
+
+ /**
+ * 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
+ */
+ function validateFilters($vcardData, array $filters, $test) {
+
+
+ if (!$filters) return true;
+ $vcard = 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 = [];
+ if ($filter['param-filters']) {
+ $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']);
+ }
+ if ($filter['text-matches']) {
+ $texts = [];
+ foreach ($vProperties as $vProperty)
+ $texts[] = $vProperty->getValue();
+
+ $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) {
+
+ // Destroy circular references to PHP will GC the object.
+ $vcard->destroy();
+
+ return true;
+ }
+ if ($test === 'allof' && !$success) {
+
+ // Destroy circular references to PHP will GC the object.
+ $vcard->destroy();
+
+ return false;
+ }
+
+ } // foreach
+
+
+ // Destroy circular references to PHP will GC the object.
+ $vcard->destroy();
+
+ // 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 = DAV\StringUtil::textMatch($vProperty[$filter['name']]->getValue(), $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 = 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 when fetching properties.
+ *
+ * This event is scheduled late in the process, after most work for
+ * propfind has been done.
+ *
+ * @param DAV\PropFind $propFind
+ * @param DAV\INode $node
+ * @return void
+ */
+ function propFindLate(DAV\PropFind $propFind, DAV\INode $node) {
+
+ // 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 (strpos($this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird') === false) {
+ return;
+ }
+ $contentType = $propFind->get('{DAV:}getcontenttype');
+ list($part) = explode(';', $contentType);
+ if ($part === 'text/x-vcard' || $part === 'text/vcard') {
+ $propFind->set('{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 addressbooks.
+ *
+ * @param DAV\INode $node
+ * @param string $output
+ * @return bool
+ */
+ function htmlActionsPanel(DAV\INode $node, &$output) {
+
+ if (!$node instanceof AddressBookHome)
+ return;
+
+ $output .= '<tr><td colspan="2"><form method="post" action="">
+ <h3>Create new address book</h3>
+ <input type="hidden" name="sabreAction" value="mkcol" />
+ <input type="hidden" name="resourceType" value="{DAV:}collection,{' . self::NS_CARDDAV . '}addressbook" />
+ <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 event is triggered after GET requests.
+ *
+ * This is used to transform data into jCal, if this was requested.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return void
+ */
+ function httpAfterGet(RequestInterface $request, ResponseInterface $response) {
+
+ if (strpos($response->getHeader('Content-Type'), 'text/vcard') === false) {
+ return;
+ }
+
+ $target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType);
+
+ $newBody = $this->convertVCard(
+ $response->getBody(),
+ $target
+ );
+
+ $response->setBody($newBody);
+ $response->setHeader('Content-Type', $mimeType . '; charset=utf-8');
+ $response->setHeader('Content-Length', strlen($newBody));
+
+ }
+
+ /**
+ * This helper function performs the content-type negotiation for vcards.
+ *
+ * It will return one of the following strings:
+ * 1. vcard3
+ * 2. vcard4
+ * 3. jcard
+ *
+ * It defaults to vcard3.
+ *
+ * @param string $input
+ * @param string $mimeType
+ * @return string
+ */
+ protected function negotiateVCard($input, &$mimeType = null) {
+
+ $result = HTTP\Util::negotiate(
+ $input,
+ [
+ // Most often used mime-type. Version 3
+ 'text/x-vcard',
+ // The correct standard mime-type. Defaults to version 3 as
+ // well.
+ 'text/vcard',
+ // vCard 4
+ 'text/vcard; version=4.0',
+ // vCard 3
+ 'text/vcard; version=3.0',
+ // jCard
+ 'application/vcard+json',
+ ]
+ );
+
+ $mimeType = $result;
+ switch ($result) {
+
+ default :
+ case 'text/x-vcard' :
+ case 'text/vcard' :
+ case 'text/vcard; version=3.0' :
+ $mimeType = 'text/vcard';
+ return 'vcard3';
+ case 'text/vcard; version=4.0' :
+ return 'vcard4';
+ case 'application/vcard+json' :
+ return 'jcard';
+
+ // @codeCoverageIgnoreStart
+ }
+ // @codeCoverageIgnoreEnd
+
+ }
+
+ /**
+ * Converts a vcard blob to a different version, or jcard.
+ *
+ * @param string $data
+ * @param string $target
+ * @return string
+ */
+ protected function convertVCard($data, $target) {
+
+ $data = VObject\Reader::read($data);
+ switch ($target) {
+ default :
+ case 'vcard3' :
+ $data = $data->convert(VObject\Document::VCARD30);
+ $newResult = $data->serialize();
+ break;
+ case 'vcard4' :
+ $data = $data->convert(VObject\Document::VCARD40);
+ $newResult = $data->serialize();
+ break;
+ case 'jcard' :
+ $data = $data->convert(VObject\Document::VCARD40);
+ $newResult = json_encode($data->jsonSerialize());
+ break;
+
+ }
+ // Destroy circular references to PHP will GC the object.
+ $data->destroy();
+
+ return $newResult;
+
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ function getPluginName() {
+
+ return 'carddav';
+
+ }
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * Providing this information is optional, and is mainly displayed by the
+ * Browser plugin.
+ *
+ * The description key in the returned array may contain html and will not
+ * be sanitized.
+ *
+ * @return array
+ */
+ function getPluginInfo() {
+
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'Adds support for CardDAV (rfc6352)',
+ 'link' => 'http://sabre.io/dav/carddav/',
+ ];
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php b/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php
new file mode 100644
index 000000000..de8b3bb84
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php
@@ -0,0 +1,152 @@
+<?php
+
+namespace Sabre\CardDAV;
+
+use Sabre\DAV;
+use Sabre\VObject;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+
+/**
+ * VCF Exporter
+ *
+ * This plugin adds the ability to export entire address books as .vcf files.
+ * This is useful for clients that don't support CardDAV yet. They often do
+ * support vcf files.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @author Thomas Tanghus (http://tanghus.net/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class VCFExportPlugin extends DAV\ServerPlugin {
+
+ /**
+ * Reference to Server class
+ *
+ * @var Sabre\DAV\Server
+ */
+ protected $server;
+
+ /**
+ * Initializes the plugin and registers event handlers
+ *
+ * @param DAV\Server $server
+ * @return void
+ */
+ function initialize(DAV\Server $server) {
+
+ $this->server = $server;
+ $this->server->on('method:GET', [$this, 'httpGet'], 90);
+ $server->on('browserButtonActions', function($path, $node, &$actions) {
+ if ($node instanceof IAddressBook) {
+ $actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="book"></span></a>';
+ }
+ });
+ }
+
+ /**
+ * Intercepts GET requests on addressbook urls ending with ?export.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return bool
+ */
+ function httpGet(RequestInterface $request, ResponseInterface $response) {
+
+ $queryParams = $request->getQueryParameters();
+ if (!array_key_exists('export', $queryParams)) return;
+
+ $path = $request->getPath();
+
+ $node = $this->server->tree->getNodeForPath($path);
+
+ if (!($node instanceof IAddressBook)) return;
+
+ $this->server->transactionType = 'get-addressbook-export';
+
+ // Checking ACL, if available.
+ if ($aclPlugin = $this->server->getPlugin('acl')) {
+ $aclPlugin->checkPrivileges($path, '{DAV:}read');
+ }
+
+ $response->setHeader('Content-Type', 'text/directory');
+ $response->setStatus(200);
+
+ $nodes = $this->server->getPropertiesForPath($path, [
+ '{' . Plugin::NS_CARDDAV . '}address-data',
+ ], 1);
+
+ $response->setBody($this->generateVCF($nodes));
+
+ // Returning false to break the event chain
+ return false;
+
+ }
+
+ /**
+ * Merges all vcard objects, and builds one big vcf export
+ *
+ * @param array $nodes
+ * @return string
+ */
+ function generateVCF(array $nodes) {
+
+ $output = "";
+
+ foreach ($nodes as $node) {
+
+ if (!isset($node[200]['{' . Plugin::NS_CARDDAV . '}address-data'])) {
+ continue;
+ }
+ $nodeData = $node[200]['{' . Plugin::NS_CARDDAV . '}address-data'];
+
+ // Parsing this node so VObject can clean up the output.
+ $vcard = VObject\Reader::read($nodeData);
+ $output .= $vcard->serialize();
+
+ // Destroy circular references to PHP will GC the object.
+ $vcard->destroy();
+
+ }
+
+ return $output;
+
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using \Sabre\DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ function getPluginName() {
+
+ return 'vcf-export';
+
+ }
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * Providing this information is optional, and is mainly displayed by the
+ * Browser plugin.
+ *
+ * The description key in the returned array may contain html and will not
+ * be sanitized.
+ *
+ * @return array
+ */
+ function getPluginInfo() {
+
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'Adds the ability to export CardDAV addressbooks as a single vCard file.',
+ 'link' => 'http://sabre.io/dav/vcf-export-plugin/',
+ ];
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php
new file mode 100644
index 000000000..34028db85
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Sabre\CardDAV\Xml\Filter;
+
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+
+/**
+ * AddressData parser.
+ *
+ * This class parses the {urn:ietf:params:xml:ns:carddav}address-data XML
+ * element, as defined in:
+ *
+ * http://tools.ietf.org/html/rfc6352#section-10.4
+ *
+ * This element is used in two distinct places, but this one specifically
+ * encodes the address-data element as it appears in the addressbook-query
+ * adressbook-multiget REPORT requests.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class AddressData implements XmlDeserializable {
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $result = [
+ 'contentType' => $reader->getAttribute('content-type') ?: 'text/vcard',
+ 'version' => $reader->getAttribute('version') ?: '3.0',
+ ];
+
+ $reader->next();
+ return $result;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php
new file mode 100644
index 000000000..9646ae3e6
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Sabre\CardDAV\Xml\Filter;
+
+use Sabre\Xml\Element;
+use Sabre\Xml\Reader;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\CardDAV\Plugin;
+
+/**
+ * ParamFilter parser.
+ *
+ * This class parses the {urn:ietf:params:xml:ns:carddav}param-filter XML
+ * element, as defined in:
+ *
+ * http://tools.ietf.org/html/rfc6352#section-10.5.2
+ *
+ * The result will be spit out as an array.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+abstract class ParamFilter implements Element {
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $result = [
+ 'name' => null,
+ 'is-not-defined' => false,
+ 'text-match' => null,
+ ];
+
+ $att = $reader->parseAttributes();
+ $result['name'] = $att['name'];
+
+ $elems = $reader->parseInnerTree();
+
+ if (is_array($elems)) foreach ($elems as $elem) {
+
+ switch ($elem['name']) {
+
+ case '{' . Plugin::NS_CARDDAV . '}is-not-defined' :
+ $result['is-not-defined'] = true;
+ break;
+ case '{' . Plugin::NS_CARDDAV . '}text-match' :
+ $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains';
+
+ if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) {
+ throw new BadRequest('Unknown match-type: ' . $matchType);
+ }
+ $result['text-match'] = [
+ 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
+ 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
+ 'value' => $elem['value'],
+ 'match-type' => $matchType,
+ ];
+ break;
+
+ }
+
+ }
+
+ return $result;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php
new file mode 100644
index 000000000..c162da160
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Sabre\CardDAV\Xml\Filter;
+
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\CardDAV\Plugin;
+
+/**
+ * PropFilter parser.
+ *
+ * This class parses the {urn:ietf:params:xml:ns:carddav}prop-filter XML
+ * element, as defined in:
+ *
+ * http://tools.ietf.org/html/rfc6352#section-10.5.1
+ *
+ * The result will be spit out as an array.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class PropFilter implements XmlDeserializable {
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $result = [
+ 'name' => null,
+ 'test' => 'anyof',
+ 'is-not-defined' => false,
+ 'param-filters' => [],
+ 'text-matches' => [],
+ ];
+
+ $att = $reader->parseAttributes();
+ $result['name'] = $att['name'];
+
+ if (isset($att['test']) && $att['test'] === 'allof') {
+ $result['test'] = 'allof';
+ }
+
+ $elems = $reader->parseInnerTree();
+
+ if (is_array($elems)) foreach ($elems as $elem) {
+
+ switch ($elem['name']) {
+
+ case '{' . Plugin::NS_CARDDAV . '}param-filter' :
+ $result['param-filters'][] = $elem['value'];
+ break;
+ case '{' . Plugin::NS_CARDDAV . '}is-not-defined' :
+ $result['is-not-defined'] = true;
+ break;
+ case '{' . Plugin::NS_CARDDAV . '}text-match' :
+ $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains';
+
+ if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) {
+ throw new BadRequest('Unknown match-type: ' . $matchType);
+ }
+ $result['text-matches'][] = [
+ 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
+ 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
+ 'value' => $elem['value'],
+ 'match-type' => $matchType,
+ ];
+ break;
+
+ }
+
+ }
+
+ return $result;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php b/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php
new file mode 100644
index 000000000..6ff57b6e3
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Sabre\CardDAV\Xml\Property;
+
+use Sabre\Xml\Writer;
+use Sabre\Xml\XmlSerializable;
+use Sabre\CardDAV\Plugin;
+
+/**
+ * Supported-address-data property
+ *
+ * This property is a representation of the supported-address-data property
+ * in the CardDAV namespace.
+ *
+ * This property is defined in:
+ *
+ * http://tools.ietf.org/html/rfc6352#section-6.2.2
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class SupportedAddressData implements XmlSerializable {
+
+ /**
+ * supported versions
+ *
+ * @var array
+ */
+ protected $supportedData = [];
+
+ /**
+ * Creates the property
+ *
+ * @param array|null $supportedData
+ */
+ function __construct(array $supportedData = null) {
+
+ if (is_null($supportedData)) {
+ $supportedData = [
+ ['contentType' => 'text/vcard', 'version' => '3.0'],
+ ['contentType' => 'text/vcard', 'version' => '4.0'],
+ ['contentType' => 'application/vcard+json', 'version' => '4.0'],
+ ];
+ }
+
+ $this->supportedData = $supportedData;
+
+ }
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ foreach ($this->supportedData as $supported) {
+ $writer->startElement('{' . Plugin::NS_CARDDAV . '}address-data-type');
+ $writer->writeAttributes([
+ 'content-type' => $supported['contentType'],
+ 'version' => $supported['version']
+ ]);
+ $writer->endElement(); // address-data-type
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php b/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php
new file mode 100644
index 000000000..1fc064900
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Sabre\CardDAV\Xml\Property;
+
+use Sabre\Xml\Writer;
+use Sabre\Xml\XmlSerializable;
+
+/**
+ * supported-collation-set property
+ *
+ * This property is a representation of the supported-collation-set property
+ * in the CardDAV namespace.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class SupportedCollationSet implements XmlSerializable {
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ foreach (['i;ascii-casemap', 'i;octet', 'i;unicode-casemap'] as $coll) {
+ $writer->writeElement('{urn:ietf:params:xml:ns:carddav}supported-collation', $coll);
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php b/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php
new file mode 100644
index 000000000..c97c5eb4f
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace Sabre\CardDAV\Xml\Request;
+
+use Sabre\CardDAV\Plugin;
+use Sabre\Uri;
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+
+/**
+ * AddressBookMultiGetReport request parser.
+ *
+ * This class parses the {urn:ietf:params:xml:ns:carddav}addressbook-multiget
+ * REPORT, as defined in:
+ *
+ * http://tools.ietf.org/html/rfc6352#section-8.7
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class AddressBookMultiGetReport implements XmlDeserializable {
+
+ /**
+ * An array with requested properties.
+ *
+ * @var array
+ */
+ public $properties;
+
+ /**
+ * This is an array with the urls that are being requested.
+ *
+ * @var array
+ */
+ public $hrefs;
+
+ /**
+ * The mimetype of the content that should be returend. Usually
+ * text/vcard.
+ *
+ * @var string
+ */
+ public $contentType = null;
+
+ /**
+ * The version of vcard data that should be returned. Usually 3.0,
+ * referring to vCard 3.0.
+ *
+ * @var string
+ */
+ public $version = null;
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $elems = $reader->parseInnerTree([
+ '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData',
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
+ ]);
+
+ $newProps = [
+ 'hrefs' => [],
+ 'properties' => []
+ ];
+
+ foreach ($elems as $elem) {
+
+ switch ($elem['name']) {
+
+ case '{DAV:}prop' :
+ $newProps['properties'] = array_keys($elem['value']);
+ if (isset($elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'])) {
+ $newProps += $elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'];
+ }
+ break;
+ case '{DAV:}href' :
+ $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']);
+ break;
+
+ }
+
+ }
+
+ $obj = new self();
+ foreach ($newProps as $key => $value) {
+ $obj->$key = $value;
+ }
+ return $obj;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php b/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php
new file mode 100644
index 000000000..a68ac5800
--- /dev/null
+++ b/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Sabre\CardDAV\Xml\Request;
+
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\CardDAV\Plugin;
+
+/**
+ * AddressBookQueryReport request parser.
+ *
+ * This class parses the {urn:ietf:params:xml:ns:carddav}addressbook-query
+ * REPORT, as defined in:
+ *
+ * http://tools.ietf.org/html/rfc6352#section-8.6
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class AddressBookQueryReport implements XmlDeserializable {
+
+ /**
+ * An array with requested properties.
+ *
+ * @var array
+ */
+ public $properties;
+
+ /**
+ * List of property/component filters.
+ *
+ * This is an array with filters. Every item is a property filter. Every
+ * property filter has the following keys:
+ * * name - name of the component to filter on
+ * * test - anyof or allof
+ * * is-not-defined - Test for non-existence
+ * * param-filters - A list of parameter filters on the property
+ * * text-matches - A list of text values the filter needs to match
+ *
+ * Each param-filter has the following keys:
+ * * name - name of the parameter
+ * * is-not-defined - Test for non-existence
+ * * text-match - Match the parameter value
+ *
+ * Each text-match in property filters, and the single text-match in
+ * param-filters have the following keys:
+ *
+ * * value - value to match
+ * * match-type - contains, starts-with, ends-with, equals
+ * * negate-condition - Do the opposite match
+ * * collation - Usually i;unicode-casemap
+ *
+ * @var array
+ */
+ public $filters;
+
+ /**
+ * 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;
+
+ /**
+ * Either 'anyof' or 'allof'
+ *
+ * @var string
+ */
+ public $test;
+
+ /**
+ * The mimetype of the content that should be returend. Usually
+ * text/vcard.
+ *
+ * @var string
+ */
+ public $contentType = null;
+
+ /**
+ * The version of vcard data that should be returned. Usually 3.0,
+ * referring to vCard 3.0.
+ *
+ * @var string
+ */
+ public $version = null;
+
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $elems = (array)$reader->parseInnerTree([
+ '{urn:ietf:params:xml:ns:carddav}prop-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\PropFilter',
+ '{urn:ietf:params:xml:ns:carddav}param-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\ParamFilter',
+ '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData',
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
+ ]);
+
+ $newProps = [
+ 'filters' => null,
+ 'properties' => [],
+ 'test' => 'anyof',
+ 'limit' => null,
+ ];
+
+ if (!is_array($elems)) $elems = [];
+
+ foreach ($elems as $elem) {
+
+ switch ($elem['name']) {
+
+ case '{DAV:}prop' :
+ $newProps['properties'] = array_keys($elem['value']);
+ if (isset($elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'])) {
+ $newProps += $elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'];
+ }
+ break;
+ case '{' . Plugin::NS_CARDDAV . '}filter' :
+
+ if (!is_null($newProps['filters'])) {
+ throw new BadRequest('You can only include 1 {' . Plugin::NS_CARDDAV . '}filter element');
+ }
+ if (isset($elem['attributes']['test'])) {
+ $newProps['test'] = $elem['attributes']['test'];
+ if ($newProps['test'] !== 'allof' && $newProps['test'] !== 'anyof') {
+ throw new BadRequest('The "test" attribute must be one of "allof" or "anyof"');
+ }
+ }
+
+ $newProps['filters'] = [];
+ foreach ((array)$elem['value'] as $subElem) {
+ if ($subElem['name'] === '{' . Plugin::NS_CARDDAV . '}prop-filter') {
+ $newProps['filters'][] = $subElem['value'];
+ }
+ }
+ break;
+ case '{' . Plugin::NS_CARDDAV . '}limit' :
+ foreach ($elem['value'] as $child) {
+ if ($child['name'] === '{' . Plugin::NS_CARDDAV . '}nresults') {
+ $newProps['limit'] = (int)$child['value'];
+ }
+ }
+ break;
+
+ }
+
+ }
+
+ if (is_null($newProps['filters'])) {
+ /*
+ * We are supposed to throw this error, but KDE sometimes does not
+ * include the filter element, and we need to treat it as if no
+ * filters are supplied
+ */
+ //throw new BadRequest('The {' . Plugin::NS_CARDDAV . '}filter element is required for this request');
+ $newProps['filters'] = [];
+
+ }
+
+ $obj = new self();
+ foreach ($newProps as $key => $value) {
+ $obj->$key = $value;
+ }
+
+ return $obj;
+
+ }
+
+}