$cm->register(function() use ($cm) {
$db = \OC::$server->getDatabaseConnection();
$userId = \OC::$server->getUserSession()->getUser()->getUID();
- $cardDav = new \OCA\DAV\CardDAV\CardDavBackend($db);
+ $principal = new \OCA\DAV\Connector\Sabre\Principal(
+ \OC::$server->getConfig(),
+ \OC::$server->getUserManager()
+ );
+ $cardDav = new \OCA\DAV\CardDAV\CardDavBackend($db, $principal, \OC::$server->getLogger());
$addressBooks = $cardDav->getAddressBooksForUser("principals/$userId");
- foreach ($addressBooks as $addressBook) {
- $cm->registerAddressBook(new OCA\DAV\CardDAV\AddressBookImpl($addressBook));
+ foreach ($addressBooks as $addressBookInfo) {
+ $addressBook = new \OCA\DAV\CardDAV\AddressBook($cardDav, $addressBookInfo);
+ $cm->registerAddressBook(
+ new OCA\DAV\CardDAV\AddressBookImpl(
+ $addressBook,
+ $addressBookInfo,
+ $cardDav
+ )
+ );
}
});
</declaration>
</table>
+ <table>
+ <name>*dbprefix*cards_properties</name>
+ <declaration>
+ <field>
+ <name>id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <unsigned>true</unsigned>
+ <length>11</length>
+ </field>
+ <field>
+ <name>addressbookid</name>
+ <type>integer</type>
+ <default></default>
+ <notnull>true</notnull>
+ <length>11</length>
+ </field>
+ <field>
+ <name>cardid</name>
+ <type>integer</type>
+ <default></default>
+ <notnull>true</notnull>
+ <unsigned>true</unsigned>
+ <length>11</length>
+ </field>
+ <field>
+ <name>name</name>
+ <type>text</type>
+ <default></default>
+ <notnull>false</notnull>
+ <length>64</length>
+ </field>
+ <field>
+ <name>value</name>
+ <type>text</type>
+ <default></default>
+ <notnull>false</notnull>
+ <length>255</length>
+ </field>
+ <field>
+ <name>preferred</name>
+ <type>integer</type>
+ <default>1</default>
+ <notnull>true</notnull>
+ <length>4</length>
+ </field>
+ <index>
+ <name>card_contactid_index</name>
+ <field>
+ <name>cardid</name>
+ <sorting>ascending</sorting>
+ </field>
+ </index>
+ <index>
+ <name>card_name_index</name>
+ <field>
+ <name>name</name>
+ <sorting>ascending</sorting>
+ </field>
+ </index>
+ <index>
+ <name>card_value_index</name>
+ <field>
+ <name>value</name>
+ <sorting>ascending</sorting>
+ </field>
+ </index>
+ </declaration>
+ </table>
+
<table>
<name>*dbprefix*dav_shares</name>
<declaration>
$dbConnection = \OC::$server->getDatabaseConnection();
$userManager = OC::$server->getUserManager();
$config = \OC::$server->getConfig();
+$logger = \OC::$server->getLogger();
/** @var Symfony\Component\Console\Application $application */
-$application->add(new CreateAddressBook($userManager, $dbConnection, $config));
+$application->add(new CreateAddressBook($userManager, $dbConnection, $config, $logger));
$application->add(new CreateCalendar($userManager, $dbConnection));
$application->add(new SyncSystemAddressBook($userManager, $dbConnection, $config));
use OCA\DAV\Connector\Sabre\Principal;
use OCP\IConfig;
use OCP\IDBConnection;
+use OCP\ILogger;
use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
/** @var IConfig */
private $config;
+ /** @var ILogger */
+ private $logger;
+
/**
* @param IUserManager $userManager
* @param IDBConnection $dbConnection
+ * @param IConfig $config
+ * @param ILogger $logger
*/
- function __construct(IUserManager $userManager, IDBConnection $dbConnection, IConfig $config) {
+ function __construct(IUserManager $userManager,
+ IDBConnection $dbConnection,
+ IConfig $config,
+ ILogger $logger
+ ) {
parent::__construct();
$this->userManager = $userManager;
$this->dbConnection = $dbConnection;
$this->config = $config;
+ $this->logger = $logger;
}
protected function configure() {
+++ /dev/null
-<?php
-/**
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @copyright Copyright (c) 2015, ownCloud, Inc.
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-
-namespace OCA\DAV\CardDAV;
-
-class AddressBookImpl implements \OCP\IAddressBook {
-
- public function __construct(array $values, CardDavBackend $backend) {
- $this->values = $values;
- $this->backend = $backend;
- /*
- * 'id' => $row['id'],
- 'uri' => $row['uri'],
- 'principaluri' => $row['principaluri'],
- '{DAV:}displayname' => $row['displayname'],
- '{' . 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 string defining the technical unique key
- * @since 5.0.0
- */
- public function getKey() {
- return $this->values['id'];
- }
-
- /**
- * In comparison to getKey() this function returns a human readable (maybe translated) name
- *
- * @return mixed
- * @since 5.0.0
- */
- public function getDisplayName() {
- return $this->values['{DAV:}displayname'];
- }
-
- /**
- * @param string $pattern which should match within the $searchProperties
- * @param array $searchProperties defines the properties within the query pattern should match
- * @param array $options - for future use. One should always have options!
- * @return array an array of contacts which are arrays of key-value-pairs
- * @since 5.0.0
- */
- public function search($pattern, $searchProperties, $options) {
- // TODO: Implement search() method.
- $cards = $this->backend->getCards($this->values['id']);
-
- //
- // TODO: search now
- //
-
- }
-
- /**
- * @param array $properties this array if key-value-pairs defines a contact
- * @return array an array representing the contact just created or updated
- * @since 5.0.0
- */
- public function createOrUpdate($properties) {
- // TODO: Implement createOrUpdate() method.
- }
-
- /**
- * @return mixed
- * @since 5.0.0
- */
- public function getPermissions() {
- // TODO: Implement getPermissions() method.
- }
-
- /**
- * @param object $id the unique identifier to a contact
- * @return bool successful or not
- * @since 5.0.0
- */
- public function delete($id) {
- // TODO: Implement delete() method.
- }
-}
--- /dev/null
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Björn Schießle <schiessle@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\CardDAV;
+
+use OCP\Constants;
+use OCP\IAddressBook;
+use Sabre\VObject\Component\VCard;
+use Sabre\VObject\Property\Text;
+use Sabre\VObject\Reader;
+use Sabre\VObject\UUIDUtil;
+
+class AddressBookImpl implements IAddressBook {
+
+ /** @var CardDavBackend */
+ private $backend;
+
+ /** @var array */
+ private $addressBookInfo;
+
+ /** @var AddressBook */
+ private $addressBook;
+
+ /**
+ * AddressBookImpl constructor.
+ *
+ * @param AddressBook $addressBook
+ * @param array $addressBookInfo
+ * @param CardDavBackend $backend
+ */
+ public function __construct(
+ AddressBook $addressBook,
+ array $addressBookInfo,
+ CardDavBackend $backend) {
+
+ $this->addressBook = $addressBook;
+ $this->addressBookInfo = $addressBookInfo;
+ $this->backend = $backend;
+ }
+
+ /**
+ * @return string defining the technical unique key
+ * @since 5.0.0
+ */
+ public function getKey() {
+ return $this->addressBookInfo['id'];
+ }
+
+ /**
+ * In comparison to getKey() this function returns a human readable (maybe translated) name
+ *
+ * @return mixed
+ * @since 5.0.0
+ */
+ public function getDisplayName() {
+ return $this->addressBookInfo['{DAV:}displayname'];
+ }
+
+ /**
+ * @param string $pattern which should match within the $searchProperties
+ * @param array $searchProperties defines the properties within the query pattern should match
+ * @param array $options - for future use. One should always have options!
+ * @return array an array of contacts which are arrays of key-value-pairs
+ * @since 5.0.0
+ */
+ public function search($pattern, $searchProperties, $options) {
+ $result = $this->backend->search($this->getKey(), $pattern, $searchProperties);
+
+ $vCards = [];
+ foreach ($result as $cardData) {
+ $vCards[] = $this->vCard2Array($this->readCard($cardData));
+ }
+
+ return $vCards;
+ }
+
+ /**
+ * @param array $properties this array if key-value-pairs defines a contact
+ * @return array an array representing the contact just created or updated
+ * @since 5.0.0
+ */
+ public function createOrUpdate($properties) {
+ $update = false;
+ if (!isset($properties['UID'])) { // create a new contact
+ $uid = $this->createUid();
+ $uri = $uid . '.vcf';
+ $vCard = $this->createEmptyVCard($uid);
+ } else { // update existing contact
+ $uid = $properties['UID'];
+ $uri = $uid . '.vcf';
+ $vCardData = $this->backend->getCard($this->getKey(), $uri);
+ $vCard = $this->readCard($vCardData['carddata']);
+ $update = true;
+ }
+
+ foreach ($properties as $key => $value) {
+ $vCard->$key = $vCard->createProperty($key, $value);
+ }
+
+ if ($update) {
+ $this->backend->updateCard($this->getKey(), $uri, $vCard->serialize());
+ } else {
+ $this->backend->createCard($this->getKey(), $uri, $vCard->serialize());
+ }
+
+ return $this->vCard2Array($vCard);
+
+ }
+
+ /**
+ * @return mixed
+ * @since 5.0.0
+ */
+ public function getPermissions() {
+ $permissions = $this->addressBook->getACL();
+ $result = 0;
+ foreach ($permissions as $permission) {
+ switch($permission['privilege']) {
+ case '{DAV:}read':
+ $result |= Constants::PERMISSION_READ;
+ break;
+ case '{DAV:}write':
+ $result |= Constants::PERMISSION_CREATE;
+ $result |= Constants::PERMISSION_UPDATE;
+ break;
+ case '{DAV:}all':
+ $result |= Constants::PERMISSION_ALL;
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param object $id the unique identifier to a contact
+ * @return bool successful or not
+ * @since 5.0.0
+ */
+ public function delete($id) {
+ $uri = $this->backend->getCardUri($id);
+ return $this->backend->deleteCard($this->addressBookInfo['id'], $uri);
+ }
+
+ /**
+ * read vCard data into a vCard object
+ *
+ * @param string $cardData
+ * @return VCard
+ */
+ protected function readCard($cardData) {
+ return Reader::read($cardData);
+ }
+
+ /**
+ * create UID for contact
+ *
+ * @return string
+ */
+ protected function createUid() {
+ do {
+ $uid = $this->getUid();
+ } while (!empty($this->backend->getContact($uid . '.vcf')));
+
+ return $uid;
+ }
+
+ /**
+ * getUid is only there for testing, use createUid instead
+ */
+ protected function getUid() {
+ return UUIDUtil::getUUID();
+ }
+
+ /**
+ * create empty vcard
+ *
+ * @param string $uid
+ * @return VCard
+ */
+ protected function createEmptyVCard($uid) {
+ $vCard = new VCard();
+ $vCard->add(new Text($vCard, 'UID', $uid));
+ return $vCard;
+ }
+
+ /**
+ * create array with all vCard properties
+ *
+ * @param VCard $vCard
+ * @return array
+ */
+ protected function vCard2Array(VCard $vCard) {
+ $result = [];
+ foreach ($vCard->children as $property) {
+ $result[$property->name] = $property->getValue();
+ }
+ return $result;
+ }
+}
namespace OCA\DAV\CardDAV;
use OCA\DAV\Connector\Sabre\Principal;
+use OCP\IDBConnection;
+use OCP\ILogger;
use Sabre\CardDAV\Backend\BackendInterface;
use Sabre\CardDAV\Backend\SyncSupport;
use Sabre\CardDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
+use Sabre\VObject\Reader;
class CardDavBackend implements BackendInterface, SyncSupport {
/** @var Principal */
private $principalBackend;
- public function __construct(\OCP\IDBConnection $db, Principal $principalBackend) {
+ /** @var ILogger */
+ private $logger;
+
+ /** @var string */
+ private $dbCardsTable = 'cards';
+
+ /** @var string */
+ private $dbCardsPropertiesTable = 'cards_properties';
+
+ /** @var IDBConnection */
+ private $db;
+
+ /** @var array properties to index */
+ public static $indexProperties = array(
+ 'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
+ 'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD');
+
+ /**
+ * CardDavBackend constructor.
+ *
+ * @param IDBConnection $db
+ * @param Principal $principalBackend
+ * @param ILogger $logger
+ */
+ public function __construct(IDBConnection $db, Principal $principalBackend, ILogger $logger) {
$this->db = $db;
$this->principalBackend = $principalBackend;
+ $this->logger = $logger;
}
/**
->where($query->expr()->eq('resourceid', $query->createNamedParameter($addressBookId)))
->andWhere($query->expr()->eq('type', $query->createNamedParameter('addressbook')))
->execute();
+
+ $query->delete($this->dbCardsPropertiesTable)
+ ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
+ ->execute();
+
}
/**
->execute();
$this->addChange($addressBookId, $cardUri, 1);
+ $this->updateProperties($addressBookId, $cardUri, $cardData);
return '"' . $etag . '"';
}
->execute();
$this->addChange($addressBookId, $cardUri, 2);
+ $this->updateProperties($addressBookId, $cardUri, $cardData);
return '"' . $etag . '"';
}
* @return bool
*/
function deleteCard($addressBookId, $cardUri) {
+ $cardId = $this->getCardId($cardUri);
$query = $this->db->getQueryBuilder();
$ret = $query->delete('cards')
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
$this->addChange($addressBookId, $cardUri, 3);
- return $ret === 1;
+ if ($ret === 1) {
+ $this->purgeProperties($addressBookId, $cardId);
+ return true;
+ }
+
+ return false;
}
/**
}
}
+ /**
+ * search contact
+ *
+ * @param int $addressBookId
+ * @param string $pattern which should match within the $searchProperties
+ * @param array $searchProperties defines the properties within the query pattern should match
+ * @return array an array of contacts which are arrays of key-value-pairs
+ */
+ public function search($addressBookId, $pattern, $searchProperties) {
+ $query = $this->db->getQueryBuilder();
+ $query->selectDistinct('carddata', 'c')
+ ->from($this->dbCardsTable, 'c')
+ ->leftJoin('c', $this->dbCardsPropertiesTable, 'cp', $query->expr()->eq('cp.cardid', 'c.id'));
+ foreach ($searchProperties as $property) {
+ $query->orWhere(
+ $query->expr()->andX(
+ $query->expr()->eq('cp.name', $query->createNamedParameter($property)),
+ $query->expr()->like('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))
+ )
+ );
+ }
+ $query->andWhere($query->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
+ $result = $query->execute();
+ $cards = $result->fetchAll();
+ $result->closeCursor();
+
+ return array_map(function($array) {return $this->readBlob($array['carddata']);}, $cards);
+
+ }
+
+ /**
+ * get URI from a given contact
+ *
+ * @param int $id
+ * @return string
+ */
+ public function getCardUri($id) {
+ $query = $this->db->getQueryBuilder();
+ $query->select('uri')->from($this->dbCardsTable)
+ ->where($query->expr()->eq('id', $query->createParameter('id')))
+ ->setParameter('id', $id);
+
+ $result = $query->execute();
+ $uri = $result->fetch();
+ $result->closeCursor();
+
+ if (!isset($uri['uri'])) {
+ throw new \InvalidArgumentException('Card does not exists: ' . $id);
+ }
+
+ return $uri['uri'];
+ }
+
+ /**
+ * return contact with the given URI
+ *
+ * @param string $uri
+ * @returns array
+ */
+ public function getContact($uri) {
+ $result = [];
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')->from($this->dbCardsTable)
+ ->where($query->expr()->eq('uri', $query->createParameter('uri')))
+ ->setParameter('uri', $uri);
+ $queryResult = $query->execute();
+ $contact = $queryResult->fetch();
+ $queryResult->closeCursor();
+
+ if (is_array($contact)) {
+ $result = $contact;
+ }
+
+ return $result;
+ }
+
+
/**
* @param string $addressBookUri
* @param string $element
return $shares;
}
+
+ /**
+ * update properties table
+ *
+ * @param int $addressBookId
+ * @param string $cardUri
+ * @param string $vCardSerialized
+ */
+ protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
+ $cardId = $this->getCardId($cardUri);
+ $vCard = $this->readCard($vCardSerialized);
+
+ $this->purgeProperties($addressBookId, $cardId);
+
+ $query = $this->db->getQueryBuilder();
+ $query->insert($this->dbCardsPropertiesTable)
+ ->values(
+ [
+ 'addressbookid' => $query->createNamedParameter($addressBookId),
+ 'cardid' => $query->createNamedParameter($cardId),
+ 'name' => $query->createParameter('name'),
+ 'value' => $query->createParameter('value'),
+ 'preferred' => $query->createParameter('preferred')
+ ]
+ );
+
+ foreach ($vCard->children as $property) {
+ if(!in_array($property->name, self::$indexProperties)) {
+ continue;
+ }
+ $preferred = 0;
+ foreach($property->parameters as $parameter) {
+ if ($parameter->name == 'TYPE' && strtoupper($parameter->getValue()) == 'PREF') {
+ $preferred = 1;
+ break;
+ }
+ }
+ $query->setParameter('name', $property->name);
+ $query->setParameter('value', substr($property->getValue(), 0, 254));
+ $query->setParameter('preferred', $preferred);
+ $query->execute();
+ }
+ }
+
+ /**
+ * read vCard data into a vCard object
+ *
+ * @param string $cardData
+ * @return VCard
+ */
+ protected function readCard($cardData) {
+ return Reader::read($cardData);
+ }
+
+ /**
+ * delete all properties from a given card
+ *
+ * @param int $addressBookId
+ * @param int $cardId
+ */
+ protected function purgeProperties($addressBookId, $cardId) {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->dbCardsPropertiesTable)
+ ->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
+ ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
+ $query->execute();
+ }
+
+ /**
+ * get ID from a given contact
+ *
+ * @param string $uri
+ * @return int
+ */
+ protected function getCardId($uri) {
+ $query = $this->db->getQueryBuilder();
+ $query->select('id')->from($this->dbCardsTable)
+ ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)));
+
+ $result = $query->execute();
+ $cardIds = $result->fetch();
+ $result->closeCursor();
+
+ if (!isset($cardIds['id'])) {
+ throw new \InvalidArgumentException('Card does not exists: ' . $uri);
+ }
+
+ return (int)$cardIds['id'];
+ }
}
\OC::$server->getSystemTagObjectMapper()
);
- $usersCardDavBackend = new CardDavBackend($db, $principalBackend);
+ $usersCardDavBackend = new CardDavBackend($db, $principalBackend, \OC::$server->getLogger());
$usersAddressBookRoot = new AddressBookRoot($principalBackend, $usersCardDavBackend, 'principals/users');
$usersAddressBookRoot->disableListing = $disableListing;
- $systemCardDavBackend = new CardDavBackend($db, $principalBackend);
+ $systemCardDavBackend = new CardDavBackend($db, $principalBackend, \OC::$server->getLogger());
$systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, 'principals/system');
$systemAddressBookRoot->disableListing = $disableListing;
--- /dev/null
+<?php
+/**
+ * @author Björn Schießle <schiessle@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OCA\DAV\Tests\Unit\CardDAV;
+
+
+use OCA\DAV\CardDAV\AddressBook;
+use OCA\DAV\CardDAV\AddressBookImpl;
+use OCA\DAV\CardDAV\CardDavBackend;
+use Sabre\VObject\Component\VCard;
+use Sabre\VObject\Property\Text;
+use Test\TestCase;
+
+class AddressBookImplTest extends TestCase {
+
+ /** @var AddressBookImpl */
+ private $addressBookImpl;
+
+ /** @var array */
+ private $addressBookInfo;
+
+ /** @var AddressBook | \PHPUnit_Framework_MockObject_MockObject */
+ private $addressBook;
+
+ /** @var CardDavBackend | \PHPUnit_Framework_MockObject_MockObject */
+ private $backend;
+
+ /** @var VCard | \PHPUnit_Framework_MockObject_MockObject */
+ private $vCard;
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->addressBookInfo = [
+ 'id' => 42,
+ '{DAV:}displayname' => 'display name'
+ ];
+ $this->addressBook = $this->getMockBuilder('OCA\DAV\CardDAV\AddressBook')
+ ->disableOriginalConstructor()->getMock();
+ $this->backend = $this->getMockBuilder('\OCA\DAV\CardDAV\CardDavBackend')
+ ->disableOriginalConstructor()->getMock();
+ $this->vCard = $this->getMock('Sabre\VObject\Component\VCard');
+
+ $this->addressBookImpl = new AddressBookImpl(
+ $this->addressBook,
+ $this->addressBookInfo,
+ $this->backend
+ );
+ }
+
+ public function testGetKey() {
+ $this->assertSame($this->addressBookInfo['id'],
+ $this->addressBookImpl->getKey());
+ }
+
+ public function testGetDisplayName() {
+ $this->assertSame($this->addressBookInfo['{DAV:}displayname'],
+ $this->addressBookImpl->getDisplayName());
+ }
+
+ public function testSearch() {
+
+ /** @var \PHPUnit_Framework_MockObject_MockObject | AddressBookImpl $addressBookImpl */
+ $addressBookImpl = $this->getMockBuilder('OCA\DAV\CardDAV\AddressBookImpl')
+ ->setConstructorArgs(
+ [
+ $this->addressBook,
+ $this->addressBookInfo,
+ $this->backend
+ ]
+ )
+ ->setMethods(['vCard2Array', 'readCard'])
+ ->getMock();
+
+ $pattern = 'pattern';
+ $searchProperties = 'properties';
+
+ $this->backend->expects($this->once())->method('search')
+ ->with($this->addressBookInfo['id'], $pattern, $searchProperties)
+ ->willReturn(
+ [
+ 'cardData1',
+ 'cardData2'
+ ]
+ );
+
+ $addressBookImpl->expects($this->exactly(2))->method('readCard')
+ ->willReturn($this->vCard);
+ $addressBookImpl->expects($this->exactly(2))->method('vCard2Array')
+ ->with($this->vCard)->willReturn('vCard');
+
+ $result = $addressBookImpl->search($pattern, $searchProperties, []);
+ $this->assertTrue((is_array($result)));
+ $this->assertSame(2, count($result));
+ }
+
+ /**
+ * @dataProvider dataTestCreate
+ *
+ * @param array $properties
+ */
+ public function testCreate($properties) {
+
+ $uid = 'uid';
+
+ /** @var \PHPUnit_Framework_MockObject_MockObject | AddressBookImpl $addressBookImpl */
+ $addressBookImpl = $this->getMockBuilder('OCA\DAV\CardDAV\AddressBookImpl')
+ ->setConstructorArgs(
+ [
+ $this->addressBook,
+ $this->addressBookInfo,
+ $this->backend
+ ]
+ )
+ ->setMethods(['vCard2Array', 'createUid', 'createEmptyVCard'])
+ ->getMock();
+
+ $addressBookImpl->expects($this->once())->method('createUid')
+ ->willReturn($uid);
+ $addressBookImpl->expects($this->once())->method('createEmptyVCard')
+ ->with($uid)->willReturn($this->vCard);
+ $this->vCard->expects($this->exactly(count($properties)))
+ ->method('createProperty');
+ $this->backend->expects($this->once())->method('createCard');
+ $this->backend->expects($this->never())->method('updateCard');
+ $this->backend->expects($this->never())->method('getCard');
+ $addressBookImpl->expects($this->once())->method('vCard2Array')
+ ->with($this->vCard)->willReturn(true);
+
+ $this->assertTrue($addressBookImpl->createOrUpdate($properties));
+ }
+
+ public function dataTestCreate() {
+ return [
+ [[]],
+ [['FN' => 'John Doe']]
+ ];
+ }
+
+ public function testUpdate() {
+
+ $uid = 'uid';
+ $properties = ['UID' => $uid, 'FN' => 'John Doe'];
+
+ /** @var \PHPUnit_Framework_MockObject_MockObject | AddressBookImpl $addressBookImpl */
+ $addressBookImpl = $this->getMockBuilder('OCA\DAV\CardDAV\AddressBookImpl')
+ ->setConstructorArgs(
+ [
+ $this->addressBook,
+ $this->addressBookInfo,
+ $this->backend
+ ]
+ )
+ ->setMethods(['vCard2Array', 'createUid', 'createEmptyVCard', 'readCard'])
+ ->getMock();
+
+ $addressBookImpl->expects($this->never())->method('createUid');
+ $addressBookImpl->expects($this->never())->method('createEmptyVCard');
+ $this->backend->expects($this->once())->method('getCard')
+ ->with($this->addressBookInfo['id'], $uid . '.vcf')
+ ->willReturn(['carddata' => 'data']);
+ $addressBookImpl->expects($this->once())->method('readCard')
+ ->with('data')->willReturn($this->vCard);
+ $this->vCard->expects($this->exactly(count($properties)))
+ ->method('createProperty');
+ $this->backend->expects($this->never())->method('createCard');
+ $this->backend->expects($this->once())->method('updateCard');
+ $addressBookImpl->expects($this->once())->method('vCard2Array')
+ ->with($this->vCard)->willReturn(true);
+
+ $this->assertTrue($addressBookImpl->createOrUpdate($properties));
+ }
+
+ /**
+ * @dataProvider dataTestGetPermissions
+ *
+ * @param array $permissions
+ * @param int $expected
+ */
+ public function testGetPermissions($permissions, $expected) {
+ $this->addressBook->expects($this->once())->method('getACL')
+ ->willReturn($permissions);
+
+ $this->assertSame($expected,
+ $this->addressBookImpl->getPermissions()
+ );
+ }
+
+ public function dataTestGetPermissions() {
+ return [
+ [[], 0],
+ [[['privilege' => '{DAV:}read']], 1],
+ [[['privilege' => '{DAV:}write']], 6],
+ [[['privilege' => '{DAV:}all']], 31],
+ [[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write']], 7],
+ [[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}all']], 31],
+ [[['privilege' => '{DAV:}all'],['privilege' => '{DAV:}write']], 31],
+ [[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write'],['privilege' => '{DAV:}all']], 31],
+ [[['privilege' => '{DAV:}all'],['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write']], 31],
+ ];
+ }
+
+ public function testDelete() {
+ $cardId = 1;
+ $cardUri = 'cardUri';
+ $this->backend->expects($this->once())->method('getCardUri')
+ ->with($cardId)->willReturn($cardUri);
+ $this->backend->expects($this->once())->method('deleteCard')
+ ->with($this->addressBookInfo['id'], $cardUri)
+ ->willReturn(true);
+
+ $this->assertTrue($this->addressBookImpl->delete($cardId));
+ }
+
+ public function testReadCard() {
+ $vCard = new VCard();
+ $vCard->add(new Text($vCard, 'UID', 'uid'));
+ $vCardSerialized = $vCard->serialize();
+
+ $result = $this->invokePrivate($this->addressBookImpl, 'readCard', [$vCardSerialized]);
+ $resultSerialized = $result->serialize();
+
+ $this->assertSame($vCardSerialized, $resultSerialized);
+ }
+
+ public function testCreateUid() {
+ /** @var \PHPUnit_Framework_MockObject_MockObject | AddressBookImpl $addressBookImpl */
+ $addressBookImpl = $this->getMockBuilder('OCA\DAV\CardDAV\AddressBookImpl')
+ ->setConstructorArgs(
+ [
+ $this->addressBook,
+ $this->addressBookInfo,
+ $this->backend
+ ]
+ )
+ ->setMethods(['getUid'])
+ ->getMock();
+
+ $addressBookImpl->expects($this->at(0))->method('getUid')->willReturn('uid0');
+ $addressBookImpl->expects($this->at(1))->method('getUid')->willReturn('uid1');
+
+ // simulate that 'uid0' already exists, so the second uid will be returned
+ $this->backend->expects($this->exactly(2))->method('getContact')
+ ->willReturnCallback(
+ function($uid) {
+ return ($uid === 'uid0.vcf');
+ }
+ );
+
+ $this->assertSame('uid1',
+ $this->invokePrivate($addressBookImpl, 'createUid', [])
+ );
+
+ }
+
+ public function testCreateEmptyVCard() {
+ $uid = 'uid';
+ $expectedVCard = new VCard();
+ $expectedVCard->add(new Text($expectedVCard, 'UID', $uid));
+ $expectedVCardSerialized = $expectedVCard->serialize();
+
+ $result = $this->invokePrivate($this->addressBookImpl, 'createEmptyVCard', [$uid]);
+ $resultSerialized = $result->serialize();
+
+ $this->assertSame($expectedVCardSerialized, $resultSerialized);
+ }
+
+}
namespace OCA\DAV\Tests\Unit\CardDAV;
use OCA\DAV\CardDAV\CardDavBackend;
+use OCA\DAV\Connector\Sabre\Principal;
+use OCP\IDBConnection;
+use OCP\ILogger;
use Sabre\DAV\PropPatch;
+use Sabre\VObject\Component\VCard;
+use Sabre\VObject\Property\Text;
use Test\TestCase;
/**
/** @var CardDavBackend */
private $backend;
+ /** @var Principal | \PHPUnit_Framework_MockObject_MockObject */
+ private $principal;
+
+ /** @var ILogger | \PHPUnit_Framework_MockObject_MockObject */
+ private $logger;
+
+ /** @var IDBConnection */
+ private $db;
+
+ /** @var string */
+ private $dbCardsTable = 'cards';
+
+ /** @var string */
+ private $dbCardsPropertiesTable = 'cards_properties';
+
const UNIT_TEST_USER = 'carddav-unit-test';
public function setUp() {
parent::setUp();
- $principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal')
+ $this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal')
->disableOriginalConstructor()
->setMethods(['getPrincipalByPath'])
->getMock();
- $principal->method('getPrincipalByPath')
+ $this->principal->method('getPrincipalByPath')
->willReturn([
'uri' => 'principals/best-friend'
]);
+ $this->logger = $this->getMock('\OCP\ILogger');
+
+ $this->db = \OC::$server->getDatabaseConnection();
+
+ $this->backend = new CardDavBackend($this->db, $this->principal, $this->logger);
+
+ // start every test with a empty cards_properties and cards table
+ $query = $this->db->getQueryBuilder();
+ $query->delete('cards_properties')->execute();
+ $query = $this->db->getQueryBuilder();
+ $query->delete('cards')->execute();
- $db = \OC::$server->getDatabaseConnection();
- $this->backend = new CardDavBackend($db, $principal);
$this->tearDown();
}
}
public function testCardOperations() {
+
+ $backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
+ ->setConstructorArgs([$this->db, $this->principal, $this->logger])
+ ->setMethods(['updateProperties', 'purgeProperties'])->getMock();
+
// create a new address book
- $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
- $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
+ $backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
+ $books = $backend->getAddressBooksForUser(self::UNIT_TEST_USER);
$this->assertEquals(1, count($books));
$bookId = $books[0]['id'];
- // create a card
$uri = $this->getUniqueID('card');
- $this->backend->createCard($bookId, $uri, '');
+ // updateProperties is expected twice, once for createCard and once for updateCard
+ $backend->expects($this->at(0))->method('updateProperties')->with($bookId, $uri, '');
+ $backend->expects($this->at(1))->method('updateProperties')->with($bookId, $uri, '***');
+ // create a card
+ $backend->createCard($bookId, $uri, '');
// get all the cards
- $cards = $this->backend->getCards($bookId);
+ $cards = $backend->getCards($bookId);
$this->assertEquals(1, count($cards));
$this->assertEquals('', $cards[0]['carddata']);
// get the cards
- $card = $this->backend->getCard($bookId, $uri);
+ $card = $backend->getCard($bookId, $uri);
$this->assertNotNull($card);
$this->assertArrayHasKey('id', $card);
$this->assertArrayHasKey('uri', $card);
$this->assertEquals('', $card['carddata']);
// update the card
- $this->backend->updateCard($bookId, $uri, '***');
- $card = $this->backend->getCard($bookId, $uri);
+ $backend->updateCard($bookId, $uri, '***');
+ $card = $backend->getCard($bookId, $uri);
$this->assertEquals('***', $card['carddata']);
// delete the card
- $this->backend->deleteCard($bookId, $uri);
- $cards = $this->backend->getCards($bookId);
+ $backend->expects($this->once())->method('purgeProperties')->with($bookId, $card['id']);
+ $backend->deleteCard($bookId, $uri);
+ $cards = $backend->getCards($bookId);
$this->assertEquals(0, count($cards));
}
public function testMultiCard() {
+
+ $this->backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
+ ->setConstructorArgs([$this->db, $this->principal, $this->logger])
+ ->setMethods(['updateProperties'])->getMock();
+
// create a new address book
$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
}
public function testSyncSupport() {
+
+ $this->backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
+ ->setConstructorArgs([$this->db, $this->principal, $this->logger])
+ ->setMethods(['updateProperties'])->getMock();
+
// create a new address book
$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
$books = $this->backend->getAddressBooksForUser('principals/best-friend');
$this->assertEquals(0, count($books));
}
+
+ public function testUpdateProperties() {
+
+ $bookId = 42;
+ $cardUri = 'card-uri';
+ $cardId = 2;
+
+ $backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
+ ->setConstructorArgs([$this->db, $this->principal, $this->logger])
+ ->setMethods(['getCardId'])->getMock();
+
+ $backend->expects($this->any())->method('getCardId')->willReturn($cardId);
+
+ // add properties for new vCard
+ $vCard = new VCard();
+ $vCard->add(new Text($vCard, 'UID', $cardUri));
+ $vCard->add(new Text($vCard, 'FN', 'John Doe'));
+ $this->invokePrivate($backend, 'updateProperties', [$bookId, $cardUri, $vCard->serialize()]);
+
+ $query = $this->db->getQueryBuilder();
+ $result = $query->select('*')->from('cards_properties')->execute()->fetchAll();
+
+ $this->assertSame(2, count($result));
+
+ $this->assertSame('UID', $result[0]['name']);
+ $this->assertSame($cardUri, $result[0]['value']);
+ $this->assertSame($bookId, (int)$result[0]['addressbookid']);
+ $this->assertSame($cardId, (int)$result[0]['cardid']);
+
+ $this->assertSame('FN', $result[1]['name']);
+ $this->assertSame('John Doe', $result[1]['value']);
+ $this->assertSame($bookId, (int)$result[1]['addressbookid']);
+ $this->assertSame($cardId, (int)$result[1]['cardid']);
+
+ // update properties for existing vCard
+ $vCard = new VCard();
+ $vCard->add(new Text($vCard, 'FN', 'John Doe'));
+ $this->invokePrivate($backend, 'updateProperties', [$bookId, $cardUri, $vCard->serialize()]);
+
+ $query = $this->db->getQueryBuilder();
+ $result = $query->select('*')->from('cards_properties')->execute()->fetchAll();
+
+ $this->assertSame(1, count($result));
+
+ $this->assertSame('FN', $result[0]['name']);
+ $this->assertSame('John Doe', $result[0]['value']);
+ $this->assertSame($bookId, (int)$result[0]['addressbookid']);
+ $this->assertSame($cardId, (int)$result[0]['cardid']);
+ }
+
+ public function testPurgeProperties() {
+
+ $query = $this->db->getQueryBuilder();
+ $query->insert('cards_properties')
+ ->values(
+ [
+ 'addressbookid' => $query->createNamedParameter(1),
+ 'cardid' => $query->createNamedParameter(1),
+ 'name' => $query->createNamedParameter('name1'),
+ 'value' => $query->createNamedParameter('value1'),
+ 'preferred' => $query->createNamedParameter(0)
+ ]
+ );
+ $query->execute();
+
+ $query = $this->db->getQueryBuilder();
+ $query->insert('cards_properties')
+ ->values(
+ [
+ 'addressbookid' => $query->createNamedParameter(1),
+ 'cardid' => $query->createNamedParameter(2),
+ 'name' => $query->createNamedParameter('name2'),
+ 'value' => $query->createNamedParameter('value2'),
+ 'preferred' => $query->createNamedParameter(0)
+ ]
+ );
+ $query->execute();
+
+ $this->invokePrivate($this->backend, 'purgeProperties', [1, 1]);
+
+ $query = $this->db->getQueryBuilder();
+ $result = $query->select('*')->from('cards_properties')->execute()->fetchAll();
+ $this->assertSame(1, count($result));
+ $this->assertSame(1 ,(int)$result[0]['addressbookid']);
+ $this->assertSame(2 ,(int)$result[0]['cardid']);
+
+ }
+
+ public function testGetCardId() {
+ $query = $this->db->getQueryBuilder();
+
+ $query->insert('cards')
+ ->values(
+ [
+ 'addressbookid' => $query->createNamedParameter(1),
+ 'carddata' => $query->createNamedParameter(''),
+ 'uri' => $query->createNamedParameter('uri'),
+ 'lastmodified' => $query->createNamedParameter(4738743),
+ 'etag' => $query->createNamedParameter('etag'),
+ 'size' => $query->createNamedParameter(120)
+ ]
+ );
+ $query->execute();
+ $id = $query->getLastInsertId();
+
+ $this->assertSame($id,
+ $this->invokePrivate($this->backend, 'getCardId', ['uri']));
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testGetCardIdFailed() {
+ $this->invokePrivate($this->backend, 'getCardId', ['uri']);
+ }
+
+ /**
+ * @dataProvider dataTestSearch
+ *
+ * @param string $pattern
+ * @param array $expected
+ */
+ public function testSearch($pattern, $expected) {
+ $vCards = [];
+ $vCards[0] = new VCard();
+ $vCards[0]->add(new Text($vCards[0], 'UID', 'uid'));
+ $vCards[0]->add(new Text($vCards[0], 'FN', 'John Doe'));
+ $vCards[1] = new VCard();
+ $vCards[1]->add(new Text($vCards[1], 'UID', 'uid'));
+ $vCards[1]->add(new Text($vCards[1], 'FN', 'John M. Doe'));
+
+ $vCardIds = [];
+ $query = $this->db->getQueryBuilder();
+ for($i=0; $i<2; $i++) {
+ $query->insert($this->dbCardsTable)
+ ->values(
+ [
+ 'addressbookid' => $query->createNamedParameter(0),
+ 'carddata' => $query->createNamedParameter($vCards[$i]->serialize()),
+ 'uri' => $query->createNamedParameter('uri' . $i),
+ 'lastmodified' => $query->createNamedParameter(5489543),
+ 'etag' => $query->createNamedParameter('etag' . $i),
+ 'size' => $query->createNamedParameter(120),
+ ]
+ );
+ $query->execute();
+ $vCardIds[] = $query->getLastInsertId();
+ }
+
+ $query->insert($this->dbCardsPropertiesTable)
+ ->values(
+ [
+ 'addressbookid' => $query->createNamedParameter(0),
+ 'cardid' => $query->createNamedParameter($vCardIds[0]),
+ 'name' => $query->createNamedParameter('FN'),
+ 'value' => $query->createNamedParameter('John Doe'),
+ 'preferred' => $query->createNamedParameter(0)
+ ]
+ );
+ $query->execute();
+ $query->insert($this->dbCardsPropertiesTable)
+ ->values(
+ [
+ 'addressbookid' => $query->createNamedParameter(0),
+ 'cardid' => $query->createNamedParameter($vCardIds[1]),
+ 'name' => $query->createNamedParameter('FN'),
+ 'value' => $query->createNamedParameter('John M. Doe'),
+ 'preferred' => $query->createNamedParameter(0)
+ ]
+ );
+ $query->execute();
+
+ $result = $this->backend->search(0, $pattern, ['FN']);
+
+ // check result
+ $this->assertSame(count($expected), count($result));
+ $found = [];
+ foreach ($result as $r) {
+ foreach ($expected as $exp) {
+ if (strpos($r, $exp) > 0) {
+ $found[$exp] = true;
+ break;
+ }
+ }
+ }
+
+ $this->assertSame(count($expected), count($found));
+ }
+
+ public function dataTestSearch() {
+ return [
+ ['John', ['John Doe', 'John M. Doe']],
+ ['M. Doe', ['John M. Doe']],
+ ['Do', ['John Doe', 'John M. Doe']]
+ ];
+ }
+
+ public function testGetCardUri() {
+ $query = $this->db->getQueryBuilder();
+ $query->insert($this->dbCardsTable)
+ ->values(
+ [
+ 'addressbookid' => $query->createNamedParameter(1),
+ 'carddata' => $query->createNamedParameter('carddata', \PDO::PARAM_LOB),
+ 'uri' => $query->createNamedParameter('uri'),
+ 'lastmodified' => $query->createNamedParameter(5489543),
+ 'etag' => $query->createNamedParameter('etag'),
+ 'size' => $query->createNamedParameter(120),
+ ]
+ );
+ $query->execute();
+
+ $id = $query->getLastInsertId();
+
+ $this->assertSame('uri', $this->backend->getCardUri($id));
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testGetCardUriFailed() {
+ $this->backend->getCardUri(1);
+ }
+
+ public function testGetContact() {
+ $query = $this->db->getQueryBuilder();
+ for($i=0; $i<2; $i++) {
+ $query->insert($this->dbCardsTable)
+ ->values(
+ [
+ 'addressbookid' => $query->createNamedParameter($i),
+ 'carddata' => $query->createNamedParameter('carddata' . $i, \PDO::PARAM_LOB),
+ 'uri' => $query->createNamedParameter('uri' . $i),
+ 'lastmodified' => $query->createNamedParameter(5489543),
+ 'etag' => $query->createNamedParameter('etag' . $i),
+ 'size' => $query->createNamedParameter(120),
+ ]
+ );
+ $query->execute();
+ }
+
+ $result = $this->backend->getContact('uri0');
+ $this->assertSame(7, count($result));
+ $this->assertSame(0, (int)$result['addressbookid']);
+ $this->assertSame('uri0', $result['uri']);
+ $this->assertSame(5489543, (int)$result['lastmodified']);
+ $this->assertSame('etag0', $result['etag']);
+ $this->assertSame(120, (int)$result['size']);
+ }
+
+ public function testGetContactFail() {
+ $this->assertEmpty($this->backend->getContact('uri'));
+ }
+
}