commit vendor
This commit is contained in:
335
vendor/sabre/dav/lib/CardDAV/AddressBook.php
vendored
Normal file
335
vendor/sabre/dav/lib/CardDAV/AddressBook.php
vendored
Normal file
@ -0,0 +1,335 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* This is an array with addressbook information.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $addressBookInfo;
|
||||
|
||||
/**
|
||||
* CardDAV backend.
|
||||
*
|
||||
* @var Backend\BackendInterface
|
||||
*/
|
||||
protected $carddavBackend;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo)
|
||||
{
|
||||
$this->carddavBackend = $carddavBackend;
|
||||
$this->addressBookInfo = $addressBookInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the addressbook.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->addressBookInfo['uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a card.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return Card
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the addressbook.
|
||||
*
|
||||
* @param string $newName
|
||||
*/
|
||||
public function setName($newName)
|
||||
{
|
||||
throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification date as a unix timestamp.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public function getOwner()
|
||||
{
|
||||
return $this->addressBookInfo['principaluri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function getChildACL()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'privilege' => '{DAV:}all',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public function getChanges($syncToken, $syncLevel, $limit = null)
|
||||
{
|
||||
if (!$this->carddavBackend instanceof Backend\SyncSupport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->carddavBackend->getChangesForAddressBook(
|
||||
$this->addressBookInfo['id'],
|
||||
$syncToken,
|
||||
$syncLevel,
|
||||
$limit
|
||||
);
|
||||
}
|
||||
}
|
||||
178
vendor/sabre/dav/lib/CardDAV/AddressBookHome.php
vendored
Normal file
178
vendor/sabre/dav/lib/CardDAV/AddressBookHome.php
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* Principal uri.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $principalUri;
|
||||
|
||||
/**
|
||||
* carddavBackend.
|
||||
*
|
||||
* @var Backend\BackendInterface
|
||||
*/
|
||||
protected $carddavBackend;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $principalUri
|
||||
*/
|
||||
public function __construct(Backend\BackendInterface $carddavBackend, $principalUri)
|
||||
{
|
||||
$this->carddavBackend = $carddavBackend;
|
||||
$this->principalUri = $principalUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
list(, $name) = Uri\split($this->principalUri);
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the name of this object.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
throw new DAV\Exception\MethodNotAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this object.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
throw new DAV\Exception\MethodNotAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification date.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new file under this object.
|
||||
*
|
||||
* This is currently not allowed
|
||||
*
|
||||
* @param string $filename
|
||||
* @param resource $data
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*
|
||||
* @throws DAV\Exception\InvalidResourceType
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public function getOwner()
|
||||
{
|
||||
return $this->principalUri;
|
||||
}
|
||||
}
|
||||
75
vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php
vendored
Normal file
75
vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 DAVACL\PrincipalBackend\BackendInterface
|
||||
*/
|
||||
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 string $principalPrefix
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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.
|
||||
*
|
||||
* @return \Sabre\DAV\INode
|
||||
*/
|
||||
public function getChildForPrincipal(array $principal)
|
||||
{
|
||||
return new AddressBookHome($this->carddavBackend, $principal['uri']);
|
||||
}
|
||||
}
|
||||
38
vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php
vendored
Normal file
38
vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMultipleCards($addressBookId, array $uris)
|
||||
{
|
||||
return array_map(function ($uri) use ($addressBookId) {
|
||||
return $this->getCard($addressBookId, $uri);
|
||||
}, $uris);
|
||||
}
|
||||
}
|
||||
194
vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php
vendored
Normal file
194
vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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
|
||||
*/
|
||||
public 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 documentation for more info and examples.
|
||||
*
|
||||
* @param string $addressBookId
|
||||
*/
|
||||
public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch);
|
||||
|
||||
/**
|
||||
* Creates a new address book.
|
||||
*
|
||||
* This method should return the id of the new address book. The id can be
|
||||
* in any format, including ints, strings, arrays or objects.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $url just the 'basename' of the url
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function createAddressBook($principalUri, $url, array $properties);
|
||||
|
||||
/**
|
||||
* Deletes an entire addressbook and all its contents.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
*/
|
||||
public function deleteAddressBook($addressBookId);
|
||||
|
||||
/**
|
||||
* Returns all cards for a specific addressbook id.
|
||||
*
|
||||
* This method should return the following properties for each card:
|
||||
* * carddata - raw vcard data
|
||||
* * uri - Some unique url
|
||||
* * lastmodified - A unix timestamp
|
||||
*
|
||||
* It's recommended to also return the following properties:
|
||||
* * etag - A unique etag. This must change every time the card changes.
|
||||
* * size - The size of the card in bytes.
|
||||
*
|
||||
* If these last two properties are provided, less time will be spent
|
||||
* calculating them. If they are specified, you can also ommit carddata.
|
||||
* This may speed up certain requests, especially with large cards.
|
||||
*
|
||||
* @param mixed $addressbookId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public function createCard($addressBookId, $cardUri, $cardData);
|
||||
|
||||
/**
|
||||
* Updates a card.
|
||||
*
|
||||
* The addressbook id will be passed as the first argument. This is the
|
||||
* same id as it is returned from the getAddressBooksForUser method.
|
||||
*
|
||||
* The cardUri is a base uri, and doesn't include the full path. The
|
||||
* cardData argument is the vcard body, and is passed as a string.
|
||||
*
|
||||
* It is possible to return an ETag from this method. This ETag should
|
||||
* match that of the updated resource, and must be enclosed with double
|
||||
* quotes (that is: the string itself must contain the actual quotes).
|
||||
*
|
||||
* You should only return the ETag if you store the carddata as-is. If a
|
||||
* subsequent GET request on the same card does not have the same body,
|
||||
* byte-by-byte and you did return an ETag here, clients tend to get
|
||||
* confused.
|
||||
*
|
||||
* If you don't return an ETag, you can just return null.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @param string $cardData
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function updateCard($addressBookId, $cardUri, $cardData);
|
||||
|
||||
/**
|
||||
* Deletes a card.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteCard($addressBookId, $cardUri);
|
||||
}
|
||||
538
vendor/sabre/dav/lib/CardDAV/Backend/PDO.php
vendored
Normal file
538
vendor/sabre/dav/lib/CardDAV/Backend/PDO.php
vendored
Normal file
@ -0,0 +1,538 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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.
|
||||
*/
|
||||
public function __construct(\PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of addressbooks for a specific user.
|
||||
*
|
||||
* @param string $principalUri
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAddressBooksForUser($principalUri)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, 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 documentation for more info and examples.
|
||||
*
|
||||
* @param string $addressBookId
|
||||
*/
|
||||
public 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
|
||||
*
|
||||
* @return int Last insert id
|
||||
*/
|
||||
public 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(
|
||||
$this->addressBooksTableName.'_id_seq'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an entire addressbook and all its contents.
|
||||
*
|
||||
* @param int $addressBookId
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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'].'"';
|
||||
$row['lastmodified'] = (int) $row['lastmodified'];
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific 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
|
||||
*/
|
||||
public 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'].'"';
|
||||
$result['lastmodified'] = (int) $result['lastmodified'];
|
||||
|
||||
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
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public 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'].'"';
|
||||
$row['lastmodified'] = (int) $row['lastmodified'];
|
||||
$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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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 1 === $stmt->rowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
83
vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php
vendored
Normal file
83
vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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
|
||||
*/
|
||||
public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null);
|
||||
}
|
||||
202
vendor/sabre/dav/lib/CardDAV/Card.php
vendored
Normal file
202
vendor/sabre/dav/lib/CardDAV/Card.php
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAVACL;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->cardData['uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the VCard-formatted object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
// Pre-populating 'carddata' is optional. If we don't yet have it
|
||||
// already, we fetch it from the backend.
|
||||
if (!isset($this->cardData['carddata'])) {
|
||||
$this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']);
|
||||
}
|
||||
|
||||
return $this->cardData['carddata'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the VCard-formatted object.
|
||||
*
|
||||
* @param string $cardData
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function put($cardData)
|
||||
{
|
||||
if (is_resource($cardData)) {
|
||||
$cardData = stream_get_contents($cardData);
|
||||
}
|
||||
|
||||
// Converting to UTF-8, if needed
|
||||
$cardData = 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.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->carddavBackend->deleteCard($this->addressBookInfo['id'], $this->cardData['uri']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mime content-type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContentType()
|
||||
{
|
||||
return 'text/vcard; charset=utf-8';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ETag for this object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getETag()
|
||||
{
|
||||
if (isset($this->cardData['etag'])) {
|
||||
return $this->cardData['etag'];
|
||||
} else {
|
||||
$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
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
return isset($this->cardData['lastmodified']) ? $this->cardData['lastmodified'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of this object in bytes.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
if (array_key_exists('size', $this->cardData)) {
|
||||
return $this->cardData['size'];
|
||||
} else {
|
||||
return strlen($this->get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner principal.
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOwner()
|
||||
{
|
||||
return $this->addressBookInfo['principaluri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of ACE's for this node.
|
||||
*
|
||||
* Each ACE has the following properties:
|
||||
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
|
||||
* currently the only supported privileges
|
||||
* * 'principal', a url to the principal who owns the node
|
||||
* * 'protected' (optional), indicating that this ACE is not allowed to
|
||||
* be updated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getACL()
|
||||
{
|
||||
// An alternative acl may be specified through the cardData array.
|
||||
if (isset($this->cardData['acl'])) {
|
||||
return $this->cardData['acl'];
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'privilege' => '{DAV:}all',
|
||||
'principal' => $this->addressBookInfo['principaluri'],
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
20
vendor/sabre/dav/lib/CardDAV/IAddressBook.php
vendored
Normal file
20
vendor/sabre/dav/lib/CardDAV/IAddressBook.php
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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
|
||||
{
|
||||
}
|
||||
21
vendor/sabre/dav/lib/CardDAV/ICard.php
vendored
Normal file
21
vendor/sabre/dav/lib/CardDAV/ICard.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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
|
||||
{
|
||||
}
|
||||
22
vendor/sabre/dav/lib/CardDAV/IDirectory.php
vendored
Normal file
22
vendor/sabre/dav/lib/CardDAV/IDirectory.php
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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
|
||||
{
|
||||
}
|
||||
879
vendor/sabre/dav/lib/CardDAV/Plugin.php
vendored
Normal file
879
vendor/sabre/dav/lib/CardDAV/Plugin.php
vendored
Normal file
@ -0,0 +1,879 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\Exception\ReportNotSupported;
|
||||
use Sabre\DAV\Xml\Property\LocalHref;
|
||||
use Sabre\DAVACL;
|
||||
use Sabre\HTTP;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
use Sabre\Uri;
|
||||
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 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.
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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 LocalHref($this->getAddressBookHomeForPrincipal($path).'/');
|
||||
});
|
||||
|
||||
if ($this->directories) {
|
||||
$propFind->handle('{'.self::NS_CARDDAV.'}directory-gateway', function () {
|
||||
return new LocalHref($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
|
||||
*/
|
||||
public 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) = Uri\split($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
|
||||
*/
|
||||
public 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, 'minimal' === $prefer['return']));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 resource $data
|
||||
* @param bool $modified should be set to true, if this event handler
|
||||
* changed &$data
|
||||
*/
|
||||
public 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 bool $modified should be set to true, if this event handler
|
||||
* changed &$data
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
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 = $data;
|
||||
|
||||
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 ('VCARD' !== $vobj->name) {
|
||||
throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
|
||||
}
|
||||
|
||||
$options = VObject\Node::PROFILE_CARDDAV;
|
||||
$prefer = $this->server->getHTTPPrefer();
|
||||
|
||||
if ('strict' !== $prefer['handling']) {
|
||||
$options |= VObject\Node::REPAIR;
|
||||
}
|
||||
|
||||
$messages = $vobj->validate($options);
|
||||
|
||||
$highestLevel = 0;
|
||||
$warningMessage = null;
|
||||
|
||||
// $messages contains a list of problems with the vcard, along with
|
||||
// their severity.
|
||||
foreach ($messages as $message) {
|
||||
if ($message['level'] > $highestLevel) {
|
||||
// Recording the highest reported error level.
|
||||
$highestLevel = $message['level'];
|
||||
$warningMessage = $message['message'];
|
||||
}
|
||||
|
||||
switch ($message['level']) {
|
||||
case 1:
|
||||
// Level 1 means that there was a problem, but it was repaired.
|
||||
$modified = true;
|
||||
break;
|
||||
case 2:
|
||||
// Level 2 means a warning, but not critical
|
||||
break;
|
||||
case 3:
|
||||
// Level 3 means a critical error
|
||||
throw new DAV\Exception\UnsupportedMediaType('Validation error in vCard: '.$message['message']);
|
||||
}
|
||||
}
|
||||
if ($warningMessage) {
|
||||
$this->server->httpResponse->setHeader(
|
||||
'X-Sabre-Ew-Gross',
|
||||
'vCard validation warning: '.$warningMessage
|
||||
);
|
||||
|
||||
// Re-serializing object.
|
||||
$data = $vobj->serialize();
|
||||
if (!$modified && 0 !== strcmp($data, $before)) {
|
||||
// This ensures that the system does not send an ETag back.
|
||||
$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
|
||||
*/
|
||||
protected function addressbookQueryReport($report)
|
||||
{
|
||||
$depth = $this->server->getHTTPDepth(0);
|
||||
|
||||
if (0 == $depth) {
|
||||
$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 (0 == $depth) {
|
||||
$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,
|
||||
$report->addressDataProperties
|
||||
);
|
||||
}
|
||||
$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, 'minimal' === $prefer['return']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a vcard makes it throught a list of filters.
|
||||
*
|
||||
* @param string $vcardData
|
||||
* @param string $test anyof or allof (which means OR or AND)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public 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 (1 === count($results)) {
|
||||
$success = $results[0];
|
||||
} else {
|
||||
if ('anyof' === $filter['test']) {
|
||||
$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 ('anyof' === $test && $success) {
|
||||
// Destroy circular references to PHP will GC the object.
|
||||
$vcard->destroy();
|
||||
|
||||
return true;
|
||||
}
|
||||
if ('allof' === $test && !$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 'allof' === $test;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 ('anyof' === $test && $success) {
|
||||
return true;
|
||||
}
|
||||
if ('allof' === $test && !$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 'allof' === $test;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a text-filter can be applied to a specific property.
|
||||
*
|
||||
* @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 && 'anyof' === $test) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$success && 'allof' == $test) {
|
||||
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 'allof' === $test;
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is triggered when fetching properties.
|
||||
*
|
||||
* This event is scheduled late in the process, after most work for
|
||||
* propfind has been done.
|
||||
*/
|
||||
public 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 (false === strpos((string) $this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird')) {
|
||||
return;
|
||||
}
|
||||
$contentType = $propFind->get('{DAV:}getcontenttype');
|
||||
if (null !== $contentType) {
|
||||
list($part) = explode(';', $contentType);
|
||||
if ('text/x-vcard' === $part || 'text/vcard' === $part) {
|
||||
$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 string $output
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public function httpAfterGet(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$contentType = $response->getHeader('Content-Type');
|
||||
if (null === $contentType || false === strpos($contentType, 'text/vcard')) {
|
||||
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\negotiateContentType(
|
||||
$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|resource $data
|
||||
* @param string $target
|
||||
* @param array $propertiesFilter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function convertVCard($data, $target, array $propertiesFilter = null)
|
||||
{
|
||||
if (is_resource($data)) {
|
||||
$data = stream_get_contents($data);
|
||||
}
|
||||
$input = VObject\Reader::read($data);
|
||||
if (!empty($propertiesFilter)) {
|
||||
$propertiesFilter = array_merge(['UID', 'VERSION', 'FN'], $propertiesFilter);
|
||||
$keys = array_unique(array_map(function ($child) {
|
||||
return $child->name;
|
||||
}, $input->children()));
|
||||
$keys = array_diff($keys, $propertiesFilter);
|
||||
foreach ($keys as $key) {
|
||||
unset($input->$key);
|
||||
}
|
||||
$data = $input->serialize();
|
||||
}
|
||||
$output = null;
|
||||
try {
|
||||
switch ($target) {
|
||||
default:
|
||||
case 'vcard3':
|
||||
if (VObject\Document::VCARD30 === $input->getDocumentType()) {
|
||||
// Do nothing
|
||||
return $data;
|
||||
}
|
||||
$output = $input->convert(VObject\Document::VCARD30);
|
||||
|
||||
return $output->serialize();
|
||||
case 'vcard4':
|
||||
if (VObject\Document::VCARD40 === $input->getDocumentType()) {
|
||||
// Do nothing
|
||||
return $data;
|
||||
}
|
||||
$output = $input->convert(VObject\Document::VCARD40);
|
||||
|
||||
return $output->serialize();
|
||||
case 'jcard':
|
||||
$output = $input->convert(VObject\Document::VCARD40);
|
||||
|
||||
return json_encode($output);
|
||||
}
|
||||
} finally {
|
||||
// Destroy circular references to PHP will GC the object.
|
||||
$input->destroy();
|
||||
if (!is_null($output)) {
|
||||
$output->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a plugin name.
|
||||
*
|
||||
* Using this name other plugins will be able to access other plugins
|
||||
* using DAV\Server::getPlugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public function getPluginInfo()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getPluginName(),
|
||||
'description' => 'Adds support for CardDAV (rfc6352)',
|
||||
'link' => 'http://sabre.io/dav/carddav/',
|
||||
];
|
||||
}
|
||||
}
|
||||
165
vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php
vendored
Normal file
165
vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php
vendored
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
use Sabre\VObject;
|
||||
|
||||
/**
|
||||
* 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 DAV\Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Initializes the plugin and registers event handlers.
|
||||
*/
|
||||
public 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.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public 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');
|
||||
}
|
||||
|
||||
$nodes = $this->server->getPropertiesForPath($path, [
|
||||
'{'.Plugin::NS_CARDDAV.'}address-data',
|
||||
], 1);
|
||||
|
||||
$format = 'text/directory';
|
||||
|
||||
$output = null;
|
||||
$filenameExtension = null;
|
||||
|
||||
switch ($format) {
|
||||
case 'text/directory':
|
||||
$output = $this->generateVCF($nodes);
|
||||
$filenameExtension = '.vcf';
|
||||
break;
|
||||
}
|
||||
|
||||
$filename = preg_replace(
|
||||
'/[^a-zA-Z0-9-_ ]/um',
|
||||
'',
|
||||
$node->getName()
|
||||
);
|
||||
$filename .= '-'.date('Y-m-d').$filenameExtension;
|
||||
|
||||
$response->setHeader('Content-Disposition', 'attachment; filename="'.$filename.'"');
|
||||
$response->setHeader('Content-Type', $format);
|
||||
|
||||
$response->setStatus(200);
|
||||
$response->setBody($output);
|
||||
|
||||
// Returning false to break the event chain
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges all vcard objects, and builds one big vcf export.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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/',
|
||||
];
|
||||
}
|
||||
}
|
||||
66
vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php
vendored
Normal file
66
vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 statically, 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.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$result = [
|
||||
'contentType' => $reader->getAttribute('content-type') ?: 'text/vcard',
|
||||
'version' => $reader->getAttribute('version') ?: '3.0',
|
||||
];
|
||||
|
||||
$elems = (array) $reader->parseInnerTree();
|
||||
$elems = array_filter($elems, function ($element) {
|
||||
return '{urn:ietf:params:xml:ns:carddav}prop' === $element['name'] &&
|
||||
isset($element['attributes']['name']);
|
||||
});
|
||||
$result['addressDataProperties'] = array_map(function ($element) {
|
||||
return $element['attributes']['name'];
|
||||
}, $elems);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
86
vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php
vendored
Normal file
86
vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Xml\Filter;
|
||||
|
||||
use Sabre\CardDAV\Plugin;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\Xml\Element;
|
||||
use Sabre\Xml\Reader;
|
||||
|
||||
/**
|
||||
* 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 statically, 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.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public 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']) && 'yes' === $elem['attributes']['negate-condition'],
|
||||
'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
|
||||
'value' => $elem['value'],
|
||||
'match-type' => $matchType,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
95
vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php
vendored
Normal file
95
vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Xml\Filter;
|
||||
|
||||
use Sabre\CardDAV\Plugin;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* 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 statically, 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.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public 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']) && 'allof' === $att['test']) {
|
||||
$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']) && 'yes' === $elem['attributes']['negate-condition'],
|
||||
'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
|
||||
'value' => $elem['value'],
|
||||
'match-type' => $matchType,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
77
vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php
vendored
Normal file
77
vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Xml\Property;
|
||||
|
||||
use Sabre\CardDAV\Plugin;
|
||||
use Sabre\Xml\Writer;
|
||||
use Sabre\Xml\XmlSerializable;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public 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 method 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 XmlSerializable 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.
|
||||
*/
|
||||
public 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
|
||||
}
|
||||
}
|
||||
}
|
||||
44
vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php
vendored
Normal file
44
vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 method 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 XmlSerializable 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.
|
||||
*/
|
||||
public 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
109
vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php
vendored
Normal file
109
vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 statically, 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.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
194
vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php
vendored
Normal file
194
vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Xml\Request;
|
||||
|
||||
use Sabre\CardDAV\Plugin;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* An array with requested vcard properties.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $addressDataProperties = [];
|
||||
|
||||
/**
|
||||
* 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 statically, 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.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public 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 ('allof' !== $newProps['test'] && 'anyof' !== $newProps['test']) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user