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