Passed
Push — master ( fdcc65...954290 )
by Roeland
09:42 queued 10s
created
apps/dav/lib/CalDAV/Schedule/IMipPlugin.php 2 patches
Indentation   +519 added lines, -519 removed lines patch added patch discarded remove patch
@@ -60,191 +60,191 @@  discard block
 block discarded – undo
60 60
  */
61 61
 class IMipPlugin extends SabreIMipPlugin {
62 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;
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();
188
-			$end = clone $vevent->DTSTART;
189
-			$endDateTime = $end->getDateTime();
190
-			$endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue()));
191
-			$end->setDateTime($endDateTime, $isFloating);
192
-		} elseif (!$vevent->DTSTART->hasTime()) {
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
-			/*
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;
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();
188
+            $end = clone $vevent->DTSTART;
189
+            $endDateTime = $end->getDateTime();
190
+            $endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue()));
191
+            $end->setDateTime($endDateTime, $isFloating);
192
+        } elseif (!$vevent->DTSTART->hasTime()) {
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 248
 			** Only offer invitation accept/reject buttons, which link back to the
249 249
 			** nextcloud server, to recipients who can access the nextcloud server via
250 250
 			** their internet/intranet.  Issue #12156
@@ -263,338 +263,338 @@  discard block
 block discarded – undo
263 263
 			** To suppress URLs entirely, set invitation_link_recipients to boolean "no".
264 264
 			*/
265 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) {
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;
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']) . ', ' .
446
-			$l10n->l('datetime', $dtstartDt, ['width' => 'medium|short']);
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']) . ', ' .
461
-				$l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
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
-	}
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) {
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;
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']) . ', ' .
446
+            $l10n->l('datetime', $dtstartDt, ['width' => 'medium|short']);
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']) . ', ' .
461
+                $l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
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 600
 }
Please login to merge, or discard this patch.
Spacing   +23 added lines, -23 removed lines patch added patch discarded remove patch
@@ -217,11 +217,11 @@  discard block
 block discarded – undo
217 217
 		}
218 218
 
219 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,
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 225
 		);
226 226
 
227 227
 		$fromEMail = \OCP\Util::getDefaultEmailAddress('invitations-noreply');
@@ -232,7 +232,7 @@  discard block
 block discarded – undo
232 232
 			->setReplyTo([$sender => $senderName])
233 233
 			->setTo([$recipient => $recipientName]);
234 234
 
235
-		$template = $this->mailer->createEMailTemplate('dav.calendarInvite.' . $method, $data);
235
+		$template = $this->mailer->createEMailTemplate('dav.calendarInvite.'.$method, $data);
236 236
 		$template->addHeader();
237 237
 
238 238
 		$this->addSubjectAndHeading($template, $l10n, $method, $summary,
@@ -279,8 +279,8 @@  discard block
 block discarded – undo
279 279
 
280 280
 		$attachment = $this->mailer->createAttachment(
281 281
 			$iTipMessage->message->serialize(),
282
-			'event.ics',// TODO(leon): Make file name unique, e.g. add event id
283
-			'text/calendar; method=' . $iTipMessage->method
282
+			'event.ics', // TODO(leon): Make file name unique, e.g. add event id
283
+			'text/calendar; method='.$iTipMessage->method
284 284
 		);
285 285
 		$message->attach($attachment);
286 286
 
@@ -291,7 +291,7 @@  discard block
 block discarded – undo
291 291
 				$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' =>  implode(', ', $failed)]);
292 292
 				$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
293 293
 			}
294
-		} catch(\Exception $ex) {
294
+		} catch (\Exception $ex) {
295 295
 			$this->logger->logException($ex, ['app' => 'dav']);
296 296
 			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
297 297
 		}
@@ -327,13 +327,13 @@  discard block
 block discarded – undo
327 327
 				$lastOccurrence = $firstOccurrence;
328 328
 			}
329 329
 		} else {
330
-			$it = new EventIterator($vObject, (string)$component->UID);
330
+			$it = new EventIterator($vObject, (string) $component->UID);
331 331
 			$maxDate = new \DateTime(self::MAX_DATE);
332 332
 			if ($it->isInfinite()) {
333 333
 				$lastOccurrence = $maxDate->getTimestamp();
334 334
 			} else {
335 335
 				$end = $it->getDtEnd();
336
-				while($it->valid() && $end < $maxDate) {
336
+				while ($it->valid() && $end < $maxDate) {
337 337
 					$end = $it->getDtEnd();
338 338
 					$it->next();
339 339
 
@@ -423,7 +423,7 @@  discard block
 block discarded – undo
423 423
 			$localeStart = $l10n->l('date', $dtstartDt, ['width' => 'medium']);
424 424
 			$localeEnd = $l10n->l('date', $dtendDt, ['width' => 'medium']);
425 425
 
426
-			return $localeStart . ' - ' . $localeEnd;
426
+			return $localeStart.' - '.$localeEnd;
427 427
 		}
428 428
 
429 429
 		/** @var Property\ICalendar\DateTime $dtstart */
@@ -442,26 +442,26 @@  discard block
 block discarded – undo
442 442
 			}
443 443
 		}
444 444
 
445
-		$localeStart = $l10n->l('weekdayName', $dtstartDt, ['width' => 'abbreviated']) . ', ' .
445
+		$localeStart = $l10n->l('weekdayName', $dtstartDt, ['width' => 'abbreviated']).', '.
446 446
 			$l10n->l('datetime', $dtstartDt, ['width' => 'medium|short']);
447 447
 
448 448
 		// always show full date with timezone if timezones are different
449 449
 		if ($startTimezone !== $endTimezone) {
450 450
 			$localeEnd = $l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
451 451
 
452
-			return $localeStart . ' (' . $startTimezone . ') - ' .
453
-				$localeEnd . ' (' . $endTimezone . ')';
452
+			return $localeStart.' ('.$startTimezone.') - '.
453
+				$localeEnd.' ('.$endTimezone.')';
454 454
 		}
455 455
 
456 456
 		// show only end time if date is the same
457 457
 		if ($this->isDayEqual($dtstartDt, $dtendDt)) {
458 458
 			$localeEnd = $l10n->l('time', $dtendDt, ['width' => 'short']);
459 459
 		} else {
460
-			$localeEnd = $l10n->l('weekdayName', $dtendDt, ['width' => 'abbreviated']) . ', ' .
460
+			$localeEnd = $l10n->l('weekdayName', $dtendDt, ['width' => 'abbreviated']).', '.
461 461
 				$l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
462 462
 		}
463 463
 
464
-		return  $localeStart . ' - ' . $localeEnd . ' (' . $startTimezone . ')';
464
+		return  $localeStart.' - '.$localeEnd.' ('.$startTimezone.')';
465 465
 	}
466 466
 
467 467
 	/**
@@ -484,15 +484,15 @@  discard block
 block discarded – undo
484 484
 	private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n,
485 485
 										  $method, $summary, $attendeeName, $inviteeName) {
486 486
 		if ($method === self::METHOD_CANCEL) {
487
-			$template->setSubject('Cancelled: ' . $summary);
487
+			$template->setSubject('Cancelled: '.$summary);
488 488
 			$template->addHeading($l10n->t('Invitation canceled'), $l10n->t('Hello %s,', [$attendeeName]));
489 489
 			$template->addBodyText($l10n->t('The meeting »%1$s« with %2$s was canceled.', [$summary, $inviteeName]));
490 490
 		} else if ($method === self::METHOD_REPLY) {
491
-			$template->setSubject('Re: ' . $summary);
491
+			$template->setSubject('Re: '.$summary);
492 492
 			$template->addHeading($l10n->t('Invitation updated'), $l10n->t('Hello %s,', [$attendeeName]));
493 493
 			$template->addBodyText($l10n->t('The meeting »%1$s« with %2$s was updated.', [$summary, $inviteeName]));
494 494
 		} else {
495
-			$template->setSubject('Invitation: ' . $summary);
495
+			$template->setSubject('Invitation: '.$summary);
496 496
 			$template->addHeading($l10n->t('%1$s invited you to »%2$s«', [$inviteeName, $summary]), $l10n->t('Hello %s,', [$attendeeName]));
497 497
 		}
498 498
 	}
@@ -514,11 +514,11 @@  discard block
 block discarded – undo
514 514
 				$this->getAbsoluteImagePath('filetypes/location.svg'));
515 515
 		}
516 516
 		if ($description) {
517
-			$template->addBodyListItem((string)$description, $l10n->t('Description:'),
517
+			$template->addBodyListItem((string) $description, $l10n->t('Description:'),
518 518
 				$this->getAbsoluteImagePath('filetypes/text.svg'));
519 519
 		}
520 520
 		if ($url) {
521
-			$template->addBodyListItem((string)$url, $l10n->t('Link:'),
521
+			$template->addBodyListItem((string) $url, $l10n->t('Link:'),
522 522
 				$this->getAbsoluteImagePath('filetypes/link.svg'));
523 523
 		}
524 524
 	}
@@ -571,7 +571,7 @@  discard block
 block discarded – undo
571 571
 	 * @return string
572 572
 	 */
573 573
 	private function createInvitationToken(Message $iTipMessage, $lastOccurrence):string {
574
-		$token = $this->random->generate(60, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
574
+		$token = $this->random->generate(60, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
575 575
 
576 576
 		/** @var VEvent $vevent */
577 577
 		$vevent = $iTipMessage->message->VEVENT;
Please login to merge, or discard this patch.