Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion apps/dav/lib/CardDAV/AddressBook.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
use OCP\Server;
use Psr\Log\LoggerInterface;
use Sabre\CardDAV\Backend\BackendInterface;
use Sabre\CardDAV\IAddressBookObjectContainer;
use Sabre\CardDAV\Xml\Request\AddressBookQueryReport;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\IMoveTarget;
Expand All @@ -25,7 +27,7 @@
* @package OCA\DAV\CardDAV
* @property CardDavBackend $carddavBackend
*/
class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable, IMoveTarget {
class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable, IMoveTarget, IAddressBookObjectContainer {

Check failure on line 30 in apps/dav/lib/CardDAV/AddressBook.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedClass

apps/dav/lib/CardDAV/AddressBook.php:30:90: UndefinedClass: Class, interface or enum named Sabre\CardDAV\IAddressBookObjectContainer does not exist (see https://psalm.dev/019)
/**
* AddressBook constructor.
*
Expand Down Expand Up @@ -258,4 +260,11 @@
return false;
}
}

public function addressBookQuery(AddressBookQueryReport $report): array {
return $this->carddavBackend->addressBookReport(
$this->addressBookInfo['id'],
$report,
);
}
}
81 changes: 81 additions & 0 deletions apps/dav/lib/CardDAV/CardDavBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@
use Sabre\CardDAV\Backend\BackendInterface;
use Sabre\CardDAV\Backend\SyncSupport;
use Sabre\CardDAV\Plugin;
use Sabre\CardDAV\Xml\Request\AddressBookQueryReport;
use Sabre\DAV\Exception\BadRequest;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Reader;
use function in_array;
use function strtoupper;

class CardDavBackend implements BackendInterface, SyncSupport {
use TTransactional;
Expand Down Expand Up @@ -1533,4 +1536,82 @@ private function getUID(string $cardData): string {
// should already be handled, but just in case
throw new BadRequest('vCard can not be empty');
}

public function addressBookReport(int $addressBookId, AddressBookQueryReport $report): array {
$selectQuery = $this->db->getQueryBuilder();
$selectQuery->select('c.uri')
->from($this->dbCardsPropertiesTable, 'cp')
->join('cp', $this->dbCardsTable, 'c', $selectQuery->expr()->eq('c.id', 'cp.cardid', IQueryBuilder::PARAM_INT))
->where($selectQuery->expr()->eq('c.addressbookid', $selectQuery->createNamedParameter($addressBookId, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));

$needsPostFilter = false;
$dbFilters = [];
foreach ($report->filters as $filter) {
if (!in_array(strtoupper($filter['name']), self::$indexProperties, true)) {
// We can't push the filter down to the DB
$needsPostFilter = true;
}
if (!empty($filter['param-filter'])) {
// Parameters are not indexed
$needsPostFilter = true;
}
if ($filter['is-not-defined'] === true) {
// Can't easily look for this with a query
$needsPostFilter = true;
}

foreach ($filter['text-matches'] as $matchFilter) {
// TODO: handle negate-condition
$dbFilters[] = $selectQuery->expr()->andX(
$selectQuery->expr()->eq(
'cp.name',
$selectQuery->createNamedParameter($filter['name']),
),
match ($matchFilter['match-type']) {
'equals' => $selectQuery->expr()->eq(
'cp.value',
$selectQuery->createNamedParameter($matchFilter['value'], IQueryBuilder::PARAM_STR)
),
'contains' => $selectQuery->expr()->like(
'cp.value',
$selectQuery->createNamedParameter('%' . $matchFilter['value'] . '%', IQueryBuilder::PARAM_STR) // TODO: escaping
),
'starts-with' => $selectQuery->expr()->eq(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you use '%' still like?
Same on ends-with?

'cp.value',
$selectQuery->createNamedParameter($matchFilter['value'] . '%', IQueryBuilder::PARAM_STR) // TODO: escaping
),
'ends-with' => $selectQuery->expr()->eq(
'cp.value',
$selectQuery->createNamedParameter('%' . $matchFilter['value'], IQueryBuilder::PARAM_STR) // TODO: escaping
),
default => throw new \InvalidArgumentException('Invalid filter match'),
}
);
}
}

if ($dbFilters !== []) {
$selectQuery->andWhere(
match ($report->test) {
'allof' => $selectQuery->expr()->andX(...$dbFilters),
'anyof' => $selectQuery->expr()->orX(...$dbFilters),
default => throw new \InvalidArgumentException('Invalid filter test'),
}
);
}

$selectQuery->groupBy('c.uri'); // deduplicate
$result = $selectQuery->executeQuery();
$uris = [];
while (($uri = $result->fetchOne()) !== false) {
$uris[] = $uri;
}
$result->closeCursor();

if ($needsPostFilter) {
// TODO implement
}

return $uris;
}
}
Loading