Passed
Push — master ( fdcc65...954290 )
by Roeland
09:42 queued 10s
created

IMipPlugin::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 10
dl 0
loc 15
rs 9.9
c 0
b 0
f 0

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
 *
6
 * @author Georg Ehrke <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Leon Klingele <[email protected]>
9
 * @author Thomas Müller <[email protected]>
10
 *
11
 * @license AGPL-3.0
12
 *
13
 * This code is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License, version 3,
15
 * as published by the Free Software Foundation.
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, version 3,
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
24
 *
25
 */
26
namespace OCA\DAV\CalDAV\Schedule;
27
28
use OCP\AppFramework\Utility\ITimeFactory;
29
use OCP\Defaults;
30
use OCP\IConfig;
31
use OCP\IDBConnection;
32
use OCP\IL10N;
33
use OCP\ILogger;
34
use OCP\IURLGenerator;
35
use OCP\L10N\IFactory as L10NFactory;
36
use OCP\Mail\IEMailTemplate;
37
use OCP\Mail\IMailer;
38
use OCP\Security\ISecureRandom;
39
use Sabre\CalDAV\Schedule\IMipPlugin as SabreIMipPlugin;
40
use Sabre\VObject\Component\VCalendar;
41
use Sabre\VObject\Component\VEvent;
42
use Sabre\VObject\DateTimeParser;
43
use Sabre\VObject\ITip\Message;
44
use Sabre\VObject\Parameter;
45
use Sabre\VObject\Property;
46
use Sabre\VObject\Recur\EventIterator;
47
/**
48
 * iMIP handler.
49
 *
50
 * This class is responsible for sending out iMIP messages. iMIP is the
51
 * email-based transport for iTIP. iTIP deals with scheduling operations for
52
 * iCalendar objects.
53
 *
54
 * If you want to customize the email that gets sent out, you can do so by
55
 * extending this class and overriding the sendMessage method.
56
 *
57
 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
58
 * @author Evert Pot (http://evertpot.com/)
59
 * @license http://sabre.io/license/ Modified BSD License
60
 */
61
class IMipPlugin extends SabreIMipPlugin {
62
63
	/** @var string */
64
	private $userId;
65
66
	/** @var IConfig */
67
	private $config;
68
69
	/** @var IMailer */
70
	private $mailer;
71
72
	/** @var ILogger */
73
	private $logger;
74
75
	/** @var ITimeFactory */
76
	private $timeFactory;
77
78
	/** @var L10NFactory */
79
	private $l10nFactory;
80
81
	/** @var IURLGenerator */
82
	private $urlGenerator;
83
84
	/** @var ISecureRandom */
85
	private $random;
86
87
	/** @var IDBConnection */
88
	private $db;
89
90
	/** @var Defaults */
91
	private $defaults;
92
93
	const MAX_DATE = '2038-01-01';
94
95
	const METHOD_REQUEST = 'request';
96
	const METHOD_REPLY = 'reply';
97
	const METHOD_CANCEL = 'cancel';
98
99
	/**
100
	 * @param IConfig $config
101
	 * @param IMailer $mailer
102
	 * @param ILogger $logger
103
	 * @param ITimeFactory $timeFactory
104
	 * @param L10NFactory $l10nFactory
105
	 * @param IUrlGenerator $urlGenerator
106
	 * @param Defaults $defaults
107
	 * @param ISecureRandom $random
108
	 * @param IDBConnection $db
109
	 * @param string $userId
110
	 */
111
	public function __construct(IConfig $config, IMailer $mailer, ILogger $logger,
112
								ITimeFactory $timeFactory, L10NFactory $l10nFactory,
113
								IURLGenerator $urlGenerator, Defaults $defaults,
114
								ISecureRandom $random, IDBConnection $db, $userId) {
115
		parent::__construct('');
116
		$this->userId = $userId;
117
		$this->config = $config;
118
		$this->mailer = $mailer;
119
		$this->logger = $logger;
120
		$this->timeFactory = $timeFactory;
121
		$this->l10nFactory = $l10nFactory;
122
		$this->urlGenerator = $urlGenerator;
123
		$this->random = $random;
124
		$this->db = $db;
125
		$this->defaults = $defaults;
126
	}
127
128
	/**
129
	 * Event handler for the 'schedule' event.
130
	 *
131
	 * @param Message $iTipMessage
132
	 * @return void
133
	 */
134
	public function schedule(Message $iTipMessage) {
135
136
		// Not sending any emails if the system considers the update
137
		// insignificant.
138
		if (!$iTipMessage->significantChange) {
139
			if (!$iTipMessage->scheduleStatus) {
140
				$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
141
			}
142
			return;
143
		}
144
145
		$summary = $iTipMessage->message->VEVENT->SUMMARY;
0 ignored issues
show
Bug introduced by
The property SUMMARY does not seem to exist on Sabre\VObject\Property.
Loading history...
146
147
		if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') {
148
			return;
149
		}
150
151
		if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') {
152
			return;
153
		}
154
155
		// don't send out mails for events that already took place
156
		$lastOccurrence = $this->getLastOccurrence($iTipMessage->message);
157
		$currentTime = $this->timeFactory->getTime();
158
		if ($lastOccurrence < $currentTime) {
159
			return;
160
		}
161
162
		// Strip off mailto:
163
		$sender = substr($iTipMessage->sender, 7);
164
		$recipient = substr($iTipMessage->recipient, 7);
165
166
		$senderName = $iTipMessage->senderName ?: null;
167
		$recipientName = $iTipMessage->recipientName ?: null;
168
169
		/** @var VEvent $vevent */
170
		$vevent = $iTipMessage->message->VEVENT;
171
172
		$attendee = $this->getCurrentAttendee($iTipMessage);
173
		$defaultLang = $this->l10nFactory->findLanguage();
174
		$lang = $this->getAttendeeLangOrDefault($defaultLang, $attendee);
175
		$l10n = $this->l10nFactory->get('dav', $lang);
176
177
		$meetingAttendeeName = $recipientName ?: $recipient;
178
		$meetingInviteeName = $senderName ?: $sender;
179
180
		$meetingTitle = $vevent->SUMMARY;
181
		$meetingDescription = $vevent->DESCRIPTION;
182
183
		$start = $vevent->DTSTART;
184
		if (isset($vevent->DTEND)) {
185
			$end = $vevent->DTEND;
186
		} elseif (isset($vevent->DURATION)) {
187
			$isFloating = $vevent->DTSTART->isFloating();
0 ignored issues
show
Bug introduced by
The method isFloating() does not exist on Sabre\VObject\Property. It seems like you code against a sub-type of Sabre\VObject\Property such as Sabre\VObject\Property\ICalendar\DateTime. ( Ignorable by Annotation )

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

187
			/** @scrutinizer ignore-call */ 
188
   $isFloating = $vevent->DTSTART->isFloating();
Loading history...
188
			$end = clone $vevent->DTSTART;
189
			$endDateTime = $end->getDateTime();
0 ignored issues
show
Bug introduced by
The method getDateTime() does not exist on Sabre\VObject\Property. It seems like you code against a sub-type of Sabre\VObject\Property such as Sabre\VObject\Property\ICalendar\DateTime or Sabre\VObject\Property\VCard\DateAndOrTime. ( Ignorable by Annotation )

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

189
			/** @scrutinizer ignore-call */ 
190
   $endDateTime = $end->getDateTime();
Loading history...
190
			$endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue()));
191
			$end->setDateTime($endDateTime, $isFloating);
0 ignored issues
show
Bug introduced by
The method setDateTime() does not exist on Sabre\VObject\Property. It seems like you code against a sub-type of Sabre\VObject\Property such as Sabre\VObject\Property\ICalendar\DateTime or Sabre\VObject\Property\VCard\DateAndOrTime. ( Ignorable by Annotation )

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

191
			$end->/** @scrutinizer ignore-call */ 
192
         setDateTime($endDateTime, $isFloating);
Loading history...
192
		} elseif (!$vevent->DTSTART->hasTime()) {
0 ignored issues
show
Bug introduced by
The method hasTime() does not exist on Sabre\VObject\Property. It seems like you code against a sub-type of Sabre\VObject\Property such as Sabre\VObject\Property\ICalendar\DateTime. ( Ignorable by Annotation )

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

192
		} elseif (!$vevent->DTSTART->/** @scrutinizer ignore-call */ hasTime()) {
Loading history...
193
			$isFloating = $vevent->DTSTART->isFloating();
194
			$end = clone $vevent->DTSTART;
195
			$endDateTime = $end->getDateTime();
196
			$endDateTime = $endDateTime->modify('+1 day');
197
			$end->setDateTime($endDateTime, $isFloating);
198
		} else {
199
			$end = clone $vevent->DTSTART;
200
		}
201
202
		$meetingWhen = $this->generateWhenString($l10n, $start, $end);
203
204
		$meetingUrl = $vevent->URL;
205
		$meetingLocation = $vevent->LOCATION;
206
207
		$defaultVal = '--';
208
209
		$method = self::METHOD_REQUEST;
210
		switch (strtolower($iTipMessage->method)) {
211
			case self::METHOD_REPLY:
212
				$method = self::METHOD_REPLY;
213
				break;
214
			case self::METHOD_CANCEL:
215
				$method = self::METHOD_CANCEL;
216
				break;
217
		}
218
219
		$data = array(
220
			'attendee_name' => (string)$meetingAttendeeName ?: $defaultVal,
221
			'invitee_name' => (string)$meetingInviteeName ?: $defaultVal,
222
			'meeting_title' => (string)$meetingTitle ?: $defaultVal,
223
			'meeting_description' => (string)$meetingDescription ?: $defaultVal,
224
			'meeting_url' => (string)$meetingUrl ?: $defaultVal,
225
		);
226
227
		$fromEMail = \OCP\Util::getDefaultEmailAddress('invitations-noreply');
228
		$fromName = $l10n->t('%1$s via %2$s', [$senderName, $this->defaults->getName()]);
229
230
		$message = $this->mailer->createMessage()
231
			->setFrom([$fromEMail => $fromName])
232
			->setReplyTo([$sender => $senderName])
233
			->setTo([$recipient => $recipientName]);
234
235
		$template = $this->mailer->createEMailTemplate('dav.calendarInvite.' . $method, $data);
236
		$template->addHeader();
237
238
		$this->addSubjectAndHeading($template, $l10n, $method, $summary,
239
			$meetingAttendeeName, $meetingInviteeName);
240
		$this->addBulletList($template, $l10n, $meetingWhen, $meetingLocation,
241
			$meetingDescription, $meetingUrl);
242
243
244
		// Only add response buttons to invitation requests: Fix Issue #11230
245
		if (($method == self::METHOD_REQUEST) && $this->getAttendeeRSVP($attendee)) {
246
247
			/*
248
			** Only offer invitation accept/reject buttons, which link back to the
249
			** nextcloud server, to recipients who can access the nextcloud server via
250
			** their internet/intranet.  Issue #12156
251
			**
252
			** The app setting is stored in the appconfig database table.
253
			**
254
			** For nextcloud servers accessible to the public internet, the default
255
			** "invitation_link_recipients" value "yes" (all recipients) is appropriate.
256
			**
257
			** When the nextcloud server is restricted behind a firewall, accessible
258
			** only via an internal network or via vpn, you can set "dav.invitation_link_recipients"
259
			** to the email address or email domain, or comma separated list of addresses or domains,
260
			** of recipients who can access the server.
261
			**
262
			** To always deliver URLs, set invitation_link_recipients to "yes".
263
			** To suppress URLs entirely, set invitation_link_recipients to boolean "no".
264
			*/
265
266
			$recipientDomain = substr(strrchr($recipient, "@"), 1);
267
			$invitationLinkRecipients = explode(',', preg_replace('/\s+/', '', strtolower($this->config->getAppValue('dav', 'invitation_link_recipients', 'yes'))));
268
269
			if (strcmp('yes', $invitationLinkRecipients[0]) === 0
270
				 || in_array(strtolower($recipient), $invitationLinkRecipients)
271
				 || in_array(strtolower($recipientDomain), $invitationLinkRecipients)) {
272
				$this->addResponseButtons($template, $l10n, $iTipMessage, $lastOccurrence);
273
			}
274
		}
275
276
		$template->addFooter();
277
278
		$message->useTemplate($template);
279
280
		$attachment = $this->mailer->createAttachment(
281
			$iTipMessage->message->serialize(),
282
			'event.ics',// TODO(leon): Make file name unique, e.g. add event id
283
			'text/calendar; method=' . $iTipMessage->method
284
		);
285
		$message->attach($attachment);
286
287
		try {
288
			$failed = $this->mailer->send($message);
289
			$iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
290
			if ($failed) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $failed of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
291
				$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' =>  implode(', ', $failed)]);
292
				$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
293
			}
294
		} catch(\Exception $ex) {
295
			$this->logger->logException($ex, ['app' => 'dav']);
296
			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
297
		}
298
	}
299
300
	/**
301
	 * check if event took place in the past already
302
	 * @param VCalendar $vObject
303
	 * @return int
304
	 */
305
	private function getLastOccurrence(VCalendar $vObject) {
306
		/** @var VEvent $component */
307
		$component = $vObject->VEVENT;
308
309
		$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
310
		// Finding the last occurrence is a bit harder
311
		if (!isset($component->RRULE)) {
312
			if (isset($component->DTEND)) {
313
				$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
314
			} elseif (isset($component->DURATION)) {
315
				/** @var \DateTime $endDate */
316
				$endDate = clone $component->DTSTART->getDateTime();
317
				// $component->DTEND->getDateTime() returns DateTimeImmutable
318
				$endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
319
				$lastOccurrence = $endDate->getTimestamp();
320
			} elseif (!$component->DTSTART->hasTime()) {
321
				/** @var \DateTime $endDate */
322
				$endDate = clone $component->DTSTART->getDateTime();
323
				// $component->DTSTART->getDateTime() returns DateTimeImmutable
324
				$endDate = $endDate->modify('+1 day');
325
				$lastOccurrence = $endDate->getTimestamp();
326
			} else {
327
				$lastOccurrence = $firstOccurrence;
328
			}
329
		} else {
330
			$it = new EventIterator($vObject, (string)$component->UID);
331
			$maxDate = new \DateTime(self::MAX_DATE);
332
			if ($it->isInfinite()) {
333
				$lastOccurrence = $maxDate->getTimestamp();
334
			} else {
335
				$end = $it->getDtEnd();
336
				while($it->valid() && $end < $maxDate) {
337
					$end = $it->getDtEnd();
338
					$it->next();
339
340
				}
341
				$lastOccurrence = $end->getTimestamp();
342
			}
343
		}
344
345
		return $lastOccurrence;
346
	}
347
348
349
	/**
350
	 * @param Message $iTipMessage
351
	 * @return null|Property
352
	 */
353
	private function getCurrentAttendee(Message $iTipMessage) {
354
		/** @var VEvent $vevent */
355
		$vevent = $iTipMessage->message->VEVENT;
356
		$attendees = $vevent->select('ATTENDEE');
357
		foreach ($attendees as $attendee) {
358
			/** @var Property $attendee */
359
			if (strcasecmp($attendee->getValue(), $iTipMessage->recipient) === 0) {
360
				return $attendee;
361
			}
362
		}
363
		return null;
364
	}
365
366
	/**
367
	 * @param string $default
368
	 * @param Property|null $attendee
369
	 * @return string
370
	 */
371
	private function getAttendeeLangOrDefault($default, Property $attendee = null) {
372
		if ($attendee !== null) {
373
			$lang = $attendee->offsetGet('LANGUAGE');
374
			if ($lang instanceof Parameter) {
375
				return $lang->getValue();
376
			}
377
		}
378
		return $default;
379
	}
380
381
	/**
382
	 * @param Property|null $attendee
383
	 * @return bool
384
	 */
385
	private function getAttendeeRSVP(Property $attendee = null) {
386
		if ($attendee !== null) {
387
			$rsvp = $attendee->offsetGet('RSVP');
388
			if (($rsvp instanceof Parameter) && (strcasecmp($rsvp->getValue(), 'TRUE') === 0)) {
389
				return true;
390
			}
391
		}
392
		// RFC 5545 3.2.17: default RSVP is false
393
		return false;
394
	}
395
396
	/**
397
	 * @param IL10N $l10n
398
	 * @param Property $dtstart
399
	 * @param Property $dtend
400
	 */
401
	private function generateWhenString(IL10N $l10n, Property $dtstart, Property $dtend) {
402
		$isAllDay = $dtstart instanceof Property\ICalendar\Date;
403
404
		/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
405
		/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */
406
		/** @var \DateTimeImmutable $dtstartDt */
407
		$dtstartDt = $dtstart->getDateTime();
408
		/** @var \DateTimeImmutable $dtendDt */
409
		$dtendDt = $dtend->getDateTime();
410
411
		$diff = $dtstartDt->diff($dtendDt);
412
413
		$dtstartDt = new \DateTime($dtstartDt->format(\DateTime::ATOM));
414
		$dtendDt = new \DateTime($dtendDt->format(\DateTime::ATOM));
415
416
		if ($isAllDay) {
417
			// One day event
418
			if ($diff->days === 1) {
419
				return $l10n->l('date', $dtstartDt, ['width' => 'medium']);
420
			}
421
422
			//event that spans over multiple days
423
			$localeStart = $l10n->l('date', $dtstartDt, ['width' => 'medium']);
424
			$localeEnd = $l10n->l('date', $dtendDt, ['width' => 'medium']);
425
426
			return $localeStart . ' - ' . $localeEnd;
0 ignored issues
show
Bug introduced by
Are you sure $localeEnd of type false|integer|string can be used in concatenation? ( Ignorable by Annotation )

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

426
			return $localeStart . ' - ' . /** @scrutinizer ignore-type */ $localeEnd;
Loading history...
Bug introduced by
Are you sure $localeStart of type false|integer|string can be used in concatenation? ( Ignorable by Annotation )

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

426
			return /** @scrutinizer ignore-type */ $localeStart . ' - ' . $localeEnd;
Loading history...
427
		}
428
429
		/** @var Property\ICalendar\DateTime $dtstart */
430
		/** @var Property\ICalendar\DateTime $dtend */
431
		$isFloating = $dtstart->isFloating();
432
		$startTimezone = $endTimezone = null;
433
		if (!$isFloating) {
434
			$prop = $dtstart->offsetGet('TZID');
435
			if ($prop instanceof Parameter) {
436
				$startTimezone = $prop->getValue();
437
			}
438
439
			$prop = $dtend->offsetGet('TZID');
440
			if ($prop instanceof Parameter) {
441
				$endTimezone = $prop->getValue();
442
			}
443
		}
444
445
		$localeStart = $l10n->l('weekdayName', $dtstartDt, ['width' => 'abbreviated']) . ', ' .
0 ignored issues
show
Bug introduced by
Are you sure $l10n->l('weekdayName', ...dth' => 'abbreviated')) of type false|integer|string can be used in concatenation? ( Ignorable by Annotation )

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

445
		$localeStart = /** @scrutinizer ignore-type */ $l10n->l('weekdayName', $dtstartDt, ['width' => 'abbreviated']) . ', ' .
Loading history...
446
			$l10n->l('datetime', $dtstartDt, ['width' => 'medium|short']);
0 ignored issues
show
Bug introduced by
Are you sure $l10n->l('datetime', $dt...th' => 'medium|short')) of type false|integer|string can be used in concatenation? ( Ignorable by Annotation )

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

446
			/** @scrutinizer ignore-type */ $l10n->l('datetime', $dtstartDt, ['width' => 'medium|short']);
Loading history...
447
448
		// always show full date with timezone if timezones are different
449
		if ($startTimezone !== $endTimezone) {
450
			$localeEnd = $l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
451
452
			return $localeStart . ' (' . $startTimezone . ') - ' .
453
				$localeEnd . ' (' . $endTimezone . ')';
454
		}
455
456
		// show only end time if date is the same
457
		if ($this->isDayEqual($dtstartDt, $dtendDt)) {
458
			$localeEnd = $l10n->l('time', $dtendDt, ['width' => 'short']);
459
		} else {
460
			$localeEnd = $l10n->l('weekdayName', $dtendDt, ['width' => 'abbreviated']) . ', ' .
0 ignored issues
show
Bug introduced by
Are you sure $l10n->l('weekdayName', ...dth' => 'abbreviated')) of type false|integer|string can be used in concatenation? ( Ignorable by Annotation )

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

460
			$localeEnd = /** @scrutinizer ignore-type */ $l10n->l('weekdayName', $dtendDt, ['width' => 'abbreviated']) . ', ' .
Loading history...
461
				$l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
0 ignored issues
show
Bug introduced by
Are you sure $l10n->l('datetime', $dt...th' => 'medium|short')) of type false|integer|string can be used in concatenation? ( Ignorable by Annotation )

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

461
				/** @scrutinizer ignore-type */ $l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
Loading history...
462
		}
463
464
		return  $localeStart . ' - ' . $localeEnd . ' (' . $startTimezone . ')';
465
	}
466
467
	/**
468
	 * @param \DateTime $dtStart
469
	 * @param \DateTime $dtEnd
470
	 * @return bool
471
	 */
472
	private function isDayEqual(\DateTime $dtStart, \DateTime $dtEnd) {
473
		return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
474
	}
475
476
	/**
477
	 * @param IEMailTemplate $template
478
	 * @param IL10N $l10n
479
	 * @param string $method
480
	 * @param string $summary
481
	 * @param string $attendeeName
482
	 * @param string $inviteeName
483
	 */
484
	private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n,
485
										  $method, $summary, $attendeeName, $inviteeName) {
486
		if ($method === self::METHOD_CANCEL) {
487
			$template->setSubject('Cancelled: ' . $summary);
488
			$template->addHeading($l10n->t('Invitation canceled'), $l10n->t('Hello %s,', [$attendeeName]));
489
			$template->addBodyText($l10n->t('The meeting »%1$s« with %2$s was canceled.', [$summary, $inviteeName]));
490
		} else if ($method === self::METHOD_REPLY) {
491
			$template->setSubject('Re: ' . $summary);
492
			$template->addHeading($l10n->t('Invitation updated'), $l10n->t('Hello %s,', [$attendeeName]));
493
			$template->addBodyText($l10n->t('The meeting »%1$s« with %2$s was updated.', [$summary, $inviteeName]));
494
		} else {
495
			$template->setSubject('Invitation: ' . $summary);
496
			$template->addHeading($l10n->t('%1$s invited you to »%2$s«', [$inviteeName, $summary]), $l10n->t('Hello %s,', [$attendeeName]));
497
		}
498
	}
499
500
	/**
501
	 * @param IEMailTemplate $template
502
	 * @param IL10N $l10n
503
	 * @param string $time
504
	 * @param string $location
505
	 * @param string $description
506
	 * @param string $url
507
	 */
508
	private function addBulletList(IEMailTemplate $template, IL10N $l10n, $time, $location, $description, $url) {
509
		$template->addBodyListItem($time, $l10n->t('When:'),
510
			$this->getAbsoluteImagePath('filetypes/text-calendar.svg'));
511
512
		if ($location) {
513
			$template->addBodyListItem($location, $l10n->t('Where:'),
514
				$this->getAbsoluteImagePath('filetypes/location.svg'));
515
		}
516
		if ($description) {
517
			$template->addBodyListItem((string)$description, $l10n->t('Description:'),
518
				$this->getAbsoluteImagePath('filetypes/text.svg'));
519
		}
520
		if ($url) {
521
			$template->addBodyListItem((string)$url, $l10n->t('Link:'),
522
				$this->getAbsoluteImagePath('filetypes/link.svg'));
523
		}
524
	}
525
526
	/**
527
	 * @param IEMailTemplate $template
528
	 * @param IL10N $l10n
529
	 * @param Message $iTipMessage
530
	 * @param int $lastOccurrence
531
	 */
532
	private function addResponseButtons(IEMailTemplate $template, IL10N $l10n,
533
										Message $iTipMessage, $lastOccurrence) {
534
		$token = $this->createInvitationToken($iTipMessage, $lastOccurrence);
535
536
		$template->addBodyButtonGroup(
537
			$l10n->t('Accept'),
538
			$this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.accept', [
539
				'token' => $token,
540
			]),
541
			$l10n->t('Decline'),
542
			$this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.decline', [
543
				'token' => $token,
544
			])
545
		);
546
547
		$moreOptionsURL = $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.options', [
548
			'token' => $token,
549
		]);
550
		$html = vsprintf('<small><a href="%s">%s</a></small>', [
551
			$moreOptionsURL, $l10n->t('More options …')
552
		]);
553
		$text = $l10n->t('More options at %s', [$moreOptionsURL]);
554
555
		$template->addBodyText($html, $text);
556
	}
557
558
	/**
559
	 * @param string $path
560
	 * @return string
561
	 */
562
	private function getAbsoluteImagePath($path) {
563
		return $this->urlGenerator->getAbsoluteURL(
564
			$this->urlGenerator->imagePath('core', $path)
565
		);
566
	}
567
568
	/**
569
	 * @param Message $iTipMessage
570
	 * @param int $lastOccurrence
571
	 * @return string
572
	 */
573
	private function createInvitationToken(Message $iTipMessage, $lastOccurrence):string {
574
		$token = $this->random->generate(60, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
575
576
		/** @var VEvent $vevent */
577
		$vevent = $iTipMessage->message->VEVENT;
578
		$attendee = $iTipMessage->recipient;
579
		$organizer = $iTipMessage->sender;
580
		$sequence = $iTipMessage->sequence;
581
		$recurrenceId = isset($vevent->{'RECURRENCE-ID'}) ?
582
			$vevent->{'RECURRENCE-ID'}->serialize() : null;
583
		$uid = $vevent->{'UID'};
584
585
		$query = $this->db->getQueryBuilder();
586
		$query->insert('calendar_invitations')
587
			->values([
588
				'token' => $query->createNamedParameter($token),
589
				'attendee' => $query->createNamedParameter($attendee),
590
				'organizer' => $query->createNamedParameter($organizer),
591
				'sequence' => $query->createNamedParameter($sequence),
592
				'recurrenceid' => $query->createNamedParameter($recurrenceId),
593
				'expiration' => $query->createNamedParameter($lastOccurrence),
594
				'uid' => $query->createNamedParameter($uid)
595
			])
596
			->execute();
597
598
		return $token;
599
	}
600
}
601