Skip to content

Commit c43c995

Browse files
committed
feat(contactsinteraction): allow users to disable contacts interaction addressbook
This allows simple users to opt-out of the contacts interaction addressbook even if admins have the app installed. Similar to how the birthday calendar works, the functionnality can be toggled in the user's settings or by doing a DELETE on the addressbook. A new contacts personal section has been added to contain this new setting. Signed-off-by: Thomas Citharel <tcit@tcit.fr>
1 parent c3c58b6 commit c43c995

22 files changed

Lines changed: 600 additions & 29 deletions

apps/contactsinteraction/appinfo/info.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,11 @@
2626
<address-book-plugins>
2727
<plugin>OCA\ContactsInteraction\AddressBookProvider</plugin>
2828
</address-book-plugins>
29+
<plugins>
30+
<plugin>OCA\ContactsInteraction\DAV\Plugin</plugin>
31+
</plugins>
2932
</sabre>
33+
<settings>
34+
<personal>OCA\ContactsInteraction\Settings\Personal</personal>
35+
</settings>
3036
</info>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
return [
27+
'ocs' => [
28+
['name' => 'config#disable', 'url' => '/config/user/disable', 'verb' => 'POST'],
29+
],
30+
];

apps/contactsinteraction/composer/composer/autoload_classmap.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
'OCA\\ContactsInteraction\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
1313
'OCA\\ContactsInteraction\\BackgroundJob\\CleanupJob' => $baseDir . '/../lib/BackgroundJob/CleanupJob.php',
1414
'OCA\\ContactsInteraction\\Card' => $baseDir . '/../lib/Card.php',
15+
'OCA\\ContactsInteraction\\Controller\\ConfigController' => $baseDir . '/../lib/Controller/ConfigController.php',
16+
'OCA\\ContactsInteraction\\DAV\\Plugin' => $baseDir . '/../lib/DAV/Plugin.php',
1517
'OCA\\ContactsInteraction\\Db\\CardSearchDao' => $baseDir . '/../lib/Db/CardSearchDao.php',
1618
'OCA\\ContactsInteraction\\Db\\RecentContact' => $baseDir . '/../lib/Db/RecentContact.php',
1719
'OCA\\ContactsInteraction\\Db\\RecentContactMapper' => $baseDir . '/../lib/Db/RecentContactMapper.php',
1820
'OCA\\ContactsInteraction\\Listeners\\ContactInteractionListener' => $baseDir . '/../lib/Listeners/ContactInteractionListener.php',
1921
'OCA\\ContactsInteraction\\Migration\\Version010000Date20200304152605' => $baseDir . '/../lib/Migration/Version010000Date20200304152605.php',
22+
'OCA\\ContactsInteraction\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',
2023
);

apps/contactsinteraction/composer/composer/autoload_static.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ class ComposerStaticInitContactsInteraction
2727
'OCA\\ContactsInteraction\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
2828
'OCA\\ContactsInteraction\\BackgroundJob\\CleanupJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupJob.php',
2929
'OCA\\ContactsInteraction\\Card' => __DIR__ . '/..' . '/../lib/Card.php',
30+
'OCA\\ContactsInteraction\\Controller\\ConfigController' => __DIR__ . '/..' . '/../lib/Controller/ConfigController.php',
31+
'OCA\\ContactsInteraction\\DAV\\Plugin' => __DIR__ . '/..' . '/../lib/DAV/Plugin.php',
3032
'OCA\\ContactsInteraction\\Db\\CardSearchDao' => __DIR__ . '/..' . '/../lib/Db/CardSearchDao.php',
3133
'OCA\\ContactsInteraction\\Db\\RecentContact' => __DIR__ . '/..' . '/../lib/Db/RecentContact.php',
3234
'OCA\\ContactsInteraction\\Db\\RecentContactMapper' => __DIR__ . '/..' . '/../lib/Db/RecentContactMapper.php',
3335
'OCA\\ContactsInteraction\\Listeners\\ContactInteractionListener' => __DIR__ . '/..' . '/../lib/Listeners/ContactInteractionListener.php',
3436
'OCA\\ContactsInteraction\\Migration\\Version010000Date20200304152605' => __DIR__ . '/..' . '/../lib/Migration/Version010000Date20200304152605.php',
37+
'OCA\\ContactsInteraction\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
3538
);
3639

3740
public static function getInitializer(ClassLoader $loader)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
* @copyright Copyright (c) 2023 Thomas Citharel <nextcloud@tcit.fr>
4+
*
5+
* @author Thomas Citharel <nextcloud@tcit.fr>
6+
*
7+
* @license GNU AGPL version 3 or any later version
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License as
11+
* published by the Free Software Foundation, either version 3 of the
12+
* License, or (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Affero General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Affero General Public License
20+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
*
22+
*/
23+
namespace OCA\ContactsInteraction\Controller;
24+
25+
use OCA\ContactsInteraction\Db\RecentContactMapper;
26+
use OCP\AppFramework\Controller;
27+
use OCP\AppFramework\Http;
28+
use OCP\AppFramework\Http\DataResponse;
29+
use OCP\AppFramework\Http\Response;
30+
use OCP\IConfig;
31+
use OCP\IRequest;
32+
33+
class ConfigController extends Controller {
34+
35+
public function __construct(string $appName, IRequest $request, private IConfig $config, private RecentContactMapper $recentContactMapper, private ?string $userId) {
36+
parent::__construct($appName, $request);
37+
}
38+
39+
/**
40+
* @NoAdminRequired
41+
*/
42+
public function disable(): Response {
43+
if (!$this->userId) {
44+
return new DataResponse([], Http::STATUS_UNAUTHORIZED);
45+
}
46+
$this->config->setUserValue($this->userId, $this->appName, 'generateContactsInteraction', 'no');
47+
48+
$this->recentContactMapper->cleanForUser($this->userId);
49+
50+
return new DataResponse([]);
51+
}
52+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
/**
3+
* @copyright 2023, Thomas Citharel <nextcloud@tcit.fr>
4+
*
5+
* @author Thomas Citharel <nextcloud@tcit.fr>
6+
*
7+
* @license GNU AGPL version 3 or any later version
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License as
11+
* published by the Free Software Foundation, either version 3 of the
12+
* License, or (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Affero General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Affero General Public License
20+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
*
22+
*/
23+
namespace OCA\ContactsInteraction\DAV;
24+
25+
use OCA\ContactsInteraction\AddressBook;
26+
use OCA\ContactsInteraction\AppInfo\Application;
27+
use OCP\IConfig;
28+
use Sabre\DAV\Server;
29+
use Sabre\DAV\ServerPlugin;
30+
use Sabre\HTTP\RequestInterface;
31+
use Sabre\HTTP\ResponseInterface;
32+
33+
/**
34+
* Allows users to disable the feature by deleting the addressbook
35+
*
36+
* @package OCA\DAV\CalDAV\BirthdayCalendar
37+
*/
38+
class Plugin extends ServerPlugin {
39+
40+
protected IConfig $config;
41+
protected Server $server;
42+
43+
public function __construct(IConfig $config) {
44+
$this->config = $config;
45+
}
46+
47+
/**
48+
* This method should return a list of server-features.
49+
*
50+
* This is for example 'versioning' and is added to the DAV: header
51+
* in an OPTIONS response.
52+
*
53+
* @return string[]
54+
*/
55+
public function getFeatures() {
56+
return ['nc-disable-recently-contacted'];
57+
}
58+
59+
/**
60+
* Returns a plugin name.
61+
*
62+
* Using this name other plugins will be able to access other plugins
63+
* using Sabre\DAV\Server::getPlugin
64+
*
65+
* @return string
66+
*/
67+
public function getPluginName() {
68+
return 'nc-disable-recently-contacted';
69+
}
70+
71+
/**
72+
* This initializes the plugin.
73+
*
74+
* This function is called by Sabre\DAV\Server, after
75+
* addPlugin is called.
76+
*
77+
* This method should set up the required event subscriptions.
78+
*
79+
* @param Server $server
80+
*/
81+
public function initialize(Server $server) {
82+
$this->server = $server;
83+
84+
$this->server->on('method:DELETE', [$this, 'httpDelete']);
85+
}
86+
87+
/**
88+
* We intercept this to handle POST requests on calendar homes.
89+
*
90+
* @param RequestInterface $request
91+
* @param ResponseInterface $response
92+
*
93+
* @return bool|void
94+
*/
95+
public function httpDelete(RequestInterface $request, ResponseInterface $response) {
96+
$node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
97+
if (!($node instanceof AddressBook)) {
98+
return;
99+
}
100+
101+
$principalUri = $node->getOwner();
102+
$userId = substr($principalUri, 17);
103+
104+
$this->config->setUserValue($userId, Application::APP_ID, 'generateContactsInteraction', 'no');
105+
106+
$this->server->httpResponse->setStatus(204);
107+
108+
return false;
109+
}
110+
}

apps/contactsinteraction/lib/Db/RecentContactMapper.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,14 @@ public function cleanUp(int $olderThan): void {
128128

129129
$delete->executeStatement();
130130
}
131+
132+
public function cleanForUser(string $userId): void {
133+
$qb = $this->db->getQueryBuilder();
134+
135+
$delete = $qb
136+
->delete($this->getTableName())
137+
->where($qb->expr()->eq('actor_uid', $qb->createNamedParameter($userId)));
138+
139+
$delete->executeStatement();
140+
}
131141
}

apps/contactsinteraction/lib/Listeners/ContactInteractionListener.php

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
*/
2626
namespace OCA\ContactsInteraction\Listeners;
2727

28+
use OCA\ContactsInteraction\AppInfo\Application;
2829
use OCA\ContactsInteraction\Db\CardSearchDao;
2930
use OCA\ContactsInteraction\Db\RecentContact;
3031
use OCA\ContactsInteraction\Db\RecentContactMapper;
@@ -33,6 +34,7 @@
3334
use OCP\Contacts\Events\ContactInteractedWithEvent;
3435
use OCP\EventDispatcher\Event;
3536
use OCP\EventDispatcher\IEventListener;
37+
use OCP\IConfig;
3638
use OCP\IDBConnection;
3739
use OCP\IL10N;
3840
use OCP\IUserManager;
@@ -46,28 +48,14 @@ class ContactInteractionListener implements IEventListener {
4648

4749
use TTransactional;
4850

49-
private RecentContactMapper $mapper;
50-
private CardSearchDao $cardSearchDao;
51-
private IUserManager $userManager;
52-
private IDBConnection $dbConnection;
53-
private ITimeFactory $timeFactory;
54-
private IL10N $l10n;
55-
private LoggerInterface $logger;
56-
57-
public function __construct(RecentContactMapper $mapper,
58-
CardSearchDao $cardSearchDao,
59-
IUserManager $userManager,
60-
IDBConnection $connection,
61-
ITimeFactory $timeFactory,
62-
IL10N $l10nFactory,
63-
LoggerInterface $logger) {
64-
$this->mapper = $mapper;
65-
$this->cardSearchDao = $cardSearchDao;
66-
$this->userManager = $userManager;
67-
$this->dbConnection = $connection;
68-
$this->timeFactory = $timeFactory;
69-
$this->l10n = $l10nFactory;
70-
$this->logger = $logger;
51+
public function __construct(private RecentContactMapper $mapper,
52+
private CardSearchDao $cardSearchDao,
53+
private IUserManager $userManager,
54+
private IDBConnection $connection,
55+
private ITimeFactory $timeFactory,
56+
private IL10N $l10n,
57+
private IConfig $config,
58+
private LoggerInterface $logger) {
7159
}
7260

7361
public function handle(Event $event): void {
@@ -85,6 +73,11 @@ public function handle(Event $event): void {
8573
return;
8674
}
8775

76+
if ($this->config->getUserValue($event->getActor()->getUID(), Application::APP_ID, 'generateContactsInteraction', 'yes') === 'no') {
77+
$this->logger->debug("Ignoring contact interaction as it's disabled for this user");
78+
return;
79+
}
80+
8881
$this->atomic(function () use ($event) {
8982
$uid = $event->getUid();
9083
$email = $event->getEmail();
@@ -96,9 +89,9 @@ public function handle(Event $event): void {
9689
$federatedCloudId
9790
);
9891
if (!empty($existing)) {
99-
$now = $this->timeFactory->getTime();
92+
$now = $this->timeFactory->now();
10093
foreach ($existing as $c) {
101-
$c->setLastContact($now);
94+
$c->setLastContact($now->getTimestamp());
10295
$this->mapper->update($c);
10396
}
10497

@@ -116,7 +109,7 @@ public function handle(Event $event): void {
116109
if ($federatedCloudId !== null) {
117110
$contact->setFederatedCloudId($federatedCloudId);
118111
}
119-
$contact->setLastContact($this->timeFactory->getTime());
112+
$contact->setLastContact($this->timeFactory->now()->getTimestamp());
120113

121114
$copy = $this->cardSearchDao->findExisting(
122115
$event->getActor(),
@@ -141,7 +134,7 @@ public function handle(Event $event): void {
141134
$contact->setCard($this->generateCard($contact));
142135
}
143136
$this->mapper->insert($contact);
144-
}, $this->dbConnection);
137+
}, $this->connection);
145138
}
146139

147140
private function getDisplayName(?string $uid): ?string {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
* @copyright Copyright (c) 2023 Thomas Citharel <nextcloud@tcit.fr>
4+
*
5+
* @author Thomas Citharel <nextcloud@tcit.fr>
6+
*
7+
* @license GNU AGPL version 3 or any later version
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License as
11+
* published by the Free Software Foundation, either version 3 of the
12+
* License, or (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Affero General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Affero General Public License
20+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
*
22+
*/
23+
24+
namespace OCA\ContactsInteraction\Settings;
25+
26+
use OCA\ContactsInteraction\AppInfo\Application;
27+
use OCP\AppFramework\Http\TemplateResponse;
28+
use OCP\AppFramework\Services\IInitialState;
29+
use OCP\IConfig;
30+
use OCP\Settings\ISettings;
31+
use OCP\Util;
32+
33+
class Personal implements ISettings {
34+
public function __construct(private IInitialState $initialState, private IConfig $config, private ?string $userId) { }
35+
36+
public function getForm(): TemplateResponse {
37+
$this->initialState->provideInitialState('generateContactsInteraction', $this->config->getUserValue($this->userId, Application::APP_ID, 'generateContactsInteraction', 'yes') === 'yes');
38+
Util::addScript(Application::APP_ID, 'settings-personal');
39+
return new TemplateResponse(Application::APP_ID, 'personal');
40+
}
41+
42+
public function getSection(): string {
43+
return 'contacts';
44+
}
45+
46+
/**
47+
* @psalm-return 40
48+
*/
49+
public function getPriority(): int {
50+
return 40;
51+
}
52+
}

0 commit comments

Comments
 (0)