Passed
Push — master ( c162bd...c6df3d )
by Roeland
25:18 queued 11:39
created
apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php 1 patch
Indentation   +451 added lines, -451 removed lines patch added patch discarded remove patch
@@ -52,455 +52,455 @@
 block discarded – undo
52 52
  */
53 53
 class EmailProvider extends AbstractProvider {
54 54
 
55
-	/** @var string */
56
-	public const NOTIFICATION_TYPE = 'EMAIL';
57
-
58
-	/** @var IMailer */
59
-	private $mailer;
60
-
61
-	/**
62
-	 * @param IConfig $config
63
-	 * @param IMailer $mailer
64
-	 * @param ILogger $logger
65
-	 * @param L10NFactory $l10nFactory
66
-	 * @param IUrlGenerator $urlGenerator
67
-	 */
68
-	public function __construct(IConfig $config,
69
-								IMailer $mailer,
70
-								ILogger $logger,
71
-								L10NFactory $l10nFactory,
72
-								IURLGenerator $urlGenerator) {
73
-		parent::__construct($logger, $l10nFactory, $urlGenerator, $config);
74
-		$this->mailer = $mailer;
75
-	}
76
-
77
-	/**
78
-	 * Send out notification via email
79
-	 *
80
-	 * @param VEvent $vevent
81
-	 * @param string $calendarDisplayName
82
-	 * @param array $users
83
-	 * @throws \Exception
84
-	 */
85
-	public function send(VEvent $vevent,
86
-						 string $calendarDisplayName,
87
-						 array $users = []):void {
88
-		$fallbackLanguage = $this->getFallbackLanguage();
89
-
90
-		$emailAddressesOfSharees = $this->getEMailAddressesOfAllUsersWithWriteAccessToCalendar($users);
91
-		$emailAddressesOfAttendees = $this->getAllEMailAddressesFromEvent($vevent);
92
-
93
-		// Quote from php.net:
94
-		// If the input arrays have the same string keys, then the later value for that key will overwrite the previous one.
95
-		// => if there are duplicate email addresses, it will always take the system value
96
-		$emailAddresses = array_merge(
97
-			$emailAddressesOfAttendees,
98
-			$emailAddressesOfSharees
99
-		);
100
-
101
-		$sortedByLanguage = $this->sortEMailAddressesByLanguage($emailAddresses, $fallbackLanguage);
102
-		$organizer = $this->getOrganizerEMailAndNameFromEvent($vevent);
103
-
104
-		foreach ($sortedByLanguage as $lang => $emailAddresses) {
105
-			if (!$this->hasL10NForLang($lang)) {
106
-				$lang = $fallbackLanguage;
107
-			}
108
-			$l10n = $this->getL10NForLang($lang);
109
-			$fromEMail = \OCP\Util::getDefaultEmailAddress('reminders-noreply');
110
-
111
-			$template = $this->mailer->createEMailTemplate('dav.calendarReminder');
112
-			$template->addHeader();
113
-			$this->addSubjectAndHeading($template, $l10n, $vevent);
114
-			$this->addBulletList($template, $l10n, $calendarDisplayName, $vevent);
115
-			$template->addFooter();
116
-
117
-			foreach ($emailAddresses as $emailAddress) {
118
-				if (!$this->mailer->validateMailAddress($emailAddress)) {
119
-					$this->logger->error('Email address {address} for reminder notification is incorrect', ['app' => 'dav', 'address' => $emailAddress]);
120
-					continue;
121
-				}
122
-
123
-				$message = $this->mailer->createMessage();
124
-				$message->setFrom([$fromEMail]);
125
-				if ($organizer) {
126
-					$message->setReplyTo($organizer);
127
-				}
128
-				$message->setTo([$emailAddress]);
129
-				$message->useTemplate($template);
130
-
131
-				try {
132
-					$failed = $this->mailer->send($message);
133
-					if ($failed) {
134
-						$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]);
135
-					}
136
-				} catch (\Exception $ex) {
137
-					$this->logger->logException($ex, ['app' => 'dav']);
138
-				}
139
-			}
140
-		}
141
-	}
142
-
143
-	/**
144
-	 * @param IEMailTemplate $template
145
-	 * @param IL10N $l10n
146
-	 * @param VEvent $vevent
147
-	 */
148
-	private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n, VEvent $vevent):void {
149
-		$template->setSubject('Notification: ' . $this->getTitleFromVEvent($vevent, $l10n));
150
-		$template->addHeading($this->getTitleFromVEvent($vevent, $l10n));
151
-	}
152
-
153
-	/**
154
-	 * @param IEMailTemplate $template
155
-	 * @param IL10N $l10n
156
-	 * @param string $calendarDisplayName
157
-	 * @param array $eventData
158
-	 */
159
-	private function addBulletList(IEMailTemplate $template,
160
-								   IL10N $l10n,
161
-								   string $calendarDisplayName,
162
-								   VEvent $vevent):void {
163
-		$template->addBodyListItem($calendarDisplayName, $l10n->t('Calendar:'),
164
-			$this->getAbsoluteImagePath('actions/info.png'));
165
-
166
-		$template->addBodyListItem($this->generateDateString($l10n, $vevent), $l10n->t('Date:'),
167
-			$this->getAbsoluteImagePath('places/calendar.png'));
168
-
169
-		if (isset($vevent->LOCATION)) {
170
-			$template->addBodyListItem((string) $vevent->LOCATION, $l10n->t('Where:'),
171
-				$this->getAbsoluteImagePath('actions/address.png'));
172
-		}
173
-		if (isset($vevent->DESCRIPTION)) {
174
-			$template->addBodyListItem((string) $vevent->DESCRIPTION, $l10n->t('Description:'),
175
-				$this->getAbsoluteImagePath('actions/more.png'));
176
-		}
177
-	}
178
-
179
-	/**
180
-	 * @param string $path
181
-	 * @return string
182
-	 */
183
-	private function getAbsoluteImagePath(string $path):string {
184
-		return $this->urlGenerator->getAbsoluteURL(
185
-			$this->urlGenerator->imagePath('core', $path)
186
-		);
187
-	}
188
-
189
-	/**
190
-	 * @param VEvent $vevent
191
-	 * @return array|null
192
-	 */
193
-	private function getOrganizerEMailAndNameFromEvent(VEvent $vevent):?array {
194
-		if (!$vevent->ORGANIZER) {
195
-			return null;
196
-		}
197
-
198
-		$organizer = $vevent->ORGANIZER;
199
-		if (strcasecmp($organizer->getValue(), 'mailto:') !== 0) {
200
-			return null;
201
-		}
202
-
203
-		$organizerEMail = substr($organizer->getValue(), 7);
204
-
205
-		if (!$this->mailer->validateMailAddress($organizerEMail)) {
206
-			return null;
207
-		}
208
-
209
-		$name = $organizer->offsetGet('CN');
210
-		if ($name instanceof Parameter) {
211
-			return [$organizerEMail => $name];
212
-		}
213
-
214
-		return [$organizerEMail];
215
-	}
216
-
217
-	/**
218
-	 * @param array $emails
219
-	 * @param string $defaultLanguage
220
-	 * @return array
221
-	 */
222
-	private function sortEMailAddressesByLanguage(array $emails,
223
-												  string $defaultLanguage):array {
224
-		$sortedByLanguage = [];
225
-
226
-		foreach ($emails as $emailAddress => $parameters) {
227
-			if (isset($parameters['LANG'])) {
228
-				$lang = $parameters['LANG'];
229
-			} else {
230
-				$lang = $defaultLanguage;
231
-			}
232
-
233
-			if (!isset($sortedByLanguage[$lang])) {
234
-				$sortedByLanguage[$lang] = [];
235
-			}
236
-
237
-			$sortedByLanguage[$lang][] = $emailAddress;
238
-		}
239
-
240
-		return $sortedByLanguage;
241
-	}
242
-
243
-	/**
244
-	 * @param VEvent $vevent
245
-	 * @return array
246
-	 */
247
-	private function getAllEMailAddressesFromEvent(VEvent $vevent):array {
248
-		$emailAddresses = [];
249
-
250
-		if (isset($vevent->ATTENDEE)) {
251
-			foreach ($vevent->ATTENDEE as $attendee) {
252
-				if (!($attendee instanceof VObject\Property)) {
253
-					continue;
254
-				}
255
-
256
-				$cuType = $this->getCUTypeOfAttendee($attendee);
257
-				if (\in_array($cuType, ['RESOURCE', 'ROOM', 'UNKNOWN'])) {
258
-					// Don't send emails to things
259
-					continue;
260
-				}
261
-
262
-				$partstat = $this->getPartstatOfAttendee($attendee);
263
-				if ($partstat === 'DECLINED') {
264
-					// Don't send out emails to people who declined
265
-					continue;
266
-				}
267
-				if ($partstat === 'DELEGATED') {
268
-					$delegates = $attendee->offsetGet('DELEGATED-TO');
269
-					if (!($delegates instanceof VObject\Parameter)) {
270
-						continue;
271
-					}
272
-
273
-					$emailAddressesOfDelegates = $delegates->getParts();
274
-					foreach ($emailAddressesOfDelegates as $addressesOfDelegate) {
275
-						if (strcasecmp($addressesOfDelegate, 'mailto:') === 0) {
276
-							$emailAddresses[substr($addressesOfDelegate, 7)] = [];
277
-						}
278
-					}
279
-
280
-					continue;
281
-				}
282
-
283
-				$emailAddressOfAttendee = $this->getEMailAddressOfAttendee($attendee);
284
-				if ($emailAddressOfAttendee !== null) {
285
-					$properties = [];
286
-
287
-					$langProp = $attendee->offsetGet('LANG');
288
-					if ($langProp instanceof VObject\Parameter) {
289
-						$properties['LANG'] = $langProp->getValue();
290
-					}
291
-
292
-					$emailAddresses[$emailAddressOfAttendee] = $properties;
293
-				}
294
-			}
295
-		}
296
-
297
-		if (isset($vevent->ORGANIZER) && $this->hasAttendeeMailURI($vevent->ORGANIZER)) {
298
-			$emailAddresses[$this->getEMailAddressOfAttendee($vevent->ORGANIZER)] = [];
299
-		}
300
-
301
-		return $emailAddresses;
302
-	}
303
-
304
-
305
-
306
-	/**
307
-	 * @param VObject\Property $attendee
308
-	 * @return string
309
-	 */
310
-	private function getCUTypeOfAttendee(VObject\Property $attendee):string {
311
-		$cuType = $attendee->offsetGet('CUTYPE');
312
-		if ($cuType instanceof VObject\Parameter) {
313
-			return strtoupper($cuType->getValue());
314
-		}
315
-
316
-		return 'INDIVIDUAL';
317
-	}
318
-
319
-	/**
320
-	 * @param VObject\Property $attendee
321
-	 * @return string
322
-	 */
323
-	private function getPartstatOfAttendee(VObject\Property $attendee):string {
324
-		$partstat = $attendee->offsetGet('PARTSTAT');
325
-		if ($partstat instanceof VObject\Parameter) {
326
-			return strtoupper($partstat->getValue());
327
-		}
328
-
329
-		return 'NEEDS-ACTION';
330
-	}
331
-
332
-	/**
333
-	 * @param VObject\Property $attendee
334
-	 * @return bool
335
-	 */
336
-	private function hasAttendeeMailURI(VObject\Property $attendee):bool {
337
-		return stripos($attendee->getValue(), 'mailto:') === 0;
338
-	}
339
-
340
-	/**
341
-	 * @param VObject\Property $attendee
342
-	 * @return string|null
343
-	 */
344
-	private function getEMailAddressOfAttendee(VObject\Property $attendee):?string {
345
-		if (!$this->hasAttendeeMailURI($attendee)) {
346
-			return null;
347
-		}
348
-
349
-		return substr($attendee->getValue(), 7);
350
-	}
351
-
352
-	/**
353
-	 * @param array $users
354
-	 * @return array
355
-	 */
356
-	private function getEMailAddressesOfAllUsersWithWriteAccessToCalendar(array $users):array {
357
-		$emailAddresses = [];
358
-
359
-		foreach ($users as $user) {
360
-			$emailAddress = $user->getEMailAddress();
361
-			if ($emailAddress) {
362
-				$lang = $this->l10nFactory->getUserLanguage($user);
363
-				if ($lang) {
364
-					$emailAddresses[$emailAddress] = [
365
-						'LANG' => $lang,
366
-					];
367
-				} else {
368
-					$emailAddresses[$emailAddress] = [];
369
-				}
370
-			}
371
-		}
372
-
373
-		return $emailAddresses;
374
-	}
375
-
376
-	/**
377
-	 * @param IL10N $l10n
378
-	 * @param VEvent $vevent
379
-	 * @return string
380
-	 * @throws \Exception
381
-	 */
382
-	private function generateDateString(IL10N $l10n, VEvent $vevent):string {
383
-		$isAllDay = $vevent->DTSTART instanceof Property\ICalendar\Date;
384
-
385
-		/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
386
-		/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */
387
-		/** @var \DateTimeImmutable $dtstartDt */
388
-		$dtstartDt = $vevent->DTSTART->getDateTime();
389
-		/** @var \DateTimeImmutable $dtendDt */
390
-		$dtendDt = $this->getDTEndFromEvent($vevent)->getDateTime();
391
-
392
-		$diff = $dtstartDt->diff($dtendDt);
393
-
394
-		$dtstartDt = new \DateTime($dtstartDt->format(\DateTime::ATOM));
395
-		$dtendDt = new \DateTime($dtendDt->format(\DateTime::ATOM));
396
-
397
-		if ($isAllDay) {
398
-			// One day event
399
-			if ($diff->days === 1) {
400
-				return $this->getDateString($l10n, $dtstartDt);
401
-			}
402
-
403
-			return implode(' - ', [
404
-				$this->getDateString($l10n, $dtstartDt),
405
-				$this->getDateString($l10n, $dtendDt),
406
-			]);
407
-		}
408
-
409
-		$startTimezone = $endTimezone = null;
410
-		if (!$vevent->DTSTART->isFloating()) {
411
-			$startTimezone = $vevent->DTSTART->getDateTime()->getTimezone()->getName();
412
-			$endTimezone = $this->getDTEndFromEvent($vevent)->getDateTime()->getTimezone()->getName();
413
-		}
414
-
415
-		$localeStart = implode(', ', [
416
-			$this->getWeekDayName($l10n, $dtstartDt),
417
-			$this->getDateTimeString($l10n, $dtstartDt)
418
-		]);
419
-
420
-		// always show full date with timezone if timezones are different
421
-		if ($startTimezone !== $endTimezone) {
422
-			$localeEnd = implode(', ', [
423
-				$this->getWeekDayName($l10n, $dtendDt),
424
-				$this->getDateTimeString($l10n, $dtendDt)
425
-			]);
426
-
427
-			return $localeStart
428
-				. ' (' . $startTimezone . ') '
429
-				. ' - '
430
-				. $localeEnd
431
-				. ' (' . $endTimezone . ')';
432
-		}
433
-
434
-		// Show only the time if the day is the same
435
-		$localeEnd = $this->isDayEqual($dtstartDt, $dtendDt)
436
-			? $this->getTimeString($l10n, $dtendDt)
437
-			: implode(', ', [
438
-				$this->getWeekDayName($l10n, $dtendDt),
439
-				$this->getDateTimeString($l10n, $dtendDt)
440
-			]);
441
-
442
-		return $localeStart
443
-			. ' - '
444
-			. $localeEnd
445
-			. ' (' . $startTimezone . ')';
446
-	}
447
-
448
-	/**
449
-	 * @param DateTime $dtStart
450
-	 * @param DateTime $dtEnd
451
-	 * @return bool
452
-	 */
453
-	private function isDayEqual(DateTime $dtStart,
454
-								DateTime $dtEnd):bool {
455
-		return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
456
-	}
457
-
458
-	/**
459
-	 * @param IL10N $l10n
460
-	 * @param DateTime $dt
461
-	 * @return string
462
-	 */
463
-	private function getWeekDayName(IL10N $l10n, DateTime $dt):string {
464
-		return $l10n->l('weekdayName', $dt, ['width' => 'abbreviated']);
465
-	}
466
-
467
-	/**
468
-	 * @param IL10N $l10n
469
-	 * @param DateTime $dt
470
-	 * @return string
471
-	 */
472
-	private function getDateString(IL10N $l10n, DateTime $dt):string {
473
-		return $l10n->l('date', $dt, ['width' => 'medium']);
474
-	}
475
-
476
-	/**
477
-	 * @param IL10N $l10n
478
-	 * @param DateTime $dt
479
-	 * @return string
480
-	 */
481
-	private function getDateTimeString(IL10N $l10n, DateTime $dt):string {
482
-		return $l10n->l('datetime', $dt, ['width' => 'medium|short']);
483
-	}
484
-
485
-	/**
486
-	 * @param IL10N $l10n
487
-	 * @param DateTime $dt
488
-	 * @return string
489
-	 */
490
-	private function getTimeString(IL10N $l10n, DateTime $dt):string {
491
-		return $l10n->l('time', $dt, ['width' => 'short']);
492
-	}
493
-
494
-	/**
495
-	 * @param VEvent $vevent
496
-	 * @param IL10N $l10n
497
-	 * @return string
498
-	 */
499
-	private function getTitleFromVEvent(VEvent $vevent, IL10N $l10n):string {
500
-		if (isset($vevent->SUMMARY)) {
501
-			return (string)$vevent->SUMMARY;
502
-		}
503
-
504
-		return $l10n->t('Untitled event');
505
-	}
55
+    /** @var string */
56
+    public const NOTIFICATION_TYPE = 'EMAIL';
57
+
58
+    /** @var IMailer */
59
+    private $mailer;
60
+
61
+    /**
62
+     * @param IConfig $config
63
+     * @param IMailer $mailer
64
+     * @param ILogger $logger
65
+     * @param L10NFactory $l10nFactory
66
+     * @param IUrlGenerator $urlGenerator
67
+     */
68
+    public function __construct(IConfig $config,
69
+                                IMailer $mailer,
70
+                                ILogger $logger,
71
+                                L10NFactory $l10nFactory,
72
+                                IURLGenerator $urlGenerator) {
73
+        parent::__construct($logger, $l10nFactory, $urlGenerator, $config);
74
+        $this->mailer = $mailer;
75
+    }
76
+
77
+    /**
78
+     * Send out notification via email
79
+     *
80
+     * @param VEvent $vevent
81
+     * @param string $calendarDisplayName
82
+     * @param array $users
83
+     * @throws \Exception
84
+     */
85
+    public function send(VEvent $vevent,
86
+                            string $calendarDisplayName,
87
+                            array $users = []):void {
88
+        $fallbackLanguage = $this->getFallbackLanguage();
89
+
90
+        $emailAddressesOfSharees = $this->getEMailAddressesOfAllUsersWithWriteAccessToCalendar($users);
91
+        $emailAddressesOfAttendees = $this->getAllEMailAddressesFromEvent($vevent);
92
+
93
+        // Quote from php.net:
94
+        // If the input arrays have the same string keys, then the later value for that key will overwrite the previous one.
95
+        // => if there are duplicate email addresses, it will always take the system value
96
+        $emailAddresses = array_merge(
97
+            $emailAddressesOfAttendees,
98
+            $emailAddressesOfSharees
99
+        );
100
+
101
+        $sortedByLanguage = $this->sortEMailAddressesByLanguage($emailAddresses, $fallbackLanguage);
102
+        $organizer = $this->getOrganizerEMailAndNameFromEvent($vevent);
103
+
104
+        foreach ($sortedByLanguage as $lang => $emailAddresses) {
105
+            if (!$this->hasL10NForLang($lang)) {
106
+                $lang = $fallbackLanguage;
107
+            }
108
+            $l10n = $this->getL10NForLang($lang);
109
+            $fromEMail = \OCP\Util::getDefaultEmailAddress('reminders-noreply');
110
+
111
+            $template = $this->mailer->createEMailTemplate('dav.calendarReminder');
112
+            $template->addHeader();
113
+            $this->addSubjectAndHeading($template, $l10n, $vevent);
114
+            $this->addBulletList($template, $l10n, $calendarDisplayName, $vevent);
115
+            $template->addFooter();
116
+
117
+            foreach ($emailAddresses as $emailAddress) {
118
+                if (!$this->mailer->validateMailAddress($emailAddress)) {
119
+                    $this->logger->error('Email address {address} for reminder notification is incorrect', ['app' => 'dav', 'address' => $emailAddress]);
120
+                    continue;
121
+                }
122
+
123
+                $message = $this->mailer->createMessage();
124
+                $message->setFrom([$fromEMail]);
125
+                if ($organizer) {
126
+                    $message->setReplyTo($organizer);
127
+                }
128
+                $message->setTo([$emailAddress]);
129
+                $message->useTemplate($template);
130
+
131
+                try {
132
+                    $failed = $this->mailer->send($message);
133
+                    if ($failed) {
134
+                        $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]);
135
+                    }
136
+                } catch (\Exception $ex) {
137
+                    $this->logger->logException($ex, ['app' => 'dav']);
138
+                }
139
+            }
140
+        }
141
+    }
142
+
143
+    /**
144
+     * @param IEMailTemplate $template
145
+     * @param IL10N $l10n
146
+     * @param VEvent $vevent
147
+     */
148
+    private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n, VEvent $vevent):void {
149
+        $template->setSubject('Notification: ' . $this->getTitleFromVEvent($vevent, $l10n));
150
+        $template->addHeading($this->getTitleFromVEvent($vevent, $l10n));
151
+    }
152
+
153
+    /**
154
+     * @param IEMailTemplate $template
155
+     * @param IL10N $l10n
156
+     * @param string $calendarDisplayName
157
+     * @param array $eventData
158
+     */
159
+    private function addBulletList(IEMailTemplate $template,
160
+                                    IL10N $l10n,
161
+                                    string $calendarDisplayName,
162
+                                    VEvent $vevent):void {
163
+        $template->addBodyListItem($calendarDisplayName, $l10n->t('Calendar:'),
164
+            $this->getAbsoluteImagePath('actions/info.png'));
165
+
166
+        $template->addBodyListItem($this->generateDateString($l10n, $vevent), $l10n->t('Date:'),
167
+            $this->getAbsoluteImagePath('places/calendar.png'));
168
+
169
+        if (isset($vevent->LOCATION)) {
170
+            $template->addBodyListItem((string) $vevent->LOCATION, $l10n->t('Where:'),
171
+                $this->getAbsoluteImagePath('actions/address.png'));
172
+        }
173
+        if (isset($vevent->DESCRIPTION)) {
174
+            $template->addBodyListItem((string) $vevent->DESCRIPTION, $l10n->t('Description:'),
175
+                $this->getAbsoluteImagePath('actions/more.png'));
176
+        }
177
+    }
178
+
179
+    /**
180
+     * @param string $path
181
+     * @return string
182
+     */
183
+    private function getAbsoluteImagePath(string $path):string {
184
+        return $this->urlGenerator->getAbsoluteURL(
185
+            $this->urlGenerator->imagePath('core', $path)
186
+        );
187
+    }
188
+
189
+    /**
190
+     * @param VEvent $vevent
191
+     * @return array|null
192
+     */
193
+    private function getOrganizerEMailAndNameFromEvent(VEvent $vevent):?array {
194
+        if (!$vevent->ORGANIZER) {
195
+            return null;
196
+        }
197
+
198
+        $organizer = $vevent->ORGANIZER;
199
+        if (strcasecmp($organizer->getValue(), 'mailto:') !== 0) {
200
+            return null;
201
+        }
202
+
203
+        $organizerEMail = substr($organizer->getValue(), 7);
204
+
205
+        if (!$this->mailer->validateMailAddress($organizerEMail)) {
206
+            return null;
207
+        }
208
+
209
+        $name = $organizer->offsetGet('CN');
210
+        if ($name instanceof Parameter) {
211
+            return [$organizerEMail => $name];
212
+        }
213
+
214
+        return [$organizerEMail];
215
+    }
216
+
217
+    /**
218
+     * @param array $emails
219
+     * @param string $defaultLanguage
220
+     * @return array
221
+     */
222
+    private function sortEMailAddressesByLanguage(array $emails,
223
+                                                    string $defaultLanguage):array {
224
+        $sortedByLanguage = [];
225
+
226
+        foreach ($emails as $emailAddress => $parameters) {
227
+            if (isset($parameters['LANG'])) {
228
+                $lang = $parameters['LANG'];
229
+            } else {
230
+                $lang = $defaultLanguage;
231
+            }
232
+
233
+            if (!isset($sortedByLanguage[$lang])) {
234
+                $sortedByLanguage[$lang] = [];
235
+            }
236
+
237
+            $sortedByLanguage[$lang][] = $emailAddress;
238
+        }
239
+
240
+        return $sortedByLanguage;
241
+    }
242
+
243
+    /**
244
+     * @param VEvent $vevent
245
+     * @return array
246
+     */
247
+    private function getAllEMailAddressesFromEvent(VEvent $vevent):array {
248
+        $emailAddresses = [];
249
+
250
+        if (isset($vevent->ATTENDEE)) {
251
+            foreach ($vevent->ATTENDEE as $attendee) {
252
+                if (!($attendee instanceof VObject\Property)) {
253
+                    continue;
254
+                }
255
+
256
+                $cuType = $this->getCUTypeOfAttendee($attendee);
257
+                if (\in_array($cuType, ['RESOURCE', 'ROOM', 'UNKNOWN'])) {
258
+                    // Don't send emails to things
259
+                    continue;
260
+                }
261
+
262
+                $partstat = $this->getPartstatOfAttendee($attendee);
263
+                if ($partstat === 'DECLINED') {
264
+                    // Don't send out emails to people who declined
265
+                    continue;
266
+                }
267
+                if ($partstat === 'DELEGATED') {
268
+                    $delegates = $attendee->offsetGet('DELEGATED-TO');
269
+                    if (!($delegates instanceof VObject\Parameter)) {
270
+                        continue;
271
+                    }
272
+
273
+                    $emailAddressesOfDelegates = $delegates->getParts();
274
+                    foreach ($emailAddressesOfDelegates as $addressesOfDelegate) {
275
+                        if (strcasecmp($addressesOfDelegate, 'mailto:') === 0) {
276
+                            $emailAddresses[substr($addressesOfDelegate, 7)] = [];
277
+                        }
278
+                    }
279
+
280
+                    continue;
281
+                }
282
+
283
+                $emailAddressOfAttendee = $this->getEMailAddressOfAttendee($attendee);
284
+                if ($emailAddressOfAttendee !== null) {
285
+                    $properties = [];
286
+
287
+                    $langProp = $attendee->offsetGet('LANG');
288
+                    if ($langProp instanceof VObject\Parameter) {
289
+                        $properties['LANG'] = $langProp->getValue();
290
+                    }
291
+
292
+                    $emailAddresses[$emailAddressOfAttendee] = $properties;
293
+                }
294
+            }
295
+        }
296
+
297
+        if (isset($vevent->ORGANIZER) && $this->hasAttendeeMailURI($vevent->ORGANIZER)) {
298
+            $emailAddresses[$this->getEMailAddressOfAttendee($vevent->ORGANIZER)] = [];
299
+        }
300
+
301
+        return $emailAddresses;
302
+    }
303
+
304
+
305
+
306
+    /**
307
+     * @param VObject\Property $attendee
308
+     * @return string
309
+     */
310
+    private function getCUTypeOfAttendee(VObject\Property $attendee):string {
311
+        $cuType = $attendee->offsetGet('CUTYPE');
312
+        if ($cuType instanceof VObject\Parameter) {
313
+            return strtoupper($cuType->getValue());
314
+        }
315
+
316
+        return 'INDIVIDUAL';
317
+    }
318
+
319
+    /**
320
+     * @param VObject\Property $attendee
321
+     * @return string
322
+     */
323
+    private function getPartstatOfAttendee(VObject\Property $attendee):string {
324
+        $partstat = $attendee->offsetGet('PARTSTAT');
325
+        if ($partstat instanceof VObject\Parameter) {
326
+            return strtoupper($partstat->getValue());
327
+        }
328
+
329
+        return 'NEEDS-ACTION';
330
+    }
331
+
332
+    /**
333
+     * @param VObject\Property $attendee
334
+     * @return bool
335
+     */
336
+    private function hasAttendeeMailURI(VObject\Property $attendee):bool {
337
+        return stripos($attendee->getValue(), 'mailto:') === 0;
338
+    }
339
+
340
+    /**
341
+     * @param VObject\Property $attendee
342
+     * @return string|null
343
+     */
344
+    private function getEMailAddressOfAttendee(VObject\Property $attendee):?string {
345
+        if (!$this->hasAttendeeMailURI($attendee)) {
346
+            return null;
347
+        }
348
+
349
+        return substr($attendee->getValue(), 7);
350
+    }
351
+
352
+    /**
353
+     * @param array $users
354
+     * @return array
355
+     */
356
+    private function getEMailAddressesOfAllUsersWithWriteAccessToCalendar(array $users):array {
357
+        $emailAddresses = [];
358
+
359
+        foreach ($users as $user) {
360
+            $emailAddress = $user->getEMailAddress();
361
+            if ($emailAddress) {
362
+                $lang = $this->l10nFactory->getUserLanguage($user);
363
+                if ($lang) {
364
+                    $emailAddresses[$emailAddress] = [
365
+                        'LANG' => $lang,
366
+                    ];
367
+                } else {
368
+                    $emailAddresses[$emailAddress] = [];
369
+                }
370
+            }
371
+        }
372
+
373
+        return $emailAddresses;
374
+    }
375
+
376
+    /**
377
+     * @param IL10N $l10n
378
+     * @param VEvent $vevent
379
+     * @return string
380
+     * @throws \Exception
381
+     */
382
+    private function generateDateString(IL10N $l10n, VEvent $vevent):string {
383
+        $isAllDay = $vevent->DTSTART instanceof Property\ICalendar\Date;
384
+
385
+        /** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
386
+        /** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */
387
+        /** @var \DateTimeImmutable $dtstartDt */
388
+        $dtstartDt = $vevent->DTSTART->getDateTime();
389
+        /** @var \DateTimeImmutable $dtendDt */
390
+        $dtendDt = $this->getDTEndFromEvent($vevent)->getDateTime();
391
+
392
+        $diff = $dtstartDt->diff($dtendDt);
393
+
394
+        $dtstartDt = new \DateTime($dtstartDt->format(\DateTime::ATOM));
395
+        $dtendDt = new \DateTime($dtendDt->format(\DateTime::ATOM));
396
+
397
+        if ($isAllDay) {
398
+            // One day event
399
+            if ($diff->days === 1) {
400
+                return $this->getDateString($l10n, $dtstartDt);
401
+            }
402
+
403
+            return implode(' - ', [
404
+                $this->getDateString($l10n, $dtstartDt),
405
+                $this->getDateString($l10n, $dtendDt),
406
+            ]);
407
+        }
408
+
409
+        $startTimezone = $endTimezone = null;
410
+        if (!$vevent->DTSTART->isFloating()) {
411
+            $startTimezone = $vevent->DTSTART->getDateTime()->getTimezone()->getName();
412
+            $endTimezone = $this->getDTEndFromEvent($vevent)->getDateTime()->getTimezone()->getName();
413
+        }
414
+
415
+        $localeStart = implode(', ', [
416
+            $this->getWeekDayName($l10n, $dtstartDt),
417
+            $this->getDateTimeString($l10n, $dtstartDt)
418
+        ]);
419
+
420
+        // always show full date with timezone if timezones are different
421
+        if ($startTimezone !== $endTimezone) {
422
+            $localeEnd = implode(', ', [
423
+                $this->getWeekDayName($l10n, $dtendDt),
424
+                $this->getDateTimeString($l10n, $dtendDt)
425
+            ]);
426
+
427
+            return $localeStart
428
+                . ' (' . $startTimezone . ') '
429
+                . ' - '
430
+                . $localeEnd
431
+                . ' (' . $endTimezone . ')';
432
+        }
433
+
434
+        // Show only the time if the day is the same
435
+        $localeEnd = $this->isDayEqual($dtstartDt, $dtendDt)
436
+            ? $this->getTimeString($l10n, $dtendDt)
437
+            : implode(', ', [
438
+                $this->getWeekDayName($l10n, $dtendDt),
439
+                $this->getDateTimeString($l10n, $dtendDt)
440
+            ]);
441
+
442
+        return $localeStart
443
+            . ' - '
444
+            . $localeEnd
445
+            . ' (' . $startTimezone . ')';
446
+    }
447
+
448
+    /**
449
+     * @param DateTime $dtStart
450
+     * @param DateTime $dtEnd
451
+     * @return bool
452
+     */
453
+    private function isDayEqual(DateTime $dtStart,
454
+                                DateTime $dtEnd):bool {
455
+        return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
456
+    }
457
+
458
+    /**
459
+     * @param IL10N $l10n
460
+     * @param DateTime $dt
461
+     * @return string
462
+     */
463
+    private function getWeekDayName(IL10N $l10n, DateTime $dt):string {
464
+        return $l10n->l('weekdayName', $dt, ['width' => 'abbreviated']);
465
+    }
466
+
467
+    /**
468
+     * @param IL10N $l10n
469
+     * @param DateTime $dt
470
+     * @return string
471
+     */
472
+    private function getDateString(IL10N $l10n, DateTime $dt):string {
473
+        return $l10n->l('date', $dt, ['width' => 'medium']);
474
+    }
475
+
476
+    /**
477
+     * @param IL10N $l10n
478
+     * @param DateTime $dt
479
+     * @return string
480
+     */
481
+    private function getDateTimeString(IL10N $l10n, DateTime $dt):string {
482
+        return $l10n->l('datetime', $dt, ['width' => 'medium|short']);
483
+    }
484
+
485
+    /**
486
+     * @param IL10N $l10n
487
+     * @param DateTime $dt
488
+     * @return string
489
+     */
490
+    private function getTimeString(IL10N $l10n, DateTime $dt):string {
491
+        return $l10n->l('time', $dt, ['width' => 'short']);
492
+    }
493
+
494
+    /**
495
+     * @param VEvent $vevent
496
+     * @param IL10N $l10n
497
+     * @return string
498
+     */
499
+    private function getTitleFromVEvent(VEvent $vevent, IL10N $l10n):string {
500
+        if (isset($vevent->SUMMARY)) {
501
+            return (string)$vevent->SUMMARY;
502
+        }
503
+
504
+        return $l10n->t('Untitled event');
505
+    }
506 506
 }
Please login to merge, or discard this patch.