Passed
Push — master ( 5cdc85...37718d )
by Morris
38:53 queued 21:57
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->config->getUserValue($this->userId, 'core', 'lang', $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,
0 ignored issues
show
Bug introduced by
It seems like $meetingWhen can also be of type false; however, parameter $time of OCA\DAV\CalDAV\Schedule\...Plugin::addBulletList() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

240
		$this->addBulletList($template, $l10n, /** @scrutinizer ignore-type */ $meetingWhen, $meetingLocation,
Loading history...
241
			$meetingDescription, $meetingUrl);
242
		$this->addResponseButtons($template, $l10n, $iTipMessage, $lastOccurrence);
243
244
		$template->addFooter();
245
		$message->useTemplate($template);
246
247
		$attachment = $this->mailer->createAttachment(
248
			$iTipMessage->message->serialize(),
249
			'event.ics',// TODO(leon): Make file name unique, e.g. add event id
250
			'text/calendar; method=' . $iTipMessage->method
251
		);
252
		$message->attach($attachment);
253
254
		try {
255
			$failed = $this->mailer->send($message);
256
			$iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
257
			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...
258
				$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' =>  implode(', ', $failed)]);
259
				$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
260
			}
261
		} catch(\Exception $ex) {
262
			$this->logger->logException($ex, ['app' => 'dav']);
263
			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
264
		}
265
	}
266
267
	/**
268
	 * check if event took place in the past already
269
	 * @param VCalendar $vObject
270
	 * @return int
271
	 */
272
	private function getLastOccurrence(VCalendar $vObject) {
273
		/** @var VEvent $component */
274
		$component = $vObject->VEVENT;
275
276
		$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
277
		// Finding the last occurrence is a bit harder
278
		if (!isset($component->RRULE)) {
279
			if (isset($component->DTEND)) {
280
				$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
281
			} elseif (isset($component->DURATION)) {
282
				/** @var \DateTime $endDate */
283
				$endDate = clone $component->DTSTART->getDateTime();
284
				// $component->DTEND->getDateTime() returns DateTimeImmutable
285
				$endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
0 ignored issues
show
Bug introduced by
It seems like Sabre\VObject\DateTimePa...->DURATION->getValue()) can also be of type DateTimeImmutable; however, parameter $interval of DateTime::add() does only seem to accept DateInterval, maybe add an additional type check? ( Ignorable by Annotation )

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

285
				$endDate = $endDate->add(/** @scrutinizer ignore-type */ DateTimeParser::parse($component->DURATION->getValue()));
Loading history...
286
				$lastOccurrence = $endDate->getTimestamp();
287
			} elseif (!$component->DTSTART->hasTime()) {
288
				/** @var \DateTime $endDate */
289
				$endDate = clone $component->DTSTART->getDateTime();
290
				// $component->DTSTART->getDateTime() returns DateTimeImmutable
291
				$endDate = $endDate->modify('+1 day');
292
				$lastOccurrence = $endDate->getTimestamp();
293
			} else {
294
				$lastOccurrence = $firstOccurrence;
295
			}
296
		} else {
297
			$it = new EventIterator($vObject, (string)$component->UID);
298
			$maxDate = new \DateTime(self::MAX_DATE);
299
			if ($it->isInfinite()) {
300
				$lastOccurrence = $maxDate->getTimestamp();
301
			} else {
302
				$end = $it->getDtEnd();
303
				while($it->valid() && $end < $maxDate) {
304
					$end = $it->getDtEnd();
305
					$it->next();
306
307
				}
308
				$lastOccurrence = $end->getTimestamp();
309
			}
310
		}
311
312
		return $lastOccurrence;
313
	}
314
315
316
	/**
317
	 * @param Message $iTipMessage
318
	 * @return null|Property
319
	 */
320
	private function getCurrentAttendee(Message $iTipMessage) {
321
		/** @var VEvent $vevent */
322
		$vevent = $iTipMessage->message->VEVENT;
323
		$attendees = $vevent->select('ATTENDEE');
324
		foreach ($attendees as $attendee) {
325
			/** @var Property $attendee */
326
			if (strcasecmp($attendee->getValue(), $iTipMessage->recipient) === 0) {
327
				return $attendee;
328
			}
329
		}
330
		return null;
331
	}
332
333
	/**
334
	 * @param string $default
335
	 * @param Property|null $attendee
336
	 * @return string
337
	 */
338
	private function getAttendeeLangOrDefault($default, Property $attendee = null) {
339
		if ($attendee !== null) {
340
			$lang = $attendee->offsetGet('LANGUAGE');
341
			if ($lang instanceof Parameter) {
342
				return $lang->getValue();
343
			}
344
		}
345
		return $default;
346
	}
347
348
	/**
349
	 * @param IL10N $l10n
350
	 * @param Property $dtstart
351
	 * @param Property $dtend
352
	 */
353
	private function generateWhenString(IL10N $l10n, Property $dtstart, Property $dtend) {
354
		$isAllDay = $dtstart instanceof Property\ICalendar\Date;
355
356
		/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
357
		/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */
358
		/** @var \DateTimeImmutable $dtstartDt */
359
		$dtstartDt = $dtstart->getDateTime();
360
		/** @var \DateTimeImmutable $dtendDt */
361
		$dtendDt = $dtend->getDateTime();
362
363
		$diff = $dtstartDt->diff($dtendDt);
364
365
		$dtstartDt = new \DateTime($dtstartDt->format(\DateTime::ATOM));
366
		$dtendDt = new \DateTime($dtendDt->format(\DateTime::ATOM));
367
368
		if ($isAllDay) {
369
			// One day event
370
			if ($diff->days === 1) {
371
				return $l10n->l('date', $dtstartDt, ['width' => 'medium']);
372
			}
373
374
			//event that spans over multiple days
375
			$localeStart = $l10n->l('date', $dtstartDt, ['width' => 'medium']);
376
			$localeEnd = $l10n->l('date', $dtendDt, ['width' => 'medium']);
377
378
			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

378
			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

378
			return /** @scrutinizer ignore-type */ $localeStart . ' - ' . $localeEnd;
Loading history...
379
		}
380
381
		/** @var Property\ICalendar\DateTime $dtstart */
382
		/** @var Property\ICalendar\DateTime $dtend */
383
		$isFloating = $dtstart->isFloating();
384
		$startTimezone = $endTimezone = null;
385
		if (!$isFloating) {
386
			$prop = $dtstart->offsetGet('TZID');
387
			if ($prop instanceof Parameter) {
388
				$startTimezone = $prop->getValue();
389
			}
390
391
			$prop = $dtend->offsetGet('TZID');
392
			if ($prop instanceof Parameter) {
393
				$endTimezone = $prop->getValue();
394
			}
395
		}
396
397
		$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

397
		$localeStart = /** @scrutinizer ignore-type */ $l10n->l('weekdayName', $dtstartDt, ['width' => 'abbreviated']) . ', ' .
Loading history...
398
			$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

398
			/** @scrutinizer ignore-type */ $l10n->l('datetime', $dtstartDt, ['width' => 'medium|short']);
Loading history...
399
400
		// always show full date with timezone if timezones are different
401
		if ($startTimezone !== $endTimezone) {
402
			$localeEnd = $l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
403
404
			return $localeStart . ' (' . $startTimezone . ') - ' .
405
				$localeEnd . ' (' . $endTimezone . ')';
406
		}
407
408
		// show only end time if date is the same
409
		if ($this->isDayEqual($dtstartDt, $dtendDt)) {
410
			$localeEnd = $l10n->l('time', $dtendDt, ['width' => 'short']);
411
		} else {
412
			$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

412
			$localeEnd = /** @scrutinizer ignore-type */ $l10n->l('weekdayName', $dtendDt, ['width' => 'abbreviated']) . ', ' .
Loading history...
413
				$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

413
				/** @scrutinizer ignore-type */ $l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
Loading history...
414
		}
415
416
		return  $localeStart . ' - ' . $localeEnd . ' (' . $startTimezone . ')';
417
	}
418
419
	/**
420
	 * @param \DateTime $dtStart
421
	 * @param \DateTime $dtEnd
422
	 * @return bool
423
	 */
424
	private function isDayEqual(\DateTime $dtStart, \DateTime $dtEnd) {
425
		return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
426
	}
427
428
	/**
429
	 * @param IEMailTemplate $template
430
	 * @param IL10N $l10n
431
	 * @param string $method
432
	 * @param string $summary
433
	 * @param string $attendeeName
434
	 * @param string $inviteeName
435
	 */
436
	private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n,
437
										  $method, $summary, $attendeeName, $inviteeName) {
438
		if ($method === self::METHOD_CANCEL) {
439
			$template->setSubject('Cancelled: ' . $summary);
440
			$template->addHeading($l10n->t('Invitation canceled'), $l10n->t('Hello %s,', [$attendeeName]));
441
			$template->addBodyText($l10n->t('The meeting »%1$s« with %2$s was canceled.', [$summary, $inviteeName]));
442
		} else if ($method === self::METHOD_REPLY) {
443
			$template->setSubject('Re: ' . $summary);
444
			$template->addHeading($l10n->t('Invitation updated'), $l10n->t('Hello %s,', [$attendeeName]));
445
			$template->addBodyText($l10n->t('The meeting »%1$s« with %2$s was updated.', [$summary, $inviteeName]));
446
		} else {
447
			$template->setSubject('Invitation: ' . $summary);
448
			$template->addHeading($l10n->t('%1$s invited you to »%2$s«', [$inviteeName, $summary]), $l10n->t('Hello %s,', [$attendeeName]));
449
		}
450
451
	}
452
453
	/**
454
	 * @param IEMailTemplate $template
455
	 * @param IL10N $l10n
456
	 * @param string $time
457
	 * @param string $location
458
	 * @param string $description
459
	 * @param string $url
460
	 */
461
	private function addBulletList(IEMailTemplate $template, IL10N $l10n, $time, $location, $description, $url) {
462
		$template->addBodyListItem($time, $l10n->t('When:'),
463
			$this->getAbsoluteImagePath('filetypes/text-calendar.svg'));
464
465
		if ($location) {
466
			$template->addBodyListItem($location, $l10n->t('Where:'),
467
				$this->getAbsoluteImagePath('filetypes/location.svg'));
468
		}
469
		if ($description) {
470
			$template->addBodyListItem((string)$description, $l10n->t('Description:'),
471
				$this->getAbsoluteImagePath('filetypes/text.svg'));
472
		}
473
		if ($url) {
474
			$template->addBodyListItem((string)$url, $l10n->t('Link:'),
475
				$this->getAbsoluteImagePath('filetypes/link.svg'));
476
		}
477
	}
478
479
	/**
480
	 * @param IEMailTemplate $template
481
	 * @param IL10N $l10n
482
	 * @param Message $iTipMessage
483
	 * @param int $lastOccurrence
484
	 */
485
	private function addResponseButtons(IEMailTemplate $template, IL10N $l10n,
486
										Message $iTipMessage, $lastOccurrence) {
487
		$token = $this->createInvitationToken($iTipMessage, $lastOccurrence);
488
489
		$template->addBodyButtonGroup(
490
			$l10n->t('Accept'),
491
			$this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.accept', [
492
				'token' => $token,
493
			]),
494
			$l10n->t('Decline'),
495
			$this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.decline', [
496
				'token' => $token,
497
			])
498
		);
499
500
		$moreOptionsURL = $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.options', [
501
			'token' => $token,
502
		]);
503
		$html = vsprintf('<small><a href="%s">%s</a></small>', [
504
			$moreOptionsURL, $l10n->t('More options …')
505
		]);
506
		$text = $l10n->t('More options at %s', [$moreOptionsURL]);
507
		
508
		$template->addBodyText($html, $text);
509
	}
510
511
	/**
512
	 * @param string $path
513
	 * @return string
514
	 */
515
	private function getAbsoluteImagePath($path) {
516
		return $this->urlGenerator->getAbsoluteURL(
517
			$this->urlGenerator->imagePath('core', $path)
518
		);
519
	}
520
521
	/**
522
	 * @param Message $iTipMessage
523
	 * @param int $lastOccurrence
524
	 * @return string
525
	 */
526
	private function createInvitationToken(Message $iTipMessage, $lastOccurrence):string {
527
		$token = $this->random->generate(60, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
528
529
		/** @var VEvent $vevent */
530
		$vevent = $iTipMessage->message->VEVENT;
531
		$attendee = $iTipMessage->recipient;
532
		$organizer = $iTipMessage->sender;
533
		$sequence = $iTipMessage->sequence;
534
		$recurrenceId = isset($vevent->{'RECURRENCE-ID'}) ?
535
			$vevent->{'RECURRENCE-ID'}->serialize() : null;
536
		$uid = $vevent->{'UID'};
537
538
		$query = $this->db->getQueryBuilder();
539
		$query->insert('calendar_invitations')
540
			->values([
541
				'token' => $query->createNamedParameter($token),
542
				'attendee' => $query->createNamedParameter($attendee),
543
				'organizer' => $query->createNamedParameter($organizer),
544
				'sequence' => $query->createNamedParameter($sequence),
545
				'recurrenceid' => $query->createNamedParameter($recurrenceId),
546
				'expiration' => $query->createNamedParameter($lastOccurrence),
547
				'uid' => $query->createNamedParameter($uid)
548
			])
549
			->execute();
550
551
		return $token;
552
	}
553
}
554