Passed
Push — master ( 9c88e3...9c5c86 )
by Christoph
11:54 queued 12s
created

CalendarContactInteractionListener::handle()   C

Complexity

Conditions 14
Paths 29

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 14
eloc 21
c 2
b 0
f 0
nc 29
nop 1
dl 0
loc 41
rs 6.2666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * @copyright 2021 Christoph Wurst <[email protected]>
7
 *
8
 * @author 2021 Christoph Wurst <[email protected]>
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\Listener;
27
28
use OCA\DAV\Connector\Sabre\Principal;
29
use OCA\DAV\Events\CalendarObjectCreatedEvent;
30
use OCA\DAV\Events\CalendarObjectUpdatedEvent;
31
use OCA\DAV\Events\CalendarShareUpdatedEvent;
32
use OCP\Contacts\Events\ContactInteractedWithEvent;
33
use OCP\EventDispatcher\Event;
34
use OCP\EventDispatcher\IEventDispatcher;
35
use OCP\EventDispatcher\IEventListener;
36
use OCP\IUser;
37
use OCP\IUserSession;
38
use OCP\Mail\IMailer;
39
use Psr\Log\LoggerInterface;
40
use Sabre\VObject\Component\VEvent;
41
use Sabre\VObject\Parameter;
42
use Sabre\VObject\Property;
43
use Sabre\VObject\Reader;
44
use Throwable;
45
use function strlen;
46
use function strpos;
47
use function substr;
48
49
class CalendarContactInteractionListener implements IEventListener {
50
	private const URI_USERS = 'principals/users/';
51
52
	/** @var IEventDispatcher */
53
	private $dispatcher;
54
55
	/** @var IUserSession */
56
	private $userSession;
57
58
	/** @var Principal */
59
	private $principalConnector;
60
61
	/** @var IMailer */
62
	private $mailer;
63
64
	/** @var LoggerInterface */
65
	private $logger;
66
67
	public function __construct(IEventDispatcher $dispatcher,
68
								IUserSession $userSession,
69
								Principal $principalConnector,
70
								IMailer $mailer,
71
								LoggerInterface $logger) {
72
		$this->dispatcher = $dispatcher;
73
		$this->userSession = $userSession;
74
		$this->principalConnector = $principalConnector;
75
		$this->mailer = $mailer;
76
		$this->logger = $logger;
77
	}
78
79
	public function handle(Event $event): void {
80
		if (($user = $this->userSession->getUser()) === null) {
81
			// Without user context we can't do anything
82
			return;
83
		}
84
85
		if ($event instanceof CalendarObjectCreatedEvent || $event instanceof CalendarObjectUpdatedEvent) {
86
			// users: href => principal:principals/users/admin
87
			foreach ($event->getShares() as $share) {
88
				if (!isset($share['href'])) {
89
					continue;
90
				}
91
				$this->emitFromUri($share['href'], $user);
92
			}
93
94
			// emit interaction for email attendees as well
95
			if (isset($event->getObjectData()['calendardata'])) {
96
				try {
97
					$calendar = Reader::read($event->getObjectData()['calendardata']);
98
					if ($calendar->VEVENT) {
99
						foreach ($calendar->VEVENT as $calendarEvent) {
100
							$this->emitFromObject($calendarEvent, $user);
101
						}
102
					}
103
				} catch (Throwable $e) {
104
					$this->logger->warning('Could not read calendar data for interaction events: ' . $e->getMessage(), [
105
						'exception' => $e,
106
					]);
107
				}
108
			}
109
		}
110
111
		if ($event instanceof CalendarShareUpdatedEvent && !empty($event->getAdded())) {
112
			// group: href => principal:principals/groups/admin
113
			// users: href => principal:principals/users/admin
114
			foreach ($event->getAdded() as $added) {
115
				if (!isset($added['href'])) {
116
					// Nothing to work with
117
					continue;
118
				}
119
				$this->emitFromUri($added['href'], $user);
120
			}
121
		}
122
	}
123
124
	private function emitFromUri(string $uri, IUser $user): void {
125
		$principal = $this->principalConnector->findByUri(
126
			$uri,
127
			$this->principalConnector->getPrincipalPrefix()
128
		);
129
		if ($principal === null) {
0 ignored issues
show
introduced by
The condition $principal === null is always false.
Loading history...
130
			// Invalid principal
131
			return;
132
		}
133
		if (strpos($principal, self::URI_USERS) !== 0) {
134
			// Not a user principal
135
			return;
136
		}
137
138
		$uid = substr($principal, strlen(self::URI_USERS));
139
		$this->dispatcher->dispatchTyped(
140
			(new ContactInteractedWithEvent($user))->setUid($uid)
141
		);
142
	}
143
144
	private function emitFromObject(VEvent $vevent, IUser $user): void {
145
		if (!$vevent->ATTENDEE) {
146
			// Nothing left to do
147
			return;
148
		}
149
150
		foreach ($vevent->ATTENDEE as $attendee) {
151
			if (!($attendee instanceof Property)) {
152
				continue;
153
			}
154
155
			$cuType = $attendee->offsetGet('CUTYPE');
156
			if ($cuType instanceof Parameter && $cuType->getValue() !== 'INDIVIDUAL') {
157
				// Don't care about those
158
				continue;
159
			}
160
161
			$mailTo = $attendee->getValue();
162
			if (strpos($mailTo, 'mailto:') !== 0) {
163
				// Doesn't look like an email
164
				continue;
165
			}
166
			$email = substr($mailTo, strlen('mailto:'));
167
			if (!$this->mailer->validateMailAddress($email)) {
168
				// This really isn't a valid email
169
				continue;
170
			}
171
172
			$this->dispatcher->dispatchTyped(
173
				(new ContactInteractedWithEvent($user))->setEmail($email)
174
			);
175
		}
176
	}
177
}
178