Completed
Push — master ( 64ba0f...5b554f )
by Daniel
27:10 queued 16s
created
apps/dav/lib/CalDAV/Schedule/IMipPlugin.php 1 patch
Indentation   +261 added lines, -261 removed lines patch added patch discarded remove patch
@@ -46,175 +46,175 @@  discard block
 block discarded – undo
46 46
  */
47 47
 class IMipPlugin extends SabreIMipPlugin {
48 48
 	
49
-	private ?VCalendar $vCalendar = null;
50
-	public const MAX_DATE = '2038-01-01';
51
-	public const METHOD_REQUEST = 'request';
52
-	public const METHOD_REPLY = 'reply';
53
-	public const METHOD_CANCEL = 'cancel';
54
-	public const IMIP_INDENT = 15;
55
-
56
-	public function __construct(
57
-		private IAppConfig $config,
58
-		private IMailer $mailer,
59
-		private LoggerInterface $logger,
60
-		private ITimeFactory $timeFactory,
61
-		private Defaults $defaults,
62
-		private IUserSession $userSession,
63
-		private IMipService $imipService,
64
-		private EventComparisonService $eventComparisonService,
65
-		private IMailManager $mailManager,
66
-	) {
67
-		parent::__construct('');
68
-	}
69
-
70
-	public function initialize(DAV\Server $server): void {
71
-		parent::initialize($server);
72
-		$server->on('beforeWriteContent', [$this, 'beforeWriteContent'], 10);
73
-	}
74
-
75
-	/**
76
-	 * Check quota before writing content
77
-	 *
78
-	 * @param string $uri target file URI
79
-	 * @param INode $node Sabre Node
80
-	 * @param resource $data data
81
-	 * @param bool $modified modified
82
-	 */
83
-	public function beforeWriteContent($uri, INode $node, $data, $modified): void {
84
-		if (!$node instanceof CalendarObject) {
85
-			return;
86
-		}
87
-		/** @var VCalendar $vCalendar */
88
-		$vCalendar = Reader::read($node->get());
89
-		$this->setVCalendar($vCalendar);
90
-	}
91
-
92
-	/**
93
-	 * Event handler for the 'schedule' event.
94
-	 *
95
-	 * @param Message $iTipMessage
96
-	 * @return void
97
-	 */
98
-	public function schedule(Message $iTipMessage) {
99
-
100
-		// Not sending any emails if the system considers the update insignificant
101
-		if (!$iTipMessage->significantChange) {
102
-			if (!$iTipMessage->scheduleStatus) {
103
-				$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
104
-			}
105
-			return;
106
-		}
107
-
108
-		if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto'
109
-			|| parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') {
110
-			return;
111
-		}
112
-
113
-		// don't send out mails for events that already took place
114
-		$lastOccurrence = $this->imipService->getLastOccurrence($iTipMessage->message);
115
-		$currentTime = $this->timeFactory->getTime();
116
-		if ($lastOccurrence < $currentTime) {
117
-			return;
118
-		}
119
-
120
-		// Strip off mailto:
121
-		$recipient = substr($iTipMessage->recipient, 7);
122
-		if (!$this->mailer->validateMailAddress($recipient)) {
123
-			// Nothing to send if the recipient doesn't have a valid email address
124
-			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
125
-			return;
126
-		}
127
-		$recipientName = $iTipMessage->recipientName ? (string)$iTipMessage->recipientName : null;
128
-
129
-		$newEvents = $iTipMessage->message;
130
-		$oldEvents = $this->getVCalendar();
131
-
132
-		$modified = $this->eventComparisonService->findModified($newEvents, $oldEvents);
133
-		/** @var VEvent $vEvent */
134
-		$vEvent = array_pop($modified['new']);
135
-		/** @var VEvent $oldVevent */
136
-		$oldVevent = !empty($modified['old']) && is_array($modified['old']) ? array_pop($modified['old']) : null;
137
-		$isModified = isset($oldVevent);
138
-
139
-		// No changed events after all - this shouldn't happen if there is significant change yet here we are
140
-		// The scheduling status is debatable
141
-		if (empty($vEvent)) {
142
-			$this->logger->warning('iTip message said the change was significant but comparison did not detect any updated VEvents');
143
-			$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
144
-			return;
145
-		}
146
-
147
-		// we (should) have one event component left
148
-		// as the ITip\Broker creates one iTip message per change
149
-		// and triggers the "schedule" event once per message
150
-		// we also might not have an old event as this could be a new
151
-		// invitation, or a new recurrence exception
152
-		$attendee = $this->imipService->getCurrentAttendee($iTipMessage);
153
-		if ($attendee === null) {
154
-			$uid = $vEvent->UID ?? 'no UID found';
155
-			$this->logger->debug('Could not find recipient ' . $recipient . ' as attendee for event with UID ' . $uid);
156
-			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
157
-			return;
158
-		}
159
-		// Don't send emails to rooms, resources and circles
160
-		if ($this->imipService->isRoomOrResource($attendee)
161
-				|| $this->imipService->isCircle($attendee)) {
162
-			$this->logger->debug('No invitation sent as recipient is room, resource or circle', [
163
-				'attendee' => $recipient,
164
-			]);
165
-			$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
166
-			return;
167
-		}
168
-		$this->imipService->setL10n($attendee);
169
-
170
-		// Build the sender name.
171
-		// Due to a bug in sabre, the senderName property for an iTIP message can actually also be a VObject Property
172
-		// If the iTIP message senderName is null or empty use the user session name as the senderName
173
-		if (($iTipMessage->senderName instanceof Parameter) && !empty(trim($iTipMessage->senderName->getValue()))) {
174
-			$senderName = trim($iTipMessage->senderName->getValue());
175
-		} elseif (is_string($iTipMessage->senderName) && !empty(trim($iTipMessage->senderName))) {
176
-			$senderName = trim($iTipMessage->senderName);
177
-		} elseif ($this->userSession->getUser() !== null) {
178
-			$senderName = trim($this->userSession->getUser()->getDisplayName());
179
-		} else {
180
-			$senderName = '';
181
-		}
182
-
183
-		$sender = substr($iTipMessage->sender, 7);
184
-
185
-		$replyingAttendee = null;
186
-		switch (strtolower($iTipMessage->method)) {
187
-			case self::METHOD_REPLY:
188
-				$method = self::METHOD_REPLY;
189
-				$data = $this->imipService->buildBodyData($vEvent, $oldVevent);
190
-				$replyingAttendee = $this->imipService->getReplyingAttendee($iTipMessage);
191
-				break;
192
-			case self::METHOD_CANCEL:
193
-				$method = self::METHOD_CANCEL;
194
-				$data = $this->imipService->buildCancelledBodyData($vEvent);
195
-				break;
196
-			default:
197
-				$method = self::METHOD_REQUEST;
198
-				$data = $this->imipService->buildBodyData($vEvent, $oldVevent);
199
-				break;
200
-		}
201
-
202
-		$data['attendee_name'] = ($recipientName ?: $recipient);
203
-		$data['invitee_name'] = ($senderName ?: $sender);
204
-
205
-		$fromEMail = Util::getDefaultEmailAddress('invitations-noreply');
206
-		$fromName = $this->imipService->getFrom($senderName, $this->defaults->getName());
207
-
208
-		$template = $this->mailer->createEMailTemplate('dav.calendarInvite.' . $method, $data);
209
-		$template->addHeader();
210
-
211
-		$this->imipService->addSubjectAndHeading($template, $method, $data['invitee_name'], $data['meeting_title'], $isModified, $replyingAttendee);
212
-		$this->imipService->addBulletList($template, $vEvent, $data);
213
-
214
-		// Only add response buttons to invitation requests: Fix Issue #11230
215
-		if (strcasecmp($method, self::METHOD_REQUEST) === 0 && $this->imipService->getAttendeeRsvpOrReqForParticipant($attendee)) {
216
-
217
-			/*
49
+    private ?VCalendar $vCalendar = null;
50
+    public const MAX_DATE = '2038-01-01';
51
+    public const METHOD_REQUEST = 'request';
52
+    public const METHOD_REPLY = 'reply';
53
+    public const METHOD_CANCEL = 'cancel';
54
+    public const IMIP_INDENT = 15;
55
+
56
+    public function __construct(
57
+        private IAppConfig $config,
58
+        private IMailer $mailer,
59
+        private LoggerInterface $logger,
60
+        private ITimeFactory $timeFactory,
61
+        private Defaults $defaults,
62
+        private IUserSession $userSession,
63
+        private IMipService $imipService,
64
+        private EventComparisonService $eventComparisonService,
65
+        private IMailManager $mailManager,
66
+    ) {
67
+        parent::__construct('');
68
+    }
69
+
70
+    public function initialize(DAV\Server $server): void {
71
+        parent::initialize($server);
72
+        $server->on('beforeWriteContent', [$this, 'beforeWriteContent'], 10);
73
+    }
74
+
75
+    /**
76
+     * Check quota before writing content
77
+     *
78
+     * @param string $uri target file URI
79
+     * @param INode $node Sabre Node
80
+     * @param resource $data data
81
+     * @param bool $modified modified
82
+     */
83
+    public function beforeWriteContent($uri, INode $node, $data, $modified): void {
84
+        if (!$node instanceof CalendarObject) {
85
+            return;
86
+        }
87
+        /** @var VCalendar $vCalendar */
88
+        $vCalendar = Reader::read($node->get());
89
+        $this->setVCalendar($vCalendar);
90
+    }
91
+
92
+    /**
93
+     * Event handler for the 'schedule' event.
94
+     *
95
+     * @param Message $iTipMessage
96
+     * @return void
97
+     */
98
+    public function schedule(Message $iTipMessage) {
99
+
100
+        // Not sending any emails if the system considers the update insignificant
101
+        if (!$iTipMessage->significantChange) {
102
+            if (!$iTipMessage->scheduleStatus) {
103
+                $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
104
+            }
105
+            return;
106
+        }
107
+
108
+        if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto'
109
+            || parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') {
110
+            return;
111
+        }
112
+
113
+        // don't send out mails for events that already took place
114
+        $lastOccurrence = $this->imipService->getLastOccurrence($iTipMessage->message);
115
+        $currentTime = $this->timeFactory->getTime();
116
+        if ($lastOccurrence < $currentTime) {
117
+            return;
118
+        }
119
+
120
+        // Strip off mailto:
121
+        $recipient = substr($iTipMessage->recipient, 7);
122
+        if (!$this->mailer->validateMailAddress($recipient)) {
123
+            // Nothing to send if the recipient doesn't have a valid email address
124
+            $iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
125
+            return;
126
+        }
127
+        $recipientName = $iTipMessage->recipientName ? (string)$iTipMessage->recipientName : null;
128
+
129
+        $newEvents = $iTipMessage->message;
130
+        $oldEvents = $this->getVCalendar();
131
+
132
+        $modified = $this->eventComparisonService->findModified($newEvents, $oldEvents);
133
+        /** @var VEvent $vEvent */
134
+        $vEvent = array_pop($modified['new']);
135
+        /** @var VEvent $oldVevent */
136
+        $oldVevent = !empty($modified['old']) && is_array($modified['old']) ? array_pop($modified['old']) : null;
137
+        $isModified = isset($oldVevent);
138
+
139
+        // No changed events after all - this shouldn't happen if there is significant change yet here we are
140
+        // The scheduling status is debatable
141
+        if (empty($vEvent)) {
142
+            $this->logger->warning('iTip message said the change was significant but comparison did not detect any updated VEvents');
143
+            $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
144
+            return;
145
+        }
146
+
147
+        // we (should) have one event component left
148
+        // as the ITip\Broker creates one iTip message per change
149
+        // and triggers the "schedule" event once per message
150
+        // we also might not have an old event as this could be a new
151
+        // invitation, or a new recurrence exception
152
+        $attendee = $this->imipService->getCurrentAttendee($iTipMessage);
153
+        if ($attendee === null) {
154
+            $uid = $vEvent->UID ?? 'no UID found';
155
+            $this->logger->debug('Could not find recipient ' . $recipient . ' as attendee for event with UID ' . $uid);
156
+            $iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
157
+            return;
158
+        }
159
+        // Don't send emails to rooms, resources and circles
160
+        if ($this->imipService->isRoomOrResource($attendee)
161
+                || $this->imipService->isCircle($attendee)) {
162
+            $this->logger->debug('No invitation sent as recipient is room, resource or circle', [
163
+                'attendee' => $recipient,
164
+            ]);
165
+            $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
166
+            return;
167
+        }
168
+        $this->imipService->setL10n($attendee);
169
+
170
+        // Build the sender name.
171
+        // Due to a bug in sabre, the senderName property for an iTIP message can actually also be a VObject Property
172
+        // If the iTIP message senderName is null or empty use the user session name as the senderName
173
+        if (($iTipMessage->senderName instanceof Parameter) && !empty(trim($iTipMessage->senderName->getValue()))) {
174
+            $senderName = trim($iTipMessage->senderName->getValue());
175
+        } elseif (is_string($iTipMessage->senderName) && !empty(trim($iTipMessage->senderName))) {
176
+            $senderName = trim($iTipMessage->senderName);
177
+        } elseif ($this->userSession->getUser() !== null) {
178
+            $senderName = trim($this->userSession->getUser()->getDisplayName());
179
+        } else {
180
+            $senderName = '';
181
+        }
182
+
183
+        $sender = substr($iTipMessage->sender, 7);
184
+
185
+        $replyingAttendee = null;
186
+        switch (strtolower($iTipMessage->method)) {
187
+            case self::METHOD_REPLY:
188
+                $method = self::METHOD_REPLY;
189
+                $data = $this->imipService->buildBodyData($vEvent, $oldVevent);
190
+                $replyingAttendee = $this->imipService->getReplyingAttendee($iTipMessage);
191
+                break;
192
+            case self::METHOD_CANCEL:
193
+                $method = self::METHOD_CANCEL;
194
+                $data = $this->imipService->buildCancelledBodyData($vEvent);
195
+                break;
196
+            default:
197
+                $method = self::METHOD_REQUEST;
198
+                $data = $this->imipService->buildBodyData($vEvent, $oldVevent);
199
+                break;
200
+        }
201
+
202
+        $data['attendee_name'] = ($recipientName ?: $recipient);
203
+        $data['invitee_name'] = ($senderName ?: $sender);
204
+
205
+        $fromEMail = Util::getDefaultEmailAddress('invitations-noreply');
206
+        $fromName = $this->imipService->getFrom($senderName, $this->defaults->getName());
207
+
208
+        $template = $this->mailer->createEMailTemplate('dav.calendarInvite.' . $method, $data);
209
+        $template->addHeader();
210
+
211
+        $this->imipService->addSubjectAndHeading($template, $method, $data['invitee_name'], $data['meeting_title'], $isModified, $replyingAttendee);
212
+        $this->imipService->addBulletList($template, $vEvent, $data);
213
+
214
+        // Only add response buttons to invitation requests: Fix Issue #11230
215
+        if (strcasecmp($method, self::METHOD_REQUEST) === 0 && $this->imipService->getAttendeeRsvpOrReqForParticipant($attendee)) {
216
+
217
+            /*
218 218
 			** Only offer invitation accept/reject buttons, which link back to the
219 219
 			** nextcloud server, to recipients who can access the nextcloud server via
220 220
 			** their internet/intranet.  Issue #12156
@@ -233,97 +233,97 @@  discard block
 block discarded – undo
233 233
 			** To suppress URLs entirely, set invitation_link_recipients to boolean "no".
234 234
 			*/
235 235
 
236
-			$recipientDomain = substr(strrchr($recipient, '@'), 1);
237
-			$invitationLinkRecipients = explode(',', preg_replace('/\s+/', '', strtolower($this->config->getValueString('dav', 'invitation_link_recipients', 'yes'))));
238
-
239
-			if (strcmp('yes', $invitationLinkRecipients[0]) === 0
240
-				|| in_array(strtolower($recipient), $invitationLinkRecipients)
241
-				|| in_array(strtolower($recipientDomain), $invitationLinkRecipients)) {
242
-				$token = $this->imipService->createInvitationToken($iTipMessage, $vEvent, $lastOccurrence);
243
-				$this->imipService->addResponseButtons($template, $token);
244
-				$this->imipService->addMoreOptionsButton($template, $token);
245
-			}
246
-		}
247
-
248
-		$template->addFooter();
249
-		// convert iTip Message to string
250
-		$itip_msg = $iTipMessage->message->serialize();
251
-
252
-		$user = null;
253
-		$mailService = null;
254
-
255
-		try {
256
-			if ($this->config->getValueBool('core', 'mail_providers_enabled', true)) {
257
-				// retrieve user object
258
-				$user = $this->userSession->getUser();
259
-				if ($user !== null) {
260
-					// retrieve appropriate service with the same address as sender
261
-					$mailService = $this->mailManager->findServiceByAddress($user->getUID(), $sender);
262
-				}
263
-			}
264
-			// evaluate if a mail service was found and has sending capabilities
265
-			if ($mailService !== null && $mailService instanceof IMessageSend) {
266
-				// construct mail message and set required parameters
267
-				$message = $mailService->initiateMessage();
268
-				$message->setFrom(
269
-					(new Address($sender, $fromName))
270
-				);
271
-				$message->setTo(
272
-					(new Address($recipient, $recipientName))
273
-				);
274
-				$message->setSubject($template->renderSubject());
275
-				$message->setBodyPlain($template->renderText());
276
-				$message->setBodyHtml($template->renderHtml());
277
-				$message->setAttachments((new Attachment(
278
-					$itip_msg,
279
-					null,
280
-					'text/calendar; name=event.ics; method=' . $iTipMessage->method,
281
-					true
282
-				)));
283
-				// send message
284
-				$mailService->sendMessage($message);
285
-			} else {
286
-				// construct symfony mailer message and set required parameters
287
-				$message = $this->mailer->createMessage();
288
-				$message->setFrom([$fromEMail => $fromName]);
289
-				$message->setTo(
290
-					(($recipientName !== null) ? [$recipient => $recipientName] : [$recipient])
291
-				);
292
-				$message->setReplyTo(
293
-					(($senderName !== null) ? [$sender => $senderName] : [$sender])
294
-				);
295
-				$message->useTemplate($template);
296
-				$message->attachInline(
297
-					$itip_msg,
298
-					'event.ics',
299
-					'text/calendar; method=' . $iTipMessage->method
300
-				);
301
-				$failed = $this->mailer->send($message);
302
-			}
303
-
304
-			$iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
305
-			if (!empty($failed)) {
306
-				$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]);
307
-				$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
308
-			}
309
-		} catch (\Exception $ex) {
310
-			$this->logger->error($ex->getMessage(), ['app' => 'dav', 'exception' => $ex]);
311
-			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
312
-		}
313
-	}
314
-
315
-	/**
316
-	 * @return ?VCalendar
317
-	 */
318
-	public function getVCalendar(): ?VCalendar {
319
-		return $this->vCalendar;
320
-	}
321
-
322
-	/**
323
-	 * @param ?VCalendar $vCalendar
324
-	 */
325
-	public function setVCalendar(?VCalendar $vCalendar): void {
326
-		$this->vCalendar = $vCalendar;
327
-	}
236
+            $recipientDomain = substr(strrchr($recipient, '@'), 1);
237
+            $invitationLinkRecipients = explode(',', preg_replace('/\s+/', '', strtolower($this->config->getValueString('dav', 'invitation_link_recipients', 'yes'))));
238
+
239
+            if (strcmp('yes', $invitationLinkRecipients[0]) === 0
240
+                || in_array(strtolower($recipient), $invitationLinkRecipients)
241
+                || in_array(strtolower($recipientDomain), $invitationLinkRecipients)) {
242
+                $token = $this->imipService->createInvitationToken($iTipMessage, $vEvent, $lastOccurrence);
243
+                $this->imipService->addResponseButtons($template, $token);
244
+                $this->imipService->addMoreOptionsButton($template, $token);
245
+            }
246
+        }
247
+
248
+        $template->addFooter();
249
+        // convert iTip Message to string
250
+        $itip_msg = $iTipMessage->message->serialize();
251
+
252
+        $user = null;
253
+        $mailService = null;
254
+
255
+        try {
256
+            if ($this->config->getValueBool('core', 'mail_providers_enabled', true)) {
257
+                // retrieve user object
258
+                $user = $this->userSession->getUser();
259
+                if ($user !== null) {
260
+                    // retrieve appropriate service with the same address as sender
261
+                    $mailService = $this->mailManager->findServiceByAddress($user->getUID(), $sender);
262
+                }
263
+            }
264
+            // evaluate if a mail service was found and has sending capabilities
265
+            if ($mailService !== null && $mailService instanceof IMessageSend) {
266
+                // construct mail message and set required parameters
267
+                $message = $mailService->initiateMessage();
268
+                $message->setFrom(
269
+                    (new Address($sender, $fromName))
270
+                );
271
+                $message->setTo(
272
+                    (new Address($recipient, $recipientName))
273
+                );
274
+                $message->setSubject($template->renderSubject());
275
+                $message->setBodyPlain($template->renderText());
276
+                $message->setBodyHtml($template->renderHtml());
277
+                $message->setAttachments((new Attachment(
278
+                    $itip_msg,
279
+                    null,
280
+                    'text/calendar; name=event.ics; method=' . $iTipMessage->method,
281
+                    true
282
+                )));
283
+                // send message
284
+                $mailService->sendMessage($message);
285
+            } else {
286
+                // construct symfony mailer message and set required parameters
287
+                $message = $this->mailer->createMessage();
288
+                $message->setFrom([$fromEMail => $fromName]);
289
+                $message->setTo(
290
+                    (($recipientName !== null) ? [$recipient => $recipientName] : [$recipient])
291
+                );
292
+                $message->setReplyTo(
293
+                    (($senderName !== null) ? [$sender => $senderName] : [$sender])
294
+                );
295
+                $message->useTemplate($template);
296
+                $message->attachInline(
297
+                    $itip_msg,
298
+                    'event.ics',
299
+                    'text/calendar; method=' . $iTipMessage->method
300
+                );
301
+                $failed = $this->mailer->send($message);
302
+            }
303
+
304
+            $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
305
+            if (!empty($failed)) {
306
+                $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]);
307
+                $iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
308
+            }
309
+        } catch (\Exception $ex) {
310
+            $this->logger->error($ex->getMessage(), ['app' => 'dav', 'exception' => $ex]);
311
+            $iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
312
+        }
313
+    }
314
+
315
+    /**
316
+     * @return ?VCalendar
317
+     */
318
+    public function getVCalendar(): ?VCalendar {
319
+        return $this->vCalendar;
320
+    }
321
+
322
+    /**
323
+     * @param ?VCalendar $vCalendar
324
+     */
325
+    public function setVCalendar(?VCalendar $vCalendar): void {
326
+        $this->vCalendar = $vCalendar;
327
+    }
328 328
 
329 329
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Schedule/IMipService.php 1 patch
Indentation   +1230 added lines, -1230 removed lines patch added patch discarded remove patch
@@ -27,1240 +27,1240 @@
 block discarded – undo
27 27
 
28 28
 class IMipService {
29 29
 
30
-	private IL10N $l10n;
31
-
32
-	/** @var string[] */
33
-	private const STRING_DIFF = [
34
-		'meeting_title' => 'SUMMARY',
35
-		'meeting_description' => 'DESCRIPTION',
36
-		'meeting_url' => 'URL',
37
-		'meeting_location' => 'LOCATION'
38
-	];
39
-
40
-	public function __construct(
41
-		private URLGenerator $urlGenerator,
42
-		private IConfig $config,
43
-		private IDBConnection $db,
44
-		private ISecureRandom $random,
45
-		private L10NFactory $l10nFactory,
46
-		private ITimeFactory $timeFactory,
47
-	) {
48
-		$language = $this->l10nFactory->findGenericLanguage();
49
-		$locale = $this->l10nFactory->findLocale($language);
50
-		$this->l10n = $this->l10nFactory->get('dav', $language, $locale);
51
-	}
52
-
53
-	/**
54
-	 * @param string|null $senderName
55
-	 * @param string $default
56
-	 * @return string
57
-	 */
58
-	public function getFrom(?string $senderName, string $default): string {
59
-		if ($senderName === null) {
60
-			return $default;
61
-		}
62
-
63
-		return $this->l10n->t('%1$s via %2$s', [$senderName, $default]);
64
-	}
65
-
66
-	public static function readPropertyWithDefault(VEvent $vevent, string $property, string $default) {
67
-		if (isset($vevent->$property)) {
68
-			$value = $vevent->$property->getValue();
69
-			if (!empty($value)) {
70
-				return $value;
71
-			}
72
-		}
73
-		return $default;
74
-	}
75
-
76
-	private function generateDiffString(VEvent $vevent, VEvent $oldVEvent, string $property, string $default): ?string {
77
-		$strikethrough = "<span style='text-decoration: line-through'>%s</span><br />%s";
78
-		if (!isset($vevent->$property)) {
79
-			return $default;
80
-		}
81
-		$newstring = $vevent->$property->getValue();
82
-		if (isset($oldVEvent->$property) && $oldVEvent->$property->getValue() !== $newstring) {
83
-			$oldstring = $oldVEvent->$property->getValue();
84
-			return sprintf($strikethrough, $oldstring, $newstring);
85
-		}
86
-		return $newstring;
87
-	}
88
-
89
-	/**
90
-	 * Like generateDiffString() but linkifies the property values if they are urls.
91
-	 */
92
-	private function generateLinkifiedDiffString(VEvent $vevent, VEvent $oldVEvent, string $property, string $default): ?string {
93
-		if (!isset($vevent->$property)) {
94
-			return $default;
95
-		}
96
-		/** @var string|null $newString */
97
-		$newString = $vevent->$property->getValue();
98
-		$oldString = isset($oldVEvent->$property) ? $oldVEvent->$property->getValue() : null;
99
-		if ($oldString !== $newString) {
100
-			return sprintf(
101
-				"<span style='text-decoration: line-through'>%s</span><br />%s",
102
-				$this->linkify($oldString) ?? $oldString ?? '',
103
-				$this->linkify($newString) ?? $newString ?? ''
104
-			);
105
-		}
106
-		return $this->linkify($newString) ?? $newString;
107
-	}
108
-
109
-	/**
110
-	 * Convert a given url to a html link element or return null otherwise.
111
-	 */
112
-	private function linkify(?string $url): ?string {
113
-		if ($url === null) {
114
-			return null;
115
-		}
116
-		if (!str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
117
-			return null;
118
-		}
119
-
120
-		return sprintf('<a href="%1$s">%1$s</a>', htmlspecialchars($url));
121
-	}
122
-
123
-	/**
124
-	 * @param VEvent $vEvent
125
-	 * @param VEvent|null $oldVEvent
126
-	 * @return array
127
-	 */
128
-	public function buildBodyData(VEvent $vEvent, ?VEvent $oldVEvent): array {
129
-
130
-		// construct event reader
131
-		$eventReaderCurrent = new EventReader($vEvent);
132
-		$eventReaderPrevious = !empty($oldVEvent) ? new EventReader($oldVEvent) : null;
133
-		$defaultVal = '';
134
-		$data = [];
135
-		$data['meeting_when'] = $this->generateWhenString($eventReaderCurrent);
136
-
137
-		foreach (self::STRING_DIFF as $key => $property) {
138
-			$data[$key] = self::readPropertyWithDefault($vEvent, $property, $defaultVal);
139
-		}
140
-
141
-		$data['meeting_url_html'] = self::readPropertyWithDefault($vEvent, 'URL', $defaultVal);
142
-
143
-		if (($locationHtml = $this->linkify($data['meeting_location'])) !== null) {
144
-			$data['meeting_location_html'] = $locationHtml;
145
-		}
146
-
147
-		if (!empty($oldVEvent)) {
148
-			$oldMeetingWhen = $this->generateWhenString($eventReaderPrevious);
149
-			$data['meeting_title_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'SUMMARY', $data['meeting_title']);
150
-			$data['meeting_description_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'DESCRIPTION', $data['meeting_description']);
151
-			$data['meeting_location_html'] = $this->generateLinkifiedDiffString($vEvent, $oldVEvent, 'LOCATION', $data['meeting_location']);
152
-
153
-			$oldUrl = self::readPropertyWithDefault($oldVEvent, 'URL', $defaultVal);
154
-			$data['meeting_url_html'] = !empty($oldUrl) && $oldUrl !== $data['meeting_url'] ? sprintf('<a href="%1$s">%1$s</a>', $oldUrl) : $data['meeting_url'];
155
-
156
-			$data['meeting_when_html'] = $oldMeetingWhen !== $data['meeting_when'] ? sprintf("<span style='text-decoration: line-through'>%s</span><br />%s", $oldMeetingWhen, $data['meeting_when']) : $data['meeting_when'];
157
-		}
158
-		// generate occurring next string
159
-		if ($eventReaderCurrent->recurs()) {
160
-			$data['meeting_occurring'] = $this->generateOccurringString($eventReaderCurrent);
161
-		}
30
+    private IL10N $l10n;
31
+
32
+    /** @var string[] */
33
+    private const STRING_DIFF = [
34
+        'meeting_title' => 'SUMMARY',
35
+        'meeting_description' => 'DESCRIPTION',
36
+        'meeting_url' => 'URL',
37
+        'meeting_location' => 'LOCATION'
38
+    ];
39
+
40
+    public function __construct(
41
+        private URLGenerator $urlGenerator,
42
+        private IConfig $config,
43
+        private IDBConnection $db,
44
+        private ISecureRandom $random,
45
+        private L10NFactory $l10nFactory,
46
+        private ITimeFactory $timeFactory,
47
+    ) {
48
+        $language = $this->l10nFactory->findGenericLanguage();
49
+        $locale = $this->l10nFactory->findLocale($language);
50
+        $this->l10n = $this->l10nFactory->get('dav', $language, $locale);
51
+    }
52
+
53
+    /**
54
+     * @param string|null $senderName
55
+     * @param string $default
56
+     * @return string
57
+     */
58
+    public function getFrom(?string $senderName, string $default): string {
59
+        if ($senderName === null) {
60
+            return $default;
61
+        }
62
+
63
+        return $this->l10n->t('%1$s via %2$s', [$senderName, $default]);
64
+    }
65
+
66
+    public static function readPropertyWithDefault(VEvent $vevent, string $property, string $default) {
67
+        if (isset($vevent->$property)) {
68
+            $value = $vevent->$property->getValue();
69
+            if (!empty($value)) {
70
+                return $value;
71
+            }
72
+        }
73
+        return $default;
74
+    }
75
+
76
+    private function generateDiffString(VEvent $vevent, VEvent $oldVEvent, string $property, string $default): ?string {
77
+        $strikethrough = "<span style='text-decoration: line-through'>%s</span><br />%s";
78
+        if (!isset($vevent->$property)) {
79
+            return $default;
80
+        }
81
+        $newstring = $vevent->$property->getValue();
82
+        if (isset($oldVEvent->$property) && $oldVEvent->$property->getValue() !== $newstring) {
83
+            $oldstring = $oldVEvent->$property->getValue();
84
+            return sprintf($strikethrough, $oldstring, $newstring);
85
+        }
86
+        return $newstring;
87
+    }
88
+
89
+    /**
90
+     * Like generateDiffString() but linkifies the property values if they are urls.
91
+     */
92
+    private function generateLinkifiedDiffString(VEvent $vevent, VEvent $oldVEvent, string $property, string $default): ?string {
93
+        if (!isset($vevent->$property)) {
94
+            return $default;
95
+        }
96
+        /** @var string|null $newString */
97
+        $newString = $vevent->$property->getValue();
98
+        $oldString = isset($oldVEvent->$property) ? $oldVEvent->$property->getValue() : null;
99
+        if ($oldString !== $newString) {
100
+            return sprintf(
101
+                "<span style='text-decoration: line-through'>%s</span><br />%s",
102
+                $this->linkify($oldString) ?? $oldString ?? '',
103
+                $this->linkify($newString) ?? $newString ?? ''
104
+            );
105
+        }
106
+        return $this->linkify($newString) ?? $newString;
107
+    }
108
+
109
+    /**
110
+     * Convert a given url to a html link element or return null otherwise.
111
+     */
112
+    private function linkify(?string $url): ?string {
113
+        if ($url === null) {
114
+            return null;
115
+        }
116
+        if (!str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
117
+            return null;
118
+        }
119
+
120
+        return sprintf('<a href="%1$s">%1$s</a>', htmlspecialchars($url));
121
+    }
122
+
123
+    /**
124
+     * @param VEvent $vEvent
125
+     * @param VEvent|null $oldVEvent
126
+     * @return array
127
+     */
128
+    public function buildBodyData(VEvent $vEvent, ?VEvent $oldVEvent): array {
129
+
130
+        // construct event reader
131
+        $eventReaderCurrent = new EventReader($vEvent);
132
+        $eventReaderPrevious = !empty($oldVEvent) ? new EventReader($oldVEvent) : null;
133
+        $defaultVal = '';
134
+        $data = [];
135
+        $data['meeting_when'] = $this->generateWhenString($eventReaderCurrent);
136
+
137
+        foreach (self::STRING_DIFF as $key => $property) {
138
+            $data[$key] = self::readPropertyWithDefault($vEvent, $property, $defaultVal);
139
+        }
140
+
141
+        $data['meeting_url_html'] = self::readPropertyWithDefault($vEvent, 'URL', $defaultVal);
142
+
143
+        if (($locationHtml = $this->linkify($data['meeting_location'])) !== null) {
144
+            $data['meeting_location_html'] = $locationHtml;
145
+        }
146
+
147
+        if (!empty($oldVEvent)) {
148
+            $oldMeetingWhen = $this->generateWhenString($eventReaderPrevious);
149
+            $data['meeting_title_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'SUMMARY', $data['meeting_title']);
150
+            $data['meeting_description_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'DESCRIPTION', $data['meeting_description']);
151
+            $data['meeting_location_html'] = $this->generateLinkifiedDiffString($vEvent, $oldVEvent, 'LOCATION', $data['meeting_location']);
152
+
153
+            $oldUrl = self::readPropertyWithDefault($oldVEvent, 'URL', $defaultVal);
154
+            $data['meeting_url_html'] = !empty($oldUrl) && $oldUrl !== $data['meeting_url'] ? sprintf('<a href="%1$s">%1$s</a>', $oldUrl) : $data['meeting_url'];
155
+
156
+            $data['meeting_when_html'] = $oldMeetingWhen !== $data['meeting_when'] ? sprintf("<span style='text-decoration: line-through'>%s</span><br />%s", $oldMeetingWhen, $data['meeting_when']) : $data['meeting_when'];
157
+        }
158
+        // generate occurring next string
159
+        if ($eventReaderCurrent->recurs()) {
160
+            $data['meeting_occurring'] = $this->generateOccurringString($eventReaderCurrent);
161
+        }
162 162
 		
163
-		return $data;
164
-	}
165
-
166
-	/**
167
-	 * generates a when string based on if a event has an recurrence or not
168
-	 *
169
-	 * @since 30.0.0
170
-	 *
171
-	 * @param EventReader $er
172
-	 *
173
-	 * @return string
174
-	 */
175
-	public function generateWhenString(EventReader $er): string {
176
-		return match ($er->recurs()) {
177
-			true => $this->generateWhenStringRecurring($er),
178
-			false => $this->generateWhenStringSingular($er)
179
-		};
180
-	}
181
-
182
-	/**
183
-	 * generates a when string for a non recurring event
184
-	 *
185
-	 * @since 30.0.0
186
-	 *
187
-	 * @param EventReader $er
188
-	 *
189
-	 * @return string
190
-	 */
191
-	public function generateWhenStringSingular(EventReader $er): string {
192
-		// initialize
193
-		$startTime = null;
194
-		$endTime = null;
195
-		// calculate time difference from now to start of event
196
-		$occurring = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate()));
197
-		// extract start date
198
-		$startDate = $this->l10n->l('date', $er->startDateTime(), ['width' => 'full']);
199
-		// time of the day
200
-		if (!$er->entireDay()) {
201
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
202
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
203
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
204
-		}
205
-		// generate localized when string
206
-		// TRANSLATORS
207
-		// Indicates when a calendar event will happen, shown on invitation emails
208
-		// Output produced in order:
209
-		// In a minute/hour/day/week/month/year on July 1, 2024 for the entire day
210
-		// In a minute/hour/day/week/month/year on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)
211
-		// In 2 minutes/hours/days/weeks/months/years on July 1, 2024 for the entire day
212
-		// In 2 minutes/hours/days/weeks/months/years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)
213
-		return match ([$occurring['scale'], $endTime !== null]) {
214
-			['past', false] => $this->l10n->t(
215
-				'In the past on %1$s for the entire day',
216
-				[$startDate]
217
-			),
218
-			['minute', false] => $this->l10n->n(
219
-				'In a minute on %1$s for the entire day',
220
-				'In %n minutes on %1$s for the entire day',
221
-				$occurring['interval'],
222
-				[$startDate]
223
-			),
224
-			['hour', false] => $this->l10n->n(
225
-				'In a hour on %1$s for the entire day',
226
-				'In %n hours on %1$s for the entire day',
227
-				$occurring['interval'],
228
-				[$startDate]
229
-			),
230
-			['day', false] => $this->l10n->n(
231
-				'In a day on %1$s for the entire day',
232
-				'In %n days on %1$s for the entire day',
233
-				$occurring['interval'],
234
-				[$startDate]
235
-			),
236
-			['week', false] => $this->l10n->n(
237
-				'In a week on %1$s for the entire day',
238
-				'In %n weeks on %1$s for the entire day',
239
-				$occurring['interval'],
240
-				[$startDate]
241
-			),
242
-			['month', false] => $this->l10n->n(
243
-				'In a month on %1$s for the entire day',
244
-				'In %n months on %1$s for the entire day',
245
-				$occurring['interval'],
246
-				[$startDate]
247
-			),
248
-			['year', false] => $this->l10n->n(
249
-				'In a year on %1$s for the entire day',
250
-				'In %n years on %1$s for the entire day',
251
-				$occurring['interval'],
252
-				[$startDate]
253
-			),
254
-			['past', true] => $this->l10n->t(
255
-				'In the past on %1$s between %2$s - %3$s',
256
-				[$startDate, $startTime, $endTime]
257
-			),
258
-			['minute', true] => $this->l10n->n(
259
-				'In a minute on %1$s between %2$s - %3$s',
260
-				'In %n minutes on %1$s between %2$s - %3$s',
261
-				$occurring['interval'],
262
-				[$startDate, $startTime, $endTime]
263
-			),
264
-			['hour', true] => $this->l10n->n(
265
-				'In a hour on %1$s between %2$s - %3$s',
266
-				'In %n hours on %1$s between %2$s - %3$s',
267
-				$occurring['interval'],
268
-				[$startDate, $startTime, $endTime]
269
-			),
270
-			['day', true] => $this->l10n->n(
271
-				'In a day on %1$s between %2$s - %3$s',
272
-				'In %n days on %1$s between %2$s - %3$s',
273
-				$occurring['interval'],
274
-				[$startDate, $startTime, $endTime]
275
-			),
276
-			['week', true] => $this->l10n->n(
277
-				'In a week on %1$s between %2$s - %3$s',
278
-				'In %n weeks on %1$s between %2$s - %3$s',
279
-				$occurring['interval'],
280
-				[$startDate, $startTime, $endTime]
281
-			),
282
-			['month', true] => $this->l10n->n(
283
-				'In a month on %1$s between %2$s - %3$s',
284
-				'In %n months on %1$s between %2$s - %3$s',
285
-				$occurring['interval'],
286
-				[$startDate, $startTime, $endTime]
287
-			),
288
-			['year', true] => $this->l10n->n(
289
-				'In a year on %1$s between %2$s - %3$s',
290
-				'In %n years on %1$s between %2$s - %3$s',
291
-				$occurring['interval'],
292
-				[$startDate, $startTime, $endTime]
293
-			),
294
-			default => $this->l10n->t('Could not generate when statement')
295
-		};
296
-	}
297
-
298
-	/**
299
-	 * generates a when string based on recurrence precision/frequency
300
-	 *
301
-	 * @since 30.0.0
302
-	 *
303
-	 * @param EventReader $er
304
-	 *
305
-	 * @return string
306
-	 */
307
-	public function generateWhenStringRecurring(EventReader $er): string {
308
-		return match ($er->recurringPrecision()) {
309
-			'daily' => $this->generateWhenStringRecurringDaily($er),
310
-			'weekly' => $this->generateWhenStringRecurringWeekly($er),
311
-			'monthly' => $this->generateWhenStringRecurringMonthly($er),
312
-			'yearly' => $this->generateWhenStringRecurringYearly($er),
313
-			'fixed' => $this->generateWhenStringRecurringFixed($er),
314
-		};
315
-	}
316
-
317
-	/**
318
-	 * generates a when string for a daily precision/frequency
319
-	 *
320
-	 * @since 30.0.0
321
-	 *
322
-	 * @param EventReader $er
323
-	 *
324
-	 * @return string
325
-	 */
326
-	public function generateWhenStringRecurringDaily(EventReader $er): string {
163
+        return $data;
164
+    }
165
+
166
+    /**
167
+     * generates a when string based on if a event has an recurrence or not
168
+     *
169
+     * @since 30.0.0
170
+     *
171
+     * @param EventReader $er
172
+     *
173
+     * @return string
174
+     */
175
+    public function generateWhenString(EventReader $er): string {
176
+        return match ($er->recurs()) {
177
+            true => $this->generateWhenStringRecurring($er),
178
+            false => $this->generateWhenStringSingular($er)
179
+        };
180
+    }
181
+
182
+    /**
183
+     * generates a when string for a non recurring event
184
+     *
185
+     * @since 30.0.0
186
+     *
187
+     * @param EventReader $er
188
+     *
189
+     * @return string
190
+     */
191
+    public function generateWhenStringSingular(EventReader $er): string {
192
+        // initialize
193
+        $startTime = null;
194
+        $endTime = null;
195
+        // calculate time difference from now to start of event
196
+        $occurring = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate()));
197
+        // extract start date
198
+        $startDate = $this->l10n->l('date', $er->startDateTime(), ['width' => 'full']);
199
+        // time of the day
200
+        if (!$er->entireDay()) {
201
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
202
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
203
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
204
+        }
205
+        // generate localized when string
206
+        // TRANSLATORS
207
+        // Indicates when a calendar event will happen, shown on invitation emails
208
+        // Output produced in order:
209
+        // In a minute/hour/day/week/month/year on July 1, 2024 for the entire day
210
+        // In a minute/hour/day/week/month/year on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)
211
+        // In 2 minutes/hours/days/weeks/months/years on July 1, 2024 for the entire day
212
+        // In 2 minutes/hours/days/weeks/months/years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)
213
+        return match ([$occurring['scale'], $endTime !== null]) {
214
+            ['past', false] => $this->l10n->t(
215
+                'In the past on %1$s for the entire day',
216
+                [$startDate]
217
+            ),
218
+            ['minute', false] => $this->l10n->n(
219
+                'In a minute on %1$s for the entire day',
220
+                'In %n minutes on %1$s for the entire day',
221
+                $occurring['interval'],
222
+                [$startDate]
223
+            ),
224
+            ['hour', false] => $this->l10n->n(
225
+                'In a hour on %1$s for the entire day',
226
+                'In %n hours on %1$s for the entire day',
227
+                $occurring['interval'],
228
+                [$startDate]
229
+            ),
230
+            ['day', false] => $this->l10n->n(
231
+                'In a day on %1$s for the entire day',
232
+                'In %n days on %1$s for the entire day',
233
+                $occurring['interval'],
234
+                [$startDate]
235
+            ),
236
+            ['week', false] => $this->l10n->n(
237
+                'In a week on %1$s for the entire day',
238
+                'In %n weeks on %1$s for the entire day',
239
+                $occurring['interval'],
240
+                [$startDate]
241
+            ),
242
+            ['month', false] => $this->l10n->n(
243
+                'In a month on %1$s for the entire day',
244
+                'In %n months on %1$s for the entire day',
245
+                $occurring['interval'],
246
+                [$startDate]
247
+            ),
248
+            ['year', false] => $this->l10n->n(
249
+                'In a year on %1$s for the entire day',
250
+                'In %n years on %1$s for the entire day',
251
+                $occurring['interval'],
252
+                [$startDate]
253
+            ),
254
+            ['past', true] => $this->l10n->t(
255
+                'In the past on %1$s between %2$s - %3$s',
256
+                [$startDate, $startTime, $endTime]
257
+            ),
258
+            ['minute', true] => $this->l10n->n(
259
+                'In a minute on %1$s between %2$s - %3$s',
260
+                'In %n minutes on %1$s between %2$s - %3$s',
261
+                $occurring['interval'],
262
+                [$startDate, $startTime, $endTime]
263
+            ),
264
+            ['hour', true] => $this->l10n->n(
265
+                'In a hour on %1$s between %2$s - %3$s',
266
+                'In %n hours on %1$s between %2$s - %3$s',
267
+                $occurring['interval'],
268
+                [$startDate, $startTime, $endTime]
269
+            ),
270
+            ['day', true] => $this->l10n->n(
271
+                'In a day on %1$s between %2$s - %3$s',
272
+                'In %n days on %1$s between %2$s - %3$s',
273
+                $occurring['interval'],
274
+                [$startDate, $startTime, $endTime]
275
+            ),
276
+            ['week', true] => $this->l10n->n(
277
+                'In a week on %1$s between %2$s - %3$s',
278
+                'In %n weeks on %1$s between %2$s - %3$s',
279
+                $occurring['interval'],
280
+                [$startDate, $startTime, $endTime]
281
+            ),
282
+            ['month', true] => $this->l10n->n(
283
+                'In a month on %1$s between %2$s - %3$s',
284
+                'In %n months on %1$s between %2$s - %3$s',
285
+                $occurring['interval'],
286
+                [$startDate, $startTime, $endTime]
287
+            ),
288
+            ['year', true] => $this->l10n->n(
289
+                'In a year on %1$s between %2$s - %3$s',
290
+                'In %n years on %1$s between %2$s - %3$s',
291
+                $occurring['interval'],
292
+                [$startDate, $startTime, $endTime]
293
+            ),
294
+            default => $this->l10n->t('Could not generate when statement')
295
+        };
296
+    }
297
+
298
+    /**
299
+     * generates a when string based on recurrence precision/frequency
300
+     *
301
+     * @since 30.0.0
302
+     *
303
+     * @param EventReader $er
304
+     *
305
+     * @return string
306
+     */
307
+    public function generateWhenStringRecurring(EventReader $er): string {
308
+        return match ($er->recurringPrecision()) {
309
+            'daily' => $this->generateWhenStringRecurringDaily($er),
310
+            'weekly' => $this->generateWhenStringRecurringWeekly($er),
311
+            'monthly' => $this->generateWhenStringRecurringMonthly($er),
312
+            'yearly' => $this->generateWhenStringRecurringYearly($er),
313
+            'fixed' => $this->generateWhenStringRecurringFixed($er),
314
+        };
315
+    }
316
+
317
+    /**
318
+     * generates a when string for a daily precision/frequency
319
+     *
320
+     * @since 30.0.0
321
+     *
322
+     * @param EventReader $er
323
+     *
324
+     * @return string
325
+     */
326
+    public function generateWhenStringRecurringDaily(EventReader $er): string {
327 327
 		
328
-		// initialize
329
-		$interval = (int)$er->recurringInterval();
330
-		$startTime = null;
331
-		$conclusion = null;
332
-		// time of the day
333
-		if (!$er->entireDay()) {
334
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
335
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
336
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
337
-		}
338
-		// conclusion
339
-		if ($er->recurringConcludes()) {
340
-			$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
341
-		}
342
-		// generate localized when string
343
-		// TRANSLATORS
344
-		// Indicates when a calendar event will happen, shown on invitation emails
345
-		// Output produced in order:
346
-		// Every Day for the entire day
347
-		// Every Day for the entire day until July 13, 2024
348
-		// Every Day between 8:00 AM - 9:00 AM (America/Toronto)
349
-		// Every Day between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
350
-		// Every 3 Days for the entire day
351
-		// Every 3 Days for the entire day until July 13, 2024
352
-		// Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto)
353
-		// Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
354
-		return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
355
-			[false, false, false] => $this->l10n->t('Every Day for the entire day'),
356
-			[false, false, true] => $this->l10n->t('Every Day for the entire day until %1$s', [$conclusion]),
357
-			[false, true, false] => $this->l10n->t('Every Day between %1$s - %2$s', [$startTime, $endTime]),
358
-			[false, true, true] => $this->l10n->t('Every Day between %1$s - %2$s until %3$s', [$startTime, $endTime, $conclusion]),
359
-			[true, false, false] => $this->l10n->t('Every %1$d Days for the entire day', [$interval]),
360
-			[true, false, true] => $this->l10n->t('Every %1$d Days for the entire day until %2$s', [$interval, $conclusion]),
361
-			[true, true, false] => $this->l10n->t('Every %1$d Days between %2$s - %3$s', [$interval, $startTime, $endTime]),
362
-			[true, true, true] => $this->l10n->t('Every %1$d Days between %2$s - %3$s until %4$s', [$interval, $startTime, $endTime, $conclusion]),
363
-			default => $this->l10n->t('Could not generate event recurrence statement')
364
-		};
365
-
366
-	}
367
-
368
-	/**
369
-	 * generates a when string for a weekly precision/frequency
370
-	 *
371
-	 * @since 30.0.0
372
-	 *
373
-	 * @param EventReader $er
374
-	 *
375
-	 * @return string
376
-	 */
377
-	public function generateWhenStringRecurringWeekly(EventReader $er): string {
328
+        // initialize
329
+        $interval = (int)$er->recurringInterval();
330
+        $startTime = null;
331
+        $conclusion = null;
332
+        // time of the day
333
+        if (!$er->entireDay()) {
334
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
335
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
336
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
337
+        }
338
+        // conclusion
339
+        if ($er->recurringConcludes()) {
340
+            $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
341
+        }
342
+        // generate localized when string
343
+        // TRANSLATORS
344
+        // Indicates when a calendar event will happen, shown on invitation emails
345
+        // Output produced in order:
346
+        // Every Day for the entire day
347
+        // Every Day for the entire day until July 13, 2024
348
+        // Every Day between 8:00 AM - 9:00 AM (America/Toronto)
349
+        // Every Day between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
350
+        // Every 3 Days for the entire day
351
+        // Every 3 Days for the entire day until July 13, 2024
352
+        // Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto)
353
+        // Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
354
+        return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
355
+            [false, false, false] => $this->l10n->t('Every Day for the entire day'),
356
+            [false, false, true] => $this->l10n->t('Every Day for the entire day until %1$s', [$conclusion]),
357
+            [false, true, false] => $this->l10n->t('Every Day between %1$s - %2$s', [$startTime, $endTime]),
358
+            [false, true, true] => $this->l10n->t('Every Day between %1$s - %2$s until %3$s', [$startTime, $endTime, $conclusion]),
359
+            [true, false, false] => $this->l10n->t('Every %1$d Days for the entire day', [$interval]),
360
+            [true, false, true] => $this->l10n->t('Every %1$d Days for the entire day until %2$s', [$interval, $conclusion]),
361
+            [true, true, false] => $this->l10n->t('Every %1$d Days between %2$s - %3$s', [$interval, $startTime, $endTime]),
362
+            [true, true, true] => $this->l10n->t('Every %1$d Days between %2$s - %3$s until %4$s', [$interval, $startTime, $endTime, $conclusion]),
363
+            default => $this->l10n->t('Could not generate event recurrence statement')
364
+        };
365
+
366
+    }
367
+
368
+    /**
369
+     * generates a when string for a weekly precision/frequency
370
+     *
371
+     * @since 30.0.0
372
+     *
373
+     * @param EventReader $er
374
+     *
375
+     * @return string
376
+     */
377
+    public function generateWhenStringRecurringWeekly(EventReader $er): string {
378 378
 		
379
-		// initialize
380
-		$interval = (int)$er->recurringInterval();
381
-		$startTime = null;
382
-		$conclusion = null;
383
-		// days of the week
384
-		$days = implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
385
-		// time of the day
386
-		if (!$er->entireDay()) {
387
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
388
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
389
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
390
-		}
391
-		// conclusion
392
-		if ($er->recurringConcludes()) {
393
-			$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
394
-		}
395
-		// generate localized when string
396
-		// TRANSLATORS
397
-		// Indicates when a calendar event will happen, shown on invitation emails
398
-		// Output produced in order:
399
-		// Every Week on Monday, Wednesday, Friday for the entire day
400
-		// Every Week on Monday, Wednesday, Friday for the entire day until July 13, 2024
401
-		// Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)
402
-		// Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
403
-		// Every 2 Weeks on Monday, Wednesday, Friday for the entire day
404
-		// Every 2 Weeks on Monday, Wednesday, Friday for the entire day until July 13, 2024
405
-		// Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)
406
-		// Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
407
-		return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
408
-			[false, false, false] => $this->l10n->t('Every Week on %1$s for the entire day', [$days]),
409
-			[false, false, true] => $this->l10n->t('Every Week on %1$s for the entire day until %2$s', [$days, $conclusion]),
410
-			[false, true, false] => $this->l10n->t('Every Week on %1$s between %2$s - %3$s', [$days, $startTime, $endTime]),
411
-			[false, true, true] => $this->l10n->t('Every Week on %1$s between %2$s - %3$s until %4$s', [$days, $startTime, $endTime, $conclusion]),
412
-			[true, false, false] => $this->l10n->t('Every %1$d Weeks on %2$s for the entire day', [$interval, $days]),
413
-			[true, false, true] => $this->l10n->t('Every %1$d Weeks on %2$s for the entire day until %3$s', [$interval, $days, $conclusion]),
414
-			[true, true, false] => $this->l10n->t('Every %1$d Weeks on %2$s between %3$s - %4$s', [$interval, $days, $startTime, $endTime]),
415
-			[true, true, true] => $this->l10n->t('Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s', [$interval, $days, $startTime, $endTime, $conclusion]),
416
-			default => $this->l10n->t('Could not generate event recurrence statement')
417
-		};
418
-
419
-	}
420
-
421
-	/**
422
-	 * generates a when string for a monthly precision/frequency
423
-	 *
424
-	 * @since 30.0.0
425
-	 *
426
-	 * @param EventReader $er
427
-	 *
428
-	 * @return string
429
-	 */
430
-	public function generateWhenStringRecurringMonthly(EventReader $er): string {
379
+        // initialize
380
+        $interval = (int)$er->recurringInterval();
381
+        $startTime = null;
382
+        $conclusion = null;
383
+        // days of the week
384
+        $days = implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
385
+        // time of the day
386
+        if (!$er->entireDay()) {
387
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
388
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
389
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
390
+        }
391
+        // conclusion
392
+        if ($er->recurringConcludes()) {
393
+            $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
394
+        }
395
+        // generate localized when string
396
+        // TRANSLATORS
397
+        // Indicates when a calendar event will happen, shown on invitation emails
398
+        // Output produced in order:
399
+        // Every Week on Monday, Wednesday, Friday for the entire day
400
+        // Every Week on Monday, Wednesday, Friday for the entire day until July 13, 2024
401
+        // Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)
402
+        // Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
403
+        // Every 2 Weeks on Monday, Wednesday, Friday for the entire day
404
+        // Every 2 Weeks on Monday, Wednesday, Friday for the entire day until July 13, 2024
405
+        // Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)
406
+        // Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
407
+        return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
408
+            [false, false, false] => $this->l10n->t('Every Week on %1$s for the entire day', [$days]),
409
+            [false, false, true] => $this->l10n->t('Every Week on %1$s for the entire day until %2$s', [$days, $conclusion]),
410
+            [false, true, false] => $this->l10n->t('Every Week on %1$s between %2$s - %3$s', [$days, $startTime, $endTime]),
411
+            [false, true, true] => $this->l10n->t('Every Week on %1$s between %2$s - %3$s until %4$s', [$days, $startTime, $endTime, $conclusion]),
412
+            [true, false, false] => $this->l10n->t('Every %1$d Weeks on %2$s for the entire day', [$interval, $days]),
413
+            [true, false, true] => $this->l10n->t('Every %1$d Weeks on %2$s for the entire day until %3$s', [$interval, $days, $conclusion]),
414
+            [true, true, false] => $this->l10n->t('Every %1$d Weeks on %2$s between %3$s - %4$s', [$interval, $days, $startTime, $endTime]),
415
+            [true, true, true] => $this->l10n->t('Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s', [$interval, $days, $startTime, $endTime, $conclusion]),
416
+            default => $this->l10n->t('Could not generate event recurrence statement')
417
+        };
418
+
419
+    }
420
+
421
+    /**
422
+     * generates a when string for a monthly precision/frequency
423
+     *
424
+     * @since 30.0.0
425
+     *
426
+     * @param EventReader $er
427
+     *
428
+     * @return string
429
+     */
430
+    public function generateWhenStringRecurringMonthly(EventReader $er): string {
431 431
 		
432
-		// initialize
433
-		$interval = (int)$er->recurringInterval();
434
-		$startTime = null;
435
-		$conclusion = null;
436
-		// days of month
437
-		if ($er->recurringPattern() === 'R') {
438
-			$days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' ' .
439
-					implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
440
-		} else {
441
-			$days = implode(', ', $er->recurringDaysOfMonth());
442
-		}
443
-		// time of the day
444
-		if (!$er->entireDay()) {
445
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
446
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
447
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
448
-		}
449
-		// conclusion
450
-		if ($er->recurringConcludes()) {
451
-			$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
452
-		}
453
-		// generate localized when string
454
-		// TRANSLATORS
455
-		// Indicates when a calendar event will happen, shown on invitation emails
456
-		// Output produced in order, output varies depending on if the event is absolute or releative:
457
-		// Absolute: Every Month on the 1, 8 for the entire day
458
-		// Relative: Every Month on the First Sunday, Saturday for the entire day
459
-		// Absolute: Every Month on the 1, 8 for the entire day until December 31, 2024
460
-		// Relative: Every Month on the First Sunday, Saturday for the entire day until December 31, 2024
461
-		// Absolute: Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)
462
-		// Relative: Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
463
-		// Absolute: Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
464
-		// Relative: Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
465
-		// Absolute: Every 2 Months on the 1, 8 for the entire day
466
-		// Relative: Every 2 Months on the First Sunday, Saturday for the entire day
467
-		// Absolute: Every 2 Months on the 1, 8 for the entire day until December 31, 2024
468
-		// Relative: Every 2 Months on the First Sunday, Saturday for the entire day until December 31, 2024
469
-		// Absolute: Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)
470
-		// Relative: Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
471
-		// Absolute: Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
472
-		// Relative: Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
473
-		return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
474
-			[false, false, false] => $this->l10n->t('Every Month on the %1$s for the entire day', [$days]),
475
-			[false, false, true] => $this->l10n->t('Every Month on the %1$s for the entire day until %2$s', [$days, $conclusion]),
476
-			[false, true, false] => $this->l10n->t('Every Month on the %1$s between %2$s - %3$s', [$days, $startTime, $endTime]),
477
-			[false, true, true] => $this->l10n->t('Every Month on the %1$s between %2$s - %3$s until %4$s', [$days, $startTime, $endTime, $conclusion]),
478
-			[true, false, false] => $this->l10n->t('Every %1$d Months on the %2$s for the entire day', [$interval, $days]),
479
-			[true, false, true] => $this->l10n->t('Every %1$d Months on the %2$s for the entire day until %3$s', [$interval, $days, $conclusion]),
480
-			[true, true, false] => $this->l10n->t('Every %1$d Months on the %2$s between %3$s - %4$s', [$interval, $days, $startTime, $endTime]),
481
-			[true, true, true] => $this->l10n->t('Every %1$d Months on the %2$s between %3$s - %4$s until %5$s', [$interval, $days, $startTime, $endTime, $conclusion]),
482
-			default => $this->l10n->t('Could not generate event recurrence statement')
483
-		};
484
-	}
485
-
486
-	/**
487
-	 * generates a when string for a yearly precision/frequency
488
-	 *
489
-	 * @since 30.0.0
490
-	 *
491
-	 * @param EventReader $er
492
-	 *
493
-	 * @return string
494
-	 */
495
-	public function generateWhenStringRecurringYearly(EventReader $er): string {
432
+        // initialize
433
+        $interval = (int)$er->recurringInterval();
434
+        $startTime = null;
435
+        $conclusion = null;
436
+        // days of month
437
+        if ($er->recurringPattern() === 'R') {
438
+            $days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' ' .
439
+                    implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
440
+        } else {
441
+            $days = implode(', ', $er->recurringDaysOfMonth());
442
+        }
443
+        // time of the day
444
+        if (!$er->entireDay()) {
445
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
446
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
447
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
448
+        }
449
+        // conclusion
450
+        if ($er->recurringConcludes()) {
451
+            $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
452
+        }
453
+        // generate localized when string
454
+        // TRANSLATORS
455
+        // Indicates when a calendar event will happen, shown on invitation emails
456
+        // Output produced in order, output varies depending on if the event is absolute or releative:
457
+        // Absolute: Every Month on the 1, 8 for the entire day
458
+        // Relative: Every Month on the First Sunday, Saturday for the entire day
459
+        // Absolute: Every Month on the 1, 8 for the entire day until December 31, 2024
460
+        // Relative: Every Month on the First Sunday, Saturday for the entire day until December 31, 2024
461
+        // Absolute: Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)
462
+        // Relative: Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
463
+        // Absolute: Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
464
+        // Relative: Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
465
+        // Absolute: Every 2 Months on the 1, 8 for the entire day
466
+        // Relative: Every 2 Months on the First Sunday, Saturday for the entire day
467
+        // Absolute: Every 2 Months on the 1, 8 for the entire day until December 31, 2024
468
+        // Relative: Every 2 Months on the First Sunday, Saturday for the entire day until December 31, 2024
469
+        // Absolute: Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)
470
+        // Relative: Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
471
+        // Absolute: Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
472
+        // Relative: Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024
473
+        return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
474
+            [false, false, false] => $this->l10n->t('Every Month on the %1$s for the entire day', [$days]),
475
+            [false, false, true] => $this->l10n->t('Every Month on the %1$s for the entire day until %2$s', [$days, $conclusion]),
476
+            [false, true, false] => $this->l10n->t('Every Month on the %1$s between %2$s - %3$s', [$days, $startTime, $endTime]),
477
+            [false, true, true] => $this->l10n->t('Every Month on the %1$s between %2$s - %3$s until %4$s', [$days, $startTime, $endTime, $conclusion]),
478
+            [true, false, false] => $this->l10n->t('Every %1$d Months on the %2$s for the entire day', [$interval, $days]),
479
+            [true, false, true] => $this->l10n->t('Every %1$d Months on the %2$s for the entire day until %3$s', [$interval, $days, $conclusion]),
480
+            [true, true, false] => $this->l10n->t('Every %1$d Months on the %2$s between %3$s - %4$s', [$interval, $days, $startTime, $endTime]),
481
+            [true, true, true] => $this->l10n->t('Every %1$d Months on the %2$s between %3$s - %4$s until %5$s', [$interval, $days, $startTime, $endTime, $conclusion]),
482
+            default => $this->l10n->t('Could not generate event recurrence statement')
483
+        };
484
+    }
485
+
486
+    /**
487
+     * generates a when string for a yearly precision/frequency
488
+     *
489
+     * @since 30.0.0
490
+     *
491
+     * @param EventReader $er
492
+     *
493
+     * @return string
494
+     */
495
+    public function generateWhenStringRecurringYearly(EventReader $er): string {
496 496
 		
497
-		// initialize
498
-		$interval = (int)$er->recurringInterval();
499
-		$startTime = null;
500
-		$conclusion = null;
501
-		// months of year
502
-		$months = implode(', ', array_map(function ($value) { return $this->localizeMonthName($value); }, $er->recurringMonthsOfYearNamed()));
503
-		// days of month
504
-		if ($er->recurringPattern() === 'R') {
505
-			$days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' ' .
506
-					implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
507
-		} else {
508
-			$days = $er->startDateTime()->format('jS');
509
-		}
510
-		// time of the day
511
-		if (!$er->entireDay()) {
512
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
513
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
514
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
515
-		}
516
-		// conclusion
517
-		if ($er->recurringConcludes()) {
518
-			$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
519
-		}
520
-		// generate localized when string
521
-		// TRANSLATORS
522
-		// Indicates when a calendar event will happen, shown on invitation emails
523
-		// Output produced in order, output varies depending on if the event is absolute or releative:
524
-		// Absolute: Every Year in July on the 1st for the entire day
525
-		// Relative: Every Year in July on the First Sunday, Saturday for the entire day
526
-		// Absolute: Every Year in July on the 1st for the entire day until July 31, 2026
527
-		// Relative: Every Year in July on the First Sunday, Saturday for the entire day until July 31, 2026
528
-		// Absolute: Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)
529
-		// Relative: Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
530
-		// Absolute: Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
531
-		// Relative: Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
532
-		// Absolute: Every 2 Years in July on the 1st for the entire day
533
-		// Relative: Every 2 Years in July on the First Sunday, Saturday for the entire day
534
-		// Absolute: Every 2 Years in July on the 1st for the entire day until July 31, 2026
535
-		// Relative: Every 2 Years in July on the First Sunday, Saturday for the entire day until July 31, 2026
536
-		// Absolute: Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)
537
-		// Relative: Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
538
-		// Absolute: Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
539
-		// Relative: Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
540
-		return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
541
-			[false, false, false] => $this->l10n->t('Every Year in %1$s on the %2$s for the entire day', [$months, $days]),
542
-			[false, false, true] => $this->l10n->t('Every Year in %1$s on the %2$s for the entire day until %3$s', [$months, $days, $conclusion]),
543
-			[false, true, false] => $this->l10n->t('Every Year in %1$s on the %2$s between %3$s - %4$s', [$months, $days, $startTime, $endTime]),
544
-			[false, true, true] => $this->l10n->t('Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s', [$months, $days, $startTime, $endTime, $conclusion]),
545
-			[true, false, false] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s for the entire day', [$interval, $months, $days]),
546
-			[true, false, true] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s for the entire day until %4$s', [$interval, $months,  $days, $conclusion]),
547
-			[true, true, false] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s between %4$s - %5$s', [$interval, $months, $days, $startTime, $endTime]),
548
-			[true, true, true] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s', [$interval, $months, $days, $startTime, $endTime, $conclusion]),
549
-			default => $this->l10n->t('Could not generate event recurrence statement')
550
-		};
551
-	}
552
-
553
-	/**
554
-	 * generates a when string for a fixed precision/frequency
555
-	 *
556
-	 * @since 30.0.0
557
-	 *
558
-	 * @param EventReader $er
559
-	 *
560
-	 * @return string
561
-	 */
562
-	public function generateWhenStringRecurringFixed(EventReader $er): string {
563
-		// initialize
564
-		$startTime = null;
565
-		$conclusion = null;
566
-		// time of the day
567
-		if (!$er->entireDay()) {
568
-			$startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
569
-			$startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
570
-			$endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
571
-		}
572
-		// conclusion
573
-		$conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
574
-		// generate localized when string
575
-		// TRANSLATORS
576
-		// Indicates when a calendar event will happen, shown on invitation emails
577
-		// Output produced in order:
578
-		// On specific dates for the entire day until July 13, 2024
579
-		// On specific dates between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
580
-		return match ($startTime !== null) {
581
-			false => $this->l10n->t('On specific dates for the entire day until %1$s', [$conclusion]),
582
-			true => $this->l10n->t('On specific dates between %1$s - %2$s until %3$s', [$startTime, $endTime, $conclusion]),
583
-		};
584
-	}
497
+        // initialize
498
+        $interval = (int)$er->recurringInterval();
499
+        $startTime = null;
500
+        $conclusion = null;
501
+        // months of year
502
+        $months = implode(', ', array_map(function ($value) { return $this->localizeMonthName($value); }, $er->recurringMonthsOfYearNamed()));
503
+        // days of month
504
+        if ($er->recurringPattern() === 'R') {
505
+            $days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' ' .
506
+                    implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
507
+        } else {
508
+            $days = $er->startDateTime()->format('jS');
509
+        }
510
+        // time of the day
511
+        if (!$er->entireDay()) {
512
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
513
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
514
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
515
+        }
516
+        // conclusion
517
+        if ($er->recurringConcludes()) {
518
+            $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
519
+        }
520
+        // generate localized when string
521
+        // TRANSLATORS
522
+        // Indicates when a calendar event will happen, shown on invitation emails
523
+        // Output produced in order, output varies depending on if the event is absolute or releative:
524
+        // Absolute: Every Year in July on the 1st for the entire day
525
+        // Relative: Every Year in July on the First Sunday, Saturday for the entire day
526
+        // Absolute: Every Year in July on the 1st for the entire day until July 31, 2026
527
+        // Relative: Every Year in July on the First Sunday, Saturday for the entire day until July 31, 2026
528
+        // Absolute: Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)
529
+        // Relative: Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
530
+        // Absolute: Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
531
+        // Relative: Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
532
+        // Absolute: Every 2 Years in July on the 1st for the entire day
533
+        // Relative: Every 2 Years in July on the First Sunday, Saturday for the entire day
534
+        // Absolute: Every 2 Years in July on the 1st for the entire day until July 31, 2026
535
+        // Relative: Every 2 Years in July on the First Sunday, Saturday for the entire day until July 31, 2026
536
+        // Absolute: Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)
537
+        // Relative: Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)
538
+        // Absolute: Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
539
+        // Relative: Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026
540
+        return match ([($interval > 1), $startTime !== null, $conclusion !== null]) {
541
+            [false, false, false] => $this->l10n->t('Every Year in %1$s on the %2$s for the entire day', [$months, $days]),
542
+            [false, false, true] => $this->l10n->t('Every Year in %1$s on the %2$s for the entire day until %3$s', [$months, $days, $conclusion]),
543
+            [false, true, false] => $this->l10n->t('Every Year in %1$s on the %2$s between %3$s - %4$s', [$months, $days, $startTime, $endTime]),
544
+            [false, true, true] => $this->l10n->t('Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s', [$months, $days, $startTime, $endTime, $conclusion]),
545
+            [true, false, false] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s for the entire day', [$interval, $months, $days]),
546
+            [true, false, true] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s for the entire day until %4$s', [$interval, $months,  $days, $conclusion]),
547
+            [true, true, false] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s between %4$s - %5$s', [$interval, $months, $days, $startTime, $endTime]),
548
+            [true, true, true] => $this->l10n->t('Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s', [$interval, $months, $days, $startTime, $endTime, $conclusion]),
549
+            default => $this->l10n->t('Could not generate event recurrence statement')
550
+        };
551
+    }
552
+
553
+    /**
554
+     * generates a when string for a fixed precision/frequency
555
+     *
556
+     * @since 30.0.0
557
+     *
558
+     * @param EventReader $er
559
+     *
560
+     * @return string
561
+     */
562
+    public function generateWhenStringRecurringFixed(EventReader $er): string {
563
+        // initialize
564
+        $startTime = null;
565
+        $conclusion = null;
566
+        // time of the day
567
+        if (!$er->entireDay()) {
568
+            $startTime = $this->l10n->l('time', $er->startDateTime(), ['width' => 'short']);
569
+            $startTime .= $er->startTimeZone() != $er->endTimeZone() ? ' (' . $er->startTimeZone()->getName() . ')' : '';
570
+            $endTime = $this->l10n->l('time', $er->endDateTime(), ['width' => 'short']) . ' (' . $er->endTimeZone()->getName() . ')';
571
+        }
572
+        // conclusion
573
+        $conclusion = $this->l10n->l('date', $er->recurringConcludesOn(), ['width' => 'long']);
574
+        // generate localized when string
575
+        // TRANSLATORS
576
+        // Indicates when a calendar event will happen, shown on invitation emails
577
+        // Output produced in order:
578
+        // On specific dates for the entire day until July 13, 2024
579
+        // On specific dates between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024
580
+        return match ($startTime !== null) {
581
+            false => $this->l10n->t('On specific dates for the entire day until %1$s', [$conclusion]),
582
+            true => $this->l10n->t('On specific dates between %1$s - %2$s until %3$s', [$startTime, $endTime, $conclusion]),
583
+        };
584
+    }
585 585
 	
586
-	/**
587
-	 * generates a occurring next string for a recurring event
588
-	 *
589
-	 * @since 30.0.0
590
-	 *
591
-	 * @param EventReader $er
592
-	 *
593
-	 * @return string
594
-	 */
595
-	public function generateOccurringString(EventReader $er): string {
596
-
597
-		// initialize
598
-		$occurrence = null;
599
-		$occurrence2 = null;
600
-		$occurrence3 = null;
601
-		// reset to initial occurrence
602
-		$er->recurrenceRewind();
603
-		// forward to current date
604
-		$er->recurrenceAdvanceTo($this->timeFactory->getDateTime());
605
-		// calculate time difference from now to start of next event occurrence and minimize it
606
-		$occurrenceIn = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate()));
607
-		// store next occurrence value
608
-		$occurrence = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
609
-		// forward one occurrence
610
-		$er->recurrenceAdvance();
611
-		// evaluate if occurrence is valid
612
-		if ($er->recurrenceDate() !== null) {
613
-			// store following occurrence value
614
-			$occurrence2 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
615
-			// forward one occurrence
616
-			$er->recurrenceAdvance();
617
-			// evaluate if occurrence is valid
618
-			if ($er->recurrenceDate()) {
619
-				// store following occurrence value
620
-				$occurrence3 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
621
-			}
622
-		}
623
-		// generate localized when string
624
-		// TRANSLATORS
625
-		// Indicates when a calendar event will happen, shown on invitation emails
626
-		// Output produced in order:
627
-		// In a minute/hour/day/week/month/year on July 1, 2024
628
-		// In a minute/hour/day/week/month/year on July 1, 2024 then on July 3, 2024
629
-		// In a minute/hour/day/week/month/year on July 1, 2024 then on July 3, 2024 and July 5, 2024
630
-		// In 2 minutes/hours/days/weeks/months/years on July 1, 2024
631
-		// In 2 minutes/hours/days/weeks/months/years on July 1, 2024 then on July 3, 2024
632
-		// In 2 minutes/hours/days/weeks/months/years on July 1, 2024 then on July 3, 2024 and July 5, 2024
633
-		return match ([$occurrenceIn['scale'], $occurrence2 !== null, $occurrence3 !== null]) {
634
-			['past', false, false] => $this->l10n->t(
635
-				'In the past on %1$s',
636
-				[$occurrence]
637
-			),
638
-			['minute', false, false] => $this->l10n->n(
639
-				'In a minute on %1$s',
640
-				'In %n minutes on %1$s',
641
-				$occurrenceIn['interval'],
642
-				[$occurrence]
643
-			),
644
-			['hour', false, false] => $this->l10n->n(
645
-				'In a hour on %1$s',
646
-				'In %n hours on %1$s',
647
-				$occurrenceIn['interval'],
648
-				[$occurrence]
649
-			),
650
-			['day', false, false] => $this->l10n->n(
651
-				'In a day on %1$s',
652
-				'In %n days on %1$s',
653
-				$occurrenceIn['interval'],
654
-				[$occurrence]
655
-			),
656
-			['week', false, false] => $this->l10n->n(
657
-				'In a week on %1$s',
658
-				'In %n weeks on %1$s',
659
-				$occurrenceIn['interval'],
660
-				[$occurrence]
661
-			),
662
-			['month', false, false] => $this->l10n->n(
663
-				'In a month on %1$s',
664
-				'In %n months on %1$s',
665
-				$occurrenceIn['interval'],
666
-				[$occurrence]
667
-			),
668
-			['year', false, false] => $this->l10n->n(
669
-				'In a year on %1$s',
670
-				'In %n years on %1$s',
671
-				$occurrenceIn['interval'],
672
-				[$occurrence]
673
-			),
674
-			['past', true, false] => $this->l10n->t(
675
-				'In the past on %1$s then on %2$s',
676
-				[$occurrence, $occurrence2]
677
-			),
678
-			['minute', true, false] => $this->l10n->n(
679
-				'In a minute on %1$s then on %2$s',
680
-				'In %n minutes on %1$s then on %2$s',
681
-				$occurrenceIn['interval'],
682
-				[$occurrence, $occurrence2]
683
-			),
684
-			['hour', true, false] => $this->l10n->n(
685
-				'In a hour on %1$s then on %2$s',
686
-				'In %n hours on %1$s then on %2$s',
687
-				$occurrenceIn['interval'],
688
-				[$occurrence, $occurrence2]
689
-			),
690
-			['day', true, false] => $this->l10n->n(
691
-				'In a day on %1$s then on %2$s',
692
-				'In %n days on %1$s then on %2$s',
693
-				$occurrenceIn['interval'],
694
-				[$occurrence, $occurrence2]
695
-			),
696
-			['week', true, false] => $this->l10n->n(
697
-				'In a week on %1$s then on %2$s',
698
-				'In %n weeks on %1$s then on %2$s',
699
-				$occurrenceIn['interval'],
700
-				[$occurrence, $occurrence2]
701
-			),
702
-			['month', true, false] => $this->l10n->n(
703
-				'In a month on %1$s then on %2$s',
704
-				'In %n months on %1$s then on %2$s',
705
-				$occurrenceIn['interval'],
706
-				[$occurrence, $occurrence2]
707
-			),
708
-			['year', true, false] => $this->l10n->n(
709
-				'In a year on %1$s then on %2$s',
710
-				'In %n years on %1$s then on %2$s',
711
-				$occurrenceIn['interval'],
712
-				[$occurrence, $occurrence2]
713
-			),
714
-			['past', true, true] => $this->l10n->t(
715
-				'In the past on %1$s then on %2$s and %3$s',
716
-				[$occurrence, $occurrence2, $occurrence3]
717
-			),
718
-			['minute', true, true] => $this->l10n->n(
719
-				'In a minute on %1$s then on %2$s and %3$s',
720
-				'In %n minutes on %1$s then on %2$s and %3$s',
721
-				$occurrenceIn['interval'],
722
-				[$occurrence, $occurrence2, $occurrence3]
723
-			),
724
-			['hour', true, true] => $this->l10n->n(
725
-				'In a hour on %1$s then on %2$s and %3$s',
726
-				'In %n hours on %1$s then on %2$s and %3$s',
727
-				$occurrenceIn['interval'],
728
-				[$occurrence, $occurrence2, $occurrence3]
729
-			),
730
-			['day', true, true] => $this->l10n->n(
731
-				'In a day on %1$s then on %2$s and %3$s',
732
-				'In %n days on %1$s then on %2$s and %3$s',
733
-				$occurrenceIn['interval'],
734
-				[$occurrence, $occurrence2, $occurrence3]
735
-			),
736
-			['week', true, true] => $this->l10n->n(
737
-				'In a week on %1$s then on %2$s and %3$s',
738
-				'In %n weeks on %1$s then on %2$s and %3$s',
739
-				$occurrenceIn['interval'],
740
-				[$occurrence, $occurrence2, $occurrence3]
741
-			),
742
-			['month', true, true] => $this->l10n->n(
743
-				'In a month on %1$s then on %2$s and %3$s',
744
-				'In %n months on %1$s then on %2$s and %3$s',
745
-				$occurrenceIn['interval'],
746
-				[$occurrence, $occurrence2, $occurrence3]
747
-			),
748
-			['year', true, true] => $this->l10n->n(
749
-				'In a year on %1$s then on %2$s and %3$s',
750
-				'In %n years on %1$s then on %2$s and %3$s',
751
-				$occurrenceIn['interval'],
752
-				[$occurrence, $occurrence2, $occurrence3]
753
-			),
754
-			default => $this->l10n->t('Could not generate next recurrence statement')
755
-		};
756
-
757
-	}
758
-
759
-	/**
760
-	 * @param VEvent $vEvent
761
-	 * @return array
762
-	 */
763
-	public function buildCancelledBodyData(VEvent $vEvent): array {
764
-		// construct event reader
765
-		$eventReaderCurrent = new EventReader($vEvent);
766
-		$defaultVal = '';
767
-		$strikethrough = "<span style='text-decoration: line-through'>%s</span>";
768
-
769
-		$newMeetingWhen = $this->generateWhenString($eventReaderCurrent);
770
-		$newSummary = isset($vEvent->SUMMARY) && (string)$vEvent->SUMMARY !== '' ? (string)$vEvent->SUMMARY : $this->l10n->t('Untitled event');
771
-		$newDescription = isset($vEvent->DESCRIPTION) && (string)$vEvent->DESCRIPTION !== '' ? (string)$vEvent->DESCRIPTION : $defaultVal;
772
-		$newUrl = isset($vEvent->URL) && (string)$vEvent->URL !== '' ? sprintf('<a href="%1$s">%1$s</a>', $vEvent->URL) : $defaultVal;
773
-		$newLocation = isset($vEvent->LOCATION) && (string)$vEvent->LOCATION !== '' ? (string)$vEvent->LOCATION : $defaultVal;
774
-		$newLocationHtml = $this->linkify($newLocation) ?? $newLocation;
775
-
776
-		$data = [];
777
-		$data['meeting_when_html'] = $newMeetingWhen === '' ?: sprintf($strikethrough, $newMeetingWhen);
778
-		$data['meeting_when'] = $newMeetingWhen;
779
-		$data['meeting_title_html'] = sprintf($strikethrough, $newSummary);
780
-		$data['meeting_title'] = $newSummary !== '' ? $newSummary: $this->l10n->t('Untitled event');
781
-		$data['meeting_description_html'] = $newDescription !== '' ? sprintf($strikethrough, $newDescription) : '';
782
-		$data['meeting_description'] = $newDescription;
783
-		$data['meeting_url_html'] = $newUrl !== '' ? sprintf($strikethrough, $newUrl) : '';
784
-		$data['meeting_url'] = isset($vEvent->URL) ? (string)$vEvent->URL : '';
785
-		$data['meeting_location_html'] = $newLocationHtml !== '' ? sprintf($strikethrough, $newLocationHtml) : '';
786
-		$data['meeting_location'] = $newLocation;
787
-		return $data;
788
-	}
789
-
790
-	/**
791
-	 * Check if event took place in the past
792
-	 *
793
-	 * @param VCalendar $vObject
794
-	 * @return int
795
-	 */
796
-	public function getLastOccurrence(VCalendar $vObject) {
797
-		/** @var VEvent $component */
798
-		$component = $vObject->VEVENT;
799
-
800
-		if (isset($component->RRULE)) {
801
-			$it = new EventIterator($vObject, (string)$component->UID);
802
-			$maxDate = new \DateTime(IMipPlugin::MAX_DATE);
803
-			if ($it->isInfinite()) {
804
-				return $maxDate->getTimestamp();
805
-			}
806
-
807
-			$end = $it->getDtEnd();
808
-			while ($it->valid() && $end < $maxDate) {
809
-				$end = $it->getDtEnd();
810
-				$it->next();
811
-			}
812
-			return $end->getTimestamp();
813
-		}
814
-
815
-		/** @var Property\ICalendar\DateTime $dtStart */
816
-		$dtStart = $component->DTSTART;
817
-
818
-		if (isset($component->DTEND)) {
819
-			/** @var Property\ICalendar\DateTime $dtEnd */
820
-			$dtEnd = $component->DTEND;
821
-			return $dtEnd->getDateTime()->getTimeStamp();
822
-		}
823
-
824
-		if (isset($component->DURATION)) {
825
-			/** @var \DateTime $endDate */
826
-			$endDate = clone $dtStart->getDateTime();
827
-			// $component->DTEND->getDateTime() returns DateTimeImmutable
828
-			$endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
829
-			return $endDate->getTimestamp();
830
-		}
831
-
832
-		if (!$dtStart->hasTime()) {
833
-			/** @var \DateTime $endDate */
834
-			// $component->DTSTART->getDateTime() returns DateTimeImmutable
835
-			$endDate = clone $dtStart->getDateTime();
836
-			$endDate = $endDate->modify('+1 day');
837
-			return $endDate->getTimestamp();
838
-		}
839
-
840
-		// No computation of end time possible - return start date
841
-		return $dtStart->getDateTime()->getTimeStamp();
842
-	}
843
-
844
-	/**
845
-	 * @param Property|null $attendee
846
-	 */
847
-	public function setL10n(?Property $attendee = null) {
848
-		if ($attendee === null) {
849
-			return;
850
-		}
851
-
852
-		$lang = $attendee->offsetGet('LANGUAGE');
853
-		if ($lang instanceof Parameter) {
854
-			$lang = $lang->getValue();
855
-			$this->l10n = $this->l10nFactory->get('dav', $lang);
856
-		}
857
-	}
858
-
859
-	/**
860
-	 * @param Property|null $attendee
861
-	 * @return bool
862
-	 */
863
-	public function getAttendeeRsvpOrReqForParticipant(?Property $attendee = null) {
864
-		if ($attendee === null) {
865
-			return false;
866
-		}
867
-
868
-		$rsvp = $attendee->offsetGet('RSVP');
869
-		if (($rsvp instanceof Parameter) && (strcasecmp($rsvp->getValue(), 'TRUE') === 0)) {
870
-			return true;
871
-		}
872
-		$role = $attendee->offsetGet('ROLE');
873
-		// @see https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.16
874
-		// Attendees without a role are assumed required and should receive an invitation link even if they have no RSVP set
875
-		if ($role === null
876
-			|| (($role instanceof Parameter) && (strcasecmp($role->getValue(), 'REQ-PARTICIPANT') === 0))
877
-			|| (($role instanceof Parameter) && (strcasecmp($role->getValue(), 'OPT-PARTICIPANT') === 0))
878
-		) {
879
-			return true;
880
-		}
881
-
882
-		// RFC 5545 3.2.17: default RSVP is false
883
-		return false;
884
-	}
885
-
886
-	/**
887
-	 * @param IEMailTemplate $template
888
-	 * @param string $method
889
-	 * @param string $sender
890
-	 * @param string $summary
891
-	 * @param string|null $partstat
892
-	 * @param bool $isModified
893
-	 */
894
-	public function addSubjectAndHeading(IEMailTemplate $template,
895
-		string $method, string $sender, string $summary, bool $isModified, ?Property $replyingAttendee = null): void {
896
-		if ($method === IMipPlugin::METHOD_CANCEL) {
897
-			// TRANSLATORS Subject for email, when an invitation is cancelled. Ex: "Cancelled: {{Event Name}}"
898
-			$template->setSubject($this->l10n->t('Cancelled: %1$s', [$summary]));
899
-			$template->addHeading($this->l10n->t('"%1$s" has been canceled', [$summary]));
900
-		} elseif ($method === IMipPlugin::METHOD_REPLY) {
901
-			// TRANSLATORS Subject for email, when an invitation is replied to. Ex: "Re: {{Event Name}}"
902
-			$template->setSubject($this->l10n->t('Re: %1$s', [$summary]));
903
-			// Build the strings
904
-			$partstat = (isset($replyingAttendee)) ? $replyingAttendee->offsetGet('PARTSTAT') : null;
905
-			$partstat = ($partstat instanceof Parameter) ? $partstat->getValue() : null;
906
-			switch ($partstat) {
907
-				case 'ACCEPTED':
908
-					$template->addHeading($this->l10n->t('%1$s has accepted your invitation', [$sender]));
909
-					break;
910
-				case 'TENTATIVE':
911
-					$template->addHeading($this->l10n->t('%1$s has tentatively accepted your invitation', [$sender]));
912
-					break;
913
-				case 'DECLINED':
914
-					$template->addHeading($this->l10n->t('%1$s has declined your invitation', [$sender]));
915
-					break;
916
-				case null:
917
-				default:
918
-					$template->addHeading($this->l10n->t('%1$s has responded to your invitation', [$sender]));
919
-					break;
920
-			}
921
-		} elseif ($method === IMipPlugin::METHOD_REQUEST && $isModified) {
922
-			// TRANSLATORS Subject for email, when an invitation is updated. Ex: "Invitation updated: {{Event Name}}"
923
-			$template->setSubject($this->l10n->t('Invitation updated: %1$s', [$summary]));
924
-			$template->addHeading($this->l10n->t('%1$s updated the event "%2$s"', [$sender, $summary]));
925
-		} else {
926
-			// TRANSLATORS Subject for email, when an invitation is sent. Ex: "Invitation: {{Event Name}}"
927
-			$template->setSubject($this->l10n->t('Invitation: %1$s', [$summary]));
928
-			$template->addHeading($this->l10n->t('%1$s would like to invite you to "%2$s"', [$sender, $summary]));
929
-		}
930
-	}
931
-
932
-	/**
933
-	 * @param string $path
934
-	 * @return string
935
-	 */
936
-	public function getAbsoluteImagePath($path): string {
937
-		return $this->urlGenerator->getAbsoluteURL(
938
-			$this->urlGenerator->imagePath('core', $path)
939
-		);
940
-	}
941
-
942
-	/**
943
-	 * addAttendees: add organizer and attendee names/emails to iMip mail.
944
-	 *
945
-	 * Enable with DAV setting: invitation_list_attendees (default: no)
946
-	 *
947
-	 * The default is 'no', which matches old behavior, and is privacy preserving.
948
-	 *
949
-	 * To enable including attendees in invitation emails:
950
-	 *   % php occ config:app:set dav invitation_list_attendees --value yes
951
-	 *
952
-	 * @param IEMailTemplate $template
953
-	 * @param IL10N $this->l10n
954
-	 * @param VEvent $vevent
955
-	 * @author brad2014 on github.com
956
-	 */
957
-	public function addAttendees(IEMailTemplate $template, VEvent $vevent) {
958
-		if ($this->config->getAppValue('dav', 'invitation_list_attendees', 'no') === 'no') {
959
-			return;
960
-		}
961
-
962
-		if (isset($vevent->ORGANIZER)) {
963
-			/** @var Property | Property\ICalendar\CalAddress $organizer */
964
-			$organizer = $vevent->ORGANIZER;
965
-			$organizerEmail = substr($organizer->getNormalizedValue(), 7);
966
-			/** @var string|null $organizerName */
967
-			$organizerName = isset($organizer->CN) ? $organizer->CN->getValue() : null;
968
-			$organizerHTML = sprintf('<a href="%s">%s</a>',
969
-				htmlspecialchars($organizer->getNormalizedValue()),
970
-				htmlspecialchars($organizerName ?: $organizerEmail));
971
-			$organizerText = sprintf('%s <%s>', $organizerName, $organizerEmail);
972
-			if (isset($organizer['PARTSTAT'])) {
973
-				/** @var Parameter $partstat */
974
-				$partstat = $organizer['PARTSTAT'];
975
-				if (strcasecmp($partstat->getValue(), 'ACCEPTED') === 0) {
976
-					$organizerHTML .= ' ✔︎';
977
-					$organizerText .= ' ✔︎';
978
-				}
979
-			}
980
-			$template->addBodyListItem($organizerHTML, $this->l10n->t('Organizer:'),
981
-				$this->getAbsoluteImagePath('caldav/organizer.png'),
982
-				$organizerText, '', IMipPlugin::IMIP_INDENT);
983
-		}
984
-
985
-		$attendees = $vevent->select('ATTENDEE');
986
-		if (count($attendees) === 0) {
987
-			return;
988
-		}
989
-
990
-		$attendeesHTML = [];
991
-		$attendeesText = [];
992
-		foreach ($attendees as $attendee) {
993
-			$attendeeEmail = substr($attendee->getNormalizedValue(), 7);
994
-			$attendeeName = isset($attendee['CN']) ? $attendee['CN']->getValue() : null;
995
-			$attendeeHTML = sprintf('<a href="%s">%s</a>',
996
-				htmlspecialchars($attendee->getNormalizedValue()),
997
-				htmlspecialchars($attendeeName ?: $attendeeEmail));
998
-			$attendeeText = sprintf('%s <%s>', $attendeeName, $attendeeEmail);
999
-			if (isset($attendee['PARTSTAT'])) {
1000
-				/** @var Parameter $partstat */
1001
-				$partstat = $attendee['PARTSTAT'];
1002
-				if (strcasecmp($partstat->getValue(), 'ACCEPTED') === 0) {
1003
-					$attendeeHTML .= ' ✔︎';
1004
-					$attendeeText .= ' ✔︎';
1005
-				}
1006
-			}
1007
-			$attendeesHTML[] = $attendeeHTML;
1008
-			$attendeesText[] = $attendeeText;
1009
-		}
1010
-
1011
-		$template->addBodyListItem(implode('<br/>', $attendeesHTML), $this->l10n->t('Attendees:'),
1012
-			$this->getAbsoluteImagePath('caldav/attendees.png'),
1013
-			implode("\n", $attendeesText), '', IMipPlugin::IMIP_INDENT);
1014
-	}
1015
-
1016
-	/**
1017
-	 * @param IEMailTemplate $template
1018
-	 * @param VEVENT $vevent
1019
-	 * @param $data
1020
-	 */
1021
-	public function addBulletList(IEMailTemplate $template, VEvent $vevent, $data) {
1022
-		$template->addBodyListItem(
1023
-			$data['meeting_title_html'] ?? $data['meeting_title'], $this->l10n->t('Title:'),
1024
-			$this->getAbsoluteImagePath('caldav/title.png'), $data['meeting_title'], '', IMipPlugin::IMIP_INDENT);
1025
-		if ($data['meeting_when'] !== '') {
1026
-			$template->addBodyListItem($data['meeting_when_html'] ?? $data['meeting_when'], $this->l10n->t('When:'),
1027
-				$this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_when'], '', IMipPlugin::IMIP_INDENT);
1028
-		}
1029
-		if ($data['meeting_location'] !== '') {
1030
-			$template->addBodyListItem($data['meeting_location_html'] ?? $data['meeting_location'], $this->l10n->t('Location:'),
1031
-				$this->getAbsoluteImagePath('caldav/location.png'), $data['meeting_location'], '', IMipPlugin::IMIP_INDENT);
1032
-		}
1033
-		if ($data['meeting_url'] !== '') {
1034
-			$template->addBodyListItem($data['meeting_url_html'] ?? $data['meeting_url'], $this->l10n->t('Link:'),
1035
-				$this->getAbsoluteImagePath('caldav/link.png'), $data['meeting_url'], '', IMipPlugin::IMIP_INDENT);
1036
-		}
1037
-		if (isset($data['meeting_occurring'])) {
1038
-			$template->addBodyListItem($data['meeting_occurring_html'] ?? $data['meeting_occurring'], $this->l10n->t('Occurring:'),
1039
-				$this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_occurring'], '', IMipPlugin::IMIP_INDENT);
1040
-		}
1041
-
1042
-		$this->addAttendees($template, $vevent);
1043
-
1044
-		/* Put description last, like an email body, since it can be arbitrarily long */
1045
-		if ($data['meeting_description']) {
1046
-			$template->addBodyListItem($data['meeting_description_html'] ?? $data['meeting_description'], $this->l10n->t('Description:'),
1047
-				$this->getAbsoluteImagePath('caldav/description.png'), $data['meeting_description'], '', IMipPlugin::IMIP_INDENT);
1048
-		}
1049
-	}
1050
-
1051
-	/**
1052
-	 * @param Message $iTipMessage
1053
-	 * @return null|Property
1054
-	 */
1055
-	public function getCurrentAttendee(Message $iTipMessage): ?Property {
1056
-		/** @var VEvent $vevent */
1057
-		$vevent = $iTipMessage->message->VEVENT;
1058
-		$attendees = $vevent->select('ATTENDEE');
1059
-		foreach ($attendees as $attendee) {
1060
-			if ($iTipMessage->method === 'REPLY' && strcasecmp($attendee->getValue(), $iTipMessage->sender) === 0) {
1061
-				/** @var Property $attendee */
1062
-				return $attendee;
1063
-			} elseif (strcasecmp($attendee->getValue(), $iTipMessage->recipient) === 0) {
1064
-				/** @var Property $attendee */
1065
-				return $attendee;
1066
-			}
1067
-		}
1068
-		return null;
1069
-	}
1070
-
1071
-	/**
1072
-	 * @param Message $iTipMessage
1073
-	 * @param VEvent $vevent
1074
-	 * @param int $lastOccurrence
1075
-	 * @return string
1076
-	 */
1077
-	public function createInvitationToken(Message $iTipMessage, VEvent $vevent, int $lastOccurrence): string {
1078
-		$token = $this->random->generate(60, ISecureRandom::CHAR_ALPHANUMERIC);
1079
-
1080
-		$attendee = $iTipMessage->recipient;
1081
-		$organizer = $iTipMessage->sender;
1082
-		$sequence = $iTipMessage->sequence;
1083
-		$recurrenceId = isset($vevent->{'RECURRENCE-ID'}) ?
1084
-			$vevent->{'RECURRENCE-ID'}->serialize() : null;
1085
-		$uid = $vevent->{'UID'};
1086
-
1087
-		$query = $this->db->getQueryBuilder();
1088
-		$query->insert('calendar_invitations')
1089
-			->values([
1090
-				'token' => $query->createNamedParameter($token),
1091
-				'attendee' => $query->createNamedParameter($attendee),
1092
-				'organizer' => $query->createNamedParameter($organizer),
1093
-				'sequence' => $query->createNamedParameter($sequence),
1094
-				'recurrenceid' => $query->createNamedParameter($recurrenceId),
1095
-				'expiration' => $query->createNamedParameter($lastOccurrence),
1096
-				'uid' => $query->createNamedParameter($uid)
1097
-			])
1098
-			->executeStatement();
1099
-
1100
-		return $token;
1101
-	}
1102
-
1103
-	/**
1104
-	 * @param IEMailTemplate $template
1105
-	 * @param $token
1106
-	 */
1107
-	public function addResponseButtons(IEMailTemplate $template, $token) {
1108
-		$template->addBodyButtonGroup(
1109
-			$this->l10n->t('Accept'),
1110
-			$this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.accept', [
1111
-				'token' => $token,
1112
-			]),
1113
-			$this->l10n->t('Decline'),
1114
-			$this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.decline', [
1115
-				'token' => $token,
1116
-			])
1117
-		);
1118
-	}
1119
-
1120
-	public function addMoreOptionsButton(IEMailTemplate $template, $token) {
1121
-		$moreOptionsURL = $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.options', [
1122
-			'token' => $token,
1123
-		]);
1124
-		$html = vsprintf('<small><a href="%s">%s</a></small>', [
1125
-			$moreOptionsURL, $this->l10n->t('More options …')
1126
-		]);
1127
-		$text = $this->l10n->t('More options at %s', [$moreOptionsURL]);
1128
-
1129
-		$template->addBodyText($html, $text);
1130
-	}
1131
-
1132
-	public function getReplyingAttendee(Message $iTipMessage): ?Property {
1133
-		/** @var VEvent $vevent */
1134
-		$vevent = $iTipMessage->message->VEVENT;
1135
-		$attendees = $vevent->select('ATTENDEE');
1136
-		foreach ($attendees as $attendee) {
1137
-			/** @var Property $attendee */
1138
-			if (strcasecmp($attendee->getValue(), $iTipMessage->sender) === 0) {
1139
-				return $attendee;
1140
-			}
1141
-		}
1142
-		return null;
1143
-	}
1144
-
1145
-	public function isRoomOrResource(Property $attendee): bool {
1146
-		$cuType = $attendee->offsetGet('CUTYPE');
1147
-		if (!$cuType instanceof Parameter) {
1148
-			return false;
1149
-		}
1150
-		$type = $cuType->getValue() ?? 'INDIVIDUAL';
1151
-		if (\in_array(strtoupper($type), ['RESOURCE', 'ROOM'], true)) {
1152
-			// Don't send emails to things
1153
-			return true;
1154
-		}
1155
-		return false;
1156
-	}
1157
-
1158
-	public function isCircle(Property $attendee): bool {
1159
-		$cuType = $attendee->offsetGet('CUTYPE');
1160
-		if (!$cuType instanceof Parameter) {
1161
-			return false;
1162
-		}
1163
-
1164
-		$uri = $attendee->getValue();
1165
-		if (!$uri) {
1166
-			return false;
1167
-		}
1168
-
1169
-		$cuTypeValue = $cuType->getValue();
1170
-		return $cuTypeValue === 'GROUP' && str_starts_with($uri, 'mailto:circle+');
1171
-	}
1172
-
1173
-	public function minimizeInterval(\DateInterval $dateInterval): array {
1174
-		// evaluate if time interval is in the past
1175
-		if ($dateInterval->invert == 1) {
1176
-			return ['interval' => 1, 'scale' => 'past'];
1177
-		}
1178
-		// evaluate interval parts and return smallest time period
1179
-		if ($dateInterval->y > 0) {
1180
-			$interval = $dateInterval->y;
1181
-			$scale = 'year';
1182
-		} elseif ($dateInterval->m > 0) {
1183
-			$interval = $dateInterval->m;
1184
-			$scale = 'month';
1185
-		} elseif ($dateInterval->d >= 7) {
1186
-			$interval = (int)($dateInterval->d / 7);
1187
-			$scale = 'week';
1188
-		} elseif ($dateInterval->d > 0) {
1189
-			$interval = $dateInterval->d;
1190
-			$scale = 'day';
1191
-		} elseif ($dateInterval->h > 0) {
1192
-			$interval = $dateInterval->h;
1193
-			$scale = 'hour';
1194
-		} else {
1195
-			$interval = $dateInterval->i;
1196
-			$scale = 'minute';
1197
-		}
1198
-
1199
-		return ['interval' => $interval, 'scale' => $scale];
1200
-	}
1201
-
1202
-	/**
1203
-	 * Localizes week day names to another language
1204
-	 *
1205
-	 * @param string $value
1206
-	 *
1207
-	 * @return string
1208
-	 */
1209
-	public function localizeDayName(string $value): string {
1210
-		return match ($value) {
1211
-			'Monday' => $this->l10n->t('Monday'),
1212
-			'Tuesday' => $this->l10n->t('Tuesday'),
1213
-			'Wednesday' => $this->l10n->t('Wednesday'),
1214
-			'Thursday' => $this->l10n->t('Thursday'),
1215
-			'Friday' => $this->l10n->t('Friday'),
1216
-			'Saturday' => $this->l10n->t('Saturday'),
1217
-			'Sunday' => $this->l10n->t('Sunday'),
1218
-		};
1219
-	}
1220
-
1221
-	/**
1222
-	 * Localizes month names to another language
1223
-	 *
1224
-	 * @param string $value
1225
-	 *
1226
-	 * @return string
1227
-	 */
1228
-	public function localizeMonthName(string $value): string {
1229
-		return match ($value) {
1230
-			'January' => $this->l10n->t('January'),
1231
-			'February' => $this->l10n->t('February'),
1232
-			'March' => $this->l10n->t('March'),
1233
-			'April' => $this->l10n->t('April'),
1234
-			'May' => $this->l10n->t('May'),
1235
-			'June' => $this->l10n->t('June'),
1236
-			'July' => $this->l10n->t('July'),
1237
-			'August' => $this->l10n->t('August'),
1238
-			'September' => $this->l10n->t('September'),
1239
-			'October' => $this->l10n->t('October'),
1240
-			'November' => $this->l10n->t('November'),
1241
-			'December' => $this->l10n->t('December'),
1242
-		};
1243
-	}
1244
-
1245
-	/**
1246
-	 * Localizes relative position names to another language
1247
-	 *
1248
-	 * @param string $value
1249
-	 *
1250
-	 * @return string
1251
-	 */
1252
-	public function localizeRelativePositionName(string $value): string {
1253
-		return match ($value) {
1254
-			'First' => $this->l10n->t('First'),
1255
-			'Second' => $this->l10n->t('Second'),
1256
-			'Third' => $this->l10n->t('Third'),
1257
-			'Fourth' => $this->l10n->t('Fourth'),
1258
-			'Fifth' => $this->l10n->t('Fifth'),
1259
-			'Last' => $this->l10n->t('Last'),
1260
-			'Second Last' => $this->l10n->t('Second Last'),
1261
-			'Third Last' => $this->l10n->t('Third Last'),
1262
-			'Fourth Last' => $this->l10n->t('Fourth Last'),
1263
-			'Fifth Last' => $this->l10n->t('Fifth Last'),
1264
-		};
1265
-	}
586
+    /**
587
+     * generates a occurring next string for a recurring event
588
+     *
589
+     * @since 30.0.0
590
+     *
591
+     * @param EventReader $er
592
+     *
593
+     * @return string
594
+     */
595
+    public function generateOccurringString(EventReader $er): string {
596
+
597
+        // initialize
598
+        $occurrence = null;
599
+        $occurrence2 = null;
600
+        $occurrence3 = null;
601
+        // reset to initial occurrence
602
+        $er->recurrenceRewind();
603
+        // forward to current date
604
+        $er->recurrenceAdvanceTo($this->timeFactory->getDateTime());
605
+        // calculate time difference from now to start of next event occurrence and minimize it
606
+        $occurrenceIn = $this->minimizeInterval($this->timeFactory->getDateTime()->diff($er->recurrenceDate()));
607
+        // store next occurrence value
608
+        $occurrence = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
609
+        // forward one occurrence
610
+        $er->recurrenceAdvance();
611
+        // evaluate if occurrence is valid
612
+        if ($er->recurrenceDate() !== null) {
613
+            // store following occurrence value
614
+            $occurrence2 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
615
+            // forward one occurrence
616
+            $er->recurrenceAdvance();
617
+            // evaluate if occurrence is valid
618
+            if ($er->recurrenceDate()) {
619
+                // store following occurrence value
620
+                $occurrence3 = $this->l10n->l('date', $er->recurrenceDate(), ['width' => 'long']);
621
+            }
622
+        }
623
+        // generate localized when string
624
+        // TRANSLATORS
625
+        // Indicates when a calendar event will happen, shown on invitation emails
626
+        // Output produced in order:
627
+        // In a minute/hour/day/week/month/year on July 1, 2024
628
+        // In a minute/hour/day/week/month/year on July 1, 2024 then on July 3, 2024
629
+        // In a minute/hour/day/week/month/year on July 1, 2024 then on July 3, 2024 and July 5, 2024
630
+        // In 2 minutes/hours/days/weeks/months/years on July 1, 2024
631
+        // In 2 minutes/hours/days/weeks/months/years on July 1, 2024 then on July 3, 2024
632
+        // In 2 minutes/hours/days/weeks/months/years on July 1, 2024 then on July 3, 2024 and July 5, 2024
633
+        return match ([$occurrenceIn['scale'], $occurrence2 !== null, $occurrence3 !== null]) {
634
+            ['past', false, false] => $this->l10n->t(
635
+                'In the past on %1$s',
636
+                [$occurrence]
637
+            ),
638
+            ['minute', false, false] => $this->l10n->n(
639
+                'In a minute on %1$s',
640
+                'In %n minutes on %1$s',
641
+                $occurrenceIn['interval'],
642
+                [$occurrence]
643
+            ),
644
+            ['hour', false, false] => $this->l10n->n(
645
+                'In a hour on %1$s',
646
+                'In %n hours on %1$s',
647
+                $occurrenceIn['interval'],
648
+                [$occurrence]
649
+            ),
650
+            ['day', false, false] => $this->l10n->n(
651
+                'In a day on %1$s',
652
+                'In %n days on %1$s',
653
+                $occurrenceIn['interval'],
654
+                [$occurrence]
655
+            ),
656
+            ['week', false, false] => $this->l10n->n(
657
+                'In a week on %1$s',
658
+                'In %n weeks on %1$s',
659
+                $occurrenceIn['interval'],
660
+                [$occurrence]
661
+            ),
662
+            ['month', false, false] => $this->l10n->n(
663
+                'In a month on %1$s',
664
+                'In %n months on %1$s',
665
+                $occurrenceIn['interval'],
666
+                [$occurrence]
667
+            ),
668
+            ['year', false, false] => $this->l10n->n(
669
+                'In a year on %1$s',
670
+                'In %n years on %1$s',
671
+                $occurrenceIn['interval'],
672
+                [$occurrence]
673
+            ),
674
+            ['past', true, false] => $this->l10n->t(
675
+                'In the past on %1$s then on %2$s',
676
+                [$occurrence, $occurrence2]
677
+            ),
678
+            ['minute', true, false] => $this->l10n->n(
679
+                'In a minute on %1$s then on %2$s',
680
+                'In %n minutes on %1$s then on %2$s',
681
+                $occurrenceIn['interval'],
682
+                [$occurrence, $occurrence2]
683
+            ),
684
+            ['hour', true, false] => $this->l10n->n(
685
+                'In a hour on %1$s then on %2$s',
686
+                'In %n hours on %1$s then on %2$s',
687
+                $occurrenceIn['interval'],
688
+                [$occurrence, $occurrence2]
689
+            ),
690
+            ['day', true, false] => $this->l10n->n(
691
+                'In a day on %1$s then on %2$s',
692
+                'In %n days on %1$s then on %2$s',
693
+                $occurrenceIn['interval'],
694
+                [$occurrence, $occurrence2]
695
+            ),
696
+            ['week', true, false] => $this->l10n->n(
697
+                'In a week on %1$s then on %2$s',
698
+                'In %n weeks on %1$s then on %2$s',
699
+                $occurrenceIn['interval'],
700
+                [$occurrence, $occurrence2]
701
+            ),
702
+            ['month', true, false] => $this->l10n->n(
703
+                'In a month on %1$s then on %2$s',
704
+                'In %n months on %1$s then on %2$s',
705
+                $occurrenceIn['interval'],
706
+                [$occurrence, $occurrence2]
707
+            ),
708
+            ['year', true, false] => $this->l10n->n(
709
+                'In a year on %1$s then on %2$s',
710
+                'In %n years on %1$s then on %2$s',
711
+                $occurrenceIn['interval'],
712
+                [$occurrence, $occurrence2]
713
+            ),
714
+            ['past', true, true] => $this->l10n->t(
715
+                'In the past on %1$s then on %2$s and %3$s',
716
+                [$occurrence, $occurrence2, $occurrence3]
717
+            ),
718
+            ['minute', true, true] => $this->l10n->n(
719
+                'In a minute on %1$s then on %2$s and %3$s',
720
+                'In %n minutes on %1$s then on %2$s and %3$s',
721
+                $occurrenceIn['interval'],
722
+                [$occurrence, $occurrence2, $occurrence3]
723
+            ),
724
+            ['hour', true, true] => $this->l10n->n(
725
+                'In a hour on %1$s then on %2$s and %3$s',
726
+                'In %n hours on %1$s then on %2$s and %3$s',
727
+                $occurrenceIn['interval'],
728
+                [$occurrence, $occurrence2, $occurrence3]
729
+            ),
730
+            ['day', true, true] => $this->l10n->n(
731
+                'In a day on %1$s then on %2$s and %3$s',
732
+                'In %n days on %1$s then on %2$s and %3$s',
733
+                $occurrenceIn['interval'],
734
+                [$occurrence, $occurrence2, $occurrence3]
735
+            ),
736
+            ['week', true, true] => $this->l10n->n(
737
+                'In a week on %1$s then on %2$s and %3$s',
738
+                'In %n weeks on %1$s then on %2$s and %3$s',
739
+                $occurrenceIn['interval'],
740
+                [$occurrence, $occurrence2, $occurrence3]
741
+            ),
742
+            ['month', true, true] => $this->l10n->n(
743
+                'In a month on %1$s then on %2$s and %3$s',
744
+                'In %n months on %1$s then on %2$s and %3$s',
745
+                $occurrenceIn['interval'],
746
+                [$occurrence, $occurrence2, $occurrence3]
747
+            ),
748
+            ['year', true, true] => $this->l10n->n(
749
+                'In a year on %1$s then on %2$s and %3$s',
750
+                'In %n years on %1$s then on %2$s and %3$s',
751
+                $occurrenceIn['interval'],
752
+                [$occurrence, $occurrence2, $occurrence3]
753
+            ),
754
+            default => $this->l10n->t('Could not generate next recurrence statement')
755
+        };
756
+
757
+    }
758
+
759
+    /**
760
+     * @param VEvent $vEvent
761
+     * @return array
762
+     */
763
+    public function buildCancelledBodyData(VEvent $vEvent): array {
764
+        // construct event reader
765
+        $eventReaderCurrent = new EventReader($vEvent);
766
+        $defaultVal = '';
767
+        $strikethrough = "<span style='text-decoration: line-through'>%s</span>";
768
+
769
+        $newMeetingWhen = $this->generateWhenString($eventReaderCurrent);
770
+        $newSummary = isset($vEvent->SUMMARY) && (string)$vEvent->SUMMARY !== '' ? (string)$vEvent->SUMMARY : $this->l10n->t('Untitled event');
771
+        $newDescription = isset($vEvent->DESCRIPTION) && (string)$vEvent->DESCRIPTION !== '' ? (string)$vEvent->DESCRIPTION : $defaultVal;
772
+        $newUrl = isset($vEvent->URL) && (string)$vEvent->URL !== '' ? sprintf('<a href="%1$s">%1$s</a>', $vEvent->URL) : $defaultVal;
773
+        $newLocation = isset($vEvent->LOCATION) && (string)$vEvent->LOCATION !== '' ? (string)$vEvent->LOCATION : $defaultVal;
774
+        $newLocationHtml = $this->linkify($newLocation) ?? $newLocation;
775
+
776
+        $data = [];
777
+        $data['meeting_when_html'] = $newMeetingWhen === '' ?: sprintf($strikethrough, $newMeetingWhen);
778
+        $data['meeting_when'] = $newMeetingWhen;
779
+        $data['meeting_title_html'] = sprintf($strikethrough, $newSummary);
780
+        $data['meeting_title'] = $newSummary !== '' ? $newSummary: $this->l10n->t('Untitled event');
781
+        $data['meeting_description_html'] = $newDescription !== '' ? sprintf($strikethrough, $newDescription) : '';
782
+        $data['meeting_description'] = $newDescription;
783
+        $data['meeting_url_html'] = $newUrl !== '' ? sprintf($strikethrough, $newUrl) : '';
784
+        $data['meeting_url'] = isset($vEvent->URL) ? (string)$vEvent->URL : '';
785
+        $data['meeting_location_html'] = $newLocationHtml !== '' ? sprintf($strikethrough, $newLocationHtml) : '';
786
+        $data['meeting_location'] = $newLocation;
787
+        return $data;
788
+    }
789
+
790
+    /**
791
+     * Check if event took place in the past
792
+     *
793
+     * @param VCalendar $vObject
794
+     * @return int
795
+     */
796
+    public function getLastOccurrence(VCalendar $vObject) {
797
+        /** @var VEvent $component */
798
+        $component = $vObject->VEVENT;
799
+
800
+        if (isset($component->RRULE)) {
801
+            $it = new EventIterator($vObject, (string)$component->UID);
802
+            $maxDate = new \DateTime(IMipPlugin::MAX_DATE);
803
+            if ($it->isInfinite()) {
804
+                return $maxDate->getTimestamp();
805
+            }
806
+
807
+            $end = $it->getDtEnd();
808
+            while ($it->valid() && $end < $maxDate) {
809
+                $end = $it->getDtEnd();
810
+                $it->next();
811
+            }
812
+            return $end->getTimestamp();
813
+        }
814
+
815
+        /** @var Property\ICalendar\DateTime $dtStart */
816
+        $dtStart = $component->DTSTART;
817
+
818
+        if (isset($component->DTEND)) {
819
+            /** @var Property\ICalendar\DateTime $dtEnd */
820
+            $dtEnd = $component->DTEND;
821
+            return $dtEnd->getDateTime()->getTimeStamp();
822
+        }
823
+
824
+        if (isset($component->DURATION)) {
825
+            /** @var \DateTime $endDate */
826
+            $endDate = clone $dtStart->getDateTime();
827
+            // $component->DTEND->getDateTime() returns DateTimeImmutable
828
+            $endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
829
+            return $endDate->getTimestamp();
830
+        }
831
+
832
+        if (!$dtStart->hasTime()) {
833
+            /** @var \DateTime $endDate */
834
+            // $component->DTSTART->getDateTime() returns DateTimeImmutable
835
+            $endDate = clone $dtStart->getDateTime();
836
+            $endDate = $endDate->modify('+1 day');
837
+            return $endDate->getTimestamp();
838
+        }
839
+
840
+        // No computation of end time possible - return start date
841
+        return $dtStart->getDateTime()->getTimeStamp();
842
+    }
843
+
844
+    /**
845
+     * @param Property|null $attendee
846
+     */
847
+    public function setL10n(?Property $attendee = null) {
848
+        if ($attendee === null) {
849
+            return;
850
+        }
851
+
852
+        $lang = $attendee->offsetGet('LANGUAGE');
853
+        if ($lang instanceof Parameter) {
854
+            $lang = $lang->getValue();
855
+            $this->l10n = $this->l10nFactory->get('dav', $lang);
856
+        }
857
+    }
858
+
859
+    /**
860
+     * @param Property|null $attendee
861
+     * @return bool
862
+     */
863
+    public function getAttendeeRsvpOrReqForParticipant(?Property $attendee = null) {
864
+        if ($attendee === null) {
865
+            return false;
866
+        }
867
+
868
+        $rsvp = $attendee->offsetGet('RSVP');
869
+        if (($rsvp instanceof Parameter) && (strcasecmp($rsvp->getValue(), 'TRUE') === 0)) {
870
+            return true;
871
+        }
872
+        $role = $attendee->offsetGet('ROLE');
873
+        // @see https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.16
874
+        // Attendees without a role are assumed required and should receive an invitation link even if they have no RSVP set
875
+        if ($role === null
876
+            || (($role instanceof Parameter) && (strcasecmp($role->getValue(), 'REQ-PARTICIPANT') === 0))
877
+            || (($role instanceof Parameter) && (strcasecmp($role->getValue(), 'OPT-PARTICIPANT') === 0))
878
+        ) {
879
+            return true;
880
+        }
881
+
882
+        // RFC 5545 3.2.17: default RSVP is false
883
+        return false;
884
+    }
885
+
886
+    /**
887
+     * @param IEMailTemplate $template
888
+     * @param string $method
889
+     * @param string $sender
890
+     * @param string $summary
891
+     * @param string|null $partstat
892
+     * @param bool $isModified
893
+     */
894
+    public function addSubjectAndHeading(IEMailTemplate $template,
895
+        string $method, string $sender, string $summary, bool $isModified, ?Property $replyingAttendee = null): void {
896
+        if ($method === IMipPlugin::METHOD_CANCEL) {
897
+            // TRANSLATORS Subject for email, when an invitation is cancelled. Ex: "Cancelled: {{Event Name}}"
898
+            $template->setSubject($this->l10n->t('Cancelled: %1$s', [$summary]));
899
+            $template->addHeading($this->l10n->t('"%1$s" has been canceled', [$summary]));
900
+        } elseif ($method === IMipPlugin::METHOD_REPLY) {
901
+            // TRANSLATORS Subject for email, when an invitation is replied to. Ex: "Re: {{Event Name}}"
902
+            $template->setSubject($this->l10n->t('Re: %1$s', [$summary]));
903
+            // Build the strings
904
+            $partstat = (isset($replyingAttendee)) ? $replyingAttendee->offsetGet('PARTSTAT') : null;
905
+            $partstat = ($partstat instanceof Parameter) ? $partstat->getValue() : null;
906
+            switch ($partstat) {
907
+                case 'ACCEPTED':
908
+                    $template->addHeading($this->l10n->t('%1$s has accepted your invitation', [$sender]));
909
+                    break;
910
+                case 'TENTATIVE':
911
+                    $template->addHeading($this->l10n->t('%1$s has tentatively accepted your invitation', [$sender]));
912
+                    break;
913
+                case 'DECLINED':
914
+                    $template->addHeading($this->l10n->t('%1$s has declined your invitation', [$sender]));
915
+                    break;
916
+                case null:
917
+                default:
918
+                    $template->addHeading($this->l10n->t('%1$s has responded to your invitation', [$sender]));
919
+                    break;
920
+            }
921
+        } elseif ($method === IMipPlugin::METHOD_REQUEST && $isModified) {
922
+            // TRANSLATORS Subject for email, when an invitation is updated. Ex: "Invitation updated: {{Event Name}}"
923
+            $template->setSubject($this->l10n->t('Invitation updated: %1$s', [$summary]));
924
+            $template->addHeading($this->l10n->t('%1$s updated the event "%2$s"', [$sender, $summary]));
925
+        } else {
926
+            // TRANSLATORS Subject for email, when an invitation is sent. Ex: "Invitation: {{Event Name}}"
927
+            $template->setSubject($this->l10n->t('Invitation: %1$s', [$summary]));
928
+            $template->addHeading($this->l10n->t('%1$s would like to invite you to "%2$s"', [$sender, $summary]));
929
+        }
930
+    }
931
+
932
+    /**
933
+     * @param string $path
934
+     * @return string
935
+     */
936
+    public function getAbsoluteImagePath($path): string {
937
+        return $this->urlGenerator->getAbsoluteURL(
938
+            $this->urlGenerator->imagePath('core', $path)
939
+        );
940
+    }
941
+
942
+    /**
943
+     * addAttendees: add organizer and attendee names/emails to iMip mail.
944
+     *
945
+     * Enable with DAV setting: invitation_list_attendees (default: no)
946
+     *
947
+     * The default is 'no', which matches old behavior, and is privacy preserving.
948
+     *
949
+     * To enable including attendees in invitation emails:
950
+     *   % php occ config:app:set dav invitation_list_attendees --value yes
951
+     *
952
+     * @param IEMailTemplate $template
953
+     * @param IL10N $this->l10n
954
+     * @param VEvent $vevent
955
+     * @author brad2014 on github.com
956
+     */
957
+    public function addAttendees(IEMailTemplate $template, VEvent $vevent) {
958
+        if ($this->config->getAppValue('dav', 'invitation_list_attendees', 'no') === 'no') {
959
+            return;
960
+        }
961
+
962
+        if (isset($vevent->ORGANIZER)) {
963
+            /** @var Property | Property\ICalendar\CalAddress $organizer */
964
+            $organizer = $vevent->ORGANIZER;
965
+            $organizerEmail = substr($organizer->getNormalizedValue(), 7);
966
+            /** @var string|null $organizerName */
967
+            $organizerName = isset($organizer->CN) ? $organizer->CN->getValue() : null;
968
+            $organizerHTML = sprintf('<a href="%s">%s</a>',
969
+                htmlspecialchars($organizer->getNormalizedValue()),
970
+                htmlspecialchars($organizerName ?: $organizerEmail));
971
+            $organizerText = sprintf('%s <%s>', $organizerName, $organizerEmail);
972
+            if (isset($organizer['PARTSTAT'])) {
973
+                /** @var Parameter $partstat */
974
+                $partstat = $organizer['PARTSTAT'];
975
+                if (strcasecmp($partstat->getValue(), 'ACCEPTED') === 0) {
976
+                    $organizerHTML .= ' ✔︎';
977
+                    $organizerText .= ' ✔︎';
978
+                }
979
+            }
980
+            $template->addBodyListItem($organizerHTML, $this->l10n->t('Organizer:'),
981
+                $this->getAbsoluteImagePath('caldav/organizer.png'),
982
+                $organizerText, '', IMipPlugin::IMIP_INDENT);
983
+        }
984
+
985
+        $attendees = $vevent->select('ATTENDEE');
986
+        if (count($attendees) === 0) {
987
+            return;
988
+        }
989
+
990
+        $attendeesHTML = [];
991
+        $attendeesText = [];
992
+        foreach ($attendees as $attendee) {
993
+            $attendeeEmail = substr($attendee->getNormalizedValue(), 7);
994
+            $attendeeName = isset($attendee['CN']) ? $attendee['CN']->getValue() : null;
995
+            $attendeeHTML = sprintf('<a href="%s">%s</a>',
996
+                htmlspecialchars($attendee->getNormalizedValue()),
997
+                htmlspecialchars($attendeeName ?: $attendeeEmail));
998
+            $attendeeText = sprintf('%s <%s>', $attendeeName, $attendeeEmail);
999
+            if (isset($attendee['PARTSTAT'])) {
1000
+                /** @var Parameter $partstat */
1001
+                $partstat = $attendee['PARTSTAT'];
1002
+                if (strcasecmp($partstat->getValue(), 'ACCEPTED') === 0) {
1003
+                    $attendeeHTML .= ' ✔︎';
1004
+                    $attendeeText .= ' ✔︎';
1005
+                }
1006
+            }
1007
+            $attendeesHTML[] = $attendeeHTML;
1008
+            $attendeesText[] = $attendeeText;
1009
+        }
1010
+
1011
+        $template->addBodyListItem(implode('<br/>', $attendeesHTML), $this->l10n->t('Attendees:'),
1012
+            $this->getAbsoluteImagePath('caldav/attendees.png'),
1013
+            implode("\n", $attendeesText), '', IMipPlugin::IMIP_INDENT);
1014
+    }
1015
+
1016
+    /**
1017
+     * @param IEMailTemplate $template
1018
+     * @param VEVENT $vevent
1019
+     * @param $data
1020
+     */
1021
+    public function addBulletList(IEMailTemplate $template, VEvent $vevent, $data) {
1022
+        $template->addBodyListItem(
1023
+            $data['meeting_title_html'] ?? $data['meeting_title'], $this->l10n->t('Title:'),
1024
+            $this->getAbsoluteImagePath('caldav/title.png'), $data['meeting_title'], '', IMipPlugin::IMIP_INDENT);
1025
+        if ($data['meeting_when'] !== '') {
1026
+            $template->addBodyListItem($data['meeting_when_html'] ?? $data['meeting_when'], $this->l10n->t('When:'),
1027
+                $this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_when'], '', IMipPlugin::IMIP_INDENT);
1028
+        }
1029
+        if ($data['meeting_location'] !== '') {
1030
+            $template->addBodyListItem($data['meeting_location_html'] ?? $data['meeting_location'], $this->l10n->t('Location:'),
1031
+                $this->getAbsoluteImagePath('caldav/location.png'), $data['meeting_location'], '', IMipPlugin::IMIP_INDENT);
1032
+        }
1033
+        if ($data['meeting_url'] !== '') {
1034
+            $template->addBodyListItem($data['meeting_url_html'] ?? $data['meeting_url'], $this->l10n->t('Link:'),
1035
+                $this->getAbsoluteImagePath('caldav/link.png'), $data['meeting_url'], '', IMipPlugin::IMIP_INDENT);
1036
+        }
1037
+        if (isset($data['meeting_occurring'])) {
1038
+            $template->addBodyListItem($data['meeting_occurring_html'] ?? $data['meeting_occurring'], $this->l10n->t('Occurring:'),
1039
+                $this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_occurring'], '', IMipPlugin::IMIP_INDENT);
1040
+        }
1041
+
1042
+        $this->addAttendees($template, $vevent);
1043
+
1044
+        /* Put description last, like an email body, since it can be arbitrarily long */
1045
+        if ($data['meeting_description']) {
1046
+            $template->addBodyListItem($data['meeting_description_html'] ?? $data['meeting_description'], $this->l10n->t('Description:'),
1047
+                $this->getAbsoluteImagePath('caldav/description.png'), $data['meeting_description'], '', IMipPlugin::IMIP_INDENT);
1048
+        }
1049
+    }
1050
+
1051
+    /**
1052
+     * @param Message $iTipMessage
1053
+     * @return null|Property
1054
+     */
1055
+    public function getCurrentAttendee(Message $iTipMessage): ?Property {
1056
+        /** @var VEvent $vevent */
1057
+        $vevent = $iTipMessage->message->VEVENT;
1058
+        $attendees = $vevent->select('ATTENDEE');
1059
+        foreach ($attendees as $attendee) {
1060
+            if ($iTipMessage->method === 'REPLY' && strcasecmp($attendee->getValue(), $iTipMessage->sender) === 0) {
1061
+                /** @var Property $attendee */
1062
+                return $attendee;
1063
+            } elseif (strcasecmp($attendee->getValue(), $iTipMessage->recipient) === 0) {
1064
+                /** @var Property $attendee */
1065
+                return $attendee;
1066
+            }
1067
+        }
1068
+        return null;
1069
+    }
1070
+
1071
+    /**
1072
+     * @param Message $iTipMessage
1073
+     * @param VEvent $vevent
1074
+     * @param int $lastOccurrence
1075
+     * @return string
1076
+     */
1077
+    public function createInvitationToken(Message $iTipMessage, VEvent $vevent, int $lastOccurrence): string {
1078
+        $token = $this->random->generate(60, ISecureRandom::CHAR_ALPHANUMERIC);
1079
+
1080
+        $attendee = $iTipMessage->recipient;
1081
+        $organizer = $iTipMessage->sender;
1082
+        $sequence = $iTipMessage->sequence;
1083
+        $recurrenceId = isset($vevent->{'RECURRENCE-ID'}) ?
1084
+            $vevent->{'RECURRENCE-ID'}->serialize() : null;
1085
+        $uid = $vevent->{'UID'};
1086
+
1087
+        $query = $this->db->getQueryBuilder();
1088
+        $query->insert('calendar_invitations')
1089
+            ->values([
1090
+                'token' => $query->createNamedParameter($token),
1091
+                'attendee' => $query->createNamedParameter($attendee),
1092
+                'organizer' => $query->createNamedParameter($organizer),
1093
+                'sequence' => $query->createNamedParameter($sequence),
1094
+                'recurrenceid' => $query->createNamedParameter($recurrenceId),
1095
+                'expiration' => $query->createNamedParameter($lastOccurrence),
1096
+                'uid' => $query->createNamedParameter($uid)
1097
+            ])
1098
+            ->executeStatement();
1099
+
1100
+        return $token;
1101
+    }
1102
+
1103
+    /**
1104
+     * @param IEMailTemplate $template
1105
+     * @param $token
1106
+     */
1107
+    public function addResponseButtons(IEMailTemplate $template, $token) {
1108
+        $template->addBodyButtonGroup(
1109
+            $this->l10n->t('Accept'),
1110
+            $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.accept', [
1111
+                'token' => $token,
1112
+            ]),
1113
+            $this->l10n->t('Decline'),
1114
+            $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.decline', [
1115
+                'token' => $token,
1116
+            ])
1117
+        );
1118
+    }
1119
+
1120
+    public function addMoreOptionsButton(IEMailTemplate $template, $token) {
1121
+        $moreOptionsURL = $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.options', [
1122
+            'token' => $token,
1123
+        ]);
1124
+        $html = vsprintf('<small><a href="%s">%s</a></small>', [
1125
+            $moreOptionsURL, $this->l10n->t('More options …')
1126
+        ]);
1127
+        $text = $this->l10n->t('More options at %s', [$moreOptionsURL]);
1128
+
1129
+        $template->addBodyText($html, $text);
1130
+    }
1131
+
1132
+    public function getReplyingAttendee(Message $iTipMessage): ?Property {
1133
+        /** @var VEvent $vevent */
1134
+        $vevent = $iTipMessage->message->VEVENT;
1135
+        $attendees = $vevent->select('ATTENDEE');
1136
+        foreach ($attendees as $attendee) {
1137
+            /** @var Property $attendee */
1138
+            if (strcasecmp($attendee->getValue(), $iTipMessage->sender) === 0) {
1139
+                return $attendee;
1140
+            }
1141
+        }
1142
+        return null;
1143
+    }
1144
+
1145
+    public function isRoomOrResource(Property $attendee): bool {
1146
+        $cuType = $attendee->offsetGet('CUTYPE');
1147
+        if (!$cuType instanceof Parameter) {
1148
+            return false;
1149
+        }
1150
+        $type = $cuType->getValue() ?? 'INDIVIDUAL';
1151
+        if (\in_array(strtoupper($type), ['RESOURCE', 'ROOM'], true)) {
1152
+            // Don't send emails to things
1153
+            return true;
1154
+        }
1155
+        return false;
1156
+    }
1157
+
1158
+    public function isCircle(Property $attendee): bool {
1159
+        $cuType = $attendee->offsetGet('CUTYPE');
1160
+        if (!$cuType instanceof Parameter) {
1161
+            return false;
1162
+        }
1163
+
1164
+        $uri = $attendee->getValue();
1165
+        if (!$uri) {
1166
+            return false;
1167
+        }
1168
+
1169
+        $cuTypeValue = $cuType->getValue();
1170
+        return $cuTypeValue === 'GROUP' && str_starts_with($uri, 'mailto:circle+');
1171
+    }
1172
+
1173
+    public function minimizeInterval(\DateInterval $dateInterval): array {
1174
+        // evaluate if time interval is in the past
1175
+        if ($dateInterval->invert == 1) {
1176
+            return ['interval' => 1, 'scale' => 'past'];
1177
+        }
1178
+        // evaluate interval parts and return smallest time period
1179
+        if ($dateInterval->y > 0) {
1180
+            $interval = $dateInterval->y;
1181
+            $scale = 'year';
1182
+        } elseif ($dateInterval->m > 0) {
1183
+            $interval = $dateInterval->m;
1184
+            $scale = 'month';
1185
+        } elseif ($dateInterval->d >= 7) {
1186
+            $interval = (int)($dateInterval->d / 7);
1187
+            $scale = 'week';
1188
+        } elseif ($dateInterval->d > 0) {
1189
+            $interval = $dateInterval->d;
1190
+            $scale = 'day';
1191
+        } elseif ($dateInterval->h > 0) {
1192
+            $interval = $dateInterval->h;
1193
+            $scale = 'hour';
1194
+        } else {
1195
+            $interval = $dateInterval->i;
1196
+            $scale = 'minute';
1197
+        }
1198
+
1199
+        return ['interval' => $interval, 'scale' => $scale];
1200
+    }
1201
+
1202
+    /**
1203
+     * Localizes week day names to another language
1204
+     *
1205
+     * @param string $value
1206
+     *
1207
+     * @return string
1208
+     */
1209
+    public function localizeDayName(string $value): string {
1210
+        return match ($value) {
1211
+            'Monday' => $this->l10n->t('Monday'),
1212
+            'Tuesday' => $this->l10n->t('Tuesday'),
1213
+            'Wednesday' => $this->l10n->t('Wednesday'),
1214
+            'Thursday' => $this->l10n->t('Thursday'),
1215
+            'Friday' => $this->l10n->t('Friday'),
1216
+            'Saturday' => $this->l10n->t('Saturday'),
1217
+            'Sunday' => $this->l10n->t('Sunday'),
1218
+        };
1219
+    }
1220
+
1221
+    /**
1222
+     * Localizes month names to another language
1223
+     *
1224
+     * @param string $value
1225
+     *
1226
+     * @return string
1227
+     */
1228
+    public function localizeMonthName(string $value): string {
1229
+        return match ($value) {
1230
+            'January' => $this->l10n->t('January'),
1231
+            'February' => $this->l10n->t('February'),
1232
+            'March' => $this->l10n->t('March'),
1233
+            'April' => $this->l10n->t('April'),
1234
+            'May' => $this->l10n->t('May'),
1235
+            'June' => $this->l10n->t('June'),
1236
+            'July' => $this->l10n->t('July'),
1237
+            'August' => $this->l10n->t('August'),
1238
+            'September' => $this->l10n->t('September'),
1239
+            'October' => $this->l10n->t('October'),
1240
+            'November' => $this->l10n->t('November'),
1241
+            'December' => $this->l10n->t('December'),
1242
+        };
1243
+    }
1244
+
1245
+    /**
1246
+     * Localizes relative position names to another language
1247
+     *
1248
+     * @param string $value
1249
+     *
1250
+     * @return string
1251
+     */
1252
+    public function localizeRelativePositionName(string $value): string {
1253
+        return match ($value) {
1254
+            'First' => $this->l10n->t('First'),
1255
+            'Second' => $this->l10n->t('Second'),
1256
+            'Third' => $this->l10n->t('Third'),
1257
+            'Fourth' => $this->l10n->t('Fourth'),
1258
+            'Fifth' => $this->l10n->t('Fifth'),
1259
+            'Last' => $this->l10n->t('Last'),
1260
+            'Second Last' => $this->l10n->t('Second Last'),
1261
+            'Third Last' => $this->l10n->t('Third Last'),
1262
+            'Fourth Last' => $this->l10n->t('Fourth Last'),
1263
+            'Fifth Last' => $this->l10n->t('Fifth Last'),
1264
+        };
1265
+    }
1266 1266
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php 2 patches
Indentation   +1027 added lines, -1027 removed lines patch added patch discarded remove patch
@@ -31,1080 +31,1080 @@
 block discarded – undo
31 31
 use function array_merge;
32 32
 
33 33
 interface IMailServiceMock extends IMailService, IMailMessageSend {
34
-	// workaround for creating mock class with multiple interfaces
35
-	// TODO: remove after phpUnit 10 is supported.
34
+    // workaround for creating mock class with multiple interfaces
35
+    // TODO: remove after phpUnit 10 is supported.
36 36
 }
37 37
 
38 38
 class IMipPluginTest extends TestCase {
39 39
 
40
-	/** @var IMessage|MockObject */
41
-	private $mailMessage;
40
+    /** @var IMessage|MockObject */
41
+    private $mailMessage;
42 42
 
43
-	/** @var IMailer|MockObject */
44
-	private $mailer;
43
+    /** @var IMailer|MockObject */
44
+    private $mailer;
45 45
 
46
-	/** @var IEMailTemplate|MockObject */
47
-	private $emailTemplate;
46
+    /** @var IEMailTemplate|MockObject */
47
+    private $emailTemplate;
48 48
 
49
-	/** @var IAttachment|MockObject */
50
-	private $emailAttachment;
49
+    /** @var IAttachment|MockObject */
50
+    private $emailAttachment;
51 51
 
52
-	/** @var ITimeFactory|MockObject */
53
-	private $timeFactory;
52
+    /** @var ITimeFactory|MockObject */
53
+    private $timeFactory;
54 54
 
55
-	/** @var IAppConfig|MockObject */
56
-	private $config;
55
+    /** @var IAppConfig|MockObject */
56
+    private $config;
57 57
 
58
-	/** @var IUserSession|MockObject */
59
-	private $userSession;
58
+    /** @var IUserSession|MockObject */
59
+    private $userSession;
60 60
 
61
-	/** @var IUser|MockObject */
62
-	private $user;
61
+    /** @var IUser|MockObject */
62
+    private $user;
63 63
 
64
-	/** @var IMipPlugin */
65
-	private $plugin;
64
+    /** @var IMipPlugin */
65
+    private $plugin;
66 66
 
67
-	/** @var IMipService|MockObject */
68
-	private $service;
67
+    /** @var IMipService|MockObject */
68
+    private $service;
69 69
 
70
-	/** @var Defaults|MockObject */
71
-	private $defaults;
70
+    /** @var Defaults|MockObject */
71
+    private $defaults;
72 72
 
73
-	/** @var LoggerInterface|MockObject */
74
-	private $logger;
73
+    /** @var LoggerInterface|MockObject */
74
+    private $logger;
75 75
 
76
-	/** @var EventComparisonService|MockObject */
77
-	private $eventComparisonService;
76
+    /** @var EventComparisonService|MockObject */
77
+    private $eventComparisonService;
78 78
 
79
-	/** @var IMailManager|MockObject */
80
-	private $mailManager;
79
+    /** @var IMailManager|MockObject */
80
+    private $mailManager;
81 81
 
82
-	/** @var IMailService|IMailMessageSend|MockObject */
83
-	private $mailService;
82
+    /** @var IMailService|IMailMessageSend|MockObject */
83
+    private $mailService;
84 84
 
85
-	/** @var IMailMessageNew|MockObject */
86
-	private $mailMessageNew;
85
+    /** @var IMailMessageNew|MockObject */
86
+    private $mailMessageNew;
87 87
 
88
-	protected function setUp(): void {
89
-		$this->mailMessage = $this->createMock(IMessage::class);
90
-		$this->mailMessage->method('setFrom')->willReturn($this->mailMessage);
91
-		$this->mailMessage->method('setReplyTo')->willReturn($this->mailMessage);
92
-		$this->mailMessage->method('setTo')->willReturn($this->mailMessage);
88
+    protected function setUp(): void {
89
+        $this->mailMessage = $this->createMock(IMessage::class);
90
+        $this->mailMessage->method('setFrom')->willReturn($this->mailMessage);
91
+        $this->mailMessage->method('setReplyTo')->willReturn($this->mailMessage);
92
+        $this->mailMessage->method('setTo')->willReturn($this->mailMessage);
93 93
 
94
-		$this->mailer = $this->createMock(IMailer::class);
95
-		$this->mailer->method('createMessage')->willReturn($this->mailMessage);
94
+        $this->mailer = $this->createMock(IMailer::class);
95
+        $this->mailer->method('createMessage')->willReturn($this->mailMessage);
96 96
 
97
-		$this->emailTemplate = $this->createMock(IEMailTemplate::class);
98
-		$this->mailer->method('createEMailTemplate')->willReturn($this->emailTemplate);
97
+        $this->emailTemplate = $this->createMock(IEMailTemplate::class);
98
+        $this->mailer->method('createEMailTemplate')->willReturn($this->emailTemplate);
99 99
 
100
-		$this->emailAttachment = $this->createMock(IAttachment::class);
101
-		$this->mailer->method('createAttachment')->willReturn($this->emailAttachment);
100
+        $this->emailAttachment = $this->createMock(IAttachment::class);
101
+        $this->mailer->method('createAttachment')->willReturn($this->emailAttachment);
102 102
 
103
-		$this->logger = $this->createMock(LoggerInterface::class);
103
+        $this->logger = $this->createMock(LoggerInterface::class);
104 104
 
105
-		$this->timeFactory = $this->createMock(ITimeFactory::class);
106
-		$this->timeFactory->method('getTime')->willReturn(1496912528); // 2017-01-01
105
+        $this->timeFactory = $this->createMock(ITimeFactory::class);
106
+        $this->timeFactory->method('getTime')->willReturn(1496912528); // 2017-01-01
107 107
 
108
-		$this->config = $this->createMock(IAppConfig::class);
108
+        $this->config = $this->createMock(IAppConfig::class);
109 109
 
110
-		$this->user = $this->createMock(IUser::class);
110
+        $this->user = $this->createMock(IUser::class);
111 111
 
112
-		$this->userSession = $this->createMock(IUserSession::class);
113
-		$this->userSession->method('getUser')
114
-			->willReturn($this->user);
112
+        $this->userSession = $this->createMock(IUserSession::class);
113
+        $this->userSession->method('getUser')
114
+            ->willReturn($this->user);
115 115
 
116
-		$this->defaults = $this->createMock(Defaults::class);
117
-		$this->defaults->method('getName')
118
-			->willReturn('Instance Name 123');
116
+        $this->defaults = $this->createMock(Defaults::class);
117
+        $this->defaults->method('getName')
118
+            ->willReturn('Instance Name 123');
119 119
 
120
-		$this->service = $this->createMock(IMipService::class);
120
+        $this->service = $this->createMock(IMipService::class);
121 121
 
122
-		$this->eventComparisonService = $this->createMock(EventComparisonService::class);
122
+        $this->eventComparisonService = $this->createMock(EventComparisonService::class);
123 123
 
124
-		$this->mailManager = $this->createMock(IMailManager::class);
124
+        $this->mailManager = $this->createMock(IMailManager::class);
125 125
 
126
-		$this->mailService = $this->createMock(IMailServiceMock::class);
126
+        $this->mailService = $this->createMock(IMailServiceMock::class);
127 127
 
128
-		$this->mailMessageNew = $this->createMock(IMailMessageNew::class);
128
+        $this->mailMessageNew = $this->createMock(IMailMessageNew::class);
129 129
 
130
-		$this->plugin = new IMipPlugin(
131
-			$this->config,
132
-			$this->mailer,
133
-			$this->logger,
134
-			$this->timeFactory,
135
-			$this->defaults,
136
-			$this->userSession,
137
-			$this->service,
138
-			$this->eventComparisonService,
139
-			$this->mailManager,
140
-		);
141
-	}
130
+        $this->plugin = new IMipPlugin(
131
+            $this->config,
132
+            $this->mailer,
133
+            $this->logger,
134
+            $this->timeFactory,
135
+            $this->defaults,
136
+            $this->userSession,
137
+            $this->service,
138
+            $this->eventComparisonService,
139
+            $this->mailManager,
140
+        );
141
+    }
142 142
 
143
-	public function testDeliveryNoSignificantChange(): void {
144
-		$message = new Message();
145
-		$message->method = 'REQUEST';
146
-		$message->message = new VCalendar();
147
-		$message->message->add('VEVENT', array_merge([
148
-			'UID' => 'uid-1234',
149
-			'SEQUENCE' => 0,
150
-			'SUMMARY' => 'Fellowship meeting',
151
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
152
-		], []));
153
-		$message->message->VEVENT->add('ORGANIZER', 'mailto:[email protected]');
154
-		$message->message->VEVENT->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
155
-		$message->sender = 'mailto:[email protected]';
156
-		$message->senderName = 'Mr. Wizard';
157
-		$message->recipient = 'mailto:' . '[email protected]';
158
-		$message->significantChange = false;
159
-		$this->plugin->schedule($message);
160
-		$this->assertEquals('1.0', $message->getScheduleStatus());
161
-	}
143
+    public function testDeliveryNoSignificantChange(): void {
144
+        $message = new Message();
145
+        $message->method = 'REQUEST';
146
+        $message->message = new VCalendar();
147
+        $message->message->add('VEVENT', array_merge([
148
+            'UID' => 'uid-1234',
149
+            'SEQUENCE' => 0,
150
+            'SUMMARY' => 'Fellowship meeting',
151
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
152
+        ], []));
153
+        $message->message->VEVENT->add('ORGANIZER', 'mailto:[email protected]');
154
+        $message->message->VEVENT->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
155
+        $message->sender = 'mailto:[email protected]';
156
+        $message->senderName = 'Mr. Wizard';
157
+        $message->recipient = 'mailto:' . '[email protected]';
158
+        $message->significantChange = false;
159
+        $this->plugin->schedule($message);
160
+        $this->assertEquals('1.0', $message->getScheduleStatus());
161
+    }
162 162
 
163
-	public function testParsingSingle(): void {
164
-		$message = new Message();
165
-		$message->method = 'REQUEST';
166
-		$newVCalendar = new VCalendar();
167
-		$newVevent = new VEvent($newVCalendar, 'one', array_merge([
168
-			'UID' => 'uid-1234',
169
-			'SEQUENCE' => 1,
170
-			'SUMMARY' => 'Fellowship meeting without (!) Boromir',
171
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
172
-		], []));
173
-		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
174
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
175
-		$message->message = $newVCalendar;
176
-		$message->sender = 'mailto:[email protected]';
177
-		$message->senderName = 'Mr. Wizard';
178
-		$message->recipient = 'mailto:' . '[email protected]';
179
-		// save the old copy in the plugin
180
-		$oldVCalendar = new VCalendar();
181
-		$oldVEvent = new VEvent($oldVCalendar, 'one', [
182
-			'UID' => 'uid-1234',
183
-			'SEQUENCE' => 0,
184
-			'SUMMARY' => 'Fellowship meeting',
185
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
186
-		]);
187
-		$oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
188
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
189
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
190
-		$oldVCalendar->add($oldVEvent);
191
-		$data = ['invitee_name' => 'Mr. Wizard',
192
-			'meeting_title' => 'Fellowship meeting without (!) Boromir',
193
-			'attendee_name' => '[email protected]'
194
-		];
195
-		$attendees = $newVevent->select('ATTENDEE');
196
-		$atnd = '';
197
-		foreach ($attendees as $attendee) {
198
-			if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
199
-				$atnd = $attendee;
200
-			}
201
-		}
202
-		$this->plugin->setVCalendar($oldVCalendar);
203
-		$this->service->expects(self::once())
204
-			->method('getLastOccurrence')
205
-			->willReturn(1496912700);
206
-		$this->mailer->expects(self::once())
207
-			->method('validateMailAddress')
208
-			->with('[email protected]')
209
-			->willReturn(true);
210
-		$this->eventComparisonService->expects(self::once())
211
-			->method('findModified')
212
-			->willReturn(['new' => [$newVevent], 'old' => [$oldVEvent]]);
213
-		$this->service->expects(self::once())
214
-			->method('getCurrentAttendee')
215
-			->with($message)
216
-			->willReturn($atnd);
217
-		$this->service->expects(self::once())
218
-			->method('isRoomOrResource')
219
-			->with($atnd)
220
-			->willReturn(false);
221
-		$this->service->expects(self::once())
222
-			->method('isCircle')
223
-			->with($atnd)
224
-			->willReturn(false);
225
-		$this->service->expects(self::once())
226
-			->method('buildBodyData')
227
-			->with($newVevent, $oldVEvent)
228
-			->willReturn($data);
229
-		$this->user->expects(self::any())
230
-			->method('getUID')
231
-			->willReturn('user1');
232
-		$this->user->expects(self::any())
233
-			->method('getDisplayName')
234
-			->willReturn('Mr. Wizard');
235
-		$this->userSession->expects(self::any())
236
-			->method('getUser')
237
-			->willReturn($this->user);
238
-		$this->service->expects(self::once())
239
-			->method('getFrom');
240
-		$this->service->expects(self::once())
241
-			->method('addSubjectAndHeading')
242
-			->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', true);
243
-		$this->service->expects(self::once())
244
-			->method('addBulletList')
245
-			->with($this->emailTemplate, $newVevent, $data);
246
-		$this->service->expects(self::once())
247
-			->method('getAttendeeRsvpOrReqForParticipant')
248
-			->willReturn(true);
249
-		$this->config->expects(self::once())
250
-			->method('getValueString')
251
-			->with('dav', 'invitation_link_recipients', 'yes')
252
-			->willReturn('yes');
253
-		$this->service->expects(self::once())
254
-			->method('createInvitationToken')
255
-			->with($message, $newVevent, 1496912700)
256
-			->willReturn('token');
257
-		$this->service->expects(self::once())
258
-			->method('addResponseButtons')
259
-			->with($this->emailTemplate, 'token');
260
-		$this->service->expects(self::once())
261
-			->method('addMoreOptionsButton')
262
-			->with($this->emailTemplate, 'token');
263
-		$this->mailer->expects(self::once())
264
-			->method('send')
265
-			->willReturn([]);
266
-		$this->plugin->schedule($message);
267
-		$this->assertEquals('1.1', $message->getScheduleStatus());
268
-	}
163
+    public function testParsingSingle(): void {
164
+        $message = new Message();
165
+        $message->method = 'REQUEST';
166
+        $newVCalendar = new VCalendar();
167
+        $newVevent = new VEvent($newVCalendar, 'one', array_merge([
168
+            'UID' => 'uid-1234',
169
+            'SEQUENCE' => 1,
170
+            'SUMMARY' => 'Fellowship meeting without (!) Boromir',
171
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
172
+        ], []));
173
+        $newVevent->add('ORGANIZER', 'mailto:[email protected]');
174
+        $newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
175
+        $message->message = $newVCalendar;
176
+        $message->sender = 'mailto:[email protected]';
177
+        $message->senderName = 'Mr. Wizard';
178
+        $message->recipient = 'mailto:' . '[email protected]';
179
+        // save the old copy in the plugin
180
+        $oldVCalendar = new VCalendar();
181
+        $oldVEvent = new VEvent($oldVCalendar, 'one', [
182
+            'UID' => 'uid-1234',
183
+            'SEQUENCE' => 0,
184
+            'SUMMARY' => 'Fellowship meeting',
185
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
186
+        ]);
187
+        $oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
188
+        $oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
189
+        $oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
190
+        $oldVCalendar->add($oldVEvent);
191
+        $data = ['invitee_name' => 'Mr. Wizard',
192
+            'meeting_title' => 'Fellowship meeting without (!) Boromir',
193
+            'attendee_name' => '[email protected]'
194
+        ];
195
+        $attendees = $newVevent->select('ATTENDEE');
196
+        $atnd = '';
197
+        foreach ($attendees as $attendee) {
198
+            if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
199
+                $atnd = $attendee;
200
+            }
201
+        }
202
+        $this->plugin->setVCalendar($oldVCalendar);
203
+        $this->service->expects(self::once())
204
+            ->method('getLastOccurrence')
205
+            ->willReturn(1496912700);
206
+        $this->mailer->expects(self::once())
207
+            ->method('validateMailAddress')
208
+            ->with('[email protected]')
209
+            ->willReturn(true);
210
+        $this->eventComparisonService->expects(self::once())
211
+            ->method('findModified')
212
+            ->willReturn(['new' => [$newVevent], 'old' => [$oldVEvent]]);
213
+        $this->service->expects(self::once())
214
+            ->method('getCurrentAttendee')
215
+            ->with($message)
216
+            ->willReturn($atnd);
217
+        $this->service->expects(self::once())
218
+            ->method('isRoomOrResource')
219
+            ->with($atnd)
220
+            ->willReturn(false);
221
+        $this->service->expects(self::once())
222
+            ->method('isCircle')
223
+            ->with($atnd)
224
+            ->willReturn(false);
225
+        $this->service->expects(self::once())
226
+            ->method('buildBodyData')
227
+            ->with($newVevent, $oldVEvent)
228
+            ->willReturn($data);
229
+        $this->user->expects(self::any())
230
+            ->method('getUID')
231
+            ->willReturn('user1');
232
+        $this->user->expects(self::any())
233
+            ->method('getDisplayName')
234
+            ->willReturn('Mr. Wizard');
235
+        $this->userSession->expects(self::any())
236
+            ->method('getUser')
237
+            ->willReturn($this->user);
238
+        $this->service->expects(self::once())
239
+            ->method('getFrom');
240
+        $this->service->expects(self::once())
241
+            ->method('addSubjectAndHeading')
242
+            ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', true);
243
+        $this->service->expects(self::once())
244
+            ->method('addBulletList')
245
+            ->with($this->emailTemplate, $newVevent, $data);
246
+        $this->service->expects(self::once())
247
+            ->method('getAttendeeRsvpOrReqForParticipant')
248
+            ->willReturn(true);
249
+        $this->config->expects(self::once())
250
+            ->method('getValueString')
251
+            ->with('dav', 'invitation_link_recipients', 'yes')
252
+            ->willReturn('yes');
253
+        $this->service->expects(self::once())
254
+            ->method('createInvitationToken')
255
+            ->with($message, $newVevent, 1496912700)
256
+            ->willReturn('token');
257
+        $this->service->expects(self::once())
258
+            ->method('addResponseButtons')
259
+            ->with($this->emailTemplate, 'token');
260
+        $this->service->expects(self::once())
261
+            ->method('addMoreOptionsButton')
262
+            ->with($this->emailTemplate, 'token');
263
+        $this->mailer->expects(self::once())
264
+            ->method('send')
265
+            ->willReturn([]);
266
+        $this->plugin->schedule($message);
267
+        $this->assertEquals('1.1', $message->getScheduleStatus());
268
+    }
269 269
 
270
-	public function testAttendeeIsResource(): void {
271
-		$message = new Message();
272
-		$message->method = 'REQUEST';
273
-		$newVCalendar = new VCalendar();
274
-		$newVevent = new VEvent($newVCalendar, 'one', array_merge([
275
-			'UID' => 'uid-1234',
276
-			'SEQUENCE' => 1,
277
-			'SUMMARY' => 'Fellowship meeting without (!) Boromir',
278
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
279
-		], []));
280
-		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
281
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'The Shire', 'CUTYPE' => 'ROOM']);
282
-		$message->message = $newVCalendar;
283
-		$message->sender = 'mailto:[email protected]';
284
-		$message->senderName = 'Mr. Wizard';
285
-		$message->recipient = 'mailto:' . '[email protected]';
286
-		// save the old copy in the plugin
287
-		$oldVCalendar = new VCalendar();
288
-		$oldVEvent = new VEvent($oldVCalendar, 'one', [
289
-			'UID' => 'uid-1234',
290
-			'SEQUENCE' => 0,
291
-			'SUMMARY' => 'Fellowship meeting',
292
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
293
-		]);
294
-		$oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
295
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'The Shire', 'CUTYPE' => 'ROOM']);
296
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
297
-		$oldVCalendar->add($oldVEvent);
298
-		$data = ['invitee_name' => 'Mr. Wizard',
299
-			'meeting_title' => 'Fellowship meeting without (!) Boromir',
300
-			'attendee_name' => '[email protected]'
301
-		];
302
-		$attendees = $newVevent->select('ATTENDEE');
303
-		$room = '';
304
-		foreach ($attendees as $attendee) {
305
-			if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
306
-				$room = $attendee;
307
-			}
308
-		}
309
-		$this->plugin->setVCalendar($oldVCalendar);
310
-		$this->service->expects(self::once())
311
-			->method('getLastOccurrence')
312
-			->willReturn(1496912700);
313
-		$this->mailer->expects(self::once())
314
-			->method('validateMailAddress')
315
-			->with('[email protected]')
316
-			->willReturn(true);
317
-		$this->eventComparisonService->expects(self::once())
318
-			->method('findModified')
319
-			->willReturn(['new' => [$newVevent], 'old' => [$oldVEvent]]);
320
-		$this->service->expects(self::once())
321
-			->method('getCurrentAttendee')
322
-			->with($message)
323
-			->willReturn($room);
324
-		$this->service->expects(self::once())
325
-			->method('isRoomOrResource')
326
-			->with($room)
327
-			->willReturn(true);
328
-		$this->service->expects(self::never())
329
-			->method('isCircle');
330
-		$this->service->expects(self::never())
331
-			->method('buildBodyData');
332
-		$this->user->expects(self::any())
333
-			->method('getUID')
334
-			->willReturn('user1');
335
-		$this->user->expects(self::any())
336
-			->method('getDisplayName')
337
-			->willReturn('Mr. Wizard');
338
-		$this->userSession->expects(self::any())
339
-			->method('getUser')
340
-			->willReturn($this->user);
341
-		$this->service->expects(self::never())
342
-			->method('getFrom');
343
-		$this->service->expects(self::never())
344
-			->method('addSubjectAndHeading');
345
-		$this->service->expects(self::never())
346
-			->method('addBulletList');
347
-		$this->service->expects(self::never())
348
-			->method('getAttendeeRsvpOrReqForParticipant');
349
-		$this->config->expects(self::never())
350
-			->method('getValueString');
351
-		$this->service->expects(self::never())
352
-			->method('createInvitationToken');
353
-		$this->service->expects(self::never())
354
-			->method('addResponseButtons');
355
-		$this->service->expects(self::never())
356
-			->method('addMoreOptionsButton');
357
-		$this->mailer->expects(self::never())
358
-			->method('send');
359
-		$this->plugin->schedule($message);
360
-		$this->assertEquals('1.0', $message->getScheduleStatus());
361
-	}
270
+    public function testAttendeeIsResource(): void {
271
+        $message = new Message();
272
+        $message->method = 'REQUEST';
273
+        $newVCalendar = new VCalendar();
274
+        $newVevent = new VEvent($newVCalendar, 'one', array_merge([
275
+            'UID' => 'uid-1234',
276
+            'SEQUENCE' => 1,
277
+            'SUMMARY' => 'Fellowship meeting without (!) Boromir',
278
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
279
+        ], []));
280
+        $newVevent->add('ORGANIZER', 'mailto:[email protected]');
281
+        $newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'The Shire', 'CUTYPE' => 'ROOM']);
282
+        $message->message = $newVCalendar;
283
+        $message->sender = 'mailto:[email protected]';
284
+        $message->senderName = 'Mr. Wizard';
285
+        $message->recipient = 'mailto:' . '[email protected]';
286
+        // save the old copy in the plugin
287
+        $oldVCalendar = new VCalendar();
288
+        $oldVEvent = new VEvent($oldVCalendar, 'one', [
289
+            'UID' => 'uid-1234',
290
+            'SEQUENCE' => 0,
291
+            'SUMMARY' => 'Fellowship meeting',
292
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
293
+        ]);
294
+        $oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
295
+        $oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'The Shire', 'CUTYPE' => 'ROOM']);
296
+        $oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
297
+        $oldVCalendar->add($oldVEvent);
298
+        $data = ['invitee_name' => 'Mr. Wizard',
299
+            'meeting_title' => 'Fellowship meeting without (!) Boromir',
300
+            'attendee_name' => '[email protected]'
301
+        ];
302
+        $attendees = $newVevent->select('ATTENDEE');
303
+        $room = '';
304
+        foreach ($attendees as $attendee) {
305
+            if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
306
+                $room = $attendee;
307
+            }
308
+        }
309
+        $this->plugin->setVCalendar($oldVCalendar);
310
+        $this->service->expects(self::once())
311
+            ->method('getLastOccurrence')
312
+            ->willReturn(1496912700);
313
+        $this->mailer->expects(self::once())
314
+            ->method('validateMailAddress')
315
+            ->with('[email protected]')
316
+            ->willReturn(true);
317
+        $this->eventComparisonService->expects(self::once())
318
+            ->method('findModified')
319
+            ->willReturn(['new' => [$newVevent], 'old' => [$oldVEvent]]);
320
+        $this->service->expects(self::once())
321
+            ->method('getCurrentAttendee')
322
+            ->with($message)
323
+            ->willReturn($room);
324
+        $this->service->expects(self::once())
325
+            ->method('isRoomOrResource')
326
+            ->with($room)
327
+            ->willReturn(true);
328
+        $this->service->expects(self::never())
329
+            ->method('isCircle');
330
+        $this->service->expects(self::never())
331
+            ->method('buildBodyData');
332
+        $this->user->expects(self::any())
333
+            ->method('getUID')
334
+            ->willReturn('user1');
335
+        $this->user->expects(self::any())
336
+            ->method('getDisplayName')
337
+            ->willReturn('Mr. Wizard');
338
+        $this->userSession->expects(self::any())
339
+            ->method('getUser')
340
+            ->willReturn($this->user);
341
+        $this->service->expects(self::never())
342
+            ->method('getFrom');
343
+        $this->service->expects(self::never())
344
+            ->method('addSubjectAndHeading');
345
+        $this->service->expects(self::never())
346
+            ->method('addBulletList');
347
+        $this->service->expects(self::never())
348
+            ->method('getAttendeeRsvpOrReqForParticipant');
349
+        $this->config->expects(self::never())
350
+            ->method('getValueString');
351
+        $this->service->expects(self::never())
352
+            ->method('createInvitationToken');
353
+        $this->service->expects(self::never())
354
+            ->method('addResponseButtons');
355
+        $this->service->expects(self::never())
356
+            ->method('addMoreOptionsButton');
357
+        $this->mailer->expects(self::never())
358
+            ->method('send');
359
+        $this->plugin->schedule($message);
360
+        $this->assertEquals('1.0', $message->getScheduleStatus());
361
+    }
362 362
 
363
-	public function testAttendeeIsCircle(): void {
364
-		$message = new Message();
365
-		$message->method = 'REQUEST';
366
-		$newVCalendar = new VCalendar();
367
-		$newVevent = new VEvent($newVCalendar, 'one', array_merge([
368
-			'UID' => 'uid-1234',
369
-			'SEQUENCE' => 1,
370
-			'SUMMARY' => 'Fellowship meeting without (!) Boromir',
371
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
372
-		], []));
373
-		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
374
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'The Fellowship', 'CUTYPE' => 'GROUP']);
375
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'MEMBER' => '[email protected]']);
376
-		$message->message = $newVCalendar;
377
-		$message->sender = 'mailto:[email protected]';
378
-		$message->senderName = 'Mr. Wizard';
379
-		$message->recipient = 'mailto:' . '[email protected]';
380
-		$attendees = $newVevent->select('ATTENDEE');
381
-		$circle = '';
382
-		foreach ($attendees as $attendee) {
383
-			if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
384
-				$circle = $attendee;
385
-			}
386
-		}
387
-		$this->assertNotEmpty($circle, 'Failed to find attendee belonging to the circle');
388
-		$this->service->expects(self::once())
389
-			->method('getLastOccurrence')
390
-			->willReturn(1496912700);
391
-		$this->mailer->expects(self::once())
392
-			->method('validateMailAddress')
393
-			->with('[email protected]')
394
-			->willReturn(true);
395
-		$this->eventComparisonService->expects(self::once())
396
-			->method('findModified')
397
-			->willReturn(['new' => [$newVevent], 'old' => null]);
398
-		$this->service->expects(self::once())
399
-			->method('getCurrentAttendee')
400
-			->with($message)
401
-			->willReturn($circle);
402
-		$this->service->expects(self::once())
403
-			->method('isRoomOrResource')
404
-			->with($circle)
405
-			->willReturn(false);
406
-		$this->service->expects(self::once())
407
-			->method('isCircle')
408
-			->with($circle)
409
-			->willReturn(true);
410
-		$this->service->expects(self::never())
411
-			->method('buildBodyData');
412
-		$this->user->expects(self::any())
413
-			->method('getUID')
414
-			->willReturn('user1');
415
-		$this->user->expects(self::any())
416
-			->method('getDisplayName')
417
-			->willReturn('Mr. Wizard');
418
-		$this->userSession->expects(self::any())
419
-			->method('getUser')
420
-			->willReturn($this->user);
421
-		$this->service->expects(self::never())
422
-			->method('getFrom');
423
-		$this->service->expects(self::never())
424
-			->method('addSubjectAndHeading');
425
-		$this->service->expects(self::never())
426
-			->method('addBulletList');
427
-		$this->service->expects(self::never())
428
-			->method('getAttendeeRsvpOrReqForParticipant');
429
-		$this->config->expects(self::never())
430
-			->method('getValueString');
431
-		$this->service->expects(self::never())
432
-			->method('createInvitationToken');
433
-		$this->service->expects(self::never())
434
-			->method('addResponseButtons');
435
-		$this->service->expects(self::never())
436
-			->method('addMoreOptionsButton');
437
-		$this->mailer->expects(self::never())
438
-			->method('send');
439
-		$this->plugin->schedule($message);
440
-		$this->assertEquals('1.0', $message->getScheduleStatus());
441
-	}
363
+    public function testAttendeeIsCircle(): void {
364
+        $message = new Message();
365
+        $message->method = 'REQUEST';
366
+        $newVCalendar = new VCalendar();
367
+        $newVevent = new VEvent($newVCalendar, 'one', array_merge([
368
+            'UID' => 'uid-1234',
369
+            'SEQUENCE' => 1,
370
+            'SUMMARY' => 'Fellowship meeting without (!) Boromir',
371
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
372
+        ], []));
373
+        $newVevent->add('ORGANIZER', 'mailto:[email protected]');
374
+        $newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'The Fellowship', 'CUTYPE' => 'GROUP']);
375
+        $newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'MEMBER' => '[email protected]']);
376
+        $message->message = $newVCalendar;
377
+        $message->sender = 'mailto:[email protected]';
378
+        $message->senderName = 'Mr. Wizard';
379
+        $message->recipient = 'mailto:' . '[email protected]';
380
+        $attendees = $newVevent->select('ATTENDEE');
381
+        $circle = '';
382
+        foreach ($attendees as $attendee) {
383
+            if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
384
+                $circle = $attendee;
385
+            }
386
+        }
387
+        $this->assertNotEmpty($circle, 'Failed to find attendee belonging to the circle');
388
+        $this->service->expects(self::once())
389
+            ->method('getLastOccurrence')
390
+            ->willReturn(1496912700);
391
+        $this->mailer->expects(self::once())
392
+            ->method('validateMailAddress')
393
+            ->with('[email protected]')
394
+            ->willReturn(true);
395
+        $this->eventComparisonService->expects(self::once())
396
+            ->method('findModified')
397
+            ->willReturn(['new' => [$newVevent], 'old' => null]);
398
+        $this->service->expects(self::once())
399
+            ->method('getCurrentAttendee')
400
+            ->with($message)
401
+            ->willReturn($circle);
402
+        $this->service->expects(self::once())
403
+            ->method('isRoomOrResource')
404
+            ->with($circle)
405
+            ->willReturn(false);
406
+        $this->service->expects(self::once())
407
+            ->method('isCircle')
408
+            ->with($circle)
409
+            ->willReturn(true);
410
+        $this->service->expects(self::never())
411
+            ->method('buildBodyData');
412
+        $this->user->expects(self::any())
413
+            ->method('getUID')
414
+            ->willReturn('user1');
415
+        $this->user->expects(self::any())
416
+            ->method('getDisplayName')
417
+            ->willReturn('Mr. Wizard');
418
+        $this->userSession->expects(self::any())
419
+            ->method('getUser')
420
+            ->willReturn($this->user);
421
+        $this->service->expects(self::never())
422
+            ->method('getFrom');
423
+        $this->service->expects(self::never())
424
+            ->method('addSubjectAndHeading');
425
+        $this->service->expects(self::never())
426
+            ->method('addBulletList');
427
+        $this->service->expects(self::never())
428
+            ->method('getAttendeeRsvpOrReqForParticipant');
429
+        $this->config->expects(self::never())
430
+            ->method('getValueString');
431
+        $this->service->expects(self::never())
432
+            ->method('createInvitationToken');
433
+        $this->service->expects(self::never())
434
+            ->method('addResponseButtons');
435
+        $this->service->expects(self::never())
436
+            ->method('addMoreOptionsButton');
437
+        $this->mailer->expects(self::never())
438
+            ->method('send');
439
+        $this->plugin->schedule($message);
440
+        $this->assertEquals('1.0', $message->getScheduleStatus());
441
+    }
442 442
 
443
-	public function testParsingRecurrence(): void {
444
-		$message = new Message();
445
-		$message->method = 'REQUEST';
446
-		$newVCalendar = new VCalendar();
447
-		$newVevent = new VEvent($newVCalendar, 'one', [
448
-			'UID' => 'uid-1234',
449
-			'LAST-MODIFIED' => 123456,
450
-			'SEQUENCE' => 2,
451
-			'SUMMARY' => 'Fellowship meeting',
452
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
453
-			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z'
454
-		]);
455
-		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
456
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
457
-		$newvEvent2 = new VEvent($newVCalendar, 'two', [
458
-			'UID' => 'uid-1234',
459
-			'SEQUENCE' => 1,
460
-			'SUMMARY' => 'Elevenses',
461
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
462
-			'RECURRENCE-ID' => new \DateTime('2016-01-01 00:00:00')
463
-		]);
464
-		$newvEvent2->add('ORGANIZER', 'mailto:[email protected]');
465
-		$newvEvent2->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
466
-		$message->message = $newVCalendar;
467
-		$message->sender = 'mailto:[email protected]';
468
-		$message->recipient = 'mailto:' . '[email protected]';
469
-		// save the old copy in the plugin
470
-		$oldVCalendar = new VCalendar();
471
-		$oldVEvent = new VEvent($oldVCalendar, 'one', [
472
-			'UID' => 'uid-1234',
473
-			'LAST-MODIFIED' => 123456,
474
-			'SEQUENCE' => 2,
475
-			'SUMMARY' => 'Fellowship meeting',
476
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
477
-			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z'
478
-		]);
479
-		$oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
480
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
481
-		$data = ['invitee_name' => 'Mr. Wizard',
482
-			'meeting_title' => 'Elevenses',
483
-			'attendee_name' => '[email protected]'
484
-		];
485
-		$attendees = $newVevent->select('ATTENDEE');
486
-		$atnd = '';
487
-		foreach ($attendees as $attendee) {
488
-			if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
489
-				$atnd = $attendee;
490
-			}
491
-		}
492
-		$this->plugin->setVCalendar($oldVCalendar);
493
-		$this->service->expects(self::once())
494
-			->method('getLastOccurrence')
495
-			->willReturn(1496912700);
496
-		$this->mailer->expects(self::once())
497
-			->method('validateMailAddress')
498
-			->with('[email protected]')
499
-			->willReturn(true);
500
-		$this->eventComparisonService->expects(self::once())
501
-			->method('findModified')
502
-			->willReturn(['old' => [] ,'new' => [$newVevent]]);
503
-		$this->service->expects(self::once())
504
-			->method('getCurrentAttendee')
505
-			->with($message)
506
-			->willReturn($atnd);
507
-		$this->service->expects(self::once())
508
-			->method('isRoomOrResource')
509
-			->with($atnd)
510
-			->willReturn(false);
511
-		$this->service->expects(self::once())
512
-			->method('isCircle')
513
-			->with($atnd)
514
-			->willReturn(false);
515
-		$this->service->expects(self::once())
516
-			->method('buildBodyData')
517
-			->with($newVevent, null)
518
-			->willReturn($data);
519
-		$this->user->expects(self::any())
520
-			->method('getUID')
521
-			->willReturn('user1');
522
-		$this->user->expects(self::any())
523
-			->method('getDisplayName')
524
-			->willReturn('Mr. Wizard');
525
-		$this->userSession->expects(self::any())
526
-			->method('getUser')
527
-			->willReturn($this->user);
528
-		$this->service->expects(self::once())
529
-			->method('getFrom');
530
-		$this->service->expects(self::once())
531
-			->method('addSubjectAndHeading')
532
-			->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Elevenses', false);
533
-		$this->service->expects(self::once())
534
-			->method('addBulletList')
535
-			->with($this->emailTemplate, $newVevent, $data);
536
-		$this->service->expects(self::once())
537
-			->method('getAttendeeRsvpOrReqForParticipant')
538
-			->willReturn(true);
539
-		$this->config->expects(self::once())
540
-			->method('getValueString')
541
-			->with('dav', 'invitation_link_recipients', 'yes')
542
-			->willReturn('yes');
543
-		$this->service->expects(self::once())
544
-			->method('createInvitationToken')
545
-			->with($message, $newVevent, 1496912700)
546
-			->willReturn('token');
547
-		$this->service->expects(self::once())
548
-			->method('addResponseButtons')
549
-			->with($this->emailTemplate, 'token');
550
-		$this->service->expects(self::once())
551
-			->method('addMoreOptionsButton')
552
-			->with($this->emailTemplate, 'token');
553
-		$this->mailer->expects(self::once())
554
-			->method('send')
555
-			->willReturn([]);
556
-		$this->plugin->schedule($message);
557
-		$this->assertEquals('1.1', $message->getScheduleStatus());
558
-	}
443
+    public function testParsingRecurrence(): void {
444
+        $message = new Message();
445
+        $message->method = 'REQUEST';
446
+        $newVCalendar = new VCalendar();
447
+        $newVevent = new VEvent($newVCalendar, 'one', [
448
+            'UID' => 'uid-1234',
449
+            'LAST-MODIFIED' => 123456,
450
+            'SEQUENCE' => 2,
451
+            'SUMMARY' => 'Fellowship meeting',
452
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
453
+            'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z'
454
+        ]);
455
+        $newVevent->add('ORGANIZER', 'mailto:[email protected]');
456
+        $newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
457
+        $newvEvent2 = new VEvent($newVCalendar, 'two', [
458
+            'UID' => 'uid-1234',
459
+            'SEQUENCE' => 1,
460
+            'SUMMARY' => 'Elevenses',
461
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
462
+            'RECURRENCE-ID' => new \DateTime('2016-01-01 00:00:00')
463
+        ]);
464
+        $newvEvent2->add('ORGANIZER', 'mailto:[email protected]');
465
+        $newvEvent2->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
466
+        $message->message = $newVCalendar;
467
+        $message->sender = 'mailto:[email protected]';
468
+        $message->recipient = 'mailto:' . '[email protected]';
469
+        // save the old copy in the plugin
470
+        $oldVCalendar = new VCalendar();
471
+        $oldVEvent = new VEvent($oldVCalendar, 'one', [
472
+            'UID' => 'uid-1234',
473
+            'LAST-MODIFIED' => 123456,
474
+            'SEQUENCE' => 2,
475
+            'SUMMARY' => 'Fellowship meeting',
476
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00'),
477
+            'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z'
478
+        ]);
479
+        $oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
480
+        $oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
481
+        $data = ['invitee_name' => 'Mr. Wizard',
482
+            'meeting_title' => 'Elevenses',
483
+            'attendee_name' => '[email protected]'
484
+        ];
485
+        $attendees = $newVevent->select('ATTENDEE');
486
+        $atnd = '';
487
+        foreach ($attendees as $attendee) {
488
+            if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
489
+                $atnd = $attendee;
490
+            }
491
+        }
492
+        $this->plugin->setVCalendar($oldVCalendar);
493
+        $this->service->expects(self::once())
494
+            ->method('getLastOccurrence')
495
+            ->willReturn(1496912700);
496
+        $this->mailer->expects(self::once())
497
+            ->method('validateMailAddress')
498
+            ->with('[email protected]')
499
+            ->willReturn(true);
500
+        $this->eventComparisonService->expects(self::once())
501
+            ->method('findModified')
502
+            ->willReturn(['old' => [] ,'new' => [$newVevent]]);
503
+        $this->service->expects(self::once())
504
+            ->method('getCurrentAttendee')
505
+            ->with($message)
506
+            ->willReturn($atnd);
507
+        $this->service->expects(self::once())
508
+            ->method('isRoomOrResource')
509
+            ->with($atnd)
510
+            ->willReturn(false);
511
+        $this->service->expects(self::once())
512
+            ->method('isCircle')
513
+            ->with($atnd)
514
+            ->willReturn(false);
515
+        $this->service->expects(self::once())
516
+            ->method('buildBodyData')
517
+            ->with($newVevent, null)
518
+            ->willReturn($data);
519
+        $this->user->expects(self::any())
520
+            ->method('getUID')
521
+            ->willReturn('user1');
522
+        $this->user->expects(self::any())
523
+            ->method('getDisplayName')
524
+            ->willReturn('Mr. Wizard');
525
+        $this->userSession->expects(self::any())
526
+            ->method('getUser')
527
+            ->willReturn($this->user);
528
+        $this->service->expects(self::once())
529
+            ->method('getFrom');
530
+        $this->service->expects(self::once())
531
+            ->method('addSubjectAndHeading')
532
+            ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Elevenses', false);
533
+        $this->service->expects(self::once())
534
+            ->method('addBulletList')
535
+            ->with($this->emailTemplate, $newVevent, $data);
536
+        $this->service->expects(self::once())
537
+            ->method('getAttendeeRsvpOrReqForParticipant')
538
+            ->willReturn(true);
539
+        $this->config->expects(self::once())
540
+            ->method('getValueString')
541
+            ->with('dav', 'invitation_link_recipients', 'yes')
542
+            ->willReturn('yes');
543
+        $this->service->expects(self::once())
544
+            ->method('createInvitationToken')
545
+            ->with($message, $newVevent, 1496912700)
546
+            ->willReturn('token');
547
+        $this->service->expects(self::once())
548
+            ->method('addResponseButtons')
549
+            ->with($this->emailTemplate, 'token');
550
+        $this->service->expects(self::once())
551
+            ->method('addMoreOptionsButton')
552
+            ->with($this->emailTemplate, 'token');
553
+        $this->mailer->expects(self::once())
554
+            ->method('send')
555
+            ->willReturn([]);
556
+        $this->plugin->schedule($message);
557
+        $this->assertEquals('1.1', $message->getScheduleStatus());
558
+    }
559 559
 
560
-	public function testEmailValidationFailed(): void {
561
-		$message = new Message();
562
-		$message->method = 'REQUEST';
563
-		$message->message = new VCalendar();
564
-		$message->message->add('VEVENT', array_merge([
565
-			'UID' => 'uid-1234',
566
-			'SEQUENCE' => 0,
567
-			'SUMMARY' => 'Fellowship meeting',
568
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
569
-		], []));
570
-		$message->message->VEVENT->add('ORGANIZER', 'mailto:[email protected]');
571
-		$message->message->VEVENT->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
572
-		$message->sender = 'mailto:[email protected]';
573
-		$message->senderName = 'Mr. Wizard';
574
-		$message->recipient = 'mailto:' . '[email protected]';
560
+    public function testEmailValidationFailed(): void {
561
+        $message = new Message();
562
+        $message->method = 'REQUEST';
563
+        $message->message = new VCalendar();
564
+        $message->message->add('VEVENT', array_merge([
565
+            'UID' => 'uid-1234',
566
+            'SEQUENCE' => 0,
567
+            'SUMMARY' => 'Fellowship meeting',
568
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
569
+        ], []));
570
+        $message->message->VEVENT->add('ORGANIZER', 'mailto:[email protected]');
571
+        $message->message->VEVENT->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
572
+        $message->sender = 'mailto:[email protected]';
573
+        $message->senderName = 'Mr. Wizard';
574
+        $message->recipient = 'mailto:' . '[email protected]';
575 575
 
576
-		$this->service->expects(self::once())
577
-			->method('getLastOccurrence')
578
-			->willReturn(1496912700);
579
-		$this->mailer->expects(self::once())
580
-			->method('validateMailAddress')
581
-			->with('[email protected]')
582
-			->willReturn(false);
576
+        $this->service->expects(self::once())
577
+            ->method('getLastOccurrence')
578
+            ->willReturn(1496912700);
579
+        $this->mailer->expects(self::once())
580
+            ->method('validateMailAddress')
581
+            ->with('[email protected]')
582
+            ->willReturn(false);
583 583
 
584
-		$this->plugin->schedule($message);
585
-		$this->assertEquals('5.0', $message->getScheduleStatus());
586
-	}
584
+        $this->plugin->schedule($message);
585
+        $this->assertEquals('5.0', $message->getScheduleStatus());
586
+    }
587 587
 
588
-	public function testFailedDelivery(): void {
589
-		$message = new Message();
590
-		$message->method = 'REQUEST';
591
-		$newVcalendar = new VCalendar();
592
-		$newVevent = new VEvent($newVcalendar, 'one', array_merge([
593
-			'UID' => 'uid-1234',
594
-			'SEQUENCE' => 1,
595
-			'SUMMARY' => 'Fellowship meeting without (!) Boromir',
596
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
597
-		], []));
598
-		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
599
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
600
-		$message->message = $newVcalendar;
601
-		$message->sender = 'mailto:[email protected]';
602
-		$message->senderName = 'Mr. Wizard';
603
-		$message->recipient = 'mailto:' . '[email protected]';
604
-		// save the old copy in the plugin
605
-		$oldVcalendar = new VCalendar();
606
-		$oldVevent = new VEvent($oldVcalendar, 'one', [
607
-			'UID' => 'uid-1234',
608
-			'SEQUENCE' => 0,
609
-			'SUMMARY' => 'Fellowship meeting',
610
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
611
-		]);
612
-		$oldVevent->add('ORGANIZER', 'mailto:[email protected]');
613
-		$oldVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
614
-		$oldVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
615
-		$oldVcalendar->add($oldVevent);
616
-		$data = ['invitee_name' => 'Mr. Wizard',
617
-			'meeting_title' => 'Fellowship meeting without (!) Boromir',
618
-			'attendee_name' => '[email protected]'
619
-		];
620
-		$attendees = $newVevent->select('ATTENDEE');
621
-		$atnd = '';
622
-		foreach ($attendees as $attendee) {
623
-			if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
624
-				$atnd = $attendee;
625
-			}
626
-		}
627
-		$this->plugin->setVCalendar($oldVcalendar);
628
-		$this->service->expects(self::once())
629
-			->method('getLastOccurrence')
630
-			->willReturn(1496912700);
631
-		$this->mailer->expects(self::once())
632
-			->method('validateMailAddress')
633
-			->with('[email protected]')
634
-			->willReturn(true);
635
-		$this->eventComparisonService->expects(self::once())
636
-			->method('findModified')
637
-			->willReturn(['old' => [] ,'new' => [$newVevent]]);
638
-		$this->service->expects(self::once())
639
-			->method('getCurrentAttendee')
640
-			->with($message)
641
-			->willReturn($atnd);
642
-		$this->service->expects(self::once())
643
-			->method('isRoomOrResource')
644
-			->with($atnd)
645
-			->willReturn(false);
646
-		$this->service->expects(self::once())
647
-			->method('isCircle')
648
-			->with($atnd)
649
-			->willReturn(false);
650
-		$this->service->expects(self::once())
651
-			->method('buildBodyData')
652
-			->with($newVevent, null)
653
-			->willReturn($data);
654
-		$this->user->expects(self::any())
655
-			->method('getUID')
656
-			->willReturn('user1');
657
-		$this->user->expects(self::any())
658
-			->method('getDisplayName')
659
-			->willReturn('Mr. Wizard');
660
-		$this->userSession->expects(self::any())
661
-			->method('getUser')
662
-			->willReturn($this->user);
663
-		$this->service->expects(self::once())
664
-			->method('getFrom');
665
-		$this->service->expects(self::once())
666
-			->method('addSubjectAndHeading')
667
-			->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', false);
668
-		$this->service->expects(self::once())
669
-			->method('addBulletList')
670
-			->with($this->emailTemplate, $newVevent, $data);
671
-		$this->service->expects(self::once())
672
-			->method('getAttendeeRsvpOrReqForParticipant')
673
-			->willReturn(true);
674
-		$this->config->expects(self::once())
675
-			->method('getValueString')
676
-			->with('dav', 'invitation_link_recipients', 'yes')
677
-			->willReturn('yes');
678
-		$this->service->expects(self::once())
679
-			->method('createInvitationToken')
680
-			->with($message, $newVevent, 1496912700)
681
-			->willReturn('token');
682
-		$this->service->expects(self::once())
683
-			->method('addResponseButtons')
684
-			->with($this->emailTemplate, 'token');
685
-		$this->service->expects(self::once())
686
-			->method('addMoreOptionsButton')
687
-			->with($this->emailTemplate, 'token');
688
-		$this->mailer->expects(self::once())
689
-			->method('send')
690
-			->willReturn([]);
691
-		$this->mailer
692
-			->method('send')
693
-			->willThrowException(new \Exception());
694
-		$this->logger->expects(self::once())
695
-			->method('error');
696
-		$this->plugin->schedule($message);
697
-		$this->assertEquals('5.0', $message->getScheduleStatus());
698
-	}
588
+    public function testFailedDelivery(): void {
589
+        $message = new Message();
590
+        $message->method = 'REQUEST';
591
+        $newVcalendar = new VCalendar();
592
+        $newVevent = new VEvent($newVcalendar, 'one', array_merge([
593
+            'UID' => 'uid-1234',
594
+            'SEQUENCE' => 1,
595
+            'SUMMARY' => 'Fellowship meeting without (!) Boromir',
596
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
597
+        ], []));
598
+        $newVevent->add('ORGANIZER', 'mailto:[email protected]');
599
+        $newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
600
+        $message->message = $newVcalendar;
601
+        $message->sender = 'mailto:[email protected]';
602
+        $message->senderName = 'Mr. Wizard';
603
+        $message->recipient = 'mailto:' . '[email protected]';
604
+        // save the old copy in the plugin
605
+        $oldVcalendar = new VCalendar();
606
+        $oldVevent = new VEvent($oldVcalendar, 'one', [
607
+            'UID' => 'uid-1234',
608
+            'SEQUENCE' => 0,
609
+            'SUMMARY' => 'Fellowship meeting',
610
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
611
+        ]);
612
+        $oldVevent->add('ORGANIZER', 'mailto:[email protected]');
613
+        $oldVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
614
+        $oldVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
615
+        $oldVcalendar->add($oldVevent);
616
+        $data = ['invitee_name' => 'Mr. Wizard',
617
+            'meeting_title' => 'Fellowship meeting without (!) Boromir',
618
+            'attendee_name' => '[email protected]'
619
+        ];
620
+        $attendees = $newVevent->select('ATTENDEE');
621
+        $atnd = '';
622
+        foreach ($attendees as $attendee) {
623
+            if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
624
+                $atnd = $attendee;
625
+            }
626
+        }
627
+        $this->plugin->setVCalendar($oldVcalendar);
628
+        $this->service->expects(self::once())
629
+            ->method('getLastOccurrence')
630
+            ->willReturn(1496912700);
631
+        $this->mailer->expects(self::once())
632
+            ->method('validateMailAddress')
633
+            ->with('[email protected]')
634
+            ->willReturn(true);
635
+        $this->eventComparisonService->expects(self::once())
636
+            ->method('findModified')
637
+            ->willReturn(['old' => [] ,'new' => [$newVevent]]);
638
+        $this->service->expects(self::once())
639
+            ->method('getCurrentAttendee')
640
+            ->with($message)
641
+            ->willReturn($atnd);
642
+        $this->service->expects(self::once())
643
+            ->method('isRoomOrResource')
644
+            ->with($atnd)
645
+            ->willReturn(false);
646
+        $this->service->expects(self::once())
647
+            ->method('isCircle')
648
+            ->with($atnd)
649
+            ->willReturn(false);
650
+        $this->service->expects(self::once())
651
+            ->method('buildBodyData')
652
+            ->with($newVevent, null)
653
+            ->willReturn($data);
654
+        $this->user->expects(self::any())
655
+            ->method('getUID')
656
+            ->willReturn('user1');
657
+        $this->user->expects(self::any())
658
+            ->method('getDisplayName')
659
+            ->willReturn('Mr. Wizard');
660
+        $this->userSession->expects(self::any())
661
+            ->method('getUser')
662
+            ->willReturn($this->user);
663
+        $this->service->expects(self::once())
664
+            ->method('getFrom');
665
+        $this->service->expects(self::once())
666
+            ->method('addSubjectAndHeading')
667
+            ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', false);
668
+        $this->service->expects(self::once())
669
+            ->method('addBulletList')
670
+            ->with($this->emailTemplate, $newVevent, $data);
671
+        $this->service->expects(self::once())
672
+            ->method('getAttendeeRsvpOrReqForParticipant')
673
+            ->willReturn(true);
674
+        $this->config->expects(self::once())
675
+            ->method('getValueString')
676
+            ->with('dav', 'invitation_link_recipients', 'yes')
677
+            ->willReturn('yes');
678
+        $this->service->expects(self::once())
679
+            ->method('createInvitationToken')
680
+            ->with($message, $newVevent, 1496912700)
681
+            ->willReturn('token');
682
+        $this->service->expects(self::once())
683
+            ->method('addResponseButtons')
684
+            ->with($this->emailTemplate, 'token');
685
+        $this->service->expects(self::once())
686
+            ->method('addMoreOptionsButton')
687
+            ->with($this->emailTemplate, 'token');
688
+        $this->mailer->expects(self::once())
689
+            ->method('send')
690
+            ->willReturn([]);
691
+        $this->mailer
692
+            ->method('send')
693
+            ->willThrowException(new \Exception());
694
+        $this->logger->expects(self::once())
695
+            ->method('error');
696
+        $this->plugin->schedule($message);
697
+        $this->assertEquals('5.0', $message->getScheduleStatus());
698
+    }
699 699
 
700
-	public function testMailProviderSend(): void {
701
-		// construct iTip message with event and attendees
702
-		$message = new Message();
703
-		$message->method = 'REQUEST';
704
-		$calendar = new VCalendar();
705
-		$event = new VEvent($calendar, 'one', array_merge([
706
-			'UID' => 'uid-1234',
707
-			'SEQUENCE' => 1,
708
-			'SUMMARY' => 'Fellowship meeting without (!) Boromir',
709
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
710
-		], []));
711
-		$event->add('ORGANIZER', 'mailto:[email protected]');
712
-		$event->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
713
-		$message->message = $calendar;
714
-		$message->sender = 'mailto:[email protected]';
715
-		$message->senderName = 'Mr. Wizard';
716
-		$message->recipient = 'mailto:' . '[email protected]';
717
-		// construct
718
-		foreach ($event->select('ATTENDEE') as $entry) {
719
-			if (strcasecmp($entry->getValue(), $message->recipient) === 0) {
720
-				$attendee = $entry;
721
-			}
722
-		}
723
-		// construct body data return
724
-		$data = ['invitee_name' => 'Mr. Wizard',
725
-			'meeting_title' => 'Fellowship meeting without (!) Boromir',
726
-			'attendee_name' => '[email protected]'
727
-		];
728
-		// construct system config mock returns
729
-		$this->config->expects(self::once())
730
-			->method('getValueString')
731
-			->with('dav', 'invitation_link_recipients', 'yes')
732
-			->willReturn('yes');
733
-		// construct user mock returns
734
-		$this->user->expects(self::any())
735
-			->method('getUID')
736
-			->willReturn('user1');
737
-		$this->user->expects(self::any())
738
-			->method('getDisplayName')
739
-			->willReturn('Mr. Wizard');
740
-		// construct user session mock returns
741
-		$this->userSession->expects(self::any())
742
-			->method('getUser')
743
-			->willReturn($this->user);
744
-		// construct service mock returns
745
-		$this->service->expects(self::once())
746
-			->method('getLastOccurrence')
747
-			->willReturn(1496912700);
748
-		$this->service->expects(self::once())
749
-			->method('getCurrentAttendee')
750
-			->with($message)
751
-			->willReturn($attendee);
752
-		$this->service->expects(self::once())
753
-			->method('isRoomOrResource')
754
-			->with($attendee)
755
-			->willReturn(false);
756
-		$this->service->expects(self::once())
757
-			->method('isCircle')
758
-			->with($attendee)
759
-			->willReturn(false);
760
-		$this->service->expects(self::once())
761
-			->method('buildBodyData')
762
-			->with($event, null)
763
-			->willReturn($data);
764
-		$this->service->expects(self::once())
765
-			->method('getFrom');
766
-		$this->service->expects(self::once())
767
-			->method('addSubjectAndHeading')
768
-			->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', false);
769
-		$this->service->expects(self::once())
770
-			->method('addBulletList')
771
-			->with($this->emailTemplate, $event, $data);
772
-		$this->service->expects(self::once())
773
-			->method('getAttendeeRsvpOrReqForParticipant')
774
-			->willReturn(true);
775
-		$this->service->expects(self::once())
776
-			->method('createInvitationToken')
777
-			->with($message, $event, 1496912700)
778
-			->willReturn('token');
779
-		$this->service->expects(self::once())
780
-			->method('addResponseButtons')
781
-			->with($this->emailTemplate, 'token');
782
-		$this->service->expects(self::once())
783
-			->method('addMoreOptionsButton')
784
-			->with($this->emailTemplate, 'token');
785
-		$this->eventComparisonService->expects(self::once())
786
-			->method('findModified')
787
-			->willReturn(['old' => [] ,'new' => [$event]]);
788
-		// construct mail mock returns
789
-		$this->mailer->expects(self::once())
790
-			->method('validateMailAddress')
791
-			->with('[email protected]')
792
-			->willReturn(true);
793
-		// construct mail provider mock returns
794
-		$this->mailService
795
-			->method('initiateMessage')
796
-			->willReturn($this->mailMessageNew);
797
-		$this->mailService
798
-			->method('sendMessage')
799
-			->with($this->mailMessageNew);
800
-		$this->mailManager
801
-			->method('findServiceByAddress')
802
-			->with('user1', '[email protected]')
803
-			->willReturn($this->mailService);
700
+    public function testMailProviderSend(): void {
701
+        // construct iTip message with event and attendees
702
+        $message = new Message();
703
+        $message->method = 'REQUEST';
704
+        $calendar = new VCalendar();
705
+        $event = new VEvent($calendar, 'one', array_merge([
706
+            'UID' => 'uid-1234',
707
+            'SEQUENCE' => 1,
708
+            'SUMMARY' => 'Fellowship meeting without (!) Boromir',
709
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
710
+        ], []));
711
+        $event->add('ORGANIZER', 'mailto:[email protected]');
712
+        $event->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
713
+        $message->message = $calendar;
714
+        $message->sender = 'mailto:[email protected]';
715
+        $message->senderName = 'Mr. Wizard';
716
+        $message->recipient = 'mailto:' . '[email protected]';
717
+        // construct
718
+        foreach ($event->select('ATTENDEE') as $entry) {
719
+            if (strcasecmp($entry->getValue(), $message->recipient) === 0) {
720
+                $attendee = $entry;
721
+            }
722
+        }
723
+        // construct body data return
724
+        $data = ['invitee_name' => 'Mr. Wizard',
725
+            'meeting_title' => 'Fellowship meeting without (!) Boromir',
726
+            'attendee_name' => '[email protected]'
727
+        ];
728
+        // construct system config mock returns
729
+        $this->config->expects(self::once())
730
+            ->method('getValueString')
731
+            ->with('dav', 'invitation_link_recipients', 'yes')
732
+            ->willReturn('yes');
733
+        // construct user mock returns
734
+        $this->user->expects(self::any())
735
+            ->method('getUID')
736
+            ->willReturn('user1');
737
+        $this->user->expects(self::any())
738
+            ->method('getDisplayName')
739
+            ->willReturn('Mr. Wizard');
740
+        // construct user session mock returns
741
+        $this->userSession->expects(self::any())
742
+            ->method('getUser')
743
+            ->willReturn($this->user);
744
+        // construct service mock returns
745
+        $this->service->expects(self::once())
746
+            ->method('getLastOccurrence')
747
+            ->willReturn(1496912700);
748
+        $this->service->expects(self::once())
749
+            ->method('getCurrentAttendee')
750
+            ->with($message)
751
+            ->willReturn($attendee);
752
+        $this->service->expects(self::once())
753
+            ->method('isRoomOrResource')
754
+            ->with($attendee)
755
+            ->willReturn(false);
756
+        $this->service->expects(self::once())
757
+            ->method('isCircle')
758
+            ->with($attendee)
759
+            ->willReturn(false);
760
+        $this->service->expects(self::once())
761
+            ->method('buildBodyData')
762
+            ->with($event, null)
763
+            ->willReturn($data);
764
+        $this->service->expects(self::once())
765
+            ->method('getFrom');
766
+        $this->service->expects(self::once())
767
+            ->method('addSubjectAndHeading')
768
+            ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', false);
769
+        $this->service->expects(self::once())
770
+            ->method('addBulletList')
771
+            ->with($this->emailTemplate, $event, $data);
772
+        $this->service->expects(self::once())
773
+            ->method('getAttendeeRsvpOrReqForParticipant')
774
+            ->willReturn(true);
775
+        $this->service->expects(self::once())
776
+            ->method('createInvitationToken')
777
+            ->with($message, $event, 1496912700)
778
+            ->willReturn('token');
779
+        $this->service->expects(self::once())
780
+            ->method('addResponseButtons')
781
+            ->with($this->emailTemplate, 'token');
782
+        $this->service->expects(self::once())
783
+            ->method('addMoreOptionsButton')
784
+            ->with($this->emailTemplate, 'token');
785
+        $this->eventComparisonService->expects(self::once())
786
+            ->method('findModified')
787
+            ->willReturn(['old' => [] ,'new' => [$event]]);
788
+        // construct mail mock returns
789
+        $this->mailer->expects(self::once())
790
+            ->method('validateMailAddress')
791
+            ->with('[email protected]')
792
+            ->willReturn(true);
793
+        // construct mail provider mock returns
794
+        $this->mailService
795
+            ->method('initiateMessage')
796
+            ->willReturn($this->mailMessageNew);
797
+        $this->mailService
798
+            ->method('sendMessage')
799
+            ->with($this->mailMessageNew);
800
+        $this->mailManager
801
+            ->method('findServiceByAddress')
802
+            ->with('user1', '[email protected]')
803
+            ->willReturn($this->mailService);
804 804
 
805
-		$this->plugin->schedule($message);
806
-		$this->assertEquals('1.1', $message->getScheduleStatus());
807
-	}
805
+        $this->plugin->schedule($message);
806
+        $this->assertEquals('1.1', $message->getScheduleStatus());
807
+    }
808 808
 
809
-	public function testMailProviderDisabled(): void {
810
-		$message = new Message();
811
-		$message->method = 'REQUEST';
812
-		$newVCalendar = new VCalendar();
813
-		$newVevent = new VEvent($newVCalendar, 'one', array_merge([
814
-			'UID' => 'uid-1234',
815
-			'SEQUENCE' => 1,
816
-			'SUMMARY' => 'Fellowship meeting without (!) Boromir',
817
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
818
-		], []));
819
-		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
820
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
821
-		$message->message = $newVCalendar;
822
-		$message->sender = 'mailto:[email protected]';
823
-		$message->senderName = 'Mr. Wizard';
824
-		$message->recipient = 'mailto:' . '[email protected]';
825
-		// save the old copy in the plugin
826
-		$oldVCalendar = new VCalendar();
827
-		$oldVEvent = new VEvent($oldVCalendar, 'one', [
828
-			'UID' => 'uid-1234',
829
-			'SEQUENCE' => 0,
830
-			'SUMMARY' => 'Fellowship meeting',
831
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
832
-		]);
833
-		$oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
834
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
835
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
836
-		$oldVCalendar->add($oldVEvent);
837
-		$data = ['invitee_name' => 'Mr. Wizard',
838
-			'meeting_title' => 'Fellowship meeting without (!) Boromir',
839
-			'attendee_name' => '[email protected]'
840
-		];
841
-		$attendees = $newVevent->select('ATTENDEE');
842
-		$atnd = '';
843
-		foreach ($attendees as $attendee) {
844
-			if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
845
-				$atnd = $attendee;
846
-			}
847
-		}
848
-		$this->plugin->setVCalendar($oldVCalendar);
849
-		$this->service->expects(self::once())
850
-			->method('getLastOccurrence')
851
-			->willReturn(1496912700);
852
-		$this->mailer->expects(self::once())
853
-			->method('validateMailAddress')
854
-			->with('[email protected]')
855
-			->willReturn(true);
856
-		$this->eventComparisonService->expects(self::once())
857
-			->method('findModified')
858
-			->willReturn(['new' => [$newVevent], 'old' => [$oldVEvent]]);
859
-		$this->service->expects(self::once())
860
-			->method('getCurrentAttendee')
861
-			->with($message)
862
-			->willReturn($atnd);
863
-		$this->service->expects(self::once())
864
-			->method('isRoomOrResource')
865
-			->with($atnd)
866
-			->willReturn(false);
867
-		$this->service->expects(self::once())
868
-			->method('isCircle')
869
-			->with($atnd)
870
-			->willReturn(false);
871
-		$this->service->expects(self::once())
872
-			->method('buildBodyData')
873
-			->with($newVevent, $oldVEvent)
874
-			->willReturn($data);
875
-		$this->user->expects(self::any())
876
-			->method('getUID')
877
-			->willReturn('user1');
878
-		$this->user->expects(self::any())
879
-			->method('getDisplayName')
880
-			->willReturn('Mr. Wizard');
881
-		$this->userSession->expects(self::any())
882
-			->method('getUser')
883
-			->willReturn($this->user);
884
-		$this->service->expects(self::once())
885
-			->method('getFrom');
886
-		$this->service->expects(self::once())
887
-			->method('addSubjectAndHeading')
888
-			->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', true);
889
-		$this->service->expects(self::once())
890
-			->method('addBulletList')
891
-			->with($this->emailTemplate, $newVevent, $data);
892
-		$this->service->expects(self::once())
893
-			->method('getAttendeeRsvpOrReqForParticipant')
894
-			->willReturn(true);
895
-		$this->config->expects(self::once())
896
-			->method('getValueString')
897
-			->with('dav', 'invitation_link_recipients', 'yes')
898
-			->willReturn('yes');
899
-		$this->config->expects(self::once())
900
-			->method('getValueBool')
901
-			->with('core', 'mail_providers_enabled', true)
902
-			->willReturn(false);
903
-		$this->service->expects(self::once())
904
-			->method('createInvitationToken')
905
-			->with($message, $newVevent, 1496912700)
906
-			->willReturn('token');
907
-		$this->service->expects(self::once())
908
-			->method('addResponseButtons')
909
-			->with($this->emailTemplate, 'token');
910
-		$this->service->expects(self::once())
911
-			->method('addMoreOptionsButton')
912
-			->with($this->emailTemplate, 'token');
913
-		$this->mailer->expects(self::once())
914
-			->method('send')
915
-			->willReturn([]);
916
-		$this->plugin->schedule($message);
917
-		$this->assertEquals('1.1', $message->getScheduleStatus());
918
-	}
809
+    public function testMailProviderDisabled(): void {
810
+        $message = new Message();
811
+        $message->method = 'REQUEST';
812
+        $newVCalendar = new VCalendar();
813
+        $newVevent = new VEvent($newVCalendar, 'one', array_merge([
814
+            'UID' => 'uid-1234',
815
+            'SEQUENCE' => 1,
816
+            'SUMMARY' => 'Fellowship meeting without (!) Boromir',
817
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
818
+        ], []));
819
+        $newVevent->add('ORGANIZER', 'mailto:[email protected]');
820
+        $newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
821
+        $message->message = $newVCalendar;
822
+        $message->sender = 'mailto:[email protected]';
823
+        $message->senderName = 'Mr. Wizard';
824
+        $message->recipient = 'mailto:' . '[email protected]';
825
+        // save the old copy in the plugin
826
+        $oldVCalendar = new VCalendar();
827
+        $oldVEvent = new VEvent($oldVCalendar, 'one', [
828
+            'UID' => 'uid-1234',
829
+            'SEQUENCE' => 0,
830
+            'SUMMARY' => 'Fellowship meeting',
831
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
832
+        ]);
833
+        $oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
834
+        $oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
835
+        $oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
836
+        $oldVCalendar->add($oldVEvent);
837
+        $data = ['invitee_name' => 'Mr. Wizard',
838
+            'meeting_title' => 'Fellowship meeting without (!) Boromir',
839
+            'attendee_name' => '[email protected]'
840
+        ];
841
+        $attendees = $newVevent->select('ATTENDEE');
842
+        $atnd = '';
843
+        foreach ($attendees as $attendee) {
844
+            if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
845
+                $atnd = $attendee;
846
+            }
847
+        }
848
+        $this->plugin->setVCalendar($oldVCalendar);
849
+        $this->service->expects(self::once())
850
+            ->method('getLastOccurrence')
851
+            ->willReturn(1496912700);
852
+        $this->mailer->expects(self::once())
853
+            ->method('validateMailAddress')
854
+            ->with('[email protected]')
855
+            ->willReturn(true);
856
+        $this->eventComparisonService->expects(self::once())
857
+            ->method('findModified')
858
+            ->willReturn(['new' => [$newVevent], 'old' => [$oldVEvent]]);
859
+        $this->service->expects(self::once())
860
+            ->method('getCurrentAttendee')
861
+            ->with($message)
862
+            ->willReturn($atnd);
863
+        $this->service->expects(self::once())
864
+            ->method('isRoomOrResource')
865
+            ->with($atnd)
866
+            ->willReturn(false);
867
+        $this->service->expects(self::once())
868
+            ->method('isCircle')
869
+            ->with($atnd)
870
+            ->willReturn(false);
871
+        $this->service->expects(self::once())
872
+            ->method('buildBodyData')
873
+            ->with($newVevent, $oldVEvent)
874
+            ->willReturn($data);
875
+        $this->user->expects(self::any())
876
+            ->method('getUID')
877
+            ->willReturn('user1');
878
+        $this->user->expects(self::any())
879
+            ->method('getDisplayName')
880
+            ->willReturn('Mr. Wizard');
881
+        $this->userSession->expects(self::any())
882
+            ->method('getUser')
883
+            ->willReturn($this->user);
884
+        $this->service->expects(self::once())
885
+            ->method('getFrom');
886
+        $this->service->expects(self::once())
887
+            ->method('addSubjectAndHeading')
888
+            ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', true);
889
+        $this->service->expects(self::once())
890
+            ->method('addBulletList')
891
+            ->with($this->emailTemplate, $newVevent, $data);
892
+        $this->service->expects(self::once())
893
+            ->method('getAttendeeRsvpOrReqForParticipant')
894
+            ->willReturn(true);
895
+        $this->config->expects(self::once())
896
+            ->method('getValueString')
897
+            ->with('dav', 'invitation_link_recipients', 'yes')
898
+            ->willReturn('yes');
899
+        $this->config->expects(self::once())
900
+            ->method('getValueBool')
901
+            ->with('core', 'mail_providers_enabled', true)
902
+            ->willReturn(false);
903
+        $this->service->expects(self::once())
904
+            ->method('createInvitationToken')
905
+            ->with($message, $newVevent, 1496912700)
906
+            ->willReturn('token');
907
+        $this->service->expects(self::once())
908
+            ->method('addResponseButtons')
909
+            ->with($this->emailTemplate, 'token');
910
+        $this->service->expects(self::once())
911
+            ->method('addMoreOptionsButton')
912
+            ->with($this->emailTemplate, 'token');
913
+        $this->mailer->expects(self::once())
914
+            ->method('send')
915
+            ->willReturn([]);
916
+        $this->plugin->schedule($message);
917
+        $this->assertEquals('1.1', $message->getScheduleStatus());
918
+    }
919 919
 
920
-	public function testNoOldEvent(): void {
921
-		$message = new Message();
922
-		$message->method = 'REQUEST';
923
-		$newVCalendar = new VCalendar();
924
-		$newVevent = new VEvent($newVCalendar, 'VEVENT', array_merge([
925
-			'UID' => 'uid-1234',
926
-			'SEQUENCE' => 1,
927
-			'SUMMARY' => 'Fellowship meeting',
928
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
929
-		], []));
930
-		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
931
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
932
-		$message->message = $newVCalendar;
933
-		$message->sender = 'mailto:[email protected]';
934
-		$message->senderName = 'Mr. Wizard';
935
-		$message->recipient = 'mailto:' . '[email protected]';
936
-		$data = ['invitee_name' => 'Mr. Wizard',
937
-			'meeting_title' => 'Fellowship meeting',
938
-			'attendee_name' => '[email protected]'
939
-		];
940
-		$attendees = $newVevent->select('ATTENDEE');
941
-		$atnd = '';
942
-		foreach ($attendees as $attendee) {
943
-			if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
944
-				$atnd = $attendee;
945
-			}
946
-		}
947
-		$this->service->expects(self::once())
948
-			->method('getLastOccurrence')
949
-			->willReturn(1496912700);
950
-		$this->mailer->expects(self::once())
951
-			->method('validateMailAddress')
952
-			->with('[email protected]')
953
-			->willReturn(true);
954
-		$this->eventComparisonService->expects(self::once())
955
-			->method('findModified')
956
-			->with($newVCalendar, null)
957
-			->willReturn(['old' => [] ,'new' => [$newVevent]]);
958
-		$this->service->expects(self::once())
959
-			->method('getCurrentAttendee')
960
-			->with($message)
961
-			->willReturn($atnd);
962
-		$this->service->expects(self::once())
963
-			->method('isRoomOrResource')
964
-			->with($atnd)
965
-			->willReturn(false);
966
-		$this->service->expects(self::once())
967
-			->method('isCircle')
968
-			->with($atnd)
969
-			->willReturn(false);
970
-		$this->service->expects(self::once())
971
-			->method('buildBodyData')
972
-			->with($newVevent, null)
973
-			->willReturn($data);
974
-		$this->user->expects(self::any())
975
-			->method('getUID')
976
-			->willReturn('user1');
977
-		$this->user->expects(self::any())
978
-			->method('getDisplayName')
979
-			->willReturn('Mr. Wizard');
980
-		$this->userSession->expects(self::any())
981
-			->method('getUser')
982
-			->willReturn($this->user);
983
-		$this->service->expects(self::once())
984
-			->method('getFrom');
985
-		$this->service->expects(self::once())
986
-			->method('addSubjectAndHeading')
987
-			->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting', false);
988
-		$this->service->expects(self::once())
989
-			->method('addBulletList')
990
-			->with($this->emailTemplate, $newVevent, $data);
991
-		$this->service->expects(self::once())
992
-			->method('getAttendeeRsvpOrReqForParticipant')
993
-			->willReturn(true);
994
-		$this->config->expects(self::once())
995
-			->method('getValueString')
996
-			->with('dav', 'invitation_link_recipients', 'yes')
997
-			->willReturn('yes');
998
-		$this->service->expects(self::once())
999
-			->method('createInvitationToken')
1000
-			->with($message, $newVevent, 1496912700)
1001
-			->willReturn('token');
1002
-		$this->service->expects(self::once())
1003
-			->method('addResponseButtons')
1004
-			->with($this->emailTemplate, 'token');
1005
-		$this->service->expects(self::once())
1006
-			->method('addMoreOptionsButton')
1007
-			->with($this->emailTemplate, 'token');
1008
-		$this->mailer->expects(self::once())
1009
-			->method('send')
1010
-			->willReturn([]);
1011
-		$this->mailer
1012
-			->method('send')
1013
-			->willReturn([]);
1014
-		$this->plugin->schedule($message);
1015
-		$this->assertEquals('1.1', $message->getScheduleStatus());
1016
-	}
920
+    public function testNoOldEvent(): void {
921
+        $message = new Message();
922
+        $message->method = 'REQUEST';
923
+        $newVCalendar = new VCalendar();
924
+        $newVevent = new VEvent($newVCalendar, 'VEVENT', array_merge([
925
+            'UID' => 'uid-1234',
926
+            'SEQUENCE' => 1,
927
+            'SUMMARY' => 'Fellowship meeting',
928
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
929
+        ], []));
930
+        $newVevent->add('ORGANIZER', 'mailto:[email protected]');
931
+        $newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
932
+        $message->message = $newVCalendar;
933
+        $message->sender = 'mailto:[email protected]';
934
+        $message->senderName = 'Mr. Wizard';
935
+        $message->recipient = 'mailto:' . '[email protected]';
936
+        $data = ['invitee_name' => 'Mr. Wizard',
937
+            'meeting_title' => 'Fellowship meeting',
938
+            'attendee_name' => '[email protected]'
939
+        ];
940
+        $attendees = $newVevent->select('ATTENDEE');
941
+        $atnd = '';
942
+        foreach ($attendees as $attendee) {
943
+            if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
944
+                $atnd = $attendee;
945
+            }
946
+        }
947
+        $this->service->expects(self::once())
948
+            ->method('getLastOccurrence')
949
+            ->willReturn(1496912700);
950
+        $this->mailer->expects(self::once())
951
+            ->method('validateMailAddress')
952
+            ->with('[email protected]')
953
+            ->willReturn(true);
954
+        $this->eventComparisonService->expects(self::once())
955
+            ->method('findModified')
956
+            ->with($newVCalendar, null)
957
+            ->willReturn(['old' => [] ,'new' => [$newVevent]]);
958
+        $this->service->expects(self::once())
959
+            ->method('getCurrentAttendee')
960
+            ->with($message)
961
+            ->willReturn($atnd);
962
+        $this->service->expects(self::once())
963
+            ->method('isRoomOrResource')
964
+            ->with($atnd)
965
+            ->willReturn(false);
966
+        $this->service->expects(self::once())
967
+            ->method('isCircle')
968
+            ->with($atnd)
969
+            ->willReturn(false);
970
+        $this->service->expects(self::once())
971
+            ->method('buildBodyData')
972
+            ->with($newVevent, null)
973
+            ->willReturn($data);
974
+        $this->user->expects(self::any())
975
+            ->method('getUID')
976
+            ->willReturn('user1');
977
+        $this->user->expects(self::any())
978
+            ->method('getDisplayName')
979
+            ->willReturn('Mr. Wizard');
980
+        $this->userSession->expects(self::any())
981
+            ->method('getUser')
982
+            ->willReturn($this->user);
983
+        $this->service->expects(self::once())
984
+            ->method('getFrom');
985
+        $this->service->expects(self::once())
986
+            ->method('addSubjectAndHeading')
987
+            ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting', false);
988
+        $this->service->expects(self::once())
989
+            ->method('addBulletList')
990
+            ->with($this->emailTemplate, $newVevent, $data);
991
+        $this->service->expects(self::once())
992
+            ->method('getAttendeeRsvpOrReqForParticipant')
993
+            ->willReturn(true);
994
+        $this->config->expects(self::once())
995
+            ->method('getValueString')
996
+            ->with('dav', 'invitation_link_recipients', 'yes')
997
+            ->willReturn('yes');
998
+        $this->service->expects(self::once())
999
+            ->method('createInvitationToken')
1000
+            ->with($message, $newVevent, 1496912700)
1001
+            ->willReturn('token');
1002
+        $this->service->expects(self::once())
1003
+            ->method('addResponseButtons')
1004
+            ->with($this->emailTemplate, 'token');
1005
+        $this->service->expects(self::once())
1006
+            ->method('addMoreOptionsButton')
1007
+            ->with($this->emailTemplate, 'token');
1008
+        $this->mailer->expects(self::once())
1009
+            ->method('send')
1010
+            ->willReturn([]);
1011
+        $this->mailer
1012
+            ->method('send')
1013
+            ->willReturn([]);
1014
+        $this->plugin->schedule($message);
1015
+        $this->assertEquals('1.1', $message->getScheduleStatus());
1016
+    }
1017 1017
 
1018
-	public function testNoButtons(): void {
1019
-		$message = new Message();
1020
-		$message->method = 'REQUEST';
1021
-		$newVCalendar = new VCalendar();
1022
-		$newVevent = new VEvent($newVCalendar, 'VEVENT', array_merge([
1023
-			'UID' => 'uid-1234',
1024
-			'SEQUENCE' => 1,
1025
-			'SUMMARY' => 'Fellowship meeting',
1026
-			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
1027
-		], []));
1028
-		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
1029
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
1030
-		$message->message = $newVCalendar;
1031
-		$message->sender = 'mailto:[email protected]';
1032
-		$message->recipient = 'mailto:' . '[email protected]';
1033
-		$data = ['invitee_name' => 'Mr. Wizard',
1034
-			'meeting_title' => 'Fellowship meeting',
1035
-			'attendee_name' => '[email protected]'
1036
-		];
1037
-		$attendees = $newVevent->select('ATTENDEE');
1038
-		$atnd = '';
1039
-		foreach ($attendees as $attendee) {
1040
-			if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
1041
-				$atnd = $attendee;
1042
-			}
1043
-		}
1044
-		$this->service->expects(self::once())
1045
-			->method('getLastOccurrence')
1046
-			->willReturn(1496912700);
1047
-		$this->mailer->expects(self::once())
1048
-			->method('validateMailAddress')
1049
-			->with('[email protected]')
1050
-			->willReturn(true);
1051
-		$this->eventComparisonService->expects(self::once())
1052
-			->method('findModified')
1053
-			->with($newVCalendar, null)
1054
-			->willReturn(['old' => [] ,'new' => [$newVevent]]);
1055
-		$this->service->expects(self::once())
1056
-			->method('getCurrentAttendee')
1057
-			->with($message)
1058
-			->willReturn($atnd);
1059
-		$this->service->expects(self::once())
1060
-			->method('isRoomOrResource')
1061
-			->with($atnd)
1062
-			->willReturn(false);
1063
-		$this->service->expects(self::once())
1064
-			->method('isCircle')
1065
-			->with($atnd)
1066
-			->willReturn(false);
1067
-		$this->service->expects(self::once())
1068
-			->method('buildBodyData')
1069
-			->with($newVevent, null)
1070
-			->willReturn($data);
1071
-		$this->user->expects(self::any())
1072
-			->method('getUID')
1073
-			->willReturn('user1');
1074
-		$this->user->expects(self::any())
1075
-			->method('getDisplayName')
1076
-			->willReturn('Mr. Wizard');
1077
-		$this->userSession->expects(self::any())
1078
-			->method('getUser')
1079
-			->willReturn($this->user);
1080
-		$this->service->expects(self::once())
1081
-			->method('getFrom');
1082
-		$this->service->expects(self::once())
1083
-			->method('addSubjectAndHeading')
1084
-			->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting', false);
1085
-		$this->service->expects(self::once())
1086
-			->method('addBulletList')
1087
-			->with($this->emailTemplate, $newVevent, $data);
1088
-		$this->service->expects(self::once())
1089
-			->method('getAttendeeRsvpOrReqForParticipant')
1090
-			->willReturn(true);
1091
-		$this->config->expects(self::once())
1092
-			->method('getValueString')
1093
-			->with('dav', 'invitation_link_recipients', 'yes')
1094
-			->willReturn('no');
1095
-		$this->service->expects(self::never())
1096
-			->method('createInvitationToken');
1097
-		$this->service->expects(self::never())
1098
-			->method('addResponseButtons');
1099
-		$this->service->expects(self::never())
1100
-			->method('addMoreOptionsButton');
1101
-		$this->mailer->expects(self::once())
1102
-			->method('send')
1103
-			->willReturn([]);
1104
-		$this->mailer
1105
-			->method('send')
1106
-			->willReturn([]);
1107
-		$this->plugin->schedule($message);
1108
-		$this->assertEquals('1.1', $message->getScheduleStatus());
1109
-	}
1018
+    public function testNoButtons(): void {
1019
+        $message = new Message();
1020
+        $message->method = 'REQUEST';
1021
+        $newVCalendar = new VCalendar();
1022
+        $newVevent = new VEvent($newVCalendar, 'VEVENT', array_merge([
1023
+            'UID' => 'uid-1234',
1024
+            'SEQUENCE' => 1,
1025
+            'SUMMARY' => 'Fellowship meeting',
1026
+            'DTSTART' => new \DateTime('2016-01-01 00:00:00')
1027
+        ], []));
1028
+        $newVevent->add('ORGANIZER', 'mailto:[email protected]');
1029
+        $newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
1030
+        $message->message = $newVCalendar;
1031
+        $message->sender = 'mailto:[email protected]';
1032
+        $message->recipient = 'mailto:' . '[email protected]';
1033
+        $data = ['invitee_name' => 'Mr. Wizard',
1034
+            'meeting_title' => 'Fellowship meeting',
1035
+            'attendee_name' => '[email protected]'
1036
+        ];
1037
+        $attendees = $newVevent->select('ATTENDEE');
1038
+        $atnd = '';
1039
+        foreach ($attendees as $attendee) {
1040
+            if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
1041
+                $atnd = $attendee;
1042
+            }
1043
+        }
1044
+        $this->service->expects(self::once())
1045
+            ->method('getLastOccurrence')
1046
+            ->willReturn(1496912700);
1047
+        $this->mailer->expects(self::once())
1048
+            ->method('validateMailAddress')
1049
+            ->with('[email protected]')
1050
+            ->willReturn(true);
1051
+        $this->eventComparisonService->expects(self::once())
1052
+            ->method('findModified')
1053
+            ->with($newVCalendar, null)
1054
+            ->willReturn(['old' => [] ,'new' => [$newVevent]]);
1055
+        $this->service->expects(self::once())
1056
+            ->method('getCurrentAttendee')
1057
+            ->with($message)
1058
+            ->willReturn($atnd);
1059
+        $this->service->expects(self::once())
1060
+            ->method('isRoomOrResource')
1061
+            ->with($atnd)
1062
+            ->willReturn(false);
1063
+        $this->service->expects(self::once())
1064
+            ->method('isCircle')
1065
+            ->with($atnd)
1066
+            ->willReturn(false);
1067
+        $this->service->expects(self::once())
1068
+            ->method('buildBodyData')
1069
+            ->with($newVevent, null)
1070
+            ->willReturn($data);
1071
+        $this->user->expects(self::any())
1072
+            ->method('getUID')
1073
+            ->willReturn('user1');
1074
+        $this->user->expects(self::any())
1075
+            ->method('getDisplayName')
1076
+            ->willReturn('Mr. Wizard');
1077
+        $this->userSession->expects(self::any())
1078
+            ->method('getUser')
1079
+            ->willReturn($this->user);
1080
+        $this->service->expects(self::once())
1081
+            ->method('getFrom');
1082
+        $this->service->expects(self::once())
1083
+            ->method('addSubjectAndHeading')
1084
+            ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting', false);
1085
+        $this->service->expects(self::once())
1086
+            ->method('addBulletList')
1087
+            ->with($this->emailTemplate, $newVevent, $data);
1088
+        $this->service->expects(self::once())
1089
+            ->method('getAttendeeRsvpOrReqForParticipant')
1090
+            ->willReturn(true);
1091
+        $this->config->expects(self::once())
1092
+            ->method('getValueString')
1093
+            ->with('dav', 'invitation_link_recipients', 'yes')
1094
+            ->willReturn('no');
1095
+        $this->service->expects(self::never())
1096
+            ->method('createInvitationToken');
1097
+        $this->service->expects(self::never())
1098
+            ->method('addResponseButtons');
1099
+        $this->service->expects(self::never())
1100
+            ->method('addMoreOptionsButton');
1101
+        $this->mailer->expects(self::once())
1102
+            ->method('send')
1103
+            ->willReturn([]);
1104
+        $this->mailer
1105
+            ->method('send')
1106
+            ->willReturn([]);
1107
+        $this->plugin->schedule($message);
1108
+        $this->assertEquals('1.1', $message->getScheduleStatus());
1109
+    }
1110 1110
 }
Please login to merge, or discard this patch.
Spacing   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -151,10 +151,10 @@  discard block
 block discarded – undo
151 151
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
152 152
 		], []));
153 153
 		$message->message->VEVENT->add('ORGANIZER', 'mailto:[email protected]');
154
-		$message->message->VEVENT->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
154
+		$message->message->VEVENT->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE']);
155 155
 		$message->sender = 'mailto:[email protected]';
156 156
 		$message->senderName = 'Mr. Wizard';
157
-		$message->recipient = 'mailto:' . '[email protected]';
157
+		$message->recipient = 'mailto:'.'[email protected]';
158 158
 		$message->significantChange = false;
159 159
 		$this->plugin->schedule($message);
160 160
 		$this->assertEquals('1.0', $message->getScheduleStatus());
@@ -171,11 +171,11 @@  discard block
 block discarded – undo
171 171
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
172 172
 		], []));
173 173
 		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
174
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
174
+		$newVevent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
175 175
 		$message->message = $newVCalendar;
176 176
 		$message->sender = 'mailto:[email protected]';
177 177
 		$message->senderName = 'Mr. Wizard';
178
-		$message->recipient = 'mailto:' . '[email protected]';
178
+		$message->recipient = 'mailto:'.'[email protected]';
179 179
 		// save the old copy in the plugin
180 180
 		$oldVCalendar = new VCalendar();
181 181
 		$oldVEvent = new VEvent($oldVCalendar, 'one', [
@@ -185,8 +185,8 @@  discard block
 block discarded – undo
185 185
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
186 186
 		]);
187 187
 		$oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
188
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
189
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
188
+		$oldVEvent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
189
+		$oldVEvent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE']);
190 190
 		$oldVCalendar->add($oldVEvent);
191 191
 		$data = ['invitee_name' => 'Mr. Wizard',
192 192
 			'meeting_title' => 'Fellowship meeting without (!) Boromir',
@@ -278,11 +278,11 @@  discard block
 block discarded – undo
278 278
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
279 279
 		], []));
280 280
 		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
281
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'The Shire', 'CUTYPE' => 'ROOM']);
281
+		$newVevent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'The Shire', 'CUTYPE' => 'ROOM']);
282 282
 		$message->message = $newVCalendar;
283 283
 		$message->sender = 'mailto:[email protected]';
284 284
 		$message->senderName = 'Mr. Wizard';
285
-		$message->recipient = 'mailto:' . '[email protected]';
285
+		$message->recipient = 'mailto:'.'[email protected]';
286 286
 		// save the old copy in the plugin
287 287
 		$oldVCalendar = new VCalendar();
288 288
 		$oldVEvent = new VEvent($oldVCalendar, 'one', [
@@ -292,8 +292,8 @@  discard block
 block discarded – undo
292 292
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
293 293
 		]);
294 294
 		$oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
295
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'The Shire', 'CUTYPE' => 'ROOM']);
296
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
295
+		$oldVEvent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'The Shire', 'CUTYPE' => 'ROOM']);
296
+		$oldVEvent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE']);
297 297
 		$oldVCalendar->add($oldVEvent);
298 298
 		$data = ['invitee_name' => 'Mr. Wizard',
299 299
 			'meeting_title' => 'Fellowship meeting without (!) Boromir',
@@ -371,12 +371,12 @@  discard block
 block discarded – undo
371 371
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
372 372
 		], []));
373 373
 		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
374
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'The Fellowship', 'CUTYPE' => 'GROUP']);
375
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'MEMBER' => '[email protected]']);
374
+		$newVevent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'The Fellowship', 'CUTYPE' => 'GROUP']);
375
+		$newVevent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'MEMBER' => '[email protected]']);
376 376
 		$message->message = $newVCalendar;
377 377
 		$message->sender = 'mailto:[email protected]';
378 378
 		$message->senderName = 'Mr. Wizard';
379
-		$message->recipient = 'mailto:' . '[email protected]';
379
+		$message->recipient = 'mailto:'.'[email protected]';
380 380
 		$attendees = $newVevent->select('ATTENDEE');
381 381
 		$circle = '';
382 382
 		foreach ($attendees as $attendee) {
@@ -453,7 +453,7 @@  discard block
 block discarded – undo
453 453
 			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z'
454 454
 		]);
455 455
 		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
456
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
456
+		$newVevent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
457 457
 		$newvEvent2 = new VEvent($newVCalendar, 'two', [
458 458
 			'UID' => 'uid-1234',
459 459
 			'SEQUENCE' => 1,
@@ -462,10 +462,10 @@  discard block
 block discarded – undo
462 462
 			'RECURRENCE-ID' => new \DateTime('2016-01-01 00:00:00')
463 463
 		]);
464 464
 		$newvEvent2->add('ORGANIZER', 'mailto:[email protected]');
465
-		$newvEvent2->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
465
+		$newvEvent2->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
466 466
 		$message->message = $newVCalendar;
467 467
 		$message->sender = 'mailto:[email protected]';
468
-		$message->recipient = 'mailto:' . '[email protected]';
468
+		$message->recipient = 'mailto:'.'[email protected]';
469 469
 		// save the old copy in the plugin
470 470
 		$oldVCalendar = new VCalendar();
471 471
 		$oldVEvent = new VEvent($oldVCalendar, 'one', [
@@ -477,7 +477,7 @@  discard block
 block discarded – undo
477 477
 			'RRULE' => 'FREQ=DAILY;INTERVAL=1;UNTIL=20160201T000000Z'
478 478
 		]);
479 479
 		$oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
480
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
480
+		$oldVEvent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
481 481
 		$data = ['invitee_name' => 'Mr. Wizard',
482 482
 			'meeting_title' => 'Elevenses',
483 483
 			'attendee_name' => '[email protected]'
@@ -499,7 +499,7 @@  discard block
 block discarded – undo
499 499
 			->willReturn(true);
500 500
 		$this->eventComparisonService->expects(self::once())
501 501
 			->method('findModified')
502
-			->willReturn(['old' => [] ,'new' => [$newVevent]]);
502
+			->willReturn(['old' => [], 'new' => [$newVevent]]);
503 503
 		$this->service->expects(self::once())
504 504
 			->method('getCurrentAttendee')
505 505
 			->with($message)
@@ -568,10 +568,10 @@  discard block
 block discarded – undo
568 568
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
569 569
 		], []));
570 570
 		$message->message->VEVENT->add('ORGANIZER', 'mailto:[email protected]');
571
-		$message->message->VEVENT->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
571
+		$message->message->VEVENT->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE']);
572 572
 		$message->sender = 'mailto:[email protected]';
573 573
 		$message->senderName = 'Mr. Wizard';
574
-		$message->recipient = 'mailto:' . '[email protected]';
574
+		$message->recipient = 'mailto:'.'[email protected]';
575 575
 
576 576
 		$this->service->expects(self::once())
577 577
 			->method('getLastOccurrence')
@@ -596,11 +596,11 @@  discard block
 block discarded – undo
596 596
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
597 597
 		], []));
598 598
 		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
599
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
599
+		$newVevent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
600 600
 		$message->message = $newVcalendar;
601 601
 		$message->sender = 'mailto:[email protected]';
602 602
 		$message->senderName = 'Mr. Wizard';
603
-		$message->recipient = 'mailto:' . '[email protected]';
603
+		$message->recipient = 'mailto:'.'[email protected]';
604 604
 		// save the old copy in the plugin
605 605
 		$oldVcalendar = new VCalendar();
606 606
 		$oldVevent = new VEvent($oldVcalendar, 'one', [
@@ -610,8 +610,8 @@  discard block
 block discarded – undo
610 610
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
611 611
 		]);
612 612
 		$oldVevent->add('ORGANIZER', 'mailto:[email protected]');
613
-		$oldVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
614
-		$oldVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
613
+		$oldVevent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
614
+		$oldVevent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE']);
615 615
 		$oldVcalendar->add($oldVevent);
616 616
 		$data = ['invitee_name' => 'Mr. Wizard',
617 617
 			'meeting_title' => 'Fellowship meeting without (!) Boromir',
@@ -634,7 +634,7 @@  discard block
 block discarded – undo
634 634
 			->willReturn(true);
635 635
 		$this->eventComparisonService->expects(self::once())
636 636
 			->method('findModified')
637
-			->willReturn(['old' => [] ,'new' => [$newVevent]]);
637
+			->willReturn(['old' => [], 'new' => [$newVevent]]);
638 638
 		$this->service->expects(self::once())
639 639
 			->method('getCurrentAttendee')
640 640
 			->with($message)
@@ -709,11 +709,11 @@  discard block
 block discarded – undo
709 709
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
710 710
 		], []));
711 711
 		$event->add('ORGANIZER', 'mailto:[email protected]');
712
-		$event->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
712
+		$event->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
713 713
 		$message->message = $calendar;
714 714
 		$message->sender = 'mailto:[email protected]';
715 715
 		$message->senderName = 'Mr. Wizard';
716
-		$message->recipient = 'mailto:' . '[email protected]';
716
+		$message->recipient = 'mailto:'.'[email protected]';
717 717
 		// construct
718 718
 		foreach ($event->select('ATTENDEE') as $entry) {
719 719
 			if (strcasecmp($entry->getValue(), $message->recipient) === 0) {
@@ -784,7 +784,7 @@  discard block
 block discarded – undo
784 784
 			->with($this->emailTemplate, 'token');
785 785
 		$this->eventComparisonService->expects(self::once())
786 786
 			->method('findModified')
787
-			->willReturn(['old' => [] ,'new' => [$event]]);
787
+			->willReturn(['old' => [], 'new' => [$event]]);
788 788
 		// construct mail mock returns
789 789
 		$this->mailer->expects(self::once())
790 790
 			->method('validateMailAddress')
@@ -817,11 +817,11 @@  discard block
 block discarded – undo
817 817
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
818 818
 		], []));
819 819
 		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
820
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE',  'CN' => 'Frodo']);
820
+		$newVevent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
821 821
 		$message->message = $newVCalendar;
822 822
 		$message->sender = 'mailto:[email protected]';
823 823
 		$message->senderName = 'Mr. Wizard';
824
-		$message->recipient = 'mailto:' . '[email protected]';
824
+		$message->recipient = 'mailto:'.'[email protected]';
825 825
 		// save the old copy in the plugin
826 826
 		$oldVCalendar = new VCalendar();
827 827
 		$oldVEvent = new VEvent($oldVCalendar, 'one', [
@@ -831,8 +831,8 @@  discard block
 block discarded – undo
831 831
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
832 832
 		]);
833 833
 		$oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
834
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
835
-		$oldVEvent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE']);
834
+		$oldVEvent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
835
+		$oldVEvent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE']);
836 836
 		$oldVCalendar->add($oldVEvent);
837 837
 		$data = ['invitee_name' => 'Mr. Wizard',
838 838
 			'meeting_title' => 'Fellowship meeting without (!) Boromir',
@@ -928,11 +928,11 @@  discard block
 block discarded – undo
928 928
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
929 929
 		], []));
930 930
 		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
931
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
931
+		$newVevent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
932 932
 		$message->message = $newVCalendar;
933 933
 		$message->sender = 'mailto:[email protected]';
934 934
 		$message->senderName = 'Mr. Wizard';
935
-		$message->recipient = 'mailto:' . '[email protected]';
935
+		$message->recipient = 'mailto:'.'[email protected]';
936 936
 		$data = ['invitee_name' => 'Mr. Wizard',
937 937
 			'meeting_title' => 'Fellowship meeting',
938 938
 			'attendee_name' => '[email protected]'
@@ -954,7 +954,7 @@  discard block
 block discarded – undo
954 954
 		$this->eventComparisonService->expects(self::once())
955 955
 			->method('findModified')
956 956
 			->with($newVCalendar, null)
957
-			->willReturn(['old' => [] ,'new' => [$newVevent]]);
957
+			->willReturn(['old' => [], 'new' => [$newVevent]]);
958 958
 		$this->service->expects(self::once())
959 959
 			->method('getCurrentAttendee')
960 960
 			->with($message)
@@ -1026,10 +1026,10 @@  discard block
 block discarded – undo
1026 1026
 			'DTSTART' => new \DateTime('2016-01-01 00:00:00')
1027 1027
 		], []));
1028 1028
 		$newVevent->add('ORGANIZER', 'mailto:[email protected]');
1029
-		$newVevent->add('ATTENDEE', 'mailto:' . '[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
1029
+		$newVevent->add('ATTENDEE', 'mailto:'.'[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
1030 1030
 		$message->message = $newVCalendar;
1031 1031
 		$message->sender = 'mailto:[email protected]';
1032
-		$message->recipient = 'mailto:' . '[email protected]';
1032
+		$message->recipient = 'mailto:'.'[email protected]';
1033 1033
 		$data = ['invitee_name' => 'Mr. Wizard',
1034 1034
 			'meeting_title' => 'Fellowship meeting',
1035 1035
 			'attendee_name' => '[email protected]'
@@ -1051,7 +1051,7 @@  discard block
 block discarded – undo
1051 1051
 		$this->eventComparisonService->expects(self::once())
1052 1052
 			->method('findModified')
1053 1053
 			->with($newVCalendar, null)
1054
-			->willReturn(['old' => [] ,'new' => [$newVevent]]);
1054
+			->willReturn(['old' => [], 'new' => [$newVevent]]);
1055 1055
 		$this->service->expects(self::once())
1056 1056
 			->method('getCurrentAttendee')
1057 1057
 			->with($message)
Please login to merge, or discard this patch.