feat(CardDAV): Add Sabre\DAV\IMoveTarget support to OCA\DAV\CardDAV\AddressBook

This allows to just UPDATE the card row instead of deleting it and reinsert it. It's very similar to https://github.com/nextcloud/server/pull/30120 for calendars.

As we need the addressbookid exposed, this introduces OCA\DAV\CardDAV\Card that extends Sabre's.

I chose specifically NOT to auto-inject LoggerInterface in Addressbook like in #30120 because the chain of DI is huge just for ONE simple call and it would break an existing dirty call (OCA\Contacts calling OCA\DAV) of ContactsManager in Contacts: https://github.com/nextcloud/contacts/pull/1722 (in SocialApiService), but this is debatable.

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2023-03-21 18:11:56 +01:00
Родитель 25dd264965
Коммит 13a3ebd4cc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: A061B9DDE0CA0773
8 изменённых файлов: 337 добавлений и 28 удалений

Просмотреть файл

@ -114,6 +114,7 @@ return array(
'OCA\\DAV\\CardDAV\\AddressBook' => $baseDir . '/../lib/CardDAV/AddressBook.php', 'OCA\\DAV\\CardDAV\\AddressBook' => $baseDir . '/../lib/CardDAV/AddressBook.php',
'OCA\\DAV\\CardDAV\\AddressBookImpl' => $baseDir . '/../lib/CardDAV/AddressBookImpl.php', 'OCA\\DAV\\CardDAV\\AddressBookImpl' => $baseDir . '/../lib/CardDAV/AddressBookImpl.php',
'OCA\\DAV\\CardDAV\\AddressBookRoot' => $baseDir . '/../lib/CardDAV/AddressBookRoot.php', 'OCA\\DAV\\CardDAV\\AddressBookRoot' => $baseDir . '/../lib/CardDAV/AddressBookRoot.php',
'OCA\\DAV\\CardDAV\\Card' => $baseDir . '/../lib/CardDAV/Card.php',
'OCA\\DAV\\CardDAV\\CardDavBackend' => $baseDir . '/../lib/CardDAV/CardDavBackend.php', 'OCA\\DAV\\CardDAV\\CardDavBackend' => $baseDir . '/../lib/CardDAV/CardDavBackend.php',
'OCA\\DAV\\CardDAV\\ContactsManager' => $baseDir . '/../lib/CardDAV/ContactsManager.php', 'OCA\\DAV\\CardDAV\\ContactsManager' => $baseDir . '/../lib/CardDAV/ContactsManager.php',
'OCA\\DAV\\CardDAV\\Converter' => $baseDir . '/../lib/CardDAV/Converter.php', 'OCA\\DAV\\CardDAV\\Converter' => $baseDir . '/../lib/CardDAV/Converter.php',
@ -230,6 +231,7 @@ return array(
'OCA\\DAV\\Events\\CalendarUpdatedEvent' => $baseDir . '/../lib/Events/CalendarUpdatedEvent.php', 'OCA\\DAV\\Events\\CalendarUpdatedEvent' => $baseDir . '/../lib/Events/CalendarUpdatedEvent.php',
'OCA\\DAV\\Events\\CardCreatedEvent' => $baseDir . '/../lib/Events/CardCreatedEvent.php', 'OCA\\DAV\\Events\\CardCreatedEvent' => $baseDir . '/../lib/Events/CardCreatedEvent.php',
'OCA\\DAV\\Events\\CardDeletedEvent' => $baseDir . '/../lib/Events/CardDeletedEvent.php', 'OCA\\DAV\\Events\\CardDeletedEvent' => $baseDir . '/../lib/Events/CardDeletedEvent.php',
'OCA\\DAV\\Events\\CardMovedEvent' => $baseDir . '/../lib/Events/CardMovedEvent.php',
'OCA\\DAV\\Events\\CardUpdatedEvent' => $baseDir . '/../lib/Events/CardUpdatedEvent.php', 'OCA\\DAV\\Events\\CardUpdatedEvent' => $baseDir . '/../lib/Events/CardUpdatedEvent.php',
'OCA\\DAV\\Events\\SabrePluginAuthInitEvent' => $baseDir . '/../lib/Events/SabrePluginAuthInitEvent.php', 'OCA\\DAV\\Events\\SabrePluginAuthInitEvent' => $baseDir . '/../lib/Events/SabrePluginAuthInitEvent.php',
'OCA\\DAV\\Events\\SubscriptionCreatedEvent' => $baseDir . '/../lib/Events/SubscriptionCreatedEvent.php', 'OCA\\DAV\\Events\\SubscriptionCreatedEvent' => $baseDir . '/../lib/Events/SubscriptionCreatedEvent.php',

Просмотреть файл

@ -129,6 +129,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CardDAV\\AddressBook' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBook.php', 'OCA\\DAV\\CardDAV\\AddressBook' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBook.php',
'OCA\\DAV\\CardDAV\\AddressBookImpl' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBookImpl.php', 'OCA\\DAV\\CardDAV\\AddressBookImpl' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBookImpl.php',
'OCA\\DAV\\CardDAV\\AddressBookRoot' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBookRoot.php', 'OCA\\DAV\\CardDAV\\AddressBookRoot' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBookRoot.php',
'OCA\\DAV\\CardDAV\\Card' => __DIR__ . '/..' . '/../lib/CardDAV/Card.php',
'OCA\\DAV\\CardDAV\\CardDavBackend' => __DIR__ . '/..' . '/../lib/CardDAV/CardDavBackend.php', 'OCA\\DAV\\CardDAV\\CardDavBackend' => __DIR__ . '/..' . '/../lib/CardDAV/CardDavBackend.php',
'OCA\\DAV\\CardDAV\\ContactsManager' => __DIR__ . '/..' . '/../lib/CardDAV/ContactsManager.php', 'OCA\\DAV\\CardDAV\\ContactsManager' => __DIR__ . '/..' . '/../lib/CardDAV/ContactsManager.php',
'OCA\\DAV\\CardDAV\\Converter' => __DIR__ . '/..' . '/../lib/CardDAV/Converter.php', 'OCA\\DAV\\CardDAV\\Converter' => __DIR__ . '/..' . '/../lib/CardDAV/Converter.php',
@ -245,6 +246,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Events\\CalendarUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarUpdatedEvent.php', 'OCA\\DAV\\Events\\CalendarUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarUpdatedEvent.php',
'OCA\\DAV\\Events\\CardCreatedEvent' => __DIR__ . '/..' . '/../lib/Events/CardCreatedEvent.php', 'OCA\\DAV\\Events\\CardCreatedEvent' => __DIR__ . '/..' . '/../lib/Events/CardCreatedEvent.php',
'OCA\\DAV\\Events\\CardDeletedEvent' => __DIR__ . '/..' . '/../lib/Events/CardDeletedEvent.php', 'OCA\\DAV\\Events\\CardDeletedEvent' => __DIR__ . '/..' . '/../lib/Events/CardDeletedEvent.php',
'OCA\\DAV\\Events\\CardMovedEvent' => __DIR__ . '/..' . '/../lib/Events/CardMovedEvent.php',
'OCA\\DAV\\Events\\CardUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/CardUpdatedEvent.php', 'OCA\\DAV\\Events\\CardUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/CardUpdatedEvent.php',
'OCA\\DAV\\Events\\SabrePluginAuthInitEvent' => __DIR__ . '/..' . '/../lib/Events/SabrePluginAuthInitEvent.php', 'OCA\\DAV\\Events\\SabrePluginAuthInitEvent' => __DIR__ . '/..' . '/../lib/Events/SabrePluginAuthInitEvent.php',
'OCA\\DAV\\Events\\SubscriptionCreatedEvent' => __DIR__ . '/..' . '/../lib/Events/SubscriptionCreatedEvent.php', 'OCA\\DAV\\Events\\SubscriptionCreatedEvent' => __DIR__ . '/..' . '/../lib/Events/SubscriptionCreatedEvent.php',

Просмотреть файл

@ -6,6 +6,7 @@
* @author Georg Ehrke <oc.list@georgehrke.com> * @author Georg Ehrke <oc.list@georgehrke.com>
* @author Joas Schilling <coding@schilljs.com> * @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl> * @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Citharel <nextcloud@tcit.fr>
* @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Müller <thomas.mueller@tmit.eu>
* *
* @license AGPL-3.0 * @license AGPL-3.0
@ -27,11 +28,15 @@ namespace OCA\DAV\CardDAV;
use OCA\DAV\DAV\Sharing\IShareable; use OCA\DAV\DAV\Sharing\IShareable;
use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException; use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
use OCP\DB\Exception;
use OCP\IL10N; use OCP\IL10N;
use OCP\Server;
use Psr\Log\LoggerInterface;
use Sabre\CardDAV\Backend\BackendInterface; use Sabre\CardDAV\Backend\BackendInterface;
use Sabre\CardDAV\Card;
use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\IMoveTarget;
use Sabre\DAV\INode;
use Sabre\DAV\PropPatch; use Sabre\DAV\PropPatch;
/** /**
@ -40,7 +45,7 @@ use Sabre\DAV\PropPatch;
* @package OCA\DAV\CardDAV * @package OCA\DAV\CardDAV
* @property CardDavBackend $carddavBackend * @property CardDavBackend $carddavBackend
*/ */
class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable, IMoveTarget {
/** /**
* AddressBook constructor. * AddressBook constructor.
@ -52,6 +57,7 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
public function __construct(BackendInterface $carddavBackend, array $addressBookInfo, IL10N $l10n) { public function __construct(BackendInterface $carddavBackend, array $addressBookInfo, IL10N $l10n) {
parent::__construct($carddavBackend, $addressBookInfo); parent::__construct($carddavBackend, $addressBookInfo);
if ($this->addressBookInfo['{DAV:}displayname'] === CardDavBackend::PERSONAL_ADDRESSBOOK_NAME && if ($this->addressBookInfo['{DAV:}displayname'] === CardDavBackend::PERSONAL_ADDRESSBOOK_NAME &&
$this->getName() === CardDavBackend::PERSONAL_ADDRESSBOOK_URI) { $this->getName() === CardDavBackend::PERSONAL_ADDRESSBOOK_URI) {
$this->addressBookInfo['{DAV:}displayname'] = $l10n->t('Contacts'); $this->addressBookInfo['{DAV:}displayname'] = $l10n->t('Contacts');
@ -160,6 +166,30 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
return new Card($this->carddavBackend, $this->addressBookInfo, $obj); return new Card($this->carddavBackend, $this->addressBookInfo, $obj);
} }
public function getChildren()
{
$objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);
$children = [];
foreach ($objs as $obj) {
$obj['acl'] = $this->getChildACL();
$children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
}
return $children;
}
public function getMultipleChildren(array $paths)
{
$objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths);
$children = [];
foreach ($objs as $obj) {
$obj['acl'] = $this->getChildACL();
$children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
}
return $children;
}
public function getResourceId(): int { public function getResourceId(): int {
return $this->addressBookInfo['id']; return $this->addressBookInfo['id'];
} }
@ -223,4 +253,21 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
return parent::getChanges($syncToken, $syncLevel, $limit); return parent::getChanges($syncToken, $syncLevel, $limit);
} }
/**
* @inheritDoc
*/
public function moveInto($targetName, $sourcePath, INode $sourceNode) {
if (!($sourceNode instanceof Card)) {
return false;
}
try {
return $this->carddavBackend->moveCard($sourceNode->getAddressbookId(), (int)$this->addressBookInfo['id'], $sourceNode->getUri(), $sourceNode->getOwner());
} catch (Exception $e) {
// Avoid injecting LoggerInterface everywhere
Server::get(LoggerInterface::class)->error('Could not move calendar object: ' . $e->getMessage(), ['exception' => $e]);
return false;
}
}
} }

Просмотреть файл

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023, Thomas Citharel <nextcloud@tcit.fr>
*
* @author Thomas Citharel <nextcloud@tcit.fr>
*
* @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 Card extends \Sabre\CardDAV\Card
{
public function getId(): int {
return (int) $this->cardData['id'];
}
public function getUri(): string {
return $this->cardData['uri'];
}
protected function isShared(): bool {
if (!isset($this->cardData['{http://owncloud.org/ns}owner-principal'])) {
return false;
}
return $this->cardData['{http://owncloud.org/ns}owner-principal'] !== $this->cardData['principaluri'];
}
public function getAddressbookId(): int {
return (int)$this->cardData['addressbookid'];
}
public function getPrincipalUri(): string {
return $this->addressBookInfo['principaluri'];
}
public function getOwner(): ?string {
if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'];
}
return parent::getOwner();
}
}

Просмотреть файл

@ -44,8 +44,10 @@ use OCA\DAV\Events\AddressBookShareUpdatedEvent;
use OCA\DAV\Events\AddressBookUpdatedEvent; use OCA\DAV\Events\AddressBookUpdatedEvent;
use OCA\DAV\Events\CardCreatedEvent; use OCA\DAV\Events\CardCreatedEvent;
use OCA\DAV\Events\CardDeletedEvent; use OCA\DAV\Events\CardDeletedEvent;
use OCA\DAV\Events\CardMovedEvent;
use OCA\DAV\Events\CardUpdatedEvent; use OCA\DAV\Events\CardUpdatedEvent;
use OCP\AppFramework\Db\TTransactional; use OCP\AppFramework\Db\TTransactional;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection; use OCP\IDBConnection;
@ -745,6 +747,49 @@ class CardDavBackend implements BackendInterface, SyncSupport {
}, $this->db); }, $this->db);
} }
/**
* @throws Exception
*/
public function moveCard(int $sourceAddressBookId, int $targetAddressBookId, string $cardUri, string $oldPrincipalUri): bool {
return $this->atomic(function () use ($sourceAddressBookId, $targetAddressBookId, $cardUri, $oldPrincipalUri) {
$card = $this->getCard($sourceAddressBookId, $cardUri);
if (empty($card)) {
return false;
}
$query = $this->db->getQueryBuilder();
$query->update('cards')
->set('addressbookid', $query->createNamedParameter($targetAddressBookId, IQueryBuilder::PARAM_INT))
->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri, IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR))
->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($sourceAddressBookId, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
->executeStatement();
$this->purgeProperties($sourceAddressBookId, (int)$card['id']);
$this->updateProperties($sourceAddressBookId, $card['uri'], $card['carddata']);
$this->addChange($sourceAddressBookId, $card['uri'], 3);
$this->addChange($targetAddressBookId, $card['uri'], 1);
$card = $this->getCard($targetAddressBookId, $cardUri);
// Card wasn't found - possibly because it was deleted in the meantime by a different client
if (empty($card)) {
return false;
}
$targetAddressBookRow = $this->getAddressBookById($targetAddressBookId);
// the address book this card is being moved to does not exist any longer
if (empty($targetAddressBookRow)) {
return false;
}
$sourceShares = $this->getShares($sourceAddressBookId);
$targetShares = $this->getShares($targetAddressBookId);
$sourceAddressBookRow = $this->getAddressBookById($sourceAddressBookId);
$this->dispatcher->dispatchTyped(new CardMovedEvent($sourceAddressBookId, $sourceAddressBookRow, $targetAddressBookId, $targetAddressBookRow, $sourceShares, $targetShares, $card));
return true;
}, $this->db);
}
/** /**
* Deletes a card * Deletes a card
* *

Просмотреть файл

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023, Thomas Citharel <nextcloud@tcit.fr>
*
* @author Thomas Citharel <nextcloud@tcit.fr>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\Events;
use OCP\EventDispatcher\Event;
/**
* Class CardMovedEvent
*
* @package OCA\DAV\Events
* @since 27.0.0
*/
class CardMovedEvent extends Event {
private int $sourceAddressBookId;
private array $sourceAddressBookData;
private int $targetAddressBookId;
private array $targetAddressBookData;
private array $sourceShares;
private array $targetShares;
private array $objectData;
/**
* @since 27.0.0
*/
public function __construct(int $sourceAddressBookId,
array $sourceAddressBookData,
int $targetAddressBookId,
array $targetAddressBookData,
array $sourceShares,
array $targetShares,
array $objectData) {
parent::__construct();
$this->sourceAddressBookId = $sourceAddressBookId;
$this->sourceAddressBookData = $sourceAddressBookData;
$this->targetAddressBookId = $targetAddressBookId;
$this->targetAddressBookData = $targetAddressBookData;
$this->sourceShares = $sourceShares;
$this->targetShares = $targetShares;
$this->objectData = $objectData;
}
/**
* @return int
* @since 27.0.0
*/
public function getSourceAddressBookId(): int {
return $this->sourceAddressBookId;
}
/**
* @return array
* @since 27.0.0
*/
public function getSourceAddressBookData(): array {
return $this->sourceAddressBookData;
}
/**
* @return int
* @since 27.0.0
*/
public function getTargetAddressBookId(): int {
return $this->targetAddressBookId;
}
/**
* @return array
* @since 27.0.0
*/
public function getTargetAddressBookData(): array {
return $this->targetAddressBookData;
}
/**
* @return array
* @since 27.0.0
*/
public function getSourceShares(): array {
return $this->sourceShares;
}
/**
* @return array
* @since 27.0.0
*/
public function getTargetShares(): array {
return $this->targetShares;
}
/**
* @return array
* @since 27.0.0
*/
public function getObjectData(): array {
return $this->objectData;
}
}

Просмотреть файл

@ -6,6 +6,7 @@
* @author Joas Schilling <coding@schilljs.com> * @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de> * @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl> * @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Citharel <nextcloud@tcit.fr>
* @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Müller <thomas.mueller@tmit.eu>
* *
* @license AGPL-3.0 * @license AGPL-3.0
@ -26,94 +27,122 @@
namespace OCA\DAV\Tests\unit\CardDAV; namespace OCA\DAV\Tests\unit\CardDAV;
use OCA\DAV\CardDAV\AddressBook; use OCA\DAV\CardDAV\AddressBook;
use OCA\DAV\CardDAV\Card;
use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\CardDAV\CardDavBackend;
use OCP\IL10N; use OCP\IL10N;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\PropPatch; use Sabre\DAV\PropPatch;
use Test\TestCase; use Test\TestCase;
class AddressBookTest extends TestCase { class AddressBookTest extends TestCase {
public function testMove(): void {
$backend = $this->createMock(CardDavBackend::class);
$addressBookInfo = [
'{http://owncloud.org/ns}owner-principal' => 'user1',
'{DAV:}displayname' => 'Test address book',
'principaluri' => 'user2',
'id' => 666,
'uri' => 'default',
];
$l10n = $this->createMock(IL10N::class);
$logger = $this->createMock(LoggerInterface::class);
$addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger);
$card = new Card($backend, $addressBookInfo, ['id' => 5, 'carddata' => 'RANDOM VCF DATA', 'uri' => 'something', 'addressbookid' => 23]);
$backend->expects($this->once())->method('moveCard')->with(23, 666, 'something', 'user1')->willReturn(true);
$addressBook->moveInto('new', 'old', $card);
}
public function testDelete(): void { public function testDelete(): void {
/** @var \PHPUnit\Framework\MockObject\MockObject | CardDavBackend $backend */ /** @var MockObject | CardDavBackend $backend */
$backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock();
$backend->expects($this->once())->method('updateShares'); $backend->expects($this->once())->method('updateShares');
$backend->expects($this->any())->method('getShares')->willReturn([ $backend->expects($this->any())->method('getShares')->willReturn([
['href' => 'principal:user2'] ['href' => 'principal:user2']
]); ]);
$calendarInfo = [ $addressBookInfo = [
'{http://owncloud.org/ns}owner-principal' => 'user1', '{http://owncloud.org/ns}owner-principal' => 'user1',
'{DAV:}displayname' => 'Test address book', '{DAV:}displayname' => 'Test address book',
'principaluri' => 'user2', 'principaluri' => 'user2',
'id' => 666, 'id' => 666,
'uri' => 'default', 'uri' => 'default',
]; ];
$l = $this->createMock(IL10N::class); $l10n = $this->createMock(IL10N::class);
$c = new AddressBook($backend, $calendarInfo, $l); $logger = $this->createMock(LoggerInterface::class);
$c->delete(); $addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger);
$addressBook->delete();
} }
public function testDeleteFromGroup(): void { public function testDeleteFromGroup(): void {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->expectException(Forbidden::class);
/** @var \PHPUnit\Framework\MockObject\MockObject | CardDavBackend $backend */ /** @var MockObject | CardDavBackend $backend */
$backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock();
$backend->expects($this->never())->method('updateShares'); $backend->expects($this->never())->method('updateShares');
$backend->expects($this->any())->method('getShares')->willReturn([ $backend->expects($this->any())->method('getShares')->willReturn([
['href' => 'principal:group2'] ['href' => 'principal:group2']
]); ]);
$calendarInfo = [ $addressBookInfo = [
'{http://owncloud.org/ns}owner-principal' => 'user1', '{http://owncloud.org/ns}owner-principal' => 'user1',
'{DAV:}displayname' => 'Test address book', '{DAV:}displayname' => 'Test address book',
'principaluri' => 'user2', 'principaluri' => 'user2',
'id' => 666, 'id' => 666,
'uri' => 'default', 'uri' => 'default',
]; ];
$l = $this->createMock(IL10N::class); $l10n = $this->createMock(IL10N::class);
$c = new AddressBook($backend, $calendarInfo, $l); $logger = $this->createMock(LoggerInterface::class);
$c->delete(); $addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger);
$addressBook->delete();
} }
public function testPropPatch(): void { public function testPropPatch(): void {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->expectException(Forbidden::class);
/** @var \PHPUnit\Framework\MockObject\MockObject | CardDavBackend $backend */ /** @var MockObject | CardDavBackend $backend */
$backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock();
$calendarInfo = [ $addressBookInfo = [
'{http://owncloud.org/ns}owner-principal' => 'user1', '{http://owncloud.org/ns}owner-principal' => 'user1',
'{DAV:}displayname' => 'Test address book', '{DAV:}displayname' => 'Test address book',
'principaluri' => 'user2', 'principaluri' => 'user2',
'id' => 666, 'id' => 666,
'uri' => 'default', 'uri' => 'default',
]; ];
$l = $this->createMock(IL10N::class); $l10n = $this->createMock(IL10N::class);
$c = new AddressBook($backend, $calendarInfo, $l); $logger = $this->createMock(LoggerInterface::class);
$c->propPatch(new PropPatch([])); $addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger);
$addressBook->propPatch(new PropPatch([]));
} }
/** /**
* @dataProvider providesReadOnlyInfo * @dataProvider providesReadOnlyInfo
*/ */
public function testAcl($expectsWrite, $readOnlyValue, $hasOwnerSet): void { public function testAcl($expectsWrite, $readOnlyValue, $hasOwnerSet): void {
/** @var \PHPUnit\Framework\MockObject\MockObject | CardDavBackend $backend */ /** @var MockObject | CardDavBackend $backend */
$backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock();
$backend->expects($this->any())->method('applyShareAcl')->willReturnArgument(1); $backend->expects($this->any())->method('applyShareAcl')->willReturnArgument(1);
$calendarInfo = [ $addressBookInfo = [
'{DAV:}displayname' => 'Test address book', '{DAV:}displayname' => 'Test address book',
'principaluri' => 'user2', 'principaluri' => 'user2',
'id' => 666, 'id' => 666,
'uri' => 'default' 'uri' => 'default'
]; ];
if (!is_null($readOnlyValue)) { if (!is_null($readOnlyValue)) {
$calendarInfo['{http://owncloud.org/ns}read-only'] = $readOnlyValue; $addressBookInfo['{http://owncloud.org/ns}read-only'] = $readOnlyValue;
} }
if ($hasOwnerSet) { if ($hasOwnerSet) {
$calendarInfo['{http://owncloud.org/ns}owner-principal'] = 'user1'; $addressBookInfo['{http://owncloud.org/ns}owner-principal'] = 'user1';
} }
$l = $this->createMock(IL10N::class); $l10n = $this->createMock(IL10N::class);
$c = new AddressBook($backend, $calendarInfo, $l); $logger = $this->createMock(LoggerInterface::class);
$acl = $c->getACL(); $addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger);
$childAcl = $c->getChildACL(); $acl = $addressBook->getACL();
$childAcl = $addressBook->getChildACL();
$expectedAcl = [[ $expectedAcl = [[
'privilege' => '{DAV:}read', 'privilege' => '{DAV:}read',
@ -142,7 +171,7 @@ class AddressBookTest extends TestCase {
$this->assertEquals($expectedAcl, $childAcl); $this->assertEquals($expectedAcl, $childAcl);
} }
public function providesReadOnlyInfo() { public function providesReadOnlyInfo(): array {
return [ return [
'read-only property not set' => [true, null, true], 'read-only property not set' => [true, null, true],
'read-only property is false' => [true, false, true], 'read-only property is false' => [true, false, true],

Просмотреть файл

@ -31,6 +31,11 @@
<code>$data</code> <code>$data</code>
</MoreSpecificImplementedParamType> </MoreSpecificImplementedParamType>
</file> </file>
<file src="3rdparty/sabre/dav/lib/CardDAV/Card.php">
<MoreSpecificImplementedParamType occurrences="1">
<code>$cardData</code>
</MoreSpecificImplementedParamType>
</file>
<file src="3rdparty/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php"> <file src="3rdparty/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php">
<LessSpecificImplementedReturnType> <LessSpecificImplementedReturnType>
<code>array</code> <code>array</code>