Skip to content

Commit 27cf6ac

Browse files
authored
Merge pull request nextcloud#53568 from nextcloud/refactor/dav/example-contact-service
refactor(dav): move shared logic to a dedicated example contact service
2 parents 07bf009 + 960b3ec commit 27cf6ac

14 files changed

Lines changed: 225 additions & 205 deletions

apps/dav/appinfo/routes.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@
1111
['name' => 'invitation_response#decline', 'url' => '/invitation/decline/{token}', 'verb' => 'GET'],
1212
['name' => 'invitation_response#options', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'GET'],
1313
['name' => 'invitation_response#processMoreOptionsResult', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'POST'],
14-
['name' => 'example_content#getDefaultContact', 'url' => '/api/defaultcontact/contact', 'verb' => 'GET'],
15-
['name' => 'example_content#setDefaultContact', 'url' => '/api/defaultcontact/contact', 'verb' => 'PUT'],
16-
['name' => 'example_content#setEnableDefaultContact', 'url' => '/api/defaultcontact/config', 'verb' => 'PUT'],
1714
],
1815
'ocs' => [
1916
['name' => 'direct#getUrl', 'url' => '/api/v1/direct', 'verb' => 'POST'],

apps/dav/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@
366366
'OCA\\DAV\\Server' => $baseDir . '/../lib/Server.php',
367367
'OCA\\DAV\\ServerFactory' => $baseDir . '/../lib/ServerFactory.php',
368368
'OCA\\DAV\\Service\\AbsenceService' => $baseDir . '/../lib/Service/AbsenceService.php',
369-
'OCA\\DAV\\Service\\DefaultContactService' => $baseDir . '/../lib/Service/DefaultContactService.php',
369+
'OCA\\DAV\\Service\\ExampleContactService' => $baseDir . '/../lib/Service/ExampleContactService.php',
370370
'OCA\\DAV\\Service\\ExampleEventService' => $baseDir . '/../lib/Service/ExampleEventService.php',
371371
'OCA\\DAV\\Settings\\Admin\\SystemAddressBookSettings' => $baseDir . '/../lib/Settings/Admin/SystemAddressBookSettings.php',
372372
'OCA\\DAV\\Settings\\AvailabilitySettings' => $baseDir . '/../lib/Settings/AvailabilitySettings.php',

apps/dav/composer/composer/autoload_static.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ class ComposerStaticInitDAV
381381
'OCA\\DAV\\Server' => __DIR__ . '/..' . '/../lib/Server.php',
382382
'OCA\\DAV\\ServerFactory' => __DIR__ . '/..' . '/../lib/ServerFactory.php',
383383
'OCA\\DAV\\Service\\AbsenceService' => __DIR__ . '/..' . '/../lib/Service/AbsenceService.php',
384-
'OCA\\DAV\\Service\\DefaultContactService' => __DIR__ . '/..' . '/../lib/Service/DefaultContactService.php',
384+
'OCA\\DAV\\Service\\ExampleContactService' => __DIR__ . '/..' . '/../lib/Service/ExampleContactService.php',
385385
'OCA\\DAV\\Service\\ExampleEventService' => __DIR__ . '/..' . '/../lib/Service/ExampleEventService.php',
386386
'OCA\\DAV\\Settings\\Admin\\SystemAddressBookSettings' => __DIR__ . '/..' . '/../lib/Settings/Admin/SystemAddressBookSettings.php',
387387
'OCA\\DAV\\Settings\\AvailabilitySettings' => __DIR__ . '/..' . '/../lib/Settings/AvailabilitySettings.php',

apps/dav/lib/Controller/ExampleContentController.php

Lines changed: 14 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -10,110 +10,58 @@
1010
namespace OCA\DAV\Controller;
1111

1212
use OCA\DAV\AppInfo\Application;
13+
use OCA\DAV\Service\ExampleContactService;
1314
use OCA\DAV\Service\ExampleEventService;
1415
use OCP\AppFramework\ApiController;
1516
use OCP\AppFramework\Http;
1617
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
1718
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
1819
use OCP\AppFramework\Http\DataDownloadResponse;
1920
use OCP\AppFramework\Http\JSONResponse;
20-
use OCP\Files\AppData\IAppDataFactory;
21-
use OCP\Files\IAppData;
22-
use OCP\Files\NotFoundException;
23-
use OCP\IAppConfig;
24-
use OCP\IConfig;
2521
use OCP\IRequest;
2622
use Psr\Log\LoggerInterface;
2723

2824
class ExampleContentController extends ApiController {
29-
private IAppData $appData;
30-
3125
public function __construct(
3226
IRequest $request,
33-
private IConfig $config,
34-
private IAppConfig $appConfig,
35-
private IAppDataFactory $appDataFactory,
36-
private LoggerInterface $logger,
37-
private ExampleEventService $exampleEventService,
27+
private readonly LoggerInterface $logger,
28+
private readonly ExampleEventService $exampleEventService,
29+
private readonly ExampleContactService $exampleContactService,
3830
) {
3931
parent::__construct(Application::APP_ID, $request);
40-
$this->appData = $this->appDataFactory->get('dav');
4132
}
4233

43-
public function setEnableDefaultContact($allow) {
44-
if ($allow === 'yes' && !$this->defaultContactExists()) {
34+
#[FrontpageRoute(verb: 'PUT', url: '/api/defaultcontact/config')]
35+
public function setEnableDefaultContact(bool $allow): JSONResponse {
36+
if ($allow && !$this->exampleContactService->defaultContactExists()) {
4537
try {
46-
$this->setCard();
38+
$this->exampleContactService->setCard();
4739
} catch (\Exception $e) {
4840
$this->logger->error('Could not create default contact', ['exception' => $e]);
4941
return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
5042
}
5143
}
52-
$this->config->setAppValue(Application::APP_ID, 'enableDefaultContact', $allow);
44+
$this->exampleContactService->setDefaultContactEnabled($allow);
5345
return new JSONResponse([], Http::STATUS_OK);
5446
}
5547

5648
#[NoCSRFRequired]
49+
#[FrontpageRoute(verb: 'GET', url: '/api/defaultcontact/contact')]
5750
public function getDefaultContact(): DataDownloadResponse {
58-
$cardData = $this->getCard()
51+
$cardData = $this->exampleContactService->getCard()
5952
?? file_get_contents(__DIR__ . '/../ExampleContentFiles/exampleContact.vcf');
6053
return new DataDownloadResponse($cardData, 'example_contact.vcf', 'text/vcard');
6154
}
6255

56+
#[FrontpageRoute(verb: 'PUT', url: '/api/defaultcontact/contact')]
6357
public function setDefaultContact(?string $contactData = null) {
64-
if (!$this->config->getAppValue(Application::APP_ID, 'enableDefaultContact', 'yes')) {
58+
if (!$this->exampleContactService->isDefaultContactEnabled()) {
6559
return new JSONResponse([], Http::STATUS_FORBIDDEN);
6660
}
67-
$this->setCard($contactData);
61+
$this->exampleContactService->setCard($contactData);
6862
return new JSONResponse([], Http::STATUS_OK);
6963
}
7064

71-
private function getCard(): ?string {
72-
try {
73-
$folder = $this->appData->getFolder('defaultContact');
74-
} catch (NotFoundException $e) {
75-
return null;
76-
}
77-
78-
if (!$folder->fileExists('defaultContact.vcf')) {
79-
return null;
80-
}
81-
82-
return $folder->getFile('defaultContact.vcf')->getContent();
83-
}
84-
85-
private function setCard(?string $cardData = null) {
86-
try {
87-
$folder = $this->appData->getFolder('defaultContact');
88-
} catch (NotFoundException $e) {
89-
$folder = $this->appData->newFolder('defaultContact');
90-
}
91-
92-
$isCustom = true;
93-
if (is_null($cardData)) {
94-
$cardData = file_get_contents(__DIR__ . '/../ExampleContentFiles/exampleContact.vcf');
95-
$isCustom = false;
96-
}
97-
98-
if (!$cardData) {
99-
throw new \Exception('Could not read exampleContact.vcf');
100-
}
101-
102-
$file = (!$folder->fileExists('defaultContact.vcf')) ? $folder->newFile('defaultContact.vcf') : $folder->getFile('defaultContact.vcf');
103-
$file->putContent($cardData);
104-
105-
$this->appConfig->setValueBool(Application::APP_ID, 'hasCustomDefaultContact', $isCustom);
106-
}
107-
108-
private function defaultContactExists(): bool {
109-
try {
110-
$folder = $this->appData->getFolder('defaultContact');
111-
} catch (NotFoundException $e) {
112-
return false;
113-
}
114-
return $folder->fileExists('defaultContact.vcf');
115-
}
116-
11765
#[FrontpageRoute(verb: 'POST', url: '/api/exampleEvent/enable')]
11866
public function setCreateExampleEvent(bool $enable): JSONResponse {
11967
$this->exampleEventService->setCreateExampleEvent($enable);

apps/dav/lib/Listener/UserEventsListener.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
use OCA\DAV\CalDAV\CalDavBackend;
1313
use OCA\DAV\CardDAV\CardDavBackend;
1414
use OCA\DAV\CardDAV\SyncService;
15-
use OCA\DAV\Service\DefaultContactService;
15+
use OCA\DAV\Service\ExampleContactService;
1616
use OCA\DAV\Service\ExampleEventService;
1717
use OCP\Accounts\UserUpdatedEvent;
1818
use OCP\Defaults;
@@ -46,7 +46,7 @@ public function __construct(
4646
private CalDavBackend $calDav,
4747
private CardDavBackend $cardDav,
4848
private Defaults $themingDefaults,
49-
private DefaultContactService $defaultContactService,
49+
private ExampleContactService $exampleContactService,
5050
private ExampleEventService $exampleEventService,
5151
private LoggerInterface $logger,
5252
) {
@@ -175,7 +175,7 @@ public function firstLogin(IUser $user): void {
175175
}
176176
}
177177
if ($addressBookId) {
178-
$this->defaultContactService->createDefaultContact($addressBookId);
178+
$this->exampleContactService->createDefaultContact($addressBookId);
179179
}
180180
}
181181
}

apps/dav/lib/Service/DefaultContactService.php

Lines changed: 0 additions & 77 deletions
This file was deleted.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\DAV\Service;
11+
12+
use OCA\DAV\AppInfo\Application;
13+
use OCA\DAV\CardDAV\CardDavBackend;
14+
use OCP\AppFramework\Services\IAppConfig;
15+
use OCP\Files\AppData\IAppDataFactory;
16+
use OCP\Files\IAppData;
17+
use OCP\Files\NotFoundException;
18+
use Psr\Log\LoggerInterface;
19+
use Symfony\Component\Uid\Uuid;
20+
21+
class ExampleContactService {
22+
private readonly IAppData $appData;
23+
24+
public function __construct(
25+
IAppDataFactory $appDataFactory,
26+
private readonly IAppConfig $appConfig,
27+
private readonly LoggerInterface $logger,
28+
private readonly CardDavBackend $cardDav,
29+
) {
30+
$this->appData = $appDataFactory->get(Application::APP_ID);
31+
}
32+
33+
public function isDefaultContactEnabled(): bool {
34+
return $this->appConfig->getAppValueBool('enableDefaultContact', true);
35+
}
36+
37+
public function setDefaultContactEnabled(bool $value): void {
38+
$this->appConfig->setAppValueBool('enableDefaultContact', $value);
39+
}
40+
41+
public function getCard(): ?string {
42+
try {
43+
$folder = $this->appData->getFolder('defaultContact');
44+
} catch (NotFoundException $e) {
45+
return null;
46+
}
47+
48+
if (!$folder->fileExists('defaultContact.vcf')) {
49+
return null;
50+
}
51+
52+
return $folder->getFile('defaultContact.vcf')->getContent();
53+
}
54+
55+
public function setCard(?string $cardData = null) {
56+
try {
57+
$folder = $this->appData->getFolder('defaultContact');
58+
} catch (NotFoundException $e) {
59+
$folder = $this->appData->newFolder('defaultContact');
60+
}
61+
62+
$isCustom = true;
63+
if (is_null($cardData)) {
64+
$cardData = file_get_contents(__DIR__ . '/../ExampleContentFiles/exampleContact.vcf');
65+
$isCustom = false;
66+
}
67+
68+
if (!$cardData) {
69+
throw new \Exception('Could not read exampleContact.vcf');
70+
}
71+
72+
$file = (!$folder->fileExists('defaultContact.vcf')) ? $folder->newFile('defaultContact.vcf') : $folder->getFile('defaultContact.vcf');
73+
$file->putContent($cardData);
74+
75+
$this->appConfig->setAppValueBool('hasCustomDefaultContact', $isCustom);
76+
}
77+
78+
public function defaultContactExists(): bool {
79+
try {
80+
$folder = $this->appData->getFolder('defaultContact');
81+
} catch (NotFoundException $e) {
82+
return false;
83+
}
84+
return $folder->fileExists('defaultContact.vcf');
85+
}
86+
87+
public function createDefaultContact(int $addressBookId): void {
88+
if (!$this->isDefaultContactEnabled()) {
89+
return;
90+
}
91+
92+
try {
93+
$folder = $this->appData->getFolder('defaultContact');
94+
$defaultContactFile = $folder->getFile('defaultContact.vcf');
95+
$data = $defaultContactFile->getContent();
96+
} catch (\Exception $e) {
97+
$this->logger->error('Couldn\'t get default contact file', ['exception' => $e]);
98+
return;
99+
}
100+
101+
// Make sure the UID is unique
102+
$newUid = Uuid::v4()->toRfc4122();
103+
$newRev = date('Ymd\THis\Z');
104+
$vcard = \Sabre\VObject\Reader::read($data, \Sabre\VObject\Reader::OPTION_FORGIVING);
105+
if ($vcard->UID) {
106+
$vcard->UID->setValue($newUid);
107+
} else {
108+
$vcard->add('UID', $newUid);
109+
}
110+
if ($vcard->REV) {
111+
$vcard->REV->setValue($newRev);
112+
} else {
113+
$vcard->add('REV', $newRev);
114+
}
115+
116+
// Level 3 means that the document is invalid
117+
// https://sabre.io/vobject/vcard/#validating-vcard
118+
$level3Warnings = array_filter($vcard->validate(), static function ($warning) {
119+
return $warning['level'] === 3;
120+
});
121+
122+
if (!empty($level3Warnings)) {
123+
$this->logger->error('Default contact is invalid', ['warnings' => $level3Warnings]);
124+
return;
125+
}
126+
try {
127+
$this->cardDav->createCard($addressBookId, 'default', $vcard->serialize(), false);
128+
} catch (\Exception $e) {
129+
$this->logger->error($e->getMessage(), ['exception' => $e]);
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)