Skip to content

Commit dc1b68c

Browse files
authored
Merge pull request #37326 from nextcloud/add-IMoveTarget-support-to-addressbooks
feat(CardDAV): Add Sabre\DAV\IMoveTarget support to OCA\DAV\CardDAV\AddressBook
2 parents 75b7c13 + 13a3ebd commit dc1b68c

File tree

8 files changed

+337
-28
lines changed

8 files changed

+337
-28
lines changed

apps/dav/composer/composer/autoload_classmap.php

+2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
'OCA\\DAV\\CardDAV\\AddressBook' => $baseDir . '/../lib/CardDAV/AddressBook.php',
115115
'OCA\\DAV\\CardDAV\\AddressBookImpl' => $baseDir . '/../lib/CardDAV/AddressBookImpl.php',
116116
'OCA\\DAV\\CardDAV\\AddressBookRoot' => $baseDir . '/../lib/CardDAV/AddressBookRoot.php',
117+
'OCA\\DAV\\CardDAV\\Card' => $baseDir . '/../lib/CardDAV/Card.php',
117118
'OCA\\DAV\\CardDAV\\CardDavBackend' => $baseDir . '/../lib/CardDAV/CardDavBackend.php',
118119
'OCA\\DAV\\CardDAV\\ContactsManager' => $baseDir . '/../lib/CardDAV/ContactsManager.php',
119120
'OCA\\DAV\\CardDAV\\Converter' => $baseDir . '/../lib/CardDAV/Converter.php',
@@ -230,6 +231,7 @@
230231
'OCA\\DAV\\Events\\CalendarUpdatedEvent' => $baseDir . '/../lib/Events/CalendarUpdatedEvent.php',
231232
'OCA\\DAV\\Events\\CardCreatedEvent' => $baseDir . '/../lib/Events/CardCreatedEvent.php',
232233
'OCA\\DAV\\Events\\CardDeletedEvent' => $baseDir . '/../lib/Events/CardDeletedEvent.php',
234+
'OCA\\DAV\\Events\\CardMovedEvent' => $baseDir . '/../lib/Events/CardMovedEvent.php',
233235
'OCA\\DAV\\Events\\CardUpdatedEvent' => $baseDir . '/../lib/Events/CardUpdatedEvent.php',
234236
'OCA\\DAV\\Events\\SabrePluginAuthInitEvent' => $baseDir . '/../lib/Events/SabrePluginAuthInitEvent.php',
235237
'OCA\\DAV\\Events\\SubscriptionCreatedEvent' => $baseDir . '/../lib/Events/SubscriptionCreatedEvent.php',

apps/dav/composer/composer/autoload_static.php

+2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class ComposerStaticInitDAV
129129
'OCA\\DAV\\CardDAV\\AddressBook' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBook.php',
130130
'OCA\\DAV\\CardDAV\\AddressBookImpl' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBookImpl.php',
131131
'OCA\\DAV\\CardDAV\\AddressBookRoot' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBookRoot.php',
132+
'OCA\\DAV\\CardDAV\\Card' => __DIR__ . '/..' . '/../lib/CardDAV/Card.php',
132133
'OCA\\DAV\\CardDAV\\CardDavBackend' => __DIR__ . '/..' . '/../lib/CardDAV/CardDavBackend.php',
133134
'OCA\\DAV\\CardDAV\\ContactsManager' => __DIR__ . '/..' . '/../lib/CardDAV/ContactsManager.php',
134135
'OCA\\DAV\\CardDAV\\Converter' => __DIR__ . '/..' . '/../lib/CardDAV/Converter.php',
@@ -245,6 +246,7 @@ class ComposerStaticInitDAV
245246
'OCA\\DAV\\Events\\CalendarUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarUpdatedEvent.php',
246247
'OCA\\DAV\\Events\\CardCreatedEvent' => __DIR__ . '/..' . '/../lib/Events/CardCreatedEvent.php',
247248
'OCA\\DAV\\Events\\CardDeletedEvent' => __DIR__ . '/..' . '/../lib/Events/CardDeletedEvent.php',
249+
'OCA\\DAV\\Events\\CardMovedEvent' => __DIR__ . '/..' . '/../lib/Events/CardMovedEvent.php',
248250
'OCA\\DAV\\Events\\CardUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/CardUpdatedEvent.php',
249251
'OCA\\DAV\\Events\\SabrePluginAuthInitEvent' => __DIR__ . '/..' . '/../lib/Events/SabrePluginAuthInitEvent.php',
250252
'OCA\\DAV\\Events\\SubscriptionCreatedEvent' => __DIR__ . '/..' . '/../lib/Events/SubscriptionCreatedEvent.php',

apps/dav/lib/CardDAV/AddressBook.php

+49-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* @author Georg Ehrke <oc.list@georgehrke.com>
77
* @author Joas Schilling <coding@schilljs.com>
88
* @author Roeland Jago Douma <roeland@famdouma.nl>
9+
* @author Thomas Citharel <nextcloud@tcit.fr>
910
* @author Thomas Müller <thomas.mueller@tmit.eu>
1011
*
1112
* @license AGPL-3.0
@@ -27,11 +28,15 @@
2728

2829
use OCA\DAV\DAV\Sharing\IShareable;
2930
use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
31+
use OCP\DB\Exception;
3032
use OCP\IL10N;
33+
use OCP\Server;
34+
use Psr\Log\LoggerInterface;
3135
use Sabre\CardDAV\Backend\BackendInterface;
32-
use Sabre\CardDAV\Card;
3336
use Sabre\DAV\Exception\Forbidden;
3437
use Sabre\DAV\Exception\NotFound;
38+
use Sabre\DAV\IMoveTarget;
39+
use Sabre\DAV\INode;
3540
use Sabre\DAV\PropPatch;
3641

3742
/**
@@ -40,7 +45,7 @@
4045
* @package OCA\DAV\CardDAV
4146
* @property CardDavBackend $carddavBackend
4247
*/
43-
class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
48+
class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable, IMoveTarget {
4449

4550
/**
4651
* AddressBook constructor.
@@ -52,6 +57,7 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
5257
public function __construct(BackendInterface $carddavBackend, array $addressBookInfo, IL10N $l10n) {
5358
parent::__construct($carddavBackend, $addressBookInfo);
5459

60+
5561
if ($this->addressBookInfo['{DAV:}displayname'] === CardDavBackend::PERSONAL_ADDRESSBOOK_NAME &&
5662
$this->getName() === CardDavBackend::PERSONAL_ADDRESSBOOK_URI) {
5763
$this->addressBookInfo['{DAV:}displayname'] = $l10n->t('Contacts');
@@ -160,6 +166,30 @@ public function getChild($name) {
160166
return new Card($this->carddavBackend, $this->addressBookInfo, $obj);
161167
}
162168

169+
public function getChildren()
170+
{
171+
$objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);
172+
$children = [];
173+
foreach ($objs as $obj) {
174+
$obj['acl'] = $this->getChildACL();
175+
$children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
176+
}
177+
178+
return $children;
179+
}
180+
181+
public function getMultipleChildren(array $paths)
182+
{
183+
$objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths);
184+
$children = [];
185+
foreach ($objs as $obj) {
186+
$obj['acl'] = $this->getChildACL();
187+
$children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
188+
}
189+
190+
return $children;
191+
}
192+
163193
public function getResourceId(): int {
164194
return $this->addressBookInfo['id'];
165195
}
@@ -223,4 +253,21 @@ public function getChanges($syncToken, $syncLevel, $limit = null) {
223253

224254
return parent::getChanges($syncToken, $syncLevel, $limit);
225255
}
256+
257+
/**
258+
* @inheritDoc
259+
*/
260+
public function moveInto($targetName, $sourcePath, INode $sourceNode) {
261+
if (!($sourceNode instanceof Card)) {
262+
return false;
263+
}
264+
265+
try {
266+
return $this->carddavBackend->moveCard($sourceNode->getAddressbookId(), (int)$this->addressBookInfo['id'], $sourceNode->getUri(), $sourceNode->getOwner());
267+
} catch (Exception $e) {
268+
// Avoid injecting LoggerInterface everywhere
269+
Server::get(LoggerInterface::class)->error('Could not move calendar object: ' . $e->getMessage(), ['exception' => $e]);
270+
return false;
271+
}
272+
}
226273
}

apps/dav/lib/CardDAV/Card.php

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2023, Thomas Citharel <nextcloud@tcit.fr>
7+
*
8+
* @author Thomas Citharel <nextcloud@tcit.fr>
9+
*
10+
* @license AGPL-3.0
11+
*
12+
* This code is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License, version 3,
14+
* as published by the Free Software Foundation.
15+
*
16+
* This program is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
* GNU Affero General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU Affero General Public License, version 3,
22+
* along with this program. If not, see <http://www.gnu.org/licenses/>
23+
*
24+
*/
25+
namespace OCA\DAV\CardDAV;
26+
27+
class Card extends \Sabre\CardDAV\Card
28+
{
29+
public function getId(): int {
30+
return (int) $this->cardData['id'];
31+
}
32+
33+
public function getUri(): string {
34+
return $this->cardData['uri'];
35+
}
36+
37+
protected function isShared(): bool {
38+
if (!isset($this->cardData['{http://owncloud.org/ns}owner-principal'])) {
39+
return false;
40+
}
41+
42+
return $this->cardData['{http://owncloud.org/ns}owner-principal'] !== $this->cardData['principaluri'];
43+
}
44+
45+
public function getAddressbookId(): int {
46+
return (int)$this->cardData['addressbookid'];
47+
}
48+
49+
public function getPrincipalUri(): string {
50+
return $this->addressBookInfo['principaluri'];
51+
}
52+
53+
public function getOwner(): ?string {
54+
if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
55+
return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'];
56+
}
57+
return parent::getOwner();
58+
}
59+
}

apps/dav/lib/CardDAV/CardDavBackend.php

+45
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@
4444
use OCA\DAV\Events\AddressBookUpdatedEvent;
4545
use OCA\DAV\Events\CardCreatedEvent;
4646
use OCA\DAV\Events\CardDeletedEvent;
47+
use OCA\DAV\Events\CardMovedEvent;
4748
use OCA\DAV\Events\CardUpdatedEvent;
4849
use OCP\AppFramework\Db\TTransactional;
50+
use OCP\DB\Exception;
4951
use OCP\DB\QueryBuilder\IQueryBuilder;
5052
use OCP\EventDispatcher\IEventDispatcher;
5153
use OCP\IDBConnection;
@@ -745,6 +747,49 @@ public function updateCard($addressBookId, $cardUri, $cardData) {
745747
}, $this->db);
746748
}
747749

750+
/**
751+
* @throws Exception
752+
*/
753+
public function moveCard(int $sourceAddressBookId, int $targetAddressBookId, string $cardUri, string $oldPrincipalUri): bool {
754+
return $this->atomic(function () use ($sourceAddressBookId, $targetAddressBookId, $cardUri, $oldPrincipalUri) {
755+
$card = $this->getCard($sourceAddressBookId, $cardUri);
756+
if (empty($card)) {
757+
return false;
758+
}
759+
760+
$query = $this->db->getQueryBuilder();
761+
$query->update('cards')
762+
->set('addressbookid', $query->createNamedParameter($targetAddressBookId, IQueryBuilder::PARAM_INT))
763+
->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri, IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR))
764+
->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($sourceAddressBookId, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
765+
->executeStatement();
766+
767+
$this->purgeProperties($sourceAddressBookId, (int)$card['id']);
768+
$this->updateProperties($sourceAddressBookId, $card['uri'], $card['carddata']);
769+
770+
$this->addChange($sourceAddressBookId, $card['uri'], 3);
771+
$this->addChange($targetAddressBookId, $card['uri'], 1);
772+
773+
$card = $this->getCard($targetAddressBookId, $cardUri);
774+
// Card wasn't found - possibly because it was deleted in the meantime by a different client
775+
if (empty($card)) {
776+
return false;
777+
}
778+
779+
$targetAddressBookRow = $this->getAddressBookById($targetAddressBookId);
780+
// the address book this card is being moved to does not exist any longer
781+
if (empty($targetAddressBookRow)) {
782+
return false;
783+
}
784+
785+
$sourceShares = $this->getShares($sourceAddressBookId);
786+
$targetShares = $this->getShares($targetAddressBookId);
787+
$sourceAddressBookRow = $this->getAddressBookById($sourceAddressBookId);
788+
$this->dispatcher->dispatchTyped(new CardMovedEvent($sourceAddressBookId, $sourceAddressBookRow, $targetAddressBookId, $targetAddressBookRow, $sourceShares, $targetShares, $card));
789+
return true;
790+
}, $this->db);
791+
}
792+
748793
/**
749794
* Deletes a card
750795
*
+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2023, Thomas Citharel <nextcloud@tcit.fr>
7+
*
8+
* @author Thomas Citharel <nextcloud@tcit.fr>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
namespace OCA\DAV\Events;
27+
28+
use OCP\EventDispatcher\Event;
29+
30+
/**
31+
* Class CardMovedEvent
32+
*
33+
* @package OCA\DAV\Events
34+
* @since 27.0.0
35+
*/
36+
class CardMovedEvent extends Event {
37+
private int $sourceAddressBookId;
38+
private array $sourceAddressBookData;
39+
private int $targetAddressBookId;
40+
private array $targetAddressBookData;
41+
private array $sourceShares;
42+
private array $targetShares;
43+
private array $objectData;
44+
45+
/**
46+
* @since 27.0.0
47+
*/
48+
public function __construct(int $sourceAddressBookId,
49+
array $sourceAddressBookData,
50+
int $targetAddressBookId,
51+
array $targetAddressBookData,
52+
array $sourceShares,
53+
array $targetShares,
54+
array $objectData) {
55+
parent::__construct();
56+
$this->sourceAddressBookId = $sourceAddressBookId;
57+
$this->sourceAddressBookData = $sourceAddressBookData;
58+
$this->targetAddressBookId = $targetAddressBookId;
59+
$this->targetAddressBookData = $targetAddressBookData;
60+
$this->sourceShares = $sourceShares;
61+
$this->targetShares = $targetShares;
62+
$this->objectData = $objectData;
63+
}
64+
65+
/**
66+
* @return int
67+
* @since 27.0.0
68+
*/
69+
public function getSourceAddressBookId(): int {
70+
return $this->sourceAddressBookId;
71+
}
72+
73+
/**
74+
* @return array
75+
* @since 27.0.0
76+
*/
77+
public function getSourceAddressBookData(): array {
78+
return $this->sourceAddressBookData;
79+
}
80+
81+
/**
82+
* @return int
83+
* @since 27.0.0
84+
*/
85+
public function getTargetAddressBookId(): int {
86+
return $this->targetAddressBookId;
87+
}
88+
89+
/**
90+
* @return array
91+
* @since 27.0.0
92+
*/
93+
public function getTargetAddressBookData(): array {
94+
return $this->targetAddressBookData;
95+
}
96+
97+
/**
98+
* @return array
99+
* @since 27.0.0
100+
*/
101+
public function getSourceShares(): array {
102+
return $this->sourceShares;
103+
}
104+
105+
/**
106+
* @return array
107+
* @since 27.0.0
108+
*/
109+
public function getTargetShares(): array {
110+
return $this->targetShares;
111+
}
112+
113+
/**
114+
* @return array
115+
* @since 27.0.0
116+
*/
117+
public function getObjectData(): array {
118+
return $this->objectData;
119+
}
120+
}

0 commit comments

Comments
 (0)