Passed
Push — master ( 58e4a8...07b9db )
by Blizzz
28:52 queued 14:18
created

IMipPlugin::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
c 0
b 0
f 0
nc 1
nop 9
dl 0
loc 19
rs 9.9332

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2017, Georg Ehrke
5
 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
6
 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
7
 * @copyright 2022 Anna Larch <[email protected]>
8
 *
9
 * @author brad2014 <[email protected]>
10
 * @author Brad Rubenstein <[email protected]>
11
 * @author Christoph Wurst <[email protected]>
12
 * @author Georg Ehrke <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author Leon Klingele <[email protected]>
15
 * @author Nick Sweeting <[email protected]>
16
 * @author rakekniven <[email protected]>
17
 * @author Roeland Jago Douma <[email protected]>
18
 * @author Thomas Citharel <[email protected]>
19
 * @author Thomas Müller <[email protected]>
20
 * @author Anna Larch <[email protected]>
21
 *
22
 * @license AGPL-3.0
23
 *
24
 * This code is free software: you can redistribute it and/or modify
25
 * it under the terms of the GNU Affero General Public License, version 3,
26
 * as published by the Free Software Foundation.
27
 *
28
 * This program is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31
 * GNU Affero General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU Affero General Public License, version 3,
34
 * along with this program. If not, see <http://www.gnu.org/licenses/>
35
 *
36
 */
37
namespace OCA\DAV\CalDAV\Schedule;
38
39
use OCA\DAV\CalDAV\CalendarObject;
40
use OCA\DAV\CalDAV\EventComparisonService;
41
use OCP\AppFramework\Utility\ITimeFactory;
42
use OCP\Defaults;
43
use OCP\IConfig;
44
use OCP\IDBConnection;
45
use OCP\IL10N;
46
use OCP\IURLGenerator;
47
use OCP\IUserManager;
48
use OCP\L10N\IFactory as L10NFactory;
49
use OCP\Mail\IEMailTemplate;
50
use OCP\Mail\IMailer;
51
use OCP\Security\ISecureRandom;
52
use OCP\Util;
53
use Psr\Log\LoggerInterface;
54
use Sabre\CalDAV\Schedule\IMipPlugin as SabreIMipPlugin;
55
use Sabre\DAV;
56
use Sabre\DAV\INode;
57
use Sabre\VObject\Component\VCalendar;
58
use Sabre\VObject\Component\VEvent;
59
use Sabre\VObject\Component\VTimeZone;
60
use Sabre\VObject\DateTimeParser;
61
use Sabre\VObject\ITip\Message;
62
use Sabre\VObject\Parameter;
63
use Sabre\VObject\Property;
64
use Sabre\VObject\Reader;
65
use Sabre\VObject\Recur\EventIterator;
66
67
/**
68
 * iMIP handler.
69
 *
70
 * This class is responsible for sending out iMIP messages. iMIP is the
71
 * email-based transport for iTIP. iTIP deals with scheduling operations for
72
 * iCalendar objects.
73
 *
74
 * If you want to customize the email that gets sent out, you can do so by
75
 * extending this class and overriding the sendMessage method.
76
 *
77
 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
78
 * @author Evert Pot (http://evertpot.com/)
79
 * @license http://sabre.io/license/ Modified BSD License
80
 */
81
class IMipPlugin extends SabreIMipPlugin {
82
	private ?string $userId;
83
	private IConfig $config;
84
	private IMailer $mailer;
85
	private LoggerInterface $logger;
86
	private ITimeFactory $timeFactory;
87
	private Defaults $defaults;
88
	private IUserManager $userManager;
89
	private ?VCalendar $vCalendar = null;
90
	private IMipService $imipService;
91
	public const MAX_DATE = '2038-01-01';
92
	public const METHOD_REQUEST = 'request';
93
	public const METHOD_REPLY = 'reply';
94
	public const METHOD_CANCEL = 'cancel';
95
	public const IMIP_INDENT = 15; // Enough for the length of all body bullet items, in all languages
96
	private EventComparisonService $eventComparisonService;
97
98
	public function __construct(IConfig $config,
99
								IMailer $mailer,
100
								LoggerInterface $logger,
101
								ITimeFactory $timeFactory,
102
								Defaults $defaults,
103
								IUserManager $userManager,
104
								$userId,
105
								IMipService $imipService,
106
								EventComparisonService $eventComparisonService) {
107
		parent::__construct('');
108
		$this->userId = $userId;
109
		$this->config = $config;
110
		$this->mailer = $mailer;
111
		$this->logger = $logger;
112
		$this->timeFactory = $timeFactory;
113
		$this->defaults = $defaults;
114
		$this->userManager = $userManager;
115
		$this->imipService = $imipService;
116
		$this->eventComparisonService = $eventComparisonService;
117
	}
118
119
	public function initialize(DAV\Server $server): void {
120
		parent::initialize($server);
121
		$server->on('beforeWriteContent', [$this, 'beforeWriteContent'], 10);
122
	}
123
124
	/**
125
	 * Check quota before writing content
126
	 *
127
	 * @param string $uri target file URI
128
	 * @param INode $node Sabre Node
129
	 * @param resource $data data
130
	 * @param bool $modified modified
131
	 */
132
	public function beforeWriteContent($uri, INode $node, $data, $modified): void {
0 ignored issues
show
Unused Code introduced by
The parameter $modified is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

132
	public function beforeWriteContent($uri, INode $node, $data, /** @scrutinizer ignore-unused */ $modified): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $data is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

132
	public function beforeWriteContent($uri, INode $node, /** @scrutinizer ignore-unused */ $data, $modified): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
133
		if(!$node instanceof CalendarObject) {
134
			return;
135
		}
136
		/** @var VCalendar $vCalendar */
137
		$vCalendar = Reader::read($node->get());
138
		$this->setVCalendar($vCalendar);
139
	}
140
141
	/**
142
	 * Event handler for the 'schedule' event.
143
	 *
144
	 * @param Message $iTipMessage
145
	 * @return void
146
	 */
147
	public function schedule(Message $iTipMessage) {
148
		// Not sending any emails if the system considers the update
149
		// insignificant.
150
		if (!$iTipMessage->significantChange) {
151
			if (!$iTipMessage->scheduleStatus) {
152
				$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
153
			}
154
			return;
155
		}
156
157
		if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto'
158
			|| parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') {
159
			return;
160
		}
161
162
		// don't send out mails for events that already took place
163
		$lastOccurrence = $this->imipService->getLastOccurrence($iTipMessage->message);
164
		$currentTime = $this->timeFactory->getTime();
165
		if ($lastOccurrence < $currentTime) {
166
			return;
167
		}
168
169
		// Strip off mailto:
170
		$recipient = substr($iTipMessage->recipient, 7);
171
		if (!$this->mailer->validateMailAddress($recipient)) {
172
			// Nothing to send if the recipient doesn't have a valid email address
173
			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
174
			return;
175
		}
176
		$recipientName = $iTipMessage->recipientName ?: null;
177
178
		$newEvents = $iTipMessage->message;
179
		$oldEvents = $this->getVCalendar();
180
181
		$modified = $this->eventComparisonService->findModified($newEvents, $oldEvents);
182
		/** @var VEvent $vEvent */
183
		$vEvent = array_pop($modified['new']);
184
		/** @var VEvent $oldVevent */
185
		$oldVevent = !empty($modified['old']) && is_array($modified['old']) ? array_pop($modified['old']) : null;
186
187
		// No changed events after all - this shouldn't happen if there is significant change yet here we are
188
		// The scheduling status is debatable
189
		if(empty($vEvent)) {
190
			$this->logger->warning('iTip message said the change was significant but comparison did not detect any updated VEvents');
191
			$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
192
			return;
193
		}
194
195
		// we (should) have one event component left
196
		// as the ITip\Broker creates one iTip message per change
197
		// and triggers the "schedule" event once per message
198
		// we also might not have an old event as this could be a new
199
		// invitation, or a new recurrence exception
200
		$attendee = $this->imipService->getCurrentAttendee($iTipMessage);
201
		$this->imipService->setL10n($attendee);
202
203
		// Build the sender name.
204
		// Due to a bug in sabre, the senderName property for an iTIP message
205
		// can actually also be a VObject Property
206
		/** @var Parameter|string|null $senderName */
207
		$senderName = $iTipMessage->senderName ?: null;
208
		if($senderName instanceof Parameter) {
209
			$senderName = $senderName->getValue() ?? null;
210
		}
211
212
		if ($senderName === null || empty(trim($senderName))) {
213
			$senderName = $this->userManager->getDisplayName($this->userId);
214
		}
215
		$sender = substr($iTipMessage->sender, 7);
216
217
		switch (strtolower($iTipMessage->method)) {
218
			case self::METHOD_REPLY:
219
				$method = self::METHOD_REPLY;
220
				$data = $this->imipService->buildBodyData($vEvent, $oldVevent);
221
				break;
222
			case self::METHOD_CANCEL:
223
				$method = self::METHOD_CANCEL;
224
				$data = $this->imipService->buildCancelledBodyData($vEvent);
225
				break;
226
			default:
227
				$method = self::METHOD_REQUEST;
228
				$data = $this->imipService->buildBodyData($vEvent, $oldVevent);
229
				break;
230
		}
231
232
233
		$data['attendee_name'] = ($recipientName ?: $recipient);
234
		$data['invitee_name'] = ($senderName ?: $sender);
235
236
		$fromEMail = Util::getDefaultEmailAddress('invitations-noreply');
237
		$fromName = $this->imipService->getFrom($senderName, $this->defaults->getName());
238
239
		$message = $this->mailer->createMessage()
240
			->setFrom([$fromEMail => $fromName])
241
			->setTo([$recipient => $recipientName])
242
			->setReplyTo([$sender => $senderName]);
243
244
		$template = $this->mailer->createEMailTemplate('dav.calendarInvite.' . $method, $data);
245
		$template->addHeader();
246
247
		$this->imipService->addSubjectAndHeading($template, $method, $data['invitee_name'], $data['meeting_title']);
248
		$this->imipService->addBulletList($template, $vEvent, $data);
249
250
		// Only add response buttons to invitation requests: Fix Issue #11230
251
		if (strcasecmp($method, self::METHOD_REQUEST) === 0 && $this->imipService->getAttendeeRsvpOrReqForParticipant($attendee)) {
252
253
			/*
254
			** Only offer invitation accept/reject buttons, which link back to the
255
			** nextcloud server, to recipients who can access the nextcloud server via
256
			** their internet/intranet.  Issue #12156
257
			**
258
			** The app setting is stored in the appconfig database table.
259
			**
260
			** For nextcloud servers accessible to the public internet, the default
261
			** "invitation_link_recipients" value "yes" (all recipients) is appropriate.
262
			**
263
			** When the nextcloud server is restricted behind a firewall, accessible
264
			** only via an internal network or via vpn, you can set "dav.invitation_link_recipients"
265
			** to the email address or email domain, or comma separated list of addresses or domains,
266
			** of recipients who can access the server.
267
			**
268
			** To always deliver URLs, set invitation_link_recipients to "yes".
269
			** To suppress URLs entirely, set invitation_link_recipients to boolean "no".
270
			*/
271
272
			$recipientDomain = substr(strrchr($recipient, '@'), 1);
273
			$invitationLinkRecipients = explode(',', preg_replace('/\s+/', '', strtolower($this->config->getAppValue('dav', 'invitation_link_recipients', 'yes'))));
274
275
			if (strcmp('yes', $invitationLinkRecipients[0]) === 0
276
				|| in_array(strtolower($recipient), $invitationLinkRecipients)
277
				|| in_array(strtolower($recipientDomain), $invitationLinkRecipients)) {
278
				$token = $this->imipService->createInvitationToken($iTipMessage, $vEvent, $lastOccurrence);
279
				$this->imipService->addResponseButtons($template, $token);
280
				$this->imipService->addMoreOptionsButton($template, $token);
281
			}
282
		}
283
284
		$template->addFooter();
285
286
		$message->useTemplate($template);
287
288
		$vCalendar = $this->imipService->generateVCalendar($iTipMessage, $vEvent);
289
290
		$attachment = $this->mailer->createAttachment(
291
			$vCalendar->serialize(),
292
			'event.ics',
293
			'text/calendar; method=' . $iTipMessage->method
294
		);
295
		$message->attach($attachment);
296
297
		try {
298
			$failed = $this->mailer->send($message);
299
			$iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
300
			if (!empty($failed)) {
301
				$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]);
302
				$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
303
			}
304
		} catch (\Exception $ex) {
305
			$this->logger->error($ex->getMessage(), ['app' => 'dav', 'exception' => $ex]);
306
			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
307
		}
308
	}
309
310
	/**
311
	 * @return ?VCalendar
312
	 */
313
	public function getVCalendar(): ?VCalendar {
314
		return $this->vCalendar;
315
	}
316
317
	/**
318
	 * @param ?VCalendar $vCalendar
319
	 */
320
	public function setVCalendar(?VCalendar $vCalendar): void {
321
		$this->vCalendar = $vCalendar;
322
	}
323
324
}
325