Browse Source

implement delete, create, update, search, get permissions

tags/v9.0beta1
Björn Schießle 8 years ago
parent
commit
cd55a19034

+ 14
- 3
apps/dav/appinfo/app.php View File

@@ -23,9 +23,20 @@ $cm = \OC::$server->getContactsManager();
$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
)
);
}
});

+ 72
- 0
apps/dav/appinfo/database.xml View File

@@ -571,6 +571,78 @@ CREATE TABLE calendarobjects (
</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>

+ 2
- 1
apps/dav/appinfo/register_command.php View File

@@ -8,8 +8,9 @@ $config = \OC::$server->getConfig();
$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));

+ 12
- 1
apps/dav/command/createaddressbook.php View File

@@ -6,6 +6,7 @@ use OCA\DAV\CardDAV\CardDavBackend;
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;
@@ -23,15 +24,25 @@ class CreateAddressBook extends Command {
/** @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() {

+ 0
- 100
apps/dav/lib/carddav/AddressBookImpl.php View File

@@ -1,100 +0,0 @@
<?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.
}
}

+ 219
- 0
apps/dav/lib/carddav/addressbookimpl.php 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;
}
}

+ 209
- 2
apps/dav/lib/carddav/carddavbackend.php View File

@@ -23,19 +23,47 @@
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;
}

/**
@@ -263,6 +291,11 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->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();

}

/**
@@ -408,6 +441,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->execute();

$this->addChange($addressBookId, $cardUri, 1);
$this->updateProperties($addressBookId, $cardUri, $cardData);

return '"' . $etag . '"';
}
@@ -451,6 +485,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->execute();

$this->addChange($addressBookId, $cardUri, 2);
$this->updateProperties($addressBookId, $cardUri, $cardData);

return '"' . $etag . '"';
}
@@ -463,6 +498,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @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)))
@@ -471,7 +507,12 @@ class CardDavBackend implements BackendInterface, SyncSupport {

$this->addChange($addressBookId, $cardUri, 3);

return $ret === 1;
if ($ret === 1) {
$this->purgeProperties($addressBookId, $cardId);
return true;
}

return false;
}

/**
@@ -637,6 +678,83 @@ 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();
$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
@@ -734,4 +852,93 @@ class CardDavBackend implements BackendInterface, SyncSupport {

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'];
}
}

+ 2
- 2
apps/dav/lib/rootcollection.php View File

@@ -41,11 +41,11 @@ class RootCollection extends SimpleCollection {
\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;


+ 287
- 0
apps/dav/tests/unit/carddav/addressbookimpltest.php 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);
}

}

+ 316
- 14
apps/dav/tests/unit/carddav/carddavbackendtest.php View File

@@ -21,7 +21,12 @@
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;

/**
@@ -36,22 +41,46 @@ class CardDavBackendTest extends 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();
}
@@ -96,23 +125,31 @@ class CardDavBackendTest extends TestCase {
}

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);
@@ -122,17 +159,23 @@ class CardDavBackendTest extends TestCase {
$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);
@@ -175,6 +218,11 @@ class CardDavBackendTest extends TestCase {
}

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);
@@ -221,4 +269,258 @@ class CardDavBackendTest extends TestCase {
$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'));
}

}

Loading…
Cancel
Save