Commit 792b270f authored by Thomas Müller's avatar Thomas Müller
Browse files

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

Add carddav backend to OCP\ContactsManager
parents 3818a055 3ce845e2
<?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
)
);
}
});
......@@ -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>
......
......@@ -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));
......@@ -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() {
......
<?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;
}
}
......@@ -23,19 +23,48 @@
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\Component\VCard;
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 +292,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();
}
/**
......@@ -398,7 +432,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$query = $this->db->getQueryBuilder();
$query->insert('cards')
->values([
'carddata' => $query->createNamedParameter($cardData),
'carddata' => $query->createNamedParameter($cardData, \PDO::PARAM_LOB),
'uri' => $query->createNamedParameter($cardUri),
'lastmodified' => $query->createNamedParameter(time()),
'addressbookid' => $query->createNamedParameter($addressBookId),
......@@ -408,6 +442,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->execute();
$this->addChange($addressBookId, $cardUri, 1);
$this->updateProperties($addressBookId, $cardUri, $cardData);
return '"' . $etag . '"';
}
......@@ -451,6 +486,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->execute();
$this->addChange($addressBookId, $cardUri, 2);
$this->updateProperties($addressBookId, $cardUri, $cardData);
return '"' . $etag . '"';
}
......@@ -463,6 +499,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 +508,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 +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 $element
......@@ -734,4 +857,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;