Merge pull request #20696 from owncloud/add-carddav-backends-to-ocp-contactsmanager

Add carddav backend to OCP\ContactsManager
This commit is contained in:
Thomas Müller 2015-12-17 16:36:48 +01:00
commit 792b270f22
9 changed files with 1184 additions and 21 deletions

42
apps/dav/appinfo/app.php Normal file
View File

@ -0,0 +1,42 @@
<?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/>
*
*/
$cm = \OC::$server->getContactsManager();
$cm->register(function() use ($cm) {
$db = \OC::$server->getDatabaseConnection();
$userId = \OC::$server->getUserSession()->getUser()->getUID();
$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 $addressBookInfo) {
$addressBook = new \OCA\DAV\CardDAV\AddressBook($cardDav, $addressBookInfo);
$cm->registerAddressBook(
new OCA\DAV\CardDAV\AddressBookImpl(
$addressBook,
$addressBookInfo,
$cardDav
)
);
}
});

View File

@ -571,6 +571,78 @@ CREATE TABLE calendarobjects (
</declaration> </declaration>
</table> </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> <table>
<name>*dbprefix*dav_shares</name> <name>*dbprefix*dav_shares</name>
<declaration> <declaration>

View File

@ -8,8 +8,9 @@ $config = \OC::$server->getConfig();
$dbConnection = \OC::$server->getDatabaseConnection(); $dbConnection = \OC::$server->getDatabaseConnection();
$userManager = OC::$server->getUserManager(); $userManager = OC::$server->getUserManager();
$config = \OC::$server->getConfig(); $config = \OC::$server->getConfig();
$logger = \OC::$server->getLogger();
/** @var Symfony\Component\Console\Application $application */ /** @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 CreateCalendar($userManager, $dbConnection));
$application->add(new SyncSystemAddressBook($userManager, $dbConnection, $config)); $application->add(new SyncSystemAddressBook($userManager, $dbConnection, $config));

View File

@ -6,6 +6,7 @@ use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\Connector\Sabre\Principal; use OCA\DAV\Connector\Sabre\Principal;
use OCP\IConfig; use OCP\IConfig;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCP\ILogger;
use OCP\IUserManager; use OCP\IUserManager;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
@ -23,15 +24,25 @@ class CreateAddressBook extends Command {
/** @var IConfig */ /** @var IConfig */
private $config; private $config;
/** @var ILogger */
private $logger;
/** /**
* @param IUserManager $userManager * @param IUserManager $userManager
* @param IDBConnection $dbConnection * @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(); parent::__construct();
$this->userManager = $userManager; $this->userManager = $userManager;
$this->dbConnection = $dbConnection; $this->dbConnection = $dbConnection;
$this->config = $config; $this->config = $config;
$this->logger = $logger;
} }
protected function configure() { protected function configure() {

View File

@ -0,0 +1,219 @@
<?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;
}
}

View File

@ -23,19 +23,48 @@
namespace OCA\DAV\CardDAV; namespace OCA\DAV\CardDAV;
use OCA\DAV\Connector\Sabre\Principal; use OCA\DAV\Connector\Sabre\Principal;
use OCP\IDBConnection;
use OCP\ILogger;
use Sabre\CardDAV\Backend\BackendInterface; use Sabre\CardDAV\Backend\BackendInterface;
use Sabre\CardDAV\Backend\SyncSupport; use Sabre\CardDAV\Backend\SyncSupport;
use Sabre\CardDAV\Plugin; use Sabre\CardDAV\Plugin;
use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\BadRequest;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Reader;
class CardDavBackend implements BackendInterface, SyncSupport { class CardDavBackend implements BackendInterface, SyncSupport {
/** @var Principal */ /** @var Principal */
private $principalBackend; 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->db = $db;
$this->principalBackend = $principalBackend; $this->principalBackend = $principalBackend;
$this->logger = $logger;
} }
/** /**
@ -263,6 +292,11 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->where($query->expr()->eq('resourceid', $query->createNamedParameter($addressBookId))) ->where($query->expr()->eq('resourceid', $query->createNamedParameter($addressBookId)))
->andWhere($query->expr()->eq('type', $query->createNamedParameter('addressbook'))) ->andWhere($query->expr()->eq('type', $query->createNamedParameter('addressbook')))
->execute(); ->execute();
$query->delete($this->dbCardsPropertiesTable)
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
->execute();
} }
/** /**
@ -398,7 +432,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$query = $this->db->getQueryBuilder(); $query = $this->db->getQueryBuilder();
$query->insert('cards') $query->insert('cards')
->values([ ->values([
'carddata' => $query->createNamedParameter($cardData), 'carddata' => $query->createNamedParameter($cardData, \PDO::PARAM_LOB),
'uri' => $query->createNamedParameter($cardUri), 'uri' => $query->createNamedParameter($cardUri),
'lastmodified' => $query->createNamedParameter(time()), 'lastmodified' => $query->createNamedParameter(time()),
'addressbookid' => $query->createNamedParameter($addressBookId), 'addressbookid' => $query->createNamedParameter($addressBookId),
@ -408,6 +442,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->execute(); ->execute();
$this->addChange($addressBookId, $cardUri, 1); $this->addChange($addressBookId, $cardUri, 1);
$this->updateProperties($addressBookId, $cardUri, $cardData);
return '"' . $etag . '"'; return '"' . $etag . '"';
} }
@ -451,6 +486,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->execute(); ->execute();
$this->addChange($addressBookId, $cardUri, 2); $this->addChange($addressBookId, $cardUri, 2);
$this->updateProperties($addressBookId, $cardUri, $cardData);
return '"' . $etag . '"'; return '"' . $etag . '"';
} }
@ -463,6 +499,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @return bool * @return bool
*/ */
function deleteCard($addressBookId, $cardUri) { function deleteCard($addressBookId, $cardUri) {
$cardId = $this->getCardId($cardUri);
$query = $this->db->getQueryBuilder(); $query = $this->db->getQueryBuilder();
$ret = $query->delete('cards') $ret = $query->delete('cards')
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))) ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
@ -471,7 +508,12 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$this->addChange($addressBookId, $cardUri, 3); $this->addChange($addressBookId, $cardUri, 3);
return $ret === 1; if ($ret === 1) {
$this->purgeProperties($addressBookId, $cardId);
return true;
}
return false;
} }
/** /**
@ -637,6 +679,87 @@ class CardDavBackend implements BackendInterface, SyncSupport {
} }
} }
/**
* 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();
$query2 = $this->db->getQueryBuilder();
$query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
foreach ($searchProperties as $property) {
$query2->orWhere(
$query2->expr()->andX(
$query2->expr()->eq('cp.name', $query->createNamedParameter($property)),
$query2->expr()->like('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))
)
);
}
$query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
$query->select('c.carddata')->from($this->dbCardsTable, 'c')
->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
$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 $addressBookUri
* @param string $element * @param string $element
@ -734,4 +857,93 @@ class CardDavBackend implements BackendInterface, SyncSupport {
return $shares; 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'];
}
} }

View File

@ -41,11 +41,11 @@ class RootCollection extends SimpleCollection {
\OC::$server->getSystemTagObjectMapper() \OC::$server->getSystemTagObjectMapper()
); );
$usersCardDavBackend = new CardDavBackend($db, $principalBackend); $usersCardDavBackend = new CardDavBackend($db, $principalBackend, \OC::$server->getLogger());
$usersAddressBookRoot = new AddressBookRoot($principalBackend, $usersCardDavBackend, 'principals/users'); $usersAddressBookRoot = new AddressBookRoot($principalBackend, $usersCardDavBackend, 'principals/users');
$usersAddressBookRoot->disableListing = $disableListing; $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 = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, 'principals/system');
$systemAddressBookRoot->disableListing = $disableListing; $systemAddressBookRoot->disableListing = $disableListing;

View File

@ -0,0 +1,287 @@
<?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);
}
}

View File

@ -20,8 +20,14 @@
*/ */
namespace OCA\DAV\Tests\Unit\CardDAV; namespace OCA\DAV\Tests\Unit\CardDAV;
use InvalidArgumentException;
use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\Connector\Sabre\Principal;
use OCP\IDBConnection;
use OCP\ILogger;
use Sabre\DAV\PropPatch; use Sabre\DAV\PropPatch;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Property\Text;
use Test\TestCase; use Test\TestCase;
/** /**
@ -36,22 +42,46 @@ class CardDavBackendTest extends TestCase {
/** @var CardDavBackend */ /** @var CardDavBackend */
private $backend; 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'; const UNIT_TEST_USER = 'carddav-unit-test';
public function setUp() { public function setUp() {
parent::setUp(); parent::setUp();
$principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal') $this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal')
->disableOriginalConstructor() ->disableOriginalConstructor()
->setMethods(['getPrincipalByPath']) ->setMethods(['getPrincipalByPath'])
->getMock(); ->getMock();
$principal->method('getPrincipalByPath') $this->principal->method('getPrincipalByPath')
->willReturn([ ->willReturn([
'uri' => 'principals/best-friend' '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(); $this->tearDown();
} }
@ -96,23 +126,32 @@ class CardDavBackendTest extends TestCase {
} }
public function testCardOperations() { public function testCardOperations() {
/** @var CardDavBackend | \PHPUnit_Framework_MockObject_MockObject $backend */
$backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
->setConstructorArgs([$this->db, $this->principal, $this->logger])
->setMethods(['updateProperties', 'purgeProperties'])->getMock();
// create a new address book // create a new address book
$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []); $backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER); $books = $backend->getAddressBooksForUser(self::UNIT_TEST_USER);
$this->assertEquals(1, count($books)); $this->assertEquals(1, count($books));
$bookId = $books[0]['id']; $bookId = $books[0]['id'];
// create a card
$uri = $this->getUniqueID('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 // get all the cards
$cards = $this->backend->getCards($bookId); $cards = $backend->getCards($bookId);
$this->assertEquals(1, count($cards)); $this->assertEquals(1, count($cards));
$this->assertEquals('', $cards[0]['carddata']); $this->assertEquals('', $cards[0]['carddata']);
// get the cards // get the cards
$card = $this->backend->getCard($bookId, $uri); $card = $backend->getCard($bookId, $uri);
$this->assertNotNull($card); $this->assertNotNull($card);
$this->assertArrayHasKey('id', $card); $this->assertArrayHasKey('id', $card);
$this->assertArrayHasKey('uri', $card); $this->assertArrayHasKey('uri', $card);
@ -122,17 +161,23 @@ class CardDavBackendTest extends TestCase {
$this->assertEquals('', $card['carddata']); $this->assertEquals('', $card['carddata']);
// update the card // update the card
$this->backend->updateCard($bookId, $uri, '***'); $backend->updateCard($bookId, $uri, '***');
$card = $this->backend->getCard($bookId, $uri); $card = $backend->getCard($bookId, $uri);
$this->assertEquals('***', $card['carddata']); $this->assertEquals('***', $card['carddata']);
// delete the card // delete the card
$this->backend->deleteCard($bookId, $uri); $backend->expects($this->once())->method('purgeProperties')->with($bookId, $card['id']);
$cards = $this->backend->getCards($bookId); $backend->deleteCard($bookId, $uri);
$cards = $backend->getCards($bookId);
$this->assertEquals(0, count($cards)); $this->assertEquals(0, count($cards));
} }
public function testMultiCard() { 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 // create a new address book
$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []); $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER); $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
@ -175,6 +220,11 @@ class CardDavBackendTest extends TestCase {
} }
public function testSyncSupport() { 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 // create a new address book
$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []); $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER); $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
@ -221,4 +271,273 @@ class CardDavBackendTest extends TestCase {
$books = $this->backend->getAddressBooksForUser('principals/best-friend'); $books = $this->backend->getAddressBooksForUser('principals/best-friend');
$this->assertEquals(0, count($books)); $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, $properties, $expected) {
/** @var VCard $vCards */
$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[0]->add(new Text($vCards[0], 'CLOUD', 'john@owncloud.org'));
$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(), \PDO::PARAM_LOB),
'uri' => $query->createNamedParameter('uri' . $i),
'lastmodified' => $query->createNamedParameter(time()),
'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[0]),
'name' => $query->createNamedParameter('CLOUD'),
'value' => $query->createNamedParameter('John@owncloud.org'),
'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, $properties);
// 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', ['FN'], ['John Doe', 'John M. Doe']],
['M. Doe', ['FN'], ['John M. Doe']],
['Do', ['FN'], ['John Doe', 'John M. Doe']],
// check if duplicates are handled correctly
['John', ['FN', 'CLOUD'], ['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'));
}
} }