Completed
Push — master ( 4317bb...726852 )
by Daniel
20:00 queued 15s
created
lib/private/Calendar/Manager.php 2 patches
Indentation   +583 added lines, -583 removed lines patch added patch discarded remove patch
@@ -41,587 +41,587 @@
 block discarded – undo
41 41
 use function array_merge;
42 42
 
43 43
 class Manager implements IManager {
44
-	/**
45
-	 * @var ICalendar[] holds all registered calendars
46
-	 */
47
-	private array $calendars = [];
48
-
49
-	/**
50
-	 * @var \Closure[] to call to load/register calendar providers
51
-	 */
52
-	private array $calendarLoaders = [];
53
-
54
-	public function __construct(
55
-		private Coordinator $coordinator,
56
-		private ContainerInterface $container,
57
-		private LoggerInterface $logger,
58
-		private ITimeFactory $timeFactory,
59
-		private ISecureRandom $random,
60
-		private IUserManager $userManager,
61
-		private ServerFactory $serverFactory,
62
-	) {
63
-	}
64
-
65
-	/**
66
-	 * This function is used to search and find objects within the user's calendars.
67
-	 * In case $pattern is empty all events/journals/todos will be returned.
68
-	 *
69
-	 * @param string $pattern which should match within the $searchProperties
70
-	 * @param array $searchProperties defines the properties within the query pattern should match
71
-	 * @param array $options - optional parameters:
72
-	 *                       ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]]
73
-	 * @param integer|null $limit - limit number of search results
74
-	 * @param integer|null $offset - offset for paging of search results
75
-	 * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs
76
-	 * @since 13.0.0
77
-	 */
78
-	public function search(
79
-		$pattern,
80
-		array $searchProperties = [],
81
-		array $options = [],
82
-		$limit = null,
83
-		$offset = null,
84
-	): array {
85
-		$this->loadCalendars();
86
-		$result = [];
87
-		foreach ($this->calendars as $calendar) {
88
-			$r = $calendar->search($pattern, $searchProperties, $options, $limit, $offset);
89
-			foreach ($r as $o) {
90
-				$o['calendar-key'] = $calendar->getKey();
91
-				$result[] = $o;
92
-			}
93
-		}
94
-
95
-		return $result;
96
-	}
97
-
98
-	/**
99
-	 * Check if calendars are available
100
-	 *
101
-	 * @return bool true if enabled, false if not
102
-	 * @since 13.0.0
103
-	 */
104
-	public function isEnabled(): bool {
105
-		return !empty($this->calendars) || !empty($this->calendarLoaders);
106
-	}
107
-
108
-	/**
109
-	 * Registers a calendar
110
-	 *
111
-	 * @since 13.0.0
112
-	 */
113
-	public function registerCalendar(ICalendar $calendar): void {
114
-		$this->calendars[$calendar->getKey()] = $calendar;
115
-	}
116
-
117
-	/**
118
-	 * Unregisters a calendar
119
-	 *
120
-	 * @since 13.0.0
121
-	 */
122
-	public function unregisterCalendar(ICalendar $calendar): void {
123
-		unset($this->calendars[$calendar->getKey()]);
124
-	}
125
-
126
-	/**
127
-	 * In order to improve lazy loading a closure can be registered which will be called in case
128
-	 * calendars are actually requested
129
-	 *
130
-	 * @since 13.0.0
131
-	 */
132
-	public function register(\Closure $callable): void {
133
-		$this->calendarLoaders[] = $callable;
134
-	}
135
-
136
-	/**
137
-	 * @return ICalendar[]
138
-	 *
139
-	 * @since 13.0.0
140
-	 */
141
-	public function getCalendars(): array {
142
-		$this->loadCalendars();
143
-
144
-		return array_values($this->calendars);
145
-	}
146
-
147
-	/**
148
-	 * removes all registered calendar instances
149
-	 *
150
-	 * @since 13.0.0
151
-	 */
152
-	public function clear(): void {
153
-		$this->calendars = [];
154
-		$this->calendarLoaders = [];
155
-	}
156
-
157
-	/**
158
-	 * loads all calendars
159
-	 */
160
-	private function loadCalendars(): void {
161
-		foreach ($this->calendarLoaders as $callable) {
162
-			$callable($this);
163
-		}
164
-		$this->calendarLoaders = [];
165
-	}
166
-
167
-	/**
168
-	 * @return ICreateFromString[]
169
-	 */
170
-	public function getCalendarsForPrincipal(string $principalUri, array $calendarUris = []): array {
171
-		$context = $this->coordinator->getRegistrationContext();
172
-		if ($context === null) {
173
-			return [];
174
-		}
175
-
176
-		return array_merge(
177
-			...array_map(function ($registration) use ($principalUri, $calendarUris) {
178
-				try {
179
-					/** @var ICalendarProvider $provider */
180
-					$provider = $this->container->get($registration->getService());
181
-				} catch (Throwable $e) {
182
-					$this->logger->error('Could not load calendar provider ' . $registration->getService() . ': ' . $e->getMessage(), [
183
-						'exception' => $e,
184
-					]);
185
-					return [];
186
-				}
187
-
188
-				return $provider->getCalendars($principalUri, $calendarUris);
189
-			}, $context->getCalendarProviders())
190
-		);
191
-	}
192
-
193
-	public function searchForPrincipal(ICalendarQuery $query): array {
194
-		/** @var CalendarQuery $query */
195
-		$calendars = $this->getCalendarsForPrincipal(
196
-			$query->getPrincipalUri(),
197
-			$query->getCalendarUris(),
198
-		);
199
-
200
-		$results = [];
201
-		foreach ($calendars as $calendar) {
202
-			$r = $calendar->search(
203
-				$query->getSearchPattern() ?? '',
204
-				$query->getSearchProperties(),
205
-				$query->getOptions(),
206
-				$query->getLimit(),
207
-				$query->getOffset()
208
-			);
209
-
210
-			foreach ($r as $o) {
211
-				$o['calendar-key'] = $calendar->getKey();
212
-				$o['calendar-uri'] = $calendar->getUri();
213
-				$results[] = $o;
214
-			}
215
-		}
216
-		return $results;
217
-	}
218
-
219
-	public function newQuery(string $principalUri): ICalendarQuery {
220
-		return new CalendarQuery($principalUri);
221
-	}
222
-
223
-	/**
224
-	 * @since 31.0.0
225
-	 * @throws \OCP\DB\Exception
226
-	 */
227
-	public function handleIMipRequest(
228
-		string $principalUri,
229
-		string $sender,
230
-		string $recipient,
231
-		string $calendarData,
232
-	): bool {
233
-
234
-		$userCalendars = $this->getCalendarsForPrincipal($principalUri);
235
-		if (empty($userCalendars)) {
236
-			$this->logger->warning('iMip message could not be processed because user has no calendars');
237
-			return false;
238
-		}
239
-
240
-		try {
241
-			/** @var VCalendar $vObject|null */
242
-			$calendarObject = Reader::read($calendarData);
243
-		} catch (ParseException $e) {
244
-			$this->logger->error('iMip message could not be processed because an error occurred while parsing the iMip message', ['exception' => $e]);
245
-			return false;
246
-		}
247
-
248
-		if (!isset($calendarObject->METHOD) || $calendarObject->METHOD->getValue() !== 'REQUEST') {
249
-			$this->logger->warning('iMip message contains an incorrect or invalid method');
250
-			return false;
251
-		}
252
-
253
-		if (!isset($calendarObject->VEVENT)) {
254
-			$this->logger->warning('iMip message contains no event');
255
-			return false;
256
-		}
257
-
258
-		/** @var VEvent|null $vEvent */
259
-		$eventObject = $calendarObject->VEVENT;
260
-
261
-		if (!isset($eventObject->UID)) {
262
-			$this->logger->warning('iMip message event dose not contains a UID');
263
-			return false;
264
-		}
265
-
266
-		if (!isset($eventObject->ORGANIZER)) {
267
-			$this->logger->warning('iMip message event dose not contains an organizer');
268
-			return false;
269
-		}
270
-
271
-		if (!isset($eventObject->ATTENDEE)) {
272
-			$this->logger->warning('iMip message event dose not contains any attendees');
273
-			return false;
274
-		}
275
-
276
-		foreach ($eventObject->ATTENDEE as $entry) {
277
-			$address = trim(str_replace('mailto:', '', $entry->getValue()));
278
-			if ($address === $recipient) {
279
-				$attendee = $address;
280
-				break;
281
-			}
282
-		}
283
-		if (!isset($attendee)) {
284
-			$this->logger->warning('iMip message event does not contain a attendee that matches the recipient');
285
-			return false;
286
-		}
287
-
288
-		foreach ($userCalendars as $calendar) {
289
-
290
-			if (!$calendar instanceof ICalendarIsWritable && !$calendar instanceof ICalendarIsShared) {
291
-				continue;
292
-			}
293
-
294
-			if ($calendar->isDeleted() || !$calendar->isWritable() || $calendar->isShared()) {
295
-				continue;
296
-			}
297
-
298
-			if (!empty($calendar->search($recipient, ['ATTENDEE'], ['uid' => $eventObject->UID->getValue()]))) {
299
-				try {
300
-					if ($calendar instanceof IHandleImipMessage) {
301
-						$calendar->handleIMipMessage('', $calendarData);
302
-					}
303
-					return true;
304
-				} catch (CalendarException $e) {
305
-					$this->logger->error('An error occurred while processing the iMip message event', ['exception' => $e]);
306
-					return false;
307
-				}
308
-			}
309
-		}
310
-
311
-		$this->logger->warning('iMip message event could not be processed because no corresponding event was found in any calendar');
312
-		return false;
313
-	}
314
-
315
-	/**
316
-	 * @throws \OCP\DB\Exception
317
-	 */
318
-	public function handleIMipReply(
319
-		string $principalUri,
320
-		string $sender,
321
-		string $recipient,
322
-		string $calendarData,
323
-	): bool {
324
-
325
-		$calendars = $this->getCalendarsForPrincipal($principalUri);
326
-		if (empty($calendars)) {
327
-			$this->logger->warning('iMip message could not be processed because user has no calendars');
328
-			return false;
329
-		}
330
-
331
-		try {
332
-			/** @var VCalendar $vObject|null */
333
-			$vObject = Reader::read($calendarData);
334
-		} catch (ParseException $e) {
335
-			$this->logger->error('iMip message could not be processed because an error occurred while parsing the iMip message', ['exception' => $e]);
336
-			return false;
337
-		}
338
-
339
-		if ($vObject === null) {
340
-			$this->logger->warning('iMip message contains an invalid calendar object');
341
-			return false;
342
-		}
343
-
344
-		if (!isset($vObject->METHOD) || $vObject->METHOD->getValue() !== 'REPLY') {
345
-			$this->logger->warning('iMip message contains an incorrect or invalid method');
346
-			return false;
347
-		}
348
-
349
-		if (!isset($vObject->VEVENT)) {
350
-			$this->logger->warning('iMip message contains no event');
351
-			return false;
352
-		}
353
-
354
-		/** @var VEvent|null $vEvent */
355
-		$vEvent = $vObject->VEVENT;
356
-
357
-		if (!isset($vEvent->UID)) {
358
-			$this->logger->warning('iMip message event dose not contains a UID');
359
-			return false;
360
-		}
361
-
362
-		if (!isset($vEvent->ORGANIZER)) {
363
-			$this->logger->warning('iMip message event dose not contains an organizer');
364
-			return false;
365
-		}
366
-
367
-		if (!isset($vEvent->ATTENDEE)) {
368
-			$this->logger->warning('iMip message event dose not contains any attendees');
369
-			return false;
370
-		}
371
-
372
-		// check if mail recipient and organizer are one and the same
373
-		$organizer = substr($vEvent->{'ORGANIZER'}->getValue(), 7);
374
-
375
-		if (strcasecmp($recipient, $organizer) !== 0) {
376
-			$this->logger->warning('iMip message event could not be processed because recipient and ORGANIZER must be identical');
377
-			return false;
378
-		}
379
-
380
-		//check if the event is in the future
381
-		/** @var DateTime $eventTime */
382
-		$eventTime = $vEvent->{'DTSTART'};
383
-		if ($eventTime->getDateTime()->getTimeStamp() < $this->timeFactory->getTime()) { // this might cause issues with recurrences
384
-			$this->logger->warning('iMip message event could not be processed because the event is in the past');
385
-			return false;
386
-		}
387
-
388
-		$found = null;
389
-		// if the attendee has been found in at least one calendar event with the UID of the iMIP event
390
-		// we process it.
391
-		// Benefit: no attendee lost
392
-		// Drawback: attendees that have been deleted will still be able to update their partstat
393
-		foreach ($calendars as $calendar) {
394
-			// We should not search in writable calendars
395
-			if ($calendar instanceof IHandleImipMessage) {
396
-				$o = $calendar->search($sender, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
397
-				if (!empty($o)) {
398
-					$found = $calendar;
399
-					$name = $o[0]['uri'];
400
-					break;
401
-				}
402
-			}
403
-		}
404
-
405
-		if (empty($found)) {
406
-			$this->logger->warning('iMip message event could not be processed because no corresponding event was found in any calendar', [
407
-				'principalUri' => $principalUri,
408
-				'eventUid' => $vEvent->{'UID'}->getValue(),
409
-			]);
410
-			return false;
411
-		}
412
-
413
-		try {
414
-			$found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes
415
-		} catch (CalendarException $e) {
416
-			$this->logger->error('An error occurred while processing the iMip message event', ['exception' => $e]);
417
-			return false;
418
-		}
419
-		return true;
420
-	}
421
-
422
-	/**
423
-	 * @since 25.0.0
424
-	 * @throws \OCP\DB\Exception
425
-	 */
426
-	public function handleIMipCancel(
427
-		string $principalUri,
428
-		string $sender,
429
-		?string $replyTo,
430
-		string $recipient,
431
-		string $calendarData,
432
-	): bool {
433
-
434
-		$calendars = $this->getCalendarsForPrincipal($principalUri);
435
-		if (empty($calendars)) {
436
-			$this->logger->warning('iMip message could not be processed because user has no calendars');
437
-			return false;
438
-		}
439
-
440
-		try {
441
-			/** @var VCalendar $vObject|null */
442
-			$vObject = Reader::read($calendarData);
443
-		} catch (ParseException $e) {
444
-			$this->logger->error('iMip message could not be processed because an error occurred while parsing the iMip message', ['exception' => $e]);
445
-			return false;
446
-		}
447
-
448
-		if ($vObject === null) {
449
-			$this->logger->warning('iMip message contains an invalid calendar object');
450
-			return false;
451
-		}
452
-
453
-		if (!isset($vObject->METHOD) || $vObject->METHOD->getValue() !== 'CANCEL') {
454
-			$this->logger->warning('iMip message contains an incorrect or invalid method');
455
-			return false;
456
-		}
457
-
458
-		if (!isset($vObject->VEVENT)) {
459
-			$this->logger->warning('iMip message contains no event');
460
-			return false;
461
-		}
462
-
463
-		/** @var VEvent|null $vEvent */
464
-		$vEvent = $vObject->{'VEVENT'};
465
-
466
-		if (!isset($vEvent->UID)) {
467
-			$this->logger->warning('iMip message event dose not contains a UID');
468
-			return false;
469
-		}
470
-
471
-		if (!isset($vEvent->ORGANIZER)) {
472
-			$this->logger->warning('iMip message event dose not contains an organizer');
473
-			return false;
474
-		}
475
-
476
-		if (!isset($vEvent->ATTENDEE)) {
477
-			$this->logger->warning('iMip message event dose not contains any attendees');
478
-			return false;
479
-		}
480
-
481
-		$attendee = substr($vEvent->{'ATTENDEE'}->getValue(), 7);
482
-		if (strcasecmp($recipient, $attendee) !== 0) {
483
-			$this->logger->warning('iMip message event could not be processed because recipient must be an ATTENDEE of this event');
484
-			return false;
485
-		}
486
-
487
-		// Thirdly, we need to compare the email address the CANCEL is coming from (in Mail)
488
-		// or the Reply- To Address submitted with the CANCEL email
489
-		// to the email address in the ORGANIZER.
490
-		// We don't want to accept a CANCEL request from just anyone
491
-		$organizer = substr($vEvent->{'ORGANIZER'}->getValue(), 7);
492
-		$isNotOrganizer = ($replyTo !== null) ? (strcasecmp($sender, $organizer) !== 0 && strcasecmp($replyTo, $organizer) !== 0) : (strcasecmp($sender, $organizer) !== 0);
493
-		if ($isNotOrganizer) {
494
-			$this->logger->warning('iMip message event could not be processed because sender must be the ORGANIZER of this event');
495
-			return false;
496
-		}
497
-
498
-		//check if the event is in the future
499
-		/** @var DateTime $eventTime */
500
-		$eventTime = $vEvent->{'DTSTART'};
501
-		if ($eventTime->getDateTime()->getTimeStamp() < $this->timeFactory->getTime()) { // this might cause issues with recurrences
502
-			$this->logger->warning('iMip message event could not be processed because the event is in the past');
503
-			return false;
504
-		}
505
-
506
-		$found = null;
507
-		// if the attendee has been found in at least one calendar event with the UID of the iMIP event
508
-		// we process it.
509
-		// Benefit: no attendee lost
510
-		// Drawback: attendees that have been deleted will still be able to update their partstat
511
-		foreach ($calendars as $calendar) {
512
-			// We should not search in writable calendars
513
-			if ($calendar instanceof IHandleImipMessage) {
514
-				$o = $calendar->search($recipient, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
515
-				if (!empty($o)) {
516
-					$found = $calendar;
517
-					$name = $o[0]['uri'];
518
-					break;
519
-				}
520
-			}
521
-		}
522
-
523
-		if (empty($found)) {
524
-			$this->logger->warning('iMip message event could not be processed because no corresponding event was found in any calendar', [
525
-				'principalUri' => $principalUri,
526
-				'eventUid' => $vEvent->{'UID'}->getValue(),
527
-			]);
528
-			return false;
529
-		}
530
-
531
-		try {
532
-			$found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes
533
-			return true;
534
-		} catch (CalendarException $e) {
535
-			$this->logger->error('An error occurred while processing the iMip message event', ['exception' => $e]);
536
-			return false;
537
-		}
538
-	}
539
-
540
-	public function createEventBuilder(): ICalendarEventBuilder {
541
-		$uid = $this->random->generate(32, ISecureRandom::CHAR_ALPHANUMERIC);
542
-		return new CalendarEventBuilder($uid, $this->timeFactory);
543
-	}
544
-
545
-	public function checkAvailability(
546
-		DateTimeInterface $start,
547
-		DateTimeInterface $end,
548
-		IUser $organizer,
549
-		array $attendees,
550
-	): array {
551
-		$organizerMailto = 'mailto:' . $organizer->getEMailAddress();
552
-		$request = new VCalendar();
553
-		$request->METHOD = 'REQUEST';
554
-		$request->add('VFREEBUSY', [
555
-			'DTSTART' => $start,
556
-			'DTEND' => $end,
557
-			'ORGANIZER' => $organizerMailto,
558
-			'ATTENDEE' => $organizerMailto,
559
-		]);
560
-
561
-		$mailtoLen = strlen('mailto:');
562
-		foreach ($attendees as $attendee) {
563
-			if (str_starts_with($attendee, 'mailto:')) {
564
-				$attendee = substr($attendee, $mailtoLen);
565
-			}
566
-
567
-			$attendeeUsers = $this->userManager->getByEmail($attendee);
568
-			if ($attendeeUsers === []) {
569
-				continue;
570
-			}
571
-
572
-			$request->VFREEBUSY->add('ATTENDEE', "mailto:$attendee");
573
-		}
574
-
575
-		$organizerUid = $organizer->getUID();
576
-		$server = $this->serverFactory->createAttendeeAvailabilityServer();
577
-		/** @var CustomPrincipalPlugin $plugin */
578
-		$plugin = $server->getPlugin('auth');
579
-		$plugin->setCurrentPrincipal("principals/users/$organizerUid");
580
-
581
-		$request = new Request(
582
-			'POST',
583
-			"/calendars/$organizerUid/outbox/",
584
-			[
585
-				'Content-Type' => 'text/calendar',
586
-				'Depth' => 0,
587
-			],
588
-			$request->serialize(),
589
-		);
590
-		$response = new Response();
591
-		$server->invokeMethod($request, $response, false);
592
-
593
-		$xmlService = new \Sabre\Xml\Service();
594
-		$xmlService->elementMap = [
595
-			'{urn:ietf:params:xml:ns:caldav}response' => 'Sabre\Xml\Deserializer\keyValue',
596
-			'{urn:ietf:params:xml:ns:caldav}recipient' => 'Sabre\Xml\Deserializer\keyValue',
597
-		];
598
-		$parsedResponse = $xmlService->parse($response->getBodyAsString());
599
-
600
-		$result = [];
601
-		foreach ($parsedResponse as $freeBusyResponse) {
602
-			$freeBusyResponse = $freeBusyResponse['value'];
603
-			if ($freeBusyResponse['{urn:ietf:params:xml:ns:caldav}request-status'] !== '2.0;Success') {
604
-				continue;
605
-			}
606
-
607
-			$freeBusyResponseData = \Sabre\VObject\Reader::read(
608
-				$freeBusyResponse['{urn:ietf:params:xml:ns:caldav}calendar-data']
609
-			);
610
-
611
-			$attendee = substr(
612
-				$freeBusyResponse['{urn:ietf:params:xml:ns:caldav}recipient']['{DAV:}href'],
613
-				$mailtoLen,
614
-			);
615
-
616
-			$vFreeBusy = $freeBusyResponseData->VFREEBUSY;
617
-			if (!($vFreeBusy instanceof VFreeBusy)) {
618
-				continue;
619
-			}
620
-
621
-			// TODO: actually check values of FREEBUSY properties to find a free slot
622
-			$result[] = new AvailabilityResult($attendee, $vFreeBusy->isFree($start, $end));
623
-		}
624
-
625
-		return $result;
626
-	}
44
+    /**
45
+     * @var ICalendar[] holds all registered calendars
46
+     */
47
+    private array $calendars = [];
48
+
49
+    /**
50
+     * @var \Closure[] to call to load/register calendar providers
51
+     */
52
+    private array $calendarLoaders = [];
53
+
54
+    public function __construct(
55
+        private Coordinator $coordinator,
56
+        private ContainerInterface $container,
57
+        private LoggerInterface $logger,
58
+        private ITimeFactory $timeFactory,
59
+        private ISecureRandom $random,
60
+        private IUserManager $userManager,
61
+        private ServerFactory $serverFactory,
62
+    ) {
63
+    }
64
+
65
+    /**
66
+     * This function is used to search and find objects within the user's calendars.
67
+     * In case $pattern is empty all events/journals/todos will be returned.
68
+     *
69
+     * @param string $pattern which should match within the $searchProperties
70
+     * @param array $searchProperties defines the properties within the query pattern should match
71
+     * @param array $options - optional parameters:
72
+     *                       ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]]
73
+     * @param integer|null $limit - limit number of search results
74
+     * @param integer|null $offset - offset for paging of search results
75
+     * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs
76
+     * @since 13.0.0
77
+     */
78
+    public function search(
79
+        $pattern,
80
+        array $searchProperties = [],
81
+        array $options = [],
82
+        $limit = null,
83
+        $offset = null,
84
+    ): array {
85
+        $this->loadCalendars();
86
+        $result = [];
87
+        foreach ($this->calendars as $calendar) {
88
+            $r = $calendar->search($pattern, $searchProperties, $options, $limit, $offset);
89
+            foreach ($r as $o) {
90
+                $o['calendar-key'] = $calendar->getKey();
91
+                $result[] = $o;
92
+            }
93
+        }
94
+
95
+        return $result;
96
+    }
97
+
98
+    /**
99
+     * Check if calendars are available
100
+     *
101
+     * @return bool true if enabled, false if not
102
+     * @since 13.0.0
103
+     */
104
+    public function isEnabled(): bool {
105
+        return !empty($this->calendars) || !empty($this->calendarLoaders);
106
+    }
107
+
108
+    /**
109
+     * Registers a calendar
110
+     *
111
+     * @since 13.0.0
112
+     */
113
+    public function registerCalendar(ICalendar $calendar): void {
114
+        $this->calendars[$calendar->getKey()] = $calendar;
115
+    }
116
+
117
+    /**
118
+     * Unregisters a calendar
119
+     *
120
+     * @since 13.0.0
121
+     */
122
+    public function unregisterCalendar(ICalendar $calendar): void {
123
+        unset($this->calendars[$calendar->getKey()]);
124
+    }
125
+
126
+    /**
127
+     * In order to improve lazy loading a closure can be registered which will be called in case
128
+     * calendars are actually requested
129
+     *
130
+     * @since 13.0.0
131
+     */
132
+    public function register(\Closure $callable): void {
133
+        $this->calendarLoaders[] = $callable;
134
+    }
135
+
136
+    /**
137
+     * @return ICalendar[]
138
+     *
139
+     * @since 13.0.0
140
+     */
141
+    public function getCalendars(): array {
142
+        $this->loadCalendars();
143
+
144
+        return array_values($this->calendars);
145
+    }
146
+
147
+    /**
148
+     * removes all registered calendar instances
149
+     *
150
+     * @since 13.0.0
151
+     */
152
+    public function clear(): void {
153
+        $this->calendars = [];
154
+        $this->calendarLoaders = [];
155
+    }
156
+
157
+    /**
158
+     * loads all calendars
159
+     */
160
+    private function loadCalendars(): void {
161
+        foreach ($this->calendarLoaders as $callable) {
162
+            $callable($this);
163
+        }
164
+        $this->calendarLoaders = [];
165
+    }
166
+
167
+    /**
168
+     * @return ICreateFromString[]
169
+     */
170
+    public function getCalendarsForPrincipal(string $principalUri, array $calendarUris = []): array {
171
+        $context = $this->coordinator->getRegistrationContext();
172
+        if ($context === null) {
173
+            return [];
174
+        }
175
+
176
+        return array_merge(
177
+            ...array_map(function ($registration) use ($principalUri, $calendarUris) {
178
+                try {
179
+                    /** @var ICalendarProvider $provider */
180
+                    $provider = $this->container->get($registration->getService());
181
+                } catch (Throwable $e) {
182
+                    $this->logger->error('Could not load calendar provider ' . $registration->getService() . ': ' . $e->getMessage(), [
183
+                        'exception' => $e,
184
+                    ]);
185
+                    return [];
186
+                }
187
+
188
+                return $provider->getCalendars($principalUri, $calendarUris);
189
+            }, $context->getCalendarProviders())
190
+        );
191
+    }
192
+
193
+    public function searchForPrincipal(ICalendarQuery $query): array {
194
+        /** @var CalendarQuery $query */
195
+        $calendars = $this->getCalendarsForPrincipal(
196
+            $query->getPrincipalUri(),
197
+            $query->getCalendarUris(),
198
+        );
199
+
200
+        $results = [];
201
+        foreach ($calendars as $calendar) {
202
+            $r = $calendar->search(
203
+                $query->getSearchPattern() ?? '',
204
+                $query->getSearchProperties(),
205
+                $query->getOptions(),
206
+                $query->getLimit(),
207
+                $query->getOffset()
208
+            );
209
+
210
+            foreach ($r as $o) {
211
+                $o['calendar-key'] = $calendar->getKey();
212
+                $o['calendar-uri'] = $calendar->getUri();
213
+                $results[] = $o;
214
+            }
215
+        }
216
+        return $results;
217
+    }
218
+
219
+    public function newQuery(string $principalUri): ICalendarQuery {
220
+        return new CalendarQuery($principalUri);
221
+    }
222
+
223
+    /**
224
+     * @since 31.0.0
225
+     * @throws \OCP\DB\Exception
226
+     */
227
+    public function handleIMipRequest(
228
+        string $principalUri,
229
+        string $sender,
230
+        string $recipient,
231
+        string $calendarData,
232
+    ): bool {
233
+
234
+        $userCalendars = $this->getCalendarsForPrincipal($principalUri);
235
+        if (empty($userCalendars)) {
236
+            $this->logger->warning('iMip message could not be processed because user has no calendars');
237
+            return false;
238
+        }
239
+
240
+        try {
241
+            /** @var VCalendar $vObject|null */
242
+            $calendarObject = Reader::read($calendarData);
243
+        } catch (ParseException $e) {
244
+            $this->logger->error('iMip message could not be processed because an error occurred while parsing the iMip message', ['exception' => $e]);
245
+            return false;
246
+        }
247
+
248
+        if (!isset($calendarObject->METHOD) || $calendarObject->METHOD->getValue() !== 'REQUEST') {
249
+            $this->logger->warning('iMip message contains an incorrect or invalid method');
250
+            return false;
251
+        }
252
+
253
+        if (!isset($calendarObject->VEVENT)) {
254
+            $this->logger->warning('iMip message contains no event');
255
+            return false;
256
+        }
257
+
258
+        /** @var VEvent|null $vEvent */
259
+        $eventObject = $calendarObject->VEVENT;
260
+
261
+        if (!isset($eventObject->UID)) {
262
+            $this->logger->warning('iMip message event dose not contains a UID');
263
+            return false;
264
+        }
265
+
266
+        if (!isset($eventObject->ORGANIZER)) {
267
+            $this->logger->warning('iMip message event dose not contains an organizer');
268
+            return false;
269
+        }
270
+
271
+        if (!isset($eventObject->ATTENDEE)) {
272
+            $this->logger->warning('iMip message event dose not contains any attendees');
273
+            return false;
274
+        }
275
+
276
+        foreach ($eventObject->ATTENDEE as $entry) {
277
+            $address = trim(str_replace('mailto:', '', $entry->getValue()));
278
+            if ($address === $recipient) {
279
+                $attendee = $address;
280
+                break;
281
+            }
282
+        }
283
+        if (!isset($attendee)) {
284
+            $this->logger->warning('iMip message event does not contain a attendee that matches the recipient');
285
+            return false;
286
+        }
287
+
288
+        foreach ($userCalendars as $calendar) {
289
+
290
+            if (!$calendar instanceof ICalendarIsWritable && !$calendar instanceof ICalendarIsShared) {
291
+                continue;
292
+            }
293
+
294
+            if ($calendar->isDeleted() || !$calendar->isWritable() || $calendar->isShared()) {
295
+                continue;
296
+            }
297
+
298
+            if (!empty($calendar->search($recipient, ['ATTENDEE'], ['uid' => $eventObject->UID->getValue()]))) {
299
+                try {
300
+                    if ($calendar instanceof IHandleImipMessage) {
301
+                        $calendar->handleIMipMessage('', $calendarData);
302
+                    }
303
+                    return true;
304
+                } catch (CalendarException $e) {
305
+                    $this->logger->error('An error occurred while processing the iMip message event', ['exception' => $e]);
306
+                    return false;
307
+                }
308
+            }
309
+        }
310
+
311
+        $this->logger->warning('iMip message event could not be processed because no corresponding event was found in any calendar');
312
+        return false;
313
+    }
314
+
315
+    /**
316
+     * @throws \OCP\DB\Exception
317
+     */
318
+    public function handleIMipReply(
319
+        string $principalUri,
320
+        string $sender,
321
+        string $recipient,
322
+        string $calendarData,
323
+    ): bool {
324
+
325
+        $calendars = $this->getCalendarsForPrincipal($principalUri);
326
+        if (empty($calendars)) {
327
+            $this->logger->warning('iMip message could not be processed because user has no calendars');
328
+            return false;
329
+        }
330
+
331
+        try {
332
+            /** @var VCalendar $vObject|null */
333
+            $vObject = Reader::read($calendarData);
334
+        } catch (ParseException $e) {
335
+            $this->logger->error('iMip message could not be processed because an error occurred while parsing the iMip message', ['exception' => $e]);
336
+            return false;
337
+        }
338
+
339
+        if ($vObject === null) {
340
+            $this->logger->warning('iMip message contains an invalid calendar object');
341
+            return false;
342
+        }
343
+
344
+        if (!isset($vObject->METHOD) || $vObject->METHOD->getValue() !== 'REPLY') {
345
+            $this->logger->warning('iMip message contains an incorrect or invalid method');
346
+            return false;
347
+        }
348
+
349
+        if (!isset($vObject->VEVENT)) {
350
+            $this->logger->warning('iMip message contains no event');
351
+            return false;
352
+        }
353
+
354
+        /** @var VEvent|null $vEvent */
355
+        $vEvent = $vObject->VEVENT;
356
+
357
+        if (!isset($vEvent->UID)) {
358
+            $this->logger->warning('iMip message event dose not contains a UID');
359
+            return false;
360
+        }
361
+
362
+        if (!isset($vEvent->ORGANIZER)) {
363
+            $this->logger->warning('iMip message event dose not contains an organizer');
364
+            return false;
365
+        }
366
+
367
+        if (!isset($vEvent->ATTENDEE)) {
368
+            $this->logger->warning('iMip message event dose not contains any attendees');
369
+            return false;
370
+        }
371
+
372
+        // check if mail recipient and organizer are one and the same
373
+        $organizer = substr($vEvent->{'ORGANIZER'}->getValue(), 7);
374
+
375
+        if (strcasecmp($recipient, $organizer) !== 0) {
376
+            $this->logger->warning('iMip message event could not be processed because recipient and ORGANIZER must be identical');
377
+            return false;
378
+        }
379
+
380
+        //check if the event is in the future
381
+        /** @var DateTime $eventTime */
382
+        $eventTime = $vEvent->{'DTSTART'};
383
+        if ($eventTime->getDateTime()->getTimeStamp() < $this->timeFactory->getTime()) { // this might cause issues with recurrences
384
+            $this->logger->warning('iMip message event could not be processed because the event is in the past');
385
+            return false;
386
+        }
387
+
388
+        $found = null;
389
+        // if the attendee has been found in at least one calendar event with the UID of the iMIP event
390
+        // we process it.
391
+        // Benefit: no attendee lost
392
+        // Drawback: attendees that have been deleted will still be able to update their partstat
393
+        foreach ($calendars as $calendar) {
394
+            // We should not search in writable calendars
395
+            if ($calendar instanceof IHandleImipMessage) {
396
+                $o = $calendar->search($sender, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
397
+                if (!empty($o)) {
398
+                    $found = $calendar;
399
+                    $name = $o[0]['uri'];
400
+                    break;
401
+                }
402
+            }
403
+        }
404
+
405
+        if (empty($found)) {
406
+            $this->logger->warning('iMip message event could not be processed because no corresponding event was found in any calendar', [
407
+                'principalUri' => $principalUri,
408
+                'eventUid' => $vEvent->{'UID'}->getValue(),
409
+            ]);
410
+            return false;
411
+        }
412
+
413
+        try {
414
+            $found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes
415
+        } catch (CalendarException $e) {
416
+            $this->logger->error('An error occurred while processing the iMip message event', ['exception' => $e]);
417
+            return false;
418
+        }
419
+        return true;
420
+    }
421
+
422
+    /**
423
+     * @since 25.0.0
424
+     * @throws \OCP\DB\Exception
425
+     */
426
+    public function handleIMipCancel(
427
+        string $principalUri,
428
+        string $sender,
429
+        ?string $replyTo,
430
+        string $recipient,
431
+        string $calendarData,
432
+    ): bool {
433
+
434
+        $calendars = $this->getCalendarsForPrincipal($principalUri);
435
+        if (empty($calendars)) {
436
+            $this->logger->warning('iMip message could not be processed because user has no calendars');
437
+            return false;
438
+        }
439
+
440
+        try {
441
+            /** @var VCalendar $vObject|null */
442
+            $vObject = Reader::read($calendarData);
443
+        } catch (ParseException $e) {
444
+            $this->logger->error('iMip message could not be processed because an error occurred while parsing the iMip message', ['exception' => $e]);
445
+            return false;
446
+        }
447
+
448
+        if ($vObject === null) {
449
+            $this->logger->warning('iMip message contains an invalid calendar object');
450
+            return false;
451
+        }
452
+
453
+        if (!isset($vObject->METHOD) || $vObject->METHOD->getValue() !== 'CANCEL') {
454
+            $this->logger->warning('iMip message contains an incorrect or invalid method');
455
+            return false;
456
+        }
457
+
458
+        if (!isset($vObject->VEVENT)) {
459
+            $this->logger->warning('iMip message contains no event');
460
+            return false;
461
+        }
462
+
463
+        /** @var VEvent|null $vEvent */
464
+        $vEvent = $vObject->{'VEVENT'};
465
+
466
+        if (!isset($vEvent->UID)) {
467
+            $this->logger->warning('iMip message event dose not contains a UID');
468
+            return false;
469
+        }
470
+
471
+        if (!isset($vEvent->ORGANIZER)) {
472
+            $this->logger->warning('iMip message event dose not contains an organizer');
473
+            return false;
474
+        }
475
+
476
+        if (!isset($vEvent->ATTENDEE)) {
477
+            $this->logger->warning('iMip message event dose not contains any attendees');
478
+            return false;
479
+        }
480
+
481
+        $attendee = substr($vEvent->{'ATTENDEE'}->getValue(), 7);
482
+        if (strcasecmp($recipient, $attendee) !== 0) {
483
+            $this->logger->warning('iMip message event could not be processed because recipient must be an ATTENDEE of this event');
484
+            return false;
485
+        }
486
+
487
+        // Thirdly, we need to compare the email address the CANCEL is coming from (in Mail)
488
+        // or the Reply- To Address submitted with the CANCEL email
489
+        // to the email address in the ORGANIZER.
490
+        // We don't want to accept a CANCEL request from just anyone
491
+        $organizer = substr($vEvent->{'ORGANIZER'}->getValue(), 7);
492
+        $isNotOrganizer = ($replyTo !== null) ? (strcasecmp($sender, $organizer) !== 0 && strcasecmp($replyTo, $organizer) !== 0) : (strcasecmp($sender, $organizer) !== 0);
493
+        if ($isNotOrganizer) {
494
+            $this->logger->warning('iMip message event could not be processed because sender must be the ORGANIZER of this event');
495
+            return false;
496
+        }
497
+
498
+        //check if the event is in the future
499
+        /** @var DateTime $eventTime */
500
+        $eventTime = $vEvent->{'DTSTART'};
501
+        if ($eventTime->getDateTime()->getTimeStamp() < $this->timeFactory->getTime()) { // this might cause issues with recurrences
502
+            $this->logger->warning('iMip message event could not be processed because the event is in the past');
503
+            return false;
504
+        }
505
+
506
+        $found = null;
507
+        // if the attendee has been found in at least one calendar event with the UID of the iMIP event
508
+        // we process it.
509
+        // Benefit: no attendee lost
510
+        // Drawback: attendees that have been deleted will still be able to update their partstat
511
+        foreach ($calendars as $calendar) {
512
+            // We should not search in writable calendars
513
+            if ($calendar instanceof IHandleImipMessage) {
514
+                $o = $calendar->search($recipient, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
515
+                if (!empty($o)) {
516
+                    $found = $calendar;
517
+                    $name = $o[0]['uri'];
518
+                    break;
519
+                }
520
+            }
521
+        }
522
+
523
+        if (empty($found)) {
524
+            $this->logger->warning('iMip message event could not be processed because no corresponding event was found in any calendar', [
525
+                'principalUri' => $principalUri,
526
+                'eventUid' => $vEvent->{'UID'}->getValue(),
527
+            ]);
528
+            return false;
529
+        }
530
+
531
+        try {
532
+            $found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes
533
+            return true;
534
+        } catch (CalendarException $e) {
535
+            $this->logger->error('An error occurred while processing the iMip message event', ['exception' => $e]);
536
+            return false;
537
+        }
538
+    }
539
+
540
+    public function createEventBuilder(): ICalendarEventBuilder {
541
+        $uid = $this->random->generate(32, ISecureRandom::CHAR_ALPHANUMERIC);
542
+        return new CalendarEventBuilder($uid, $this->timeFactory);
543
+    }
544
+
545
+    public function checkAvailability(
546
+        DateTimeInterface $start,
547
+        DateTimeInterface $end,
548
+        IUser $organizer,
549
+        array $attendees,
550
+    ): array {
551
+        $organizerMailto = 'mailto:' . $organizer->getEMailAddress();
552
+        $request = new VCalendar();
553
+        $request->METHOD = 'REQUEST';
554
+        $request->add('VFREEBUSY', [
555
+            'DTSTART' => $start,
556
+            'DTEND' => $end,
557
+            'ORGANIZER' => $organizerMailto,
558
+            'ATTENDEE' => $organizerMailto,
559
+        ]);
560
+
561
+        $mailtoLen = strlen('mailto:');
562
+        foreach ($attendees as $attendee) {
563
+            if (str_starts_with($attendee, 'mailto:')) {
564
+                $attendee = substr($attendee, $mailtoLen);
565
+            }
566
+
567
+            $attendeeUsers = $this->userManager->getByEmail($attendee);
568
+            if ($attendeeUsers === []) {
569
+                continue;
570
+            }
571
+
572
+            $request->VFREEBUSY->add('ATTENDEE', "mailto:$attendee");
573
+        }
574
+
575
+        $organizerUid = $organizer->getUID();
576
+        $server = $this->serverFactory->createAttendeeAvailabilityServer();
577
+        /** @var CustomPrincipalPlugin $plugin */
578
+        $plugin = $server->getPlugin('auth');
579
+        $plugin->setCurrentPrincipal("principals/users/$organizerUid");
580
+
581
+        $request = new Request(
582
+            'POST',
583
+            "/calendars/$organizerUid/outbox/",
584
+            [
585
+                'Content-Type' => 'text/calendar',
586
+                'Depth' => 0,
587
+            ],
588
+            $request->serialize(),
589
+        );
590
+        $response = new Response();
591
+        $server->invokeMethod($request, $response, false);
592
+
593
+        $xmlService = new \Sabre\Xml\Service();
594
+        $xmlService->elementMap = [
595
+            '{urn:ietf:params:xml:ns:caldav}response' => 'Sabre\Xml\Deserializer\keyValue',
596
+            '{urn:ietf:params:xml:ns:caldav}recipient' => 'Sabre\Xml\Deserializer\keyValue',
597
+        ];
598
+        $parsedResponse = $xmlService->parse($response->getBodyAsString());
599
+
600
+        $result = [];
601
+        foreach ($parsedResponse as $freeBusyResponse) {
602
+            $freeBusyResponse = $freeBusyResponse['value'];
603
+            if ($freeBusyResponse['{urn:ietf:params:xml:ns:caldav}request-status'] !== '2.0;Success') {
604
+                continue;
605
+            }
606
+
607
+            $freeBusyResponseData = \Sabre\VObject\Reader::read(
608
+                $freeBusyResponse['{urn:ietf:params:xml:ns:caldav}calendar-data']
609
+            );
610
+
611
+            $attendee = substr(
612
+                $freeBusyResponse['{urn:ietf:params:xml:ns:caldav}recipient']['{DAV:}href'],
613
+                $mailtoLen,
614
+            );
615
+
616
+            $vFreeBusy = $freeBusyResponseData->VFREEBUSY;
617
+            if (!($vFreeBusy instanceof VFreeBusy)) {
618
+                continue;
619
+            }
620
+
621
+            // TODO: actually check values of FREEBUSY properties to find a free slot
622
+            $result[] = new AvailabilityResult($attendee, $vFreeBusy->isFree($start, $end));
623
+        }
624
+
625
+        return $result;
626
+    }
627 627
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -174,12 +174,12 @@  discard block
 block discarded – undo
174 174
 		}
175 175
 
176 176
 		return array_merge(
177
-			...array_map(function ($registration) use ($principalUri, $calendarUris) {
177
+			...array_map(function($registration) use ($principalUri, $calendarUris) {
178 178
 				try {
179 179
 					/** @var ICalendarProvider $provider */
180 180
 					$provider = $this->container->get($registration->getService());
181 181
 				} catch (Throwable $e) {
182
-					$this->logger->error('Could not load calendar provider ' . $registration->getService() . ': ' . $e->getMessage(), [
182
+					$this->logger->error('Could not load calendar provider '.$registration->getService().': '.$e->getMessage(), [
183 183
 						'exception' => $e,
184 184
 					]);
185 185
 					return [];
@@ -548,7 +548,7 @@  discard block
 block discarded – undo
548 548
 		IUser $organizer,
549 549
 		array $attendees,
550 550
 	): array {
551
-		$organizerMailto = 'mailto:' . $organizer->getEMailAddress();
551
+		$organizerMailto = 'mailto:'.$organizer->getEMailAddress();
552 552
 		$request = new VCalendar();
553 553
 		$request->METHOD = 'REQUEST';
554 554
 		$request->add('VFREEBUSY', [
Please login to merge, or discard this patch.
tests/lib/Calendar/ManagerTest.php 2 patches
Indentation   +1716 added lines, -1716 removed lines patch added patch discarded remove patch
@@ -38,1587 +38,1587 @@  discard block
 block discarded – undo
38 38
 }
39 39
 
40 40
 class ManagerTest extends TestCase {
41
-	/** @var Coordinator&MockObject */
42
-	private $coordinator;
43
-
44
-	/** @var ContainerInterface&MockObject */
45
-	private $container;
46
-
47
-	/** @var LoggerInterface&MockObject */
48
-	private $logger;
49
-
50
-	/** @var Manager */
51
-	private $manager;
52
-
53
-	/** @var ITimeFactory&MockObject */
54
-	private $time;
55
-
56
-	/** @var ISecureRandom&MockObject */
57
-	private ISecureRandom $secureRandom;
58
-
59
-	private IUserManager&MockObject $userManager;
60
-	private ServerFactory&MockObject $serverFactory;
61
-
62
-	private VCalendar $vCalendar1a;
63
-	private VCalendar $vCalendar2a;
64
-	private VCalendar $vCalendar3a;
65
-
66
-	protected function setUp(): void {
67
-		parent::setUp();
68
-
69
-		$this->coordinator = $this->createMock(Coordinator::class);
70
-		$this->container = $this->createMock(ContainerInterface::class);
71
-		$this->logger = $this->createMock(LoggerInterface::class);
72
-		$this->time = $this->createMock(ITimeFactory::class);
73
-		$this->secureRandom = $this->createMock(ISecureRandom::class);
74
-		$this->userManager = $this->createMock(IUserManager::class);
75
-		$this->serverFactory = $this->createMock(ServerFactory::class);
76
-
77
-		$this->manager = new Manager(
78
-			$this->coordinator,
79
-			$this->container,
80
-			$this->logger,
81
-			$this->time,
82
-			$this->secureRandom,
83
-			$this->userManager,
84
-			$this->serverFactory,
85
-		);
86
-
87
-		// construct calendar with a 1 hour event and same start/end time zones
88
-		$this->vCalendar1a = new VCalendar();
89
-		/** @var VEvent $vEvent */
90
-		$vEvent = $this->vCalendar1a->add('VEVENT', []);
91
-		$vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
92
-		$vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
93
-		$vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
94
-		$vEvent->add('SUMMARY', 'Test Event');
95
-		$vEvent->add('SEQUENCE', 3);
96
-		$vEvent->add('STATUS', 'CONFIRMED');
97
-		$vEvent->add('TRANSP', 'OPAQUE');
98
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
99
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
100
-			'CN' => 'Attendee One',
101
-			'CUTYPE' => 'INDIVIDUAL',
102
-			'PARTSTAT' => 'NEEDS-ACTION',
103
-			'ROLE' => 'REQ-PARTICIPANT',
104
-			'RSVP' => 'TRUE'
105
-		]);
106
-
107
-		// construct calendar with a event for reply
108
-		$this->vCalendar2a = new VCalendar();
109
-		/** @var VEvent $vEvent */
110
-		$vEvent = $this->vCalendar2a->add('VEVENT', []);
111
-		$vEvent->UID->setValue('dcc733bf-b2b2-41f2-a8cf-550ae4b67aff');
112
-		$vEvent->add('DTSTART', '20210820');
113
-		$vEvent->add('DTEND', '20220821');
114
-		$vEvent->add('SUMMARY', 'berry basket');
115
-		$vEvent->add('SEQUENCE', 3);
116
-		$vEvent->add('STATUS', 'CONFIRMED');
117
-		$vEvent->add('TRANSP', 'OPAQUE');
118
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'admin']);
119
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
120
-			'CN' => '[email protected]',
121
-			'CUTYPE' => 'INDIVIDUAL',
122
-			'ROLE' => 'REQ-PARTICIPANT',
123
-			'PARTSTAT' => 'ACCEPTED',
124
-		]);
125
-
126
-		// construct calendar with a event for reply
127
-		$this->vCalendar3a = new VCalendar();
128
-		/** @var VEvent $vEvent */
129
-		$vEvent = $this->vCalendar3a->add('VEVENT', []);
130
-		$vEvent->UID->setValue('dcc733bf-b2b2-41f2-a8cf-550ae4b67aff');
131
-		$vEvent->add('DTSTART', '20210820');
132
-		$vEvent->add('DTEND', '20220821');
133
-		$vEvent->add('SUMMARY', 'berry basket');
134
-		$vEvent->add('SEQUENCE', 3);
135
-		$vEvent->add('STATUS', 'CANCELLED');
136
-		$vEvent->add('TRANSP', 'OPAQUE');
137
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'admin']);
138
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
139
-			'CN' => '[email protected]',
140
-			'CUTYPE' => 'INDIVIDUAL',
141
-			'ROLE' => 'REQ-PARTICIPANT',
142
-			'PARTSTAT' => 'ACCEPTED',
143
-		]);
144
-
145
-	}
146
-
147
-	#[\PHPUnit\Framework\Attributes\DataProvider('searchProvider')]
148
-	public function testSearch($search1, $search2, $expected): void {
149
-		/** @var ICalendar | MockObject $calendar1 */
150
-		$calendar1 = $this->createMock(ICalendar::class);
151
-		$calendar1->method('getKey')->willReturn('simple:1');
152
-		$calendar1->expects($this->once())
153
-			->method('search')
154
-			->with('', [], [], null, null)
155
-			->willReturn($search1);
156
-
157
-		/** @var ICalendar | MockObject $calendar2 */
158
-		$calendar2 = $this->createMock(ICalendar::class);
159
-		$calendar2->method('getKey')->willReturn('simple:2');
160
-		$calendar2->expects($this->once())
161
-			->method('search')
162
-			->with('', [], [], null, null)
163
-			->willReturn($search2);
164
-
165
-		$this->manager->registerCalendar($calendar1);
166
-		$this->manager->registerCalendar($calendar2);
167
-
168
-		$result = $this->manager->search('');
169
-		$this->assertEquals($expected, $result);
170
-	}
171
-
172
-	#[\PHPUnit\Framework\Attributes\DataProvider('searchProvider')]
173
-	public function testSearchOptions($search1, $search2, $expected): void {
174
-		/** @var ICalendar | MockObject $calendar1 */
175
-		$calendar1 = $this->createMock(ICalendar::class);
176
-		$calendar1->method('getKey')->willReturn('simple:1');
177
-		$calendar1->expects($this->once())
178
-			->method('search')
179
-			->with('searchTerm', ['SUMMARY', 'DESCRIPTION'],
180
-				['timerange' => ['start' => null, 'end' => null]], 5, 20)
181
-			->willReturn($search1);
182
-
183
-		/** @var ICalendar | MockObject $calendar2 */
184
-		$calendar2 = $this->createMock(ICalendar::class);
185
-		$calendar2->method('getKey')->willReturn('simple:2');
186
-		$calendar2->expects($this->once())
187
-			->method('search')
188
-			->with('searchTerm', ['SUMMARY', 'DESCRIPTION'],
189
-				['timerange' => ['start' => null, 'end' => null]], 5, 20)
190
-			->willReturn($search2);
191
-
192
-		$this->manager->registerCalendar($calendar1);
193
-		$this->manager->registerCalendar($calendar2);
194
-
195
-		$result = $this->manager->search('searchTerm', ['SUMMARY', 'DESCRIPTION'],
196
-			['timerange' => ['start' => null, 'end' => null]], 5, 20);
197
-		$this->assertEquals($expected, $result);
198
-	}
199
-
200
-	public static function searchProvider(): array {
201
-		$search1 = [
202
-			[
203
-				'id' => 1,
204
-				'data' => 'foobar',
205
-			],
206
-			[
207
-				'id' => 2,
208
-				'data' => 'barfoo',
209
-			]
210
-		];
211
-		$search2 = [
212
-			[
213
-				'id' => 3,
214
-				'data' => 'blablub',
215
-			],
216
-			[
217
-				'id' => 4,
218
-				'data' => 'blubbla',
219
-			]
220
-		];
221
-
222
-		$expected = [
223
-			[
224
-				'id' => 1,
225
-				'data' => 'foobar',
226
-				'calendar-key' => 'simple:1',
227
-			],
228
-			[
229
-				'id' => 2,
230
-				'data' => 'barfoo',
231
-				'calendar-key' => 'simple:1',
232
-			],
233
-			[
234
-				'id' => 3,
235
-				'data' => 'blablub',
236
-				'calendar-key' => 'simple:2',
237
-			],
238
-			[
239
-				'id' => 4,
240
-				'data' => 'blubbla',
241
-				'calendar-key' => 'simple:2',
242
-			]
243
-		];
244
-
245
-		return [
246
-			[
247
-				$search1,
248
-				$search2,
249
-				$expected
250
-			]
251
-		];
252
-	}
253
-
254
-	public function testRegisterUnregister(): void {
255
-		/** @var ICalendar | MockObject $calendar1 */
256
-		$calendar1 = $this->createMock(ICalendar::class);
257
-		$calendar1->method('getKey')->willReturn('key1');
258
-
259
-		/** @var ICalendar | MockObject $calendar2 */
260
-		$calendar2 = $this->createMock(ICalendar::class);
261
-		$calendar2->method('getKey')->willReturn('key2');
262
-
263
-		$this->manager->registerCalendar($calendar1);
264
-		$this->manager->registerCalendar($calendar2);
265
-
266
-		$result = $this->manager->getCalendars();
267
-		$this->assertCount(2, $result);
268
-		$this->assertContains($calendar1, $result);
269
-		$this->assertContains($calendar2, $result);
270
-
271
-		$this->manager->unregisterCalendar($calendar1);
272
-
273
-		$result = $this->manager->getCalendars();
274
-		$this->assertCount(1, $result);
275
-		$this->assertContains($calendar2, $result);
276
-	}
277
-
278
-	public function testGetCalendars(): void {
279
-		/** @var ICalendar | MockObject $calendar1 */
280
-		$calendar1 = $this->createMock(ICalendar::class);
281
-		$calendar1->method('getKey')->willReturn('key1');
282
-
283
-		/** @var ICalendar | MockObject $calendar2 */
284
-		$calendar2 = $this->createMock(ICalendar::class);
285
-		$calendar2->method('getKey')->willReturn('key2');
286
-
287
-		$this->manager->registerCalendar($calendar1);
288
-		$this->manager->registerCalendar($calendar2);
289
-
290
-		$result = $this->manager->getCalendars();
291
-		$this->assertCount(2, $result);
292
-		$this->assertContains($calendar1, $result);
293
-		$this->assertContains($calendar2, $result);
294
-
295
-		$this->manager->clear();
296
-
297
-		$result = $this->manager->getCalendars();
298
-
299
-		$this->assertCount(0, $result);
300
-	}
301
-
302
-	public function testEnabledIfNot(): void {
303
-		$isEnabled = $this->manager->isEnabled();
304
-		$this->assertFalse($isEnabled);
305
-	}
306
-
307
-	public function testIfEnabledIfSo(): void {
308
-		/** @var ICalendar | MockObject $calendar */
309
-		$calendar = $this->createMock(ICalendar::class);
310
-		$this->manager->registerCalendar($calendar);
311
-
312
-		$isEnabled = $this->manager->isEnabled();
313
-		$this->assertTrue($isEnabled);
314
-	}
315
-
316
-	public function testHandleImipRequestWithNoCalendars(): void {
317
-		// construct calendar manager returns
318
-		/** @var Manager&MockObject $manager */
319
-		$manager = $this->getMockBuilder(Manager::class)
320
-			->setConstructorArgs([
321
-				$this->coordinator,
322
-				$this->container,
323
-				$this->logger,
324
-				$this->time,
325
-				$this->secureRandom,
326
-				$this->userManager,
327
-				$this->serverFactory,
328
-			])
329
-			->onlyMethods(['getCalendarsForPrincipal'])
330
-			->getMock();
331
-		$manager->expects(self::once())
332
-			->method('getCalendarsForPrincipal')
333
-			->willReturn([]);
334
-		// construct logger returns
335
-		$this->logger->expects(self::once())->method('warning')
336
-			->with('iMip message could not be processed because user has no calendars');
337
-		// construct parameters
338
-		$principalUri = 'principals/user/attendee1';
339
-		$sender = '[email protected]';
340
-		$recipient = '[email protected]';
341
-		$calendar = $this->vCalendar1a;
342
-		$calendar->add('METHOD', 'REQUEST');
343
-		// Act
344
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
345
-		// Assert
346
-		$this->assertFalse($result);
347
-	}
348
-
349
-	public function testHandleImipRequestWithInvalidData(): void {
350
-		// construct mock user calendar
351
-		$userCalendar = $this->createMock(ITestCalendar::class);
352
-		// construct mock calendar manager and returns
353
-		/** @var Manager&MockObject $manager */
354
-		$manager = $this->getMockBuilder(Manager::class)
355
-			->setConstructorArgs([
356
-				$this->coordinator,
357
-				$this->container,
358
-				$this->logger,
359
-				$this->time,
360
-				$this->secureRandom,
361
-				$this->userManager,
362
-				$this->serverFactory,
363
-			])
364
-			->onlyMethods(['getCalendarsForPrincipal'])
365
-			->getMock();
366
-		$manager->expects(self::once())
367
-			->method('getCalendarsForPrincipal')
368
-			->willReturn([$userCalendar]);
369
-		// construct logger returns
370
-		$this->logger->expects(self::once())->method('error')
371
-			->with('iMip message could not be processed because an error occurred while parsing the iMip message');
372
-		// construct parameters
373
-		$principalUri = 'principals/user/attendee1';
374
-		$sender = '[email protected]';
375
-		$recipient = '[email protected]';
376
-		// Act
377
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, 'Invalid data');
378
-		// Assert
379
-		$this->assertFalse($result);
380
-	}
381
-
382
-	public function testHandleImipRequestWithNoMethod(): void {
383
-		// construct mock user calendar
384
-		$userCalendar = $this->createMock(ITestCalendar::class);
385
-		// construct mock calendar manager and returns
386
-		/** @var Manager&MockObject $manager */
387
-		$manager = $this->getMockBuilder(Manager::class)
388
-			->setConstructorArgs([
389
-				$this->coordinator,
390
-				$this->container,
391
-				$this->logger,
392
-				$this->time,
393
-				$this->secureRandom,
394
-				$this->userManager,
395
-				$this->serverFactory,
396
-			])
397
-			->onlyMethods(['getCalendarsForPrincipal'])
398
-			->getMock();
399
-		$manager->expects(self::once())
400
-			->method('getCalendarsForPrincipal')
401
-			->willReturn([$userCalendar]);
402
-		// construct logger returns
403
-		$this->logger->expects(self::once())->method('warning')
404
-			->with('iMip message contains an incorrect or invalid method');
405
-		// construct parameters
406
-		$principalUri = 'principals/user/attendee1';
407
-		$sender = '[email protected]';
408
-		$recipient = '[email protected]';
409
-		$calendar = $this->vCalendar1a;
410
-		// Act
411
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
412
-		// Assert
413
-		$this->assertFalse($result);
414
-	}
415
-
416
-	public function testHandleImipRequestWithInvalidMethod(): void {
417
-		// construct mock user calendar
418
-		$userCalendar = $this->createMock(ITestCalendar::class);
419
-		// construct mock calendar manager and returns
420
-		/** @var Manager&MockObject $manager */
421
-		$manager = $this->getMockBuilder(Manager::class)
422
-			->setConstructorArgs([
423
-				$this->coordinator,
424
-				$this->container,
425
-				$this->logger,
426
-				$this->time,
427
-				$this->secureRandom,
428
-				$this->userManager,
429
-				$this->serverFactory,
430
-			])
431
-			->onlyMethods(['getCalendarsForPrincipal'])
432
-			->getMock();
433
-		$manager->expects(self::once())
434
-			->method('getCalendarsForPrincipal')
435
-			->willReturn([$userCalendar]);
436
-		// construct logger returns
437
-		$this->logger->expects(self::once())->method('warning')
438
-			->with('iMip message contains an incorrect or invalid method');
439
-		// construct parameters
440
-		$principalUri = 'principals/user/attendee1';
441
-		$sender = '[email protected]';
442
-		$recipient = '[email protected]';
443
-		$calendar = $this->vCalendar1a;
444
-		$calendar->add('METHOD', 'CANCEL');
445
-		// Act
446
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
447
-		// Assert
448
-		$this->assertFalse($result);
449
-	}
450
-
451
-	public function testHandleImipRequestWithNoEvent(): void {
452
-		// construct mock user calendar
453
-		$userCalendar = $this->createMock(ITestCalendar::class);
454
-		// construct mock calendar manager and returns
455
-		/** @var Manager&MockObject $manager */
456
-		$manager = $this->getMockBuilder(Manager::class)
457
-			->setConstructorArgs([
458
-				$this->coordinator,
459
-				$this->container,
460
-				$this->logger,
461
-				$this->time,
462
-				$this->secureRandom,
463
-				$this->userManager,
464
-				$this->serverFactory,
465
-			])
466
-			->onlyMethods(['getCalendarsForPrincipal'])
467
-			->getMock();
468
-		$manager->expects(self::once())
469
-			->method('getCalendarsForPrincipal')
470
-			->willReturn([$userCalendar]);
471
-		// construct logger returns
472
-		$this->logger->expects(self::once())->method('warning')
473
-			->with('iMip message contains no event');
474
-		// construct parameters
475
-		$principalUri = 'principals/user/attendee1';
476
-		$sender = '[email protected]';
477
-		$recipient = '[email protected]';
478
-		$calendar = $this->vCalendar1a;
479
-		$calendar->add('METHOD', 'REQUEST');
480
-		$calendar->remove('VEVENT');
481
-		// Act
482
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
483
-		// Assert
484
-		$this->assertFalse($result);
485
-	}
486
-
487
-	public function testHandleImipRequestWithNoUid(): void {
488
-		// construct mock user calendar
489
-		$userCalendar = $this->createMock(ITestCalendar::class);
490
-		// construct mock calendar manager and returns
491
-		/** @var Manager&MockObject $manager */
492
-		$manager = $this->getMockBuilder(Manager::class)
493
-			->setConstructorArgs([
494
-				$this->coordinator,
495
-				$this->container,
496
-				$this->logger,
497
-				$this->time,
498
-				$this->secureRandom,
499
-				$this->userManager,
500
-				$this->serverFactory,
501
-			])
502
-			->onlyMethods(['getCalendarsForPrincipal'])
503
-			->getMock();
504
-		$manager->expects(self::once())
505
-			->method('getCalendarsForPrincipal')
506
-			->willReturn([$userCalendar]);
507
-		// construct logger returns
508
-		$this->logger->expects(self::once())->method('warning')
509
-			->with('iMip message event dose not contains a UID');
510
-		// construct parameters
511
-		$principalUri = 'principals/user/attendee1';
512
-		$sender = '[email protected]';
513
-		$recipient = '[email protected]';
514
-		$calendar = $this->vCalendar1a;
515
-		$calendar->add('METHOD', 'REQUEST');
516
-		$calendar->VEVENT->remove('UID');
517
-		// Act
518
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
519
-		// Assert
520
-		$this->assertFalse($result);
521
-	}
522
-
523
-	public function testHandleImipRequestWithNoOrganizer(): void {
524
-		// construct mock user calendar
525
-		$userCalendar = $this->createMock(ITestCalendar::class);
526
-		// construct mock calendar manager and returns
527
-		/** @var Manager&MockObject $manager */
528
-		$manager = $this->getMockBuilder(Manager::class)
529
-			->setConstructorArgs([
530
-				$this->coordinator,
531
-				$this->container,
532
-				$this->logger,
533
-				$this->time,
534
-				$this->secureRandom,
535
-				$this->userManager,
536
-				$this->serverFactory,
537
-			])
538
-			->onlyMethods(['getCalendarsForPrincipal'])
539
-			->getMock();
540
-		$manager->expects(self::once())
541
-			->method('getCalendarsForPrincipal')
542
-			->willReturn([$userCalendar]);
543
-		// construct logger returns
544
-		$this->logger->expects(self::once())->method('warning')
545
-			->with('iMip message event dose not contains an organizer');
546
-		// construct parameters
547
-		$principalUri = 'principals/user/attendee1';
548
-		$sender = '[email protected]';
549
-		$recipient = '[email protected]';
550
-		$calendar = $this->vCalendar1a;
551
-		$calendar->add('METHOD', 'REQUEST');
552
-		$calendar->VEVENT->remove('ORGANIZER');
553
-		// Act
554
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
555
-		// Assert
556
-		$this->assertFalse($result);
557
-	}
558
-
559
-	public function testHandleImipRequestWithNoAttendee(): void {
560
-		// construct mock user calendar
561
-		$userCalendar = $this->createMock(ITestCalendar::class);
562
-		// construct mock calendar manager and returns
563
-		/** @var Manager&MockObject $manager */
564
-		$manager = $this->getMockBuilder(Manager::class)
565
-			->setConstructorArgs([
566
-				$this->coordinator,
567
-				$this->container,
568
-				$this->logger,
569
-				$this->time,
570
-				$this->secureRandom,
571
-				$this->userManager,
572
-				$this->serverFactory,
573
-			])
574
-			->onlyMethods(['getCalendarsForPrincipal'])
575
-			->getMock();
576
-		$manager->expects(self::once())
577
-			->method('getCalendarsForPrincipal')
578
-			->willReturn([$userCalendar]);
579
-		// construct logger returns
580
-		$this->logger->expects(self::once())->method('warning')
581
-			->with('iMip message event dose not contains any attendees');
582
-		// construct parameters
583
-		$principalUri = 'principals/user/attendee1';
584
-		$sender = '[email protected]';
585
-		$recipient = '[email protected]';
586
-		$calendar = $this->vCalendar1a;
587
-		$calendar->add('METHOD', 'REQUEST');
588
-		$calendar->VEVENT->remove('ATTENDEE');
589
-		// Act
590
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
591
-		// Assert
592
-		$this->assertFalse($result);
593
-	}
594
-
595
-	public function testHandleImipRequestWithInvalidAttendee(): void {
596
-		// construct mock user calendar
597
-		$userCalendar = $this->createMock(ITestCalendar::class);
598
-		// construct mock calendar manager and returns
599
-		/** @var Manager&MockObject $manager */
600
-		$manager = $this->getMockBuilder(Manager::class)
601
-			->setConstructorArgs([
602
-				$this->coordinator,
603
-				$this->container,
604
-				$this->logger,
605
-				$this->time,
606
-				$this->secureRandom,
607
-				$this->userManager,
608
-				$this->serverFactory,
609
-			])
610
-			->onlyMethods(['getCalendarsForPrincipal'])
611
-			->getMock();
612
-		$manager->expects(self::once())
613
-			->method('getCalendarsForPrincipal')
614
-			->willReturn([$userCalendar]);
615
-		// construct logger returns
616
-		$this->logger->expects(self::once())->method('warning')
617
-			->with('iMip message event does not contain a attendee that matches the recipient');
618
-		// construct parameters
619
-		$principalUri = 'principals/user/attendee1';
620
-		$sender = '[email protected]';
621
-		$recipient = '[email protected]';
622
-		$calendar = $this->vCalendar1a;
623
-		$calendar->add('METHOD', 'REQUEST');
624
-		// Act
625
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
626
-		// Assert
627
-		$this->assertFalse($result);
628
-	}
629
-
630
-	public function testHandleImipRequestWithNoMatch(): void {
631
-		// construct mock user calendar
632
-		$userCalendar = $this->createMock(ITestCalendar::class);
633
-		$userCalendar->expects(self::once())
634
-			->method('isDeleted')
635
-			->willReturn(false);
636
-		$userCalendar->expects(self::once())
637
-			->method('isWritable')
638
-			->willReturn(true);
639
-		$userCalendar->expects(self::once())
640
-			->method('isShared')
641
-			->willReturn(false);
642
-		$userCalendar->expects(self::once())
643
-			->method('search')
644
-			->willReturn([]);
645
-		// construct mock calendar manager and returns
646
-		/** @var Manager&MockObject $manager */
647
-		$manager = $this->getMockBuilder(Manager::class)
648
-			->setConstructorArgs([
649
-				$this->coordinator,
650
-				$this->container,
651
-				$this->logger,
652
-				$this->time,
653
-				$this->secureRandom,
654
-				$this->userManager,
655
-				$this->serverFactory,
656
-			])
657
-			->onlyMethods(['getCalendarsForPrincipal'])
658
-			->getMock();
659
-		$manager->expects(self::once())
660
-			->method('getCalendarsForPrincipal')
661
-			->willReturn([$userCalendar]);
662
-		// construct logger returns
663
-		$this->logger->expects(self::once())->method('warning')
664
-			->with('iMip message event could not be processed because no corresponding event was found in any calendar');
665
-		// construct parameters
666
-		$principalUri = 'principals/user/attendee1';
667
-		$sender = '[email protected]';
668
-		$recipient = '[email protected]';
669
-		$calendar = $this->vCalendar1a;
670
-		$calendar->add('METHOD', 'REQUEST');
671
-		// Act
672
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
673
-		// Assert
674
-		$this->assertFalse($result);
675
-	}
676
-
677
-	public function testHandleImipRequest(): void {
678
-		// construct mock user calendar
679
-		$userCalendar = $this->createMock(ITestCalendar::class);
680
-		$userCalendar->expects(self::once())
681
-			->method('isDeleted')
682
-			->willReturn(false);
683
-		$userCalendar->expects(self::once())
684
-			->method('isWritable')
685
-			->willReturn(true);
686
-		$userCalendar->expects(self::once())
687
-			->method('isShared')
688
-			->willReturn(false);
689
-		$userCalendar->expects(self::once())
690
-			->method('search')
691
-			->willReturn([['uri' => 'principals/user/attendee1/personal']]);
692
-		// construct mock calendar manager and returns
693
-		/** @var Manager&MockObject $manager */
694
-		$manager = $this->getMockBuilder(Manager::class)
695
-			->setConstructorArgs([
696
-				$this->coordinator,
697
-				$this->container,
698
-				$this->logger,
699
-				$this->time,
700
-				$this->secureRandom,
701
-				$this->userManager,
702
-				$this->serverFactory,
703
-			])
704
-			->onlyMethods(['getCalendarsForPrincipal'])
705
-			->getMock();
706
-		$manager->expects(self::once())
707
-			->method('getCalendarsForPrincipal')
708
-			->willReturn([$userCalendar]);
709
-		// construct parameters
710
-		$principalUri = 'principals/user/attendee1';
711
-		$sender = '[email protected]';
712
-		$recipient = '[email protected]';
713
-		$calendar = $this->vCalendar1a;
714
-		$calendar->add('METHOD', 'REQUEST');
715
-		// construct user calendar returns
716
-		$userCalendar->expects(self::once())
717
-			->method('handleIMipMessage')
718
-			->with('', $calendar->serialize());
719
-		// Act
720
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
721
-		// Assert
722
-		$this->assertTrue($result);
723
-	}
724
-
725
-	public function testHandleImipReplyWithNoCalendars(): void {
726
-		// construct calendar manager returns
727
-		/** @var Manager&MockObject $manager */
728
-		$manager = $this->getMockBuilder(Manager::class)
729
-			->setConstructorArgs([
730
-				$this->coordinator,
731
-				$this->container,
732
-				$this->logger,
733
-				$this->time,
734
-				$this->secureRandom,
735
-				$this->userManager,
736
-				$this->serverFactory,
737
-			])
738
-			->onlyMethods(['getCalendarsForPrincipal'])
739
-			->getMock();
740
-		$manager->expects(self::once())
741
-			->method('getCalendarsForPrincipal')
742
-			->willReturn([]);
743
-		// construct logger returns
744
-		$this->logger->expects(self::once())->method('warning')
745
-			->with('iMip message could not be processed because user has no calendars');
746
-		// construct parameters
747
-		$principalUri = 'principals/user/linus';
748
-		$sender = '[email protected]';
749
-		$recipient = '[email protected]';
750
-		$calendar = $this->vCalendar2a;
751
-		$calendar->add('METHOD', 'REPLY');
752
-		// Act
753
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
754
-		// Assert
755
-		$this->assertFalse($result);
756
-	}
757
-
758
-	public function testHandleImipReplyWithInvalidData(): void {
759
-		// construct mock user calendar
760
-		$userCalendar = $this->createMock(ITestCalendar::class);
761
-		// construct mock calendar manager and returns
762
-		/** @var Manager&MockObject $manager */
763
-		$manager = $this->getMockBuilder(Manager::class)
764
-			->setConstructorArgs([
765
-				$this->coordinator,
766
-				$this->container,
767
-				$this->logger,
768
-				$this->time,
769
-				$this->secureRandom,
770
-				$this->userManager,
771
-				$this->serverFactory,
772
-			])
773
-			->onlyMethods(['getCalendarsForPrincipal'])
774
-			->getMock();
775
-		$manager->expects(self::once())
776
-			->method('getCalendarsForPrincipal')
777
-			->willReturn([$userCalendar]);
778
-		// construct logger returns
779
-		$this->logger->expects(self::once())->method('error')
780
-			->with('iMip message could not be processed because an error occurred while parsing the iMip message');
781
-		// construct parameters
782
-		$principalUri = 'principals/user/attendee1';
783
-		$sender = '[email protected]';
784
-		$recipient = '[email protected]';
785
-		// Act
786
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, 'Invalid data');
787
-		// Assert
788
-		$this->assertFalse($result);
789
-	}
790
-
791
-	public function testHandleImipReplyWithNoMethod(): void {
792
-		// construct mock user calendar
793
-		$userCalendar = $this->createMock(ITestCalendar::class);
794
-		// construct mock calendar manager and returns
795
-		/** @var Manager&MockObject $manager */
796
-		$manager = $this->getMockBuilder(Manager::class)
797
-			->setConstructorArgs([
798
-				$this->coordinator,
799
-				$this->container,
800
-				$this->logger,
801
-				$this->time,
802
-				$this->secureRandom,
803
-				$this->userManager,
804
-				$this->serverFactory,
805
-			])
806
-			->onlyMethods(['getCalendarsForPrincipal'])
807
-			->getMock();
808
-		$manager->expects(self::once())
809
-			->method('getCalendarsForPrincipal')
810
-			->willReturn([$userCalendar]);
811
-		// construct logger returns
812
-		$this->logger->expects(self::once())->method('warning')
813
-			->with('iMip message contains an incorrect or invalid method');
814
-		// construct parameters
815
-		$principalUri = 'principals/user/linus';
816
-		$sender = '[email protected]';
817
-		$recipient = '[email protected]';
818
-		$calendar = $this->vCalendar2a;
819
-		// Act
820
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
821
-		// Assert
822
-		$this->assertFalse($result);
823
-	}
824
-
825
-	public function testHandleImipReplyWithInvalidMethod(): void {
826
-		// construct mock user calendar
827
-		$userCalendar = $this->createMock(ITestCalendar::class);
828
-		// construct mock calendar manager and returns
829
-		/** @var Manager&MockObject $manager */
830
-		$manager = $this->getMockBuilder(Manager::class)
831
-			->setConstructorArgs([
832
-				$this->coordinator,
833
-				$this->container,
834
-				$this->logger,
835
-				$this->time,
836
-				$this->secureRandom,
837
-				$this->userManager,
838
-				$this->serverFactory,
839
-			])
840
-			->onlyMethods(['getCalendarsForPrincipal'])
841
-			->getMock();
842
-		$manager->expects(self::once())
843
-			->method('getCalendarsForPrincipal')
844
-			->willReturn([$userCalendar]);
845
-		// construct logger returns
846
-		$this->logger->expects(self::once())->method('warning')
847
-			->with('iMip message contains an incorrect or invalid method');
848
-		// construct parameters
849
-		$principalUri = 'principals/user/linus';
850
-		$sender = '[email protected]';
851
-		$recipient = '[email protected]';
852
-		$calendar = $this->vCalendar2a;
853
-		$calendar->add('METHOD', 'UNKNOWN');
854
-		// Act
855
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
856
-		// Assert
857
-		$this->assertFalse($result);
858
-	}
859
-
860
-	public function testHandleImipReplyWithNoEvent(): void {
861
-		// construct mock user calendar
862
-		$userCalendar = $this->createMock(ITestCalendar::class);
863
-		// construct mock calendar manager and returns
864
-		/** @var Manager&MockObject $manager */
865
-		$manager = $this->getMockBuilder(Manager::class)
866
-			->setConstructorArgs([
867
-				$this->coordinator,
868
-				$this->container,
869
-				$this->logger,
870
-				$this->time,
871
-				$this->secureRandom,
872
-				$this->userManager,
873
-				$this->serverFactory,
874
-			])
875
-			->onlyMethods(['getCalendarsForPrincipal'])
876
-			->getMock();
877
-		$manager->expects(self::once())
878
-			->method('getCalendarsForPrincipal')
879
-			->willReturn([$userCalendar]);
880
-		// construct logger returns
881
-		$this->logger->expects(self::once())->method('warning')
882
-			->with('iMip message contains no event');
883
-		// construct parameters
884
-		$principalUri = 'principals/user/linus';
885
-		$sender = '[email protected]';
886
-		$recipient = '[email protected]';
887
-		$calendar = $this->vCalendar2a;
888
-		$calendar->add('METHOD', 'REPLY');
889
-		$calendar->remove('VEVENT');
890
-		// Act
891
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
892
-		// Assert
893
-		$this->assertFalse($result);
894
-	}
895
-
896
-	public function testHandleImipReplyWithNoUid(): void {
897
-		// construct mock user calendar
898
-		$userCalendar = $this->createMock(ITestCalendar::class);
899
-		// construct mock calendar manager and returns
900
-		/** @var Manager&MockObject $manager */
901
-		$manager = $this->getMockBuilder(Manager::class)
902
-			->setConstructorArgs([
903
-				$this->coordinator,
904
-				$this->container,
905
-				$this->logger,
906
-				$this->time,
907
-				$this->secureRandom,
908
-				$this->userManager,
909
-				$this->serverFactory,
910
-			])
911
-			->onlyMethods(['getCalendarsForPrincipal'])
912
-			->getMock();
913
-		$manager->expects(self::once())
914
-			->method('getCalendarsForPrincipal')
915
-			->willReturn([$userCalendar]);
916
-		// construct logger returns
917
-		$this->logger->expects(self::once())->method('warning')
918
-			->with('iMip message event dose not contains a UID');
919
-		// construct parameters
920
-		$principalUri = 'principals/user/linus';
921
-		$sender = '[email protected]';
922
-		$recipient = '[email protected]';
923
-		$calendar = $this->vCalendar2a;
924
-		$calendar->add('METHOD', 'REPLY');
925
-		$calendar->VEVENT->remove('UID');
926
-		// Act
927
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
928
-		// Assert
929
-		$this->assertFalse($result);
930
-	}
931
-
932
-	public function testHandleImipReplyWithNoOrganizer(): void {
933
-		// construct mock user calendar
934
-		$userCalendar = $this->createMock(ITestCalendar::class);
935
-		// construct mock calendar manager and returns
936
-		/** @var Manager&MockObject $manager */
937
-		$manager = $this->getMockBuilder(Manager::class)
938
-			->setConstructorArgs([
939
-				$this->coordinator,
940
-				$this->container,
941
-				$this->logger,
942
-				$this->time,
943
-				$this->secureRandom,
944
-				$this->userManager,
945
-				$this->serverFactory,
946
-			])
947
-			->onlyMethods(['getCalendarsForPrincipal'])
948
-			->getMock();
949
-		$manager->expects(self::once())
950
-			->method('getCalendarsForPrincipal')
951
-			->willReturn([$userCalendar]);
952
-		// construct logger returns
953
-		$this->logger->expects(self::once())->method('warning')
954
-			->with('iMip message event dose not contains an organizer');
955
-		// construct parameters
956
-		$principalUri = 'principals/user/linus';
957
-		$sender = '[email protected]';
958
-		$recipient = '[email protected]';
959
-		$calendar = $this->vCalendar2a;
960
-		$calendar->add('METHOD', 'REPLY');
961
-		$calendar->VEVENT->remove('ORGANIZER');
962
-		// Act
963
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
964
-		// Assert
965
-		$this->assertFalse($result);
966
-	}
967
-
968
-	public function testHandleImipReplyWithNoAttendee(): void {
969
-		// construct mock user calendar
970
-		$userCalendar = $this->createMock(ITestCalendar::class);
971
-		// construct mock calendar manager and returns
972
-		/** @var Manager&MockObject $manager */
973
-		$manager = $this->getMockBuilder(Manager::class)
974
-			->setConstructorArgs([
975
-				$this->coordinator,
976
-				$this->container,
977
-				$this->logger,
978
-				$this->time,
979
-				$this->secureRandom,
980
-				$this->userManager,
981
-				$this->serverFactory,
982
-			])
983
-			->onlyMethods(['getCalendarsForPrincipal'])
984
-			->getMock();
985
-		$manager->expects(self::once())
986
-			->method('getCalendarsForPrincipal')
987
-			->willReturn([$userCalendar]);
988
-		// construct logger returns
989
-		$this->logger->expects(self::once())->method('warning')
990
-			->with('iMip message event dose not contains any attendees');
991
-		// construct parameters
992
-		$principalUri = 'principals/user/linus';
993
-		$sender = '[email protected]';
994
-		$recipient = '[email protected]';
995
-		$calendar = $this->vCalendar2a;
996
-		$calendar->add('METHOD', 'REPLY');
997
-		$calendar->VEVENT->remove('ATTENDEE');
998
-		// Act
999
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
1000
-		// Assert
1001
-		$this->assertFalse($result);
1002
-	}
1003
-
1004
-	public function testHandleImipReplyDateInThePast(): void {
1005
-		// construct mock user calendar
1006
-		$userCalendar = $this->createMock(ITestCalendar::class);
1007
-		// construct mock calendar manager and returns
1008
-		/** @var Manager&MockObject $manager */
1009
-		$manager = $this->getMockBuilder(Manager::class)
1010
-			->setConstructorArgs([
1011
-				$this->coordinator,
1012
-				$this->container,
1013
-				$this->logger,
1014
-				$this->time,
1015
-				$this->secureRandom,
1016
-				$this->userManager,
1017
-				$this->serverFactory,
1018
-			])
1019
-			->onlyMethods(['getCalendarsForPrincipal'])
1020
-			->getMock();
1021
-		$manager->expects(self::once())
1022
-			->method('getCalendarsForPrincipal')
1023
-			->willReturn([$userCalendar]);
1024
-		// construct logger and time returns
1025
-		$this->logger->expects(self::once())->method('warning')
1026
-			->with('iMip message event could not be processed because the event is in the past');
1027
-		$this->time->expects(self::once())
1028
-			->method('getTime')
1029
-			->willReturn(time());
1030
-		// construct parameters
1031
-		$principalUri = 'principals/user/linus';
1032
-		$sender = '[email protected]';
1033
-		$recipient = '[email protected]';
1034
-		$calendarData = clone $this->vCalendar2a;
1035
-		$calendarData->add('METHOD', 'REPLY');
1036
-		$calendarData->VEVENT->DTSTART = new \DateTime('2013-04-07'); // set to in the past
1037
-		// Act
1038
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
1039
-		// Assert
1040
-		$this->assertFalse($result);
1041
-	}
1042
-
1043
-	public function testHandleImipReplyEventNotFound(): void {
1044
-		// construct mock user calendar
1045
-		$userCalendar = $this->createMock(ITestCalendar::class);
1046
-		$userCalendar->expects(self::once())
1047
-			->method('search')
1048
-			->willReturn([]);
1049
-		// construct mock calendar manager and returns
1050
-		/** @var Manager&MockObject $manager */
1051
-		$manager = $this->getMockBuilder(Manager::class)
1052
-			->setConstructorArgs([
1053
-				$this->coordinator,
1054
-				$this->container,
1055
-				$this->logger,
1056
-				$this->time,
1057
-				$this->secureRandom,
1058
-				$this->userManager,
1059
-				$this->serverFactory,
1060
-			])
1061
-			->onlyMethods(['getCalendarsForPrincipal'])
1062
-			->getMock();
1063
-		$manager->expects(self::once())
1064
-			->method('getCalendarsForPrincipal')
1065
-			->willReturn([$userCalendar]);
1066
-		// construct time returns
1067
-		$this->time->expects(self::once())
1068
-			->method('getTime')
1069
-			->willReturn(1628374233);
1070
-		// construct parameters
1071
-		$principalUri = 'principals/user/linus';
1072
-		$sender = '[email protected]';
1073
-		$recipient = '[email protected]';
1074
-		$calendarData = clone $this->vCalendar2a;
1075
-		$calendarData->add('METHOD', 'REPLY');
1076
-		// construct logger return
1077
-		$this->logger->expects(self::once())->method('warning')
1078
-			->with('iMip message event could not be processed because no corresponding event was found in any calendar', ['principalUri' => $principalUri, 'eventUid' => $calendarData->VEVENT->UID->getValue()]);
1079
-		// Act
1080
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
1081
-		// Assert
1082
-		$this->assertFalse($result);
1083
-	}
1084
-
1085
-	public function testHandleImipReply(): void {
1086
-		/** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */
1087
-		$manager = $this->getMockBuilder(Manager::class)
1088
-			->setConstructorArgs([
1089
-				$this->coordinator,
1090
-				$this->container,
1091
-				$this->logger,
1092
-				$this->time,
1093
-				$this->secureRandom,
1094
-				$this->userManager,
1095
-				$this->serverFactory,
1096
-			])
1097
-			->onlyMethods([
1098
-				'getCalendarsForPrincipal'
1099
-			])
1100
-			->getMock();
1101
-		$calendar = $this->createMock(ITestCalendar::class);
1102
-		$principalUri = 'principals/user/linus';
1103
-		$sender = '[email protected]';
1104
-		$recipient = '[email protected]';
1105
-		$calendarData = clone $this->vCalendar2a;
1106
-		$calendarData->add('METHOD', 'REPLY');
1107
-
1108
-		$this->time->expects(self::once())
1109
-			->method('getTime')
1110
-			->willReturn(1628374233);
1111
-		$manager->expects(self::once())
1112
-			->method('getCalendarsForPrincipal')
1113
-			->willReturn([$calendar]);
1114
-		$calendar->expects(self::once())
1115
-			->method('search')
1116
-			->willReturn([['uri' => 'testname.ics']]);
1117
-		$calendar->expects(self::once())
1118
-			->method('handleIMipMessage')
1119
-			->with('testname.ics', $calendarData->serialize());
1120
-		// Act
1121
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
1122
-		// Assert
1123
-		$this->assertTrue($result);
1124
-	}
1125
-
1126
-	public function testHandleImipCancelWithNoCalendars(): void {
1127
-		// construct calendar manager returns
1128
-		/** @var Manager&MockObject $manager */
1129
-		$manager = $this->getMockBuilder(Manager::class)
1130
-			->setConstructorArgs([
1131
-				$this->coordinator,
1132
-				$this->container,
1133
-				$this->logger,
1134
-				$this->time,
1135
-				$this->secureRandom,
1136
-				$this->userManager,
1137
-				$this->serverFactory,
1138
-			])
1139
-			->onlyMethods(['getCalendarsForPrincipal'])
1140
-			->getMock();
1141
-		$manager->expects(self::once())
1142
-			->method('getCalendarsForPrincipal')
1143
-			->willReturn([]);
1144
-		// construct logger returns
1145
-		$this->logger->expects(self::once())->method('warning')
1146
-			->with('iMip message could not be processed because user has no calendars');
1147
-		// construct parameters
1148
-		$principalUri = 'principals/user/pierre';
1149
-		$sender = '[email protected]';
1150
-		$recipient = '[email protected]';
1151
-		$replyTo = null;
1152
-		$calendar = $this->vCalendar3a;
1153
-		$calendar->add('METHOD', 'CANCEL');
1154
-		// Act
1155
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1156
-		// Assert
1157
-		$this->assertFalse($result);
1158
-	}
1159
-
1160
-	public function testHandleImipCancelWithInvalidData(): void {
1161
-		// construct mock user calendar
1162
-		$userCalendar = $this->createMock(ITestCalendar::class);
1163
-		// construct mock calendar manager and returns
1164
-		/** @var Manager&MockObject $manager */
1165
-		$manager = $this->getMockBuilder(Manager::class)
1166
-			->setConstructorArgs([
1167
-				$this->coordinator,
1168
-				$this->container,
1169
-				$this->logger,
1170
-				$this->time,
1171
-				$this->secureRandom,
1172
-				$this->userManager,
1173
-				$this->serverFactory,
1174
-			])
1175
-			->onlyMethods(['getCalendarsForPrincipal'])
1176
-			->getMock();
1177
-		$manager->expects(self::once())
1178
-			->method('getCalendarsForPrincipal')
1179
-			->willReturn([$userCalendar]);
1180
-		// construct logger returns
1181
-		$this->logger->expects(self::once())->method('error')
1182
-			->with('iMip message could not be processed because an error occurred while parsing the iMip message');
1183
-		// construct parameters
1184
-		$principalUri = 'principals/user/attendee1';
1185
-		$sender = '[email protected]';
1186
-		$recipient = '[email protected]';
1187
-		$replyTo = null;
1188
-		// Act
1189
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, 'Invalid data');
1190
-		// Assert
1191
-		$this->assertFalse($result);
1192
-	}
1193
-
1194
-
1195
-	public function testHandleImipCancelWithNoMethod(): void {
1196
-		// construct mock user calendar
1197
-		$userCalendar = $this->createMock(ITestCalendar::class);
1198
-		// construct mock calendar manager and returns
1199
-		/** @var Manager&MockObject $manager */
1200
-		$manager = $this->getMockBuilder(Manager::class)
1201
-			->setConstructorArgs([
1202
-				$this->coordinator,
1203
-				$this->container,
1204
-				$this->logger,
1205
-				$this->time,
1206
-				$this->secureRandom,
1207
-				$this->userManager,
1208
-				$this->serverFactory,
1209
-			])
1210
-			->onlyMethods(['getCalendarsForPrincipal'])
1211
-			->getMock();
1212
-		$manager->expects(self::once())
1213
-			->method('getCalendarsForPrincipal')
1214
-			->willReturn([$userCalendar]);
1215
-		// construct logger returns
1216
-		$this->logger->expects(self::once())->method('warning')
1217
-			->with('iMip message contains an incorrect or invalid method');
1218
-		// construct parameters
1219
-		$principalUri = 'principals/user/pierre';
1220
-		$sender = '[email protected]';
1221
-		$recipient = '[email protected]';
1222
-		$replyTo = null;
1223
-		$calendar = $this->vCalendar3a;
1224
-		// Act
1225
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1226
-		// Assert
1227
-		$this->assertFalse($result);
1228
-	}
1229
-
1230
-	public function testHandleImipCancelWithInvalidMethod(): void {
1231
-		// construct mock user calendar
1232
-		$userCalendar = $this->createMock(ITestCalendar::class);
1233
-		// construct mock calendar manager and returns
1234
-		/** @var Manager&MockObject $manager */
1235
-		$manager = $this->getMockBuilder(Manager::class)
1236
-			->setConstructorArgs([
1237
-				$this->coordinator,
1238
-				$this->container,
1239
-				$this->logger,
1240
-				$this->time,
1241
-				$this->secureRandom,
1242
-				$this->userManager,
1243
-				$this->serverFactory,
1244
-			])
1245
-			->onlyMethods(['getCalendarsForPrincipal'])
1246
-			->getMock();
1247
-		$manager->expects(self::once())
1248
-			->method('getCalendarsForPrincipal')
1249
-			->willReturn([$userCalendar]);
1250
-		// construct logger returns
1251
-		$this->logger->expects(self::once())->method('warning')
1252
-			->with('iMip message contains an incorrect or invalid method');
1253
-		// construct parameters
1254
-		$principalUri = 'principals/user/pierre';
1255
-		$sender = '[email protected]';
1256
-		$recipient = '[email protected]';
1257
-		$replyTo = null;
1258
-		$calendar = $this->vCalendar3a;
1259
-		$calendar->add('METHOD', 'UNKNOWN');
1260
-		// Act
1261
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1262
-		// Assert
1263
-		$this->assertFalse($result);
1264
-	}
1265
-
1266
-	public function testHandleImipCancelWithNoEvent(): void {
1267
-		// construct mock user calendar
1268
-		$userCalendar = $this->createMock(ITestCalendar::class);
1269
-		// construct mock calendar manager and returns
1270
-		/** @var Manager&MockObject $manager */
1271
-		$manager = $this->getMockBuilder(Manager::class)
1272
-			->setConstructorArgs([
1273
-				$this->coordinator,
1274
-				$this->container,
1275
-				$this->logger,
1276
-				$this->time,
1277
-				$this->secureRandom,
1278
-				$this->userManager,
1279
-				$this->serverFactory,
1280
-			])
1281
-			->onlyMethods(['getCalendarsForPrincipal'])
1282
-			->getMock();
1283
-		$manager->expects(self::once())
1284
-			->method('getCalendarsForPrincipal')
1285
-			->willReturn([$userCalendar]);
1286
-		// construct logger returns
1287
-		$this->logger->expects(self::once())->method('warning')
1288
-			->with('iMip message contains no event');
1289
-		// construct parameters
1290
-		$principalUri = 'principals/user/pierre';
1291
-		$sender = '[email protected]';
1292
-		$recipient = '[email protected]';
1293
-		$replyTo = null;
1294
-		$calendar = $this->vCalendar3a;
1295
-		$calendar->add('METHOD', 'CANCEL');
1296
-		$calendar->remove('VEVENT');
1297
-		// Act
1298
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1299
-		// Assert
1300
-		$this->assertFalse($result);
1301
-	}
1302
-
1303
-	public function testHandleImipCancelWithNoUid(): void {
1304
-		// construct mock user calendar
1305
-		$userCalendar = $this->createMock(ITestCalendar::class);
1306
-		// construct mock calendar manager and returns
1307
-		/** @var Manager&MockObject $manager */
1308
-		$manager = $this->getMockBuilder(Manager::class)
1309
-			->setConstructorArgs([
1310
-				$this->coordinator,
1311
-				$this->container,
1312
-				$this->logger,
1313
-				$this->time,
1314
-				$this->secureRandom,
1315
-				$this->userManager,
1316
-				$this->serverFactory,
1317
-			])
1318
-			->onlyMethods(['getCalendarsForPrincipal'])
1319
-			->getMock();
1320
-		$manager->expects(self::once())
1321
-			->method('getCalendarsForPrincipal')
1322
-			->willReturn([$userCalendar]);
1323
-		// construct logger returns
1324
-		$this->logger->expects(self::once())->method('warning')
1325
-			->with('iMip message event dose not contains a UID');
1326
-		// construct parameters
1327
-		$principalUri = 'principals/user/pierre';
1328
-		$sender = '[email protected]';
1329
-		$recipient = '[email protected]';
1330
-		$replyTo = null;
1331
-		$calendar = $this->vCalendar3a;
1332
-		$calendar->add('METHOD', 'CANCEL');
1333
-		$calendar->VEVENT->remove('UID');
1334
-		// Act
1335
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1336
-		// Assert
1337
-		$this->assertFalse($result);
1338
-	}
1339
-
1340
-	public function testHandleImipCancelWithNoOrganizer(): void {
1341
-		// construct mock user calendar
1342
-		$userCalendar = $this->createMock(ITestCalendar::class);
1343
-		// construct mock calendar manager and returns
1344
-		/** @var Manager&MockObject $manager */
1345
-		$manager = $this->getMockBuilder(Manager::class)
1346
-			->setConstructorArgs([
1347
-				$this->coordinator,
1348
-				$this->container,
1349
-				$this->logger,
1350
-				$this->time,
1351
-				$this->secureRandom,
1352
-				$this->userManager,
1353
-				$this->serverFactory,
1354
-			])
1355
-			->onlyMethods(['getCalendarsForPrincipal'])
1356
-			->getMock();
1357
-		$manager->expects(self::once())
1358
-			->method('getCalendarsForPrincipal')
1359
-			->willReturn([$userCalendar]);
1360
-		// construct logger returns
1361
-		$this->logger->expects(self::once())->method('warning')
1362
-			->with('iMip message event dose not contains an organizer');
1363
-		// construct parameters
1364
-		$principalUri = 'principals/user/pierre';
1365
-		$sender = '[email protected]';
1366
-		$recipient = '[email protected]';
1367
-		$replyTo = null;
1368
-		$calendar = $this->vCalendar3a;
1369
-		$calendar->add('METHOD', 'CANCEL');
1370
-		$calendar->VEVENT->remove('ORGANIZER');
1371
-		// Act
1372
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1373
-		// Assert
1374
-		$this->assertFalse($result);
1375
-	}
1376
-
1377
-	public function testHandleImipCancelWithNoAttendee(): void {
1378
-		// construct mock user calendar
1379
-		$userCalendar = $this->createMock(ITestCalendar::class);
1380
-		// construct mock calendar manager and returns
1381
-		/** @var Manager&MockObject $manager */
1382
-		$manager = $this->getMockBuilder(Manager::class)
1383
-			->setConstructorArgs([
1384
-				$this->coordinator,
1385
-				$this->container,
1386
-				$this->logger,
1387
-				$this->time,
1388
-				$this->secureRandom,
1389
-				$this->userManager,
1390
-				$this->serverFactory,
1391
-			])
1392
-			->onlyMethods(['getCalendarsForPrincipal'])
1393
-			->getMock();
1394
-		$manager->expects(self::once())
1395
-			->method('getCalendarsForPrincipal')
1396
-			->willReturn([$userCalendar]);
1397
-		// construct logger returns
1398
-		$this->logger->expects(self::once())->method('warning')
1399
-			->with('iMip message event dose not contains any attendees');
1400
-		// construct parameters
1401
-		$principalUri = 'principals/user/pierre';
1402
-		$sender = '[email protected]';
1403
-		$recipient = '[email protected]';
1404
-		$replyTo = null;
1405
-		$calendar = $this->vCalendar3a;
1406
-		$calendar->add('METHOD', 'CANCEL');
1407
-		$calendar->VEVENT->remove('ATTENDEE');
1408
-		// Act
1409
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1410
-		// Assert
1411
-		$this->assertFalse($result);
1412
-	}
1413
-
1414
-	public function testHandleImipCancelAttendeeNotRecipient(): void {
1415
-		// construct mock user calendar
1416
-		$userCalendar = $this->createMock(ITestCalendar::class);
1417
-		// construct mock calendar manager and returns
1418
-		/** @var Manager&MockObject $manager */
1419
-		$manager = $this->getMockBuilder(Manager::class)
1420
-			->setConstructorArgs([
1421
-				$this->coordinator,
1422
-				$this->container,
1423
-				$this->logger,
1424
-				$this->time,
1425
-				$this->secureRandom,
1426
-				$this->userManager,
1427
-				$this->serverFactory,
1428
-			])
1429
-			->onlyMethods(['getCalendarsForPrincipal'])
1430
-			->getMock();
1431
-		$manager->expects(self::once())
1432
-			->method('getCalendarsForPrincipal')
1433
-			->willReturn([$userCalendar]);
1434
-		// construct logger returns
1435
-		$this->logger->expects(self::once())->method('warning')
1436
-			->with('iMip message event could not be processed because recipient must be an ATTENDEE of this event');
1437
-		// construct parameters
1438
-		$principalUri = 'principals/user/pierre';
1439
-		$sender = '[email protected]';
1440
-		$recipient = '[email protected]';
1441
-		$replyTo = null;
1442
-		$calendarData = clone $this->vCalendar3a;
1443
-		$calendarData->add('METHOD', 'CANCEL');
1444
-		// Act
1445
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
1446
-		// Assert
1447
-		$this->assertFalse($result);
1448
-	}
1449
-
1450
-	public function testHandleImipCancelDateInThePast(): void {
1451
-		// construct mock user calendar
1452
-		$userCalendar = $this->createMock(ITestCalendar::class);
1453
-		// construct mock calendar manager and returns
1454
-		/** @var Manager&MockObject $manager */
1455
-		$manager = $this->getMockBuilder(Manager::class)
1456
-			->setConstructorArgs([
1457
-				$this->coordinator,
1458
-				$this->container,
1459
-				$this->logger,
1460
-				$this->time,
1461
-				$this->secureRandom,
1462
-				$this->userManager,
1463
-				$this->serverFactory,
1464
-			])
1465
-			->onlyMethods(['getCalendarsForPrincipal'])
1466
-			->getMock();
1467
-		$manager->expects(self::once())
1468
-			->method('getCalendarsForPrincipal')
1469
-			->willReturn([$userCalendar]);
1470
-		// construct logger and time returns
1471
-		$this->logger->expects(self::once())->method('warning')
1472
-			->with('iMip message event could not be processed because the event is in the past');
1473
-		$this->time->expects(self::once())
1474
-			->method('getTime')
1475
-			->willReturn(time());
1476
-		// construct parameters
1477
-		$principalUri = 'principals/user/pierre';
1478
-		$sender = '[email protected]';
1479
-		$recipient = '[email protected]';
1480
-		$replyTo = null;
1481
-		$calendarData = clone $this->vCalendar3a;
1482
-		$calendarData->add('METHOD', 'CANCEL');
1483
-		$calendarData->VEVENT->DTSTART = new \DateTime('2013-04-07'); // set to in the past
1484
-		// Act
1485
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
1486
-		// Assert
1487
-		$this->assertFalse($result);
1488
-	}
1489
-
1490
-	public function testHandleImipCancelEventNotFound(): void {
1491
-		// construct mock user calendar
1492
-		$userCalendar = $this->createMock(ITestCalendar::class);
1493
-		$userCalendar->expects(self::once())
1494
-			->method('search')
1495
-			->willReturn([]);
1496
-		// construct mock calendar manager and returns
1497
-		/** @var Manager&MockObject $manager */
1498
-		$manager = $this->getMockBuilder(Manager::class)
1499
-			->setConstructorArgs([
1500
-				$this->coordinator,
1501
-				$this->container,
1502
-				$this->logger,
1503
-				$this->time,
1504
-				$this->secureRandom,
1505
-				$this->userManager,
1506
-				$this->serverFactory,
1507
-			])
1508
-			->onlyMethods(['getCalendarsForPrincipal'])
1509
-			->getMock();
1510
-		$manager->expects(self::once())
1511
-			->method('getCalendarsForPrincipal')
1512
-			->willReturn([$userCalendar]);
1513
-		// construct time returns
1514
-		$this->time->expects(self::once())
1515
-			->method('getTime')
1516
-			->willReturn(1628374233);
1517
-		// construct parameters
1518
-		$principalUri = 'principals/user/pierre';
1519
-		$sender = '[email protected]';
1520
-		$recipient = '[email protected]';
1521
-		$replyTo = null;
1522
-		$calendarData = clone $this->vCalendar3a;
1523
-		$calendarData->add('METHOD', 'CANCEL');
1524
-		// construct logger return
1525
-		$this->logger->expects(self::once())->method('warning')
1526
-			->with('iMip message event could not be processed because no corresponding event was found in any calendar', ['principalUri' => $principalUri, 'eventUid' => $calendarData->VEVENT->UID->getValue()]);
1527
-		// Act
1528
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
1529
-		// Assert
1530
-		$this->assertFalse($result);
1531
-	}
1532
-
1533
-	public function testHandleImipCancelOrganiserInReplyTo(): void {
1534
-		/** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */
1535
-		$manager = $this->getMockBuilder(Manager::class)
1536
-			->setConstructorArgs([
1537
-				$this->coordinator,
1538
-				$this->container,
1539
-				$this->logger,
1540
-				$this->time,
1541
-				$this->secureRandom,
1542
-				$this->userManager,
1543
-				$this->serverFactory,
1544
-			])
1545
-			->onlyMethods([
1546
-				'getCalendarsForPrincipal'
1547
-			])
1548
-			->getMock();
1549
-
1550
-		$principalUri = 'principals/user/pierre';
1551
-		$sender = '[email protected]';
1552
-		$recipient = '[email protected]';
1553
-		$replyTo = '[email protected]';
1554
-		$calendar = $this->createMock(ITestCalendar::class);
1555
-		$calendarData = clone $this->vCalendar3a;
1556
-		$calendarData->add('METHOD', 'CANCEL');
1557
-
1558
-		$this->time->expects(self::once())
1559
-			->method('getTime')
1560
-			->willReturn(1628374233);
1561
-		$manager->expects(self::once())
1562
-			->method('getCalendarsForPrincipal')
1563
-			->with($principalUri)
1564
-			->willReturn([$calendar]);
1565
-		$calendar->expects(self::once())
1566
-			->method('search')
1567
-			->willReturn([['uri' => 'testname.ics']]);
1568
-		$calendar->expects(self::once())
1569
-			->method('handleIMipMessage')
1570
-			->with('testname.ics', $calendarData->serialize());
1571
-		// Act
1572
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
1573
-		// Assert
1574
-		$this->assertTrue($result);
1575
-	}
1576
-
1577
-	public function testHandleImipCancel(): void {
1578
-		/** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */
1579
-		$manager = $this->getMockBuilder(Manager::class)
1580
-			->setConstructorArgs([
1581
-				$this->coordinator,
1582
-				$this->container,
1583
-				$this->logger,
1584
-				$this->time,
1585
-				$this->secureRandom,
1586
-				$this->userManager,
1587
-				$this->serverFactory,
1588
-			])
1589
-			->onlyMethods([
1590
-				'getCalendarsForPrincipal'
1591
-			])
1592
-			->getMock();
1593
-		$principalUri = 'principals/user/pierre';
1594
-		$sender = '[email protected]';
1595
-		$recipient = '[email protected]';
1596
-		$replyTo = null;
1597
-		$calendar = $this->createMock(ITestCalendar::class);
1598
-		$calendarData = clone $this->vCalendar3a;
1599
-		$calendarData->add('METHOD', 'CANCEL');
1600
-
1601
-		$this->time->expects(self::once())
1602
-			->method('getTime')
1603
-			->willReturn(1628374233);
1604
-		$manager->expects(self::once())
1605
-			->method('getCalendarsForPrincipal')
1606
-			->with($principalUri)
1607
-			->willReturn([$calendar]);
1608
-		$calendar->expects(self::once())
1609
-			->method('search')
1610
-			->willReturn([['uri' => 'testname.ics']]);
1611
-		$calendar->expects(self::once())
1612
-			->method('handleIMipMessage')
1613
-			->with('testname.ics', $calendarData->serialize());
1614
-		// Act
1615
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
1616
-		// Assert
1617
-		$this->assertTrue($result);
1618
-	}
1619
-
1620
-	private function getFreeBusyResponse(): string {
1621
-		return <<<EOF
41
+    /** @var Coordinator&MockObject */
42
+    private $coordinator;
43
+
44
+    /** @var ContainerInterface&MockObject */
45
+    private $container;
46
+
47
+    /** @var LoggerInterface&MockObject */
48
+    private $logger;
49
+
50
+    /** @var Manager */
51
+    private $manager;
52
+
53
+    /** @var ITimeFactory&MockObject */
54
+    private $time;
55
+
56
+    /** @var ISecureRandom&MockObject */
57
+    private ISecureRandom $secureRandom;
58
+
59
+    private IUserManager&MockObject $userManager;
60
+    private ServerFactory&MockObject $serverFactory;
61
+
62
+    private VCalendar $vCalendar1a;
63
+    private VCalendar $vCalendar2a;
64
+    private VCalendar $vCalendar3a;
65
+
66
+    protected function setUp(): void {
67
+        parent::setUp();
68
+
69
+        $this->coordinator = $this->createMock(Coordinator::class);
70
+        $this->container = $this->createMock(ContainerInterface::class);
71
+        $this->logger = $this->createMock(LoggerInterface::class);
72
+        $this->time = $this->createMock(ITimeFactory::class);
73
+        $this->secureRandom = $this->createMock(ISecureRandom::class);
74
+        $this->userManager = $this->createMock(IUserManager::class);
75
+        $this->serverFactory = $this->createMock(ServerFactory::class);
76
+
77
+        $this->manager = new Manager(
78
+            $this->coordinator,
79
+            $this->container,
80
+            $this->logger,
81
+            $this->time,
82
+            $this->secureRandom,
83
+            $this->userManager,
84
+            $this->serverFactory,
85
+        );
86
+
87
+        // construct calendar with a 1 hour event and same start/end time zones
88
+        $this->vCalendar1a = new VCalendar();
89
+        /** @var VEvent $vEvent */
90
+        $vEvent = $this->vCalendar1a->add('VEVENT', []);
91
+        $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
92
+        $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
93
+        $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
94
+        $vEvent->add('SUMMARY', 'Test Event');
95
+        $vEvent->add('SEQUENCE', 3);
96
+        $vEvent->add('STATUS', 'CONFIRMED');
97
+        $vEvent->add('TRANSP', 'OPAQUE');
98
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
99
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
100
+            'CN' => 'Attendee One',
101
+            'CUTYPE' => 'INDIVIDUAL',
102
+            'PARTSTAT' => 'NEEDS-ACTION',
103
+            'ROLE' => 'REQ-PARTICIPANT',
104
+            'RSVP' => 'TRUE'
105
+        ]);
106
+
107
+        // construct calendar with a event for reply
108
+        $this->vCalendar2a = new VCalendar();
109
+        /** @var VEvent $vEvent */
110
+        $vEvent = $this->vCalendar2a->add('VEVENT', []);
111
+        $vEvent->UID->setValue('dcc733bf-b2b2-41f2-a8cf-550ae4b67aff');
112
+        $vEvent->add('DTSTART', '20210820');
113
+        $vEvent->add('DTEND', '20220821');
114
+        $vEvent->add('SUMMARY', 'berry basket');
115
+        $vEvent->add('SEQUENCE', 3);
116
+        $vEvent->add('STATUS', 'CONFIRMED');
117
+        $vEvent->add('TRANSP', 'OPAQUE');
118
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'admin']);
119
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
120
+            'CN' => '[email protected]',
121
+            'CUTYPE' => 'INDIVIDUAL',
122
+            'ROLE' => 'REQ-PARTICIPANT',
123
+            'PARTSTAT' => 'ACCEPTED',
124
+        ]);
125
+
126
+        // construct calendar with a event for reply
127
+        $this->vCalendar3a = new VCalendar();
128
+        /** @var VEvent $vEvent */
129
+        $vEvent = $this->vCalendar3a->add('VEVENT', []);
130
+        $vEvent->UID->setValue('dcc733bf-b2b2-41f2-a8cf-550ae4b67aff');
131
+        $vEvent->add('DTSTART', '20210820');
132
+        $vEvent->add('DTEND', '20220821');
133
+        $vEvent->add('SUMMARY', 'berry basket');
134
+        $vEvent->add('SEQUENCE', 3);
135
+        $vEvent->add('STATUS', 'CANCELLED');
136
+        $vEvent->add('TRANSP', 'OPAQUE');
137
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'admin']);
138
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
139
+            'CN' => '[email protected]',
140
+            'CUTYPE' => 'INDIVIDUAL',
141
+            'ROLE' => 'REQ-PARTICIPANT',
142
+            'PARTSTAT' => 'ACCEPTED',
143
+        ]);
144
+
145
+    }
146
+
147
+    #[\PHPUnit\Framework\Attributes\DataProvider('searchProvider')]
148
+    public function testSearch($search1, $search2, $expected): void {
149
+        /** @var ICalendar | MockObject $calendar1 */
150
+        $calendar1 = $this->createMock(ICalendar::class);
151
+        $calendar1->method('getKey')->willReturn('simple:1');
152
+        $calendar1->expects($this->once())
153
+            ->method('search')
154
+            ->with('', [], [], null, null)
155
+            ->willReturn($search1);
156
+
157
+        /** @var ICalendar | MockObject $calendar2 */
158
+        $calendar2 = $this->createMock(ICalendar::class);
159
+        $calendar2->method('getKey')->willReturn('simple:2');
160
+        $calendar2->expects($this->once())
161
+            ->method('search')
162
+            ->with('', [], [], null, null)
163
+            ->willReturn($search2);
164
+
165
+        $this->manager->registerCalendar($calendar1);
166
+        $this->manager->registerCalendar($calendar2);
167
+
168
+        $result = $this->manager->search('');
169
+        $this->assertEquals($expected, $result);
170
+    }
171
+
172
+    #[\PHPUnit\Framework\Attributes\DataProvider('searchProvider')]
173
+    public function testSearchOptions($search1, $search2, $expected): void {
174
+        /** @var ICalendar | MockObject $calendar1 */
175
+        $calendar1 = $this->createMock(ICalendar::class);
176
+        $calendar1->method('getKey')->willReturn('simple:1');
177
+        $calendar1->expects($this->once())
178
+            ->method('search')
179
+            ->with('searchTerm', ['SUMMARY', 'DESCRIPTION'],
180
+                ['timerange' => ['start' => null, 'end' => null]], 5, 20)
181
+            ->willReturn($search1);
182
+
183
+        /** @var ICalendar | MockObject $calendar2 */
184
+        $calendar2 = $this->createMock(ICalendar::class);
185
+        $calendar2->method('getKey')->willReturn('simple:2');
186
+        $calendar2->expects($this->once())
187
+            ->method('search')
188
+            ->with('searchTerm', ['SUMMARY', 'DESCRIPTION'],
189
+                ['timerange' => ['start' => null, 'end' => null]], 5, 20)
190
+            ->willReturn($search2);
191
+
192
+        $this->manager->registerCalendar($calendar1);
193
+        $this->manager->registerCalendar($calendar2);
194
+
195
+        $result = $this->manager->search('searchTerm', ['SUMMARY', 'DESCRIPTION'],
196
+            ['timerange' => ['start' => null, 'end' => null]], 5, 20);
197
+        $this->assertEquals($expected, $result);
198
+    }
199
+
200
+    public static function searchProvider(): array {
201
+        $search1 = [
202
+            [
203
+                'id' => 1,
204
+                'data' => 'foobar',
205
+            ],
206
+            [
207
+                'id' => 2,
208
+                'data' => 'barfoo',
209
+            ]
210
+        ];
211
+        $search2 = [
212
+            [
213
+                'id' => 3,
214
+                'data' => 'blablub',
215
+            ],
216
+            [
217
+                'id' => 4,
218
+                'data' => 'blubbla',
219
+            ]
220
+        ];
221
+
222
+        $expected = [
223
+            [
224
+                'id' => 1,
225
+                'data' => 'foobar',
226
+                'calendar-key' => 'simple:1',
227
+            ],
228
+            [
229
+                'id' => 2,
230
+                'data' => 'barfoo',
231
+                'calendar-key' => 'simple:1',
232
+            ],
233
+            [
234
+                'id' => 3,
235
+                'data' => 'blablub',
236
+                'calendar-key' => 'simple:2',
237
+            ],
238
+            [
239
+                'id' => 4,
240
+                'data' => 'blubbla',
241
+                'calendar-key' => 'simple:2',
242
+            ]
243
+        ];
244
+
245
+        return [
246
+            [
247
+                $search1,
248
+                $search2,
249
+                $expected
250
+            ]
251
+        ];
252
+    }
253
+
254
+    public function testRegisterUnregister(): void {
255
+        /** @var ICalendar | MockObject $calendar1 */
256
+        $calendar1 = $this->createMock(ICalendar::class);
257
+        $calendar1->method('getKey')->willReturn('key1');
258
+
259
+        /** @var ICalendar | MockObject $calendar2 */
260
+        $calendar2 = $this->createMock(ICalendar::class);
261
+        $calendar2->method('getKey')->willReturn('key2');
262
+
263
+        $this->manager->registerCalendar($calendar1);
264
+        $this->manager->registerCalendar($calendar2);
265
+
266
+        $result = $this->manager->getCalendars();
267
+        $this->assertCount(2, $result);
268
+        $this->assertContains($calendar1, $result);
269
+        $this->assertContains($calendar2, $result);
270
+
271
+        $this->manager->unregisterCalendar($calendar1);
272
+
273
+        $result = $this->manager->getCalendars();
274
+        $this->assertCount(1, $result);
275
+        $this->assertContains($calendar2, $result);
276
+    }
277
+
278
+    public function testGetCalendars(): void {
279
+        /** @var ICalendar | MockObject $calendar1 */
280
+        $calendar1 = $this->createMock(ICalendar::class);
281
+        $calendar1->method('getKey')->willReturn('key1');
282
+
283
+        /** @var ICalendar | MockObject $calendar2 */
284
+        $calendar2 = $this->createMock(ICalendar::class);
285
+        $calendar2->method('getKey')->willReturn('key2');
286
+
287
+        $this->manager->registerCalendar($calendar1);
288
+        $this->manager->registerCalendar($calendar2);
289
+
290
+        $result = $this->manager->getCalendars();
291
+        $this->assertCount(2, $result);
292
+        $this->assertContains($calendar1, $result);
293
+        $this->assertContains($calendar2, $result);
294
+
295
+        $this->manager->clear();
296
+
297
+        $result = $this->manager->getCalendars();
298
+
299
+        $this->assertCount(0, $result);
300
+    }
301
+
302
+    public function testEnabledIfNot(): void {
303
+        $isEnabled = $this->manager->isEnabled();
304
+        $this->assertFalse($isEnabled);
305
+    }
306
+
307
+    public function testIfEnabledIfSo(): void {
308
+        /** @var ICalendar | MockObject $calendar */
309
+        $calendar = $this->createMock(ICalendar::class);
310
+        $this->manager->registerCalendar($calendar);
311
+
312
+        $isEnabled = $this->manager->isEnabled();
313
+        $this->assertTrue($isEnabled);
314
+    }
315
+
316
+    public function testHandleImipRequestWithNoCalendars(): void {
317
+        // construct calendar manager returns
318
+        /** @var Manager&MockObject $manager */
319
+        $manager = $this->getMockBuilder(Manager::class)
320
+            ->setConstructorArgs([
321
+                $this->coordinator,
322
+                $this->container,
323
+                $this->logger,
324
+                $this->time,
325
+                $this->secureRandom,
326
+                $this->userManager,
327
+                $this->serverFactory,
328
+            ])
329
+            ->onlyMethods(['getCalendarsForPrincipal'])
330
+            ->getMock();
331
+        $manager->expects(self::once())
332
+            ->method('getCalendarsForPrincipal')
333
+            ->willReturn([]);
334
+        // construct logger returns
335
+        $this->logger->expects(self::once())->method('warning')
336
+            ->with('iMip message could not be processed because user has no calendars');
337
+        // construct parameters
338
+        $principalUri = 'principals/user/attendee1';
339
+        $sender = '[email protected]';
340
+        $recipient = '[email protected]';
341
+        $calendar = $this->vCalendar1a;
342
+        $calendar->add('METHOD', 'REQUEST');
343
+        // Act
344
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
345
+        // Assert
346
+        $this->assertFalse($result);
347
+    }
348
+
349
+    public function testHandleImipRequestWithInvalidData(): void {
350
+        // construct mock user calendar
351
+        $userCalendar = $this->createMock(ITestCalendar::class);
352
+        // construct mock calendar manager and returns
353
+        /** @var Manager&MockObject $manager */
354
+        $manager = $this->getMockBuilder(Manager::class)
355
+            ->setConstructorArgs([
356
+                $this->coordinator,
357
+                $this->container,
358
+                $this->logger,
359
+                $this->time,
360
+                $this->secureRandom,
361
+                $this->userManager,
362
+                $this->serverFactory,
363
+            ])
364
+            ->onlyMethods(['getCalendarsForPrincipal'])
365
+            ->getMock();
366
+        $manager->expects(self::once())
367
+            ->method('getCalendarsForPrincipal')
368
+            ->willReturn([$userCalendar]);
369
+        // construct logger returns
370
+        $this->logger->expects(self::once())->method('error')
371
+            ->with('iMip message could not be processed because an error occurred while parsing the iMip message');
372
+        // construct parameters
373
+        $principalUri = 'principals/user/attendee1';
374
+        $sender = '[email protected]';
375
+        $recipient = '[email protected]';
376
+        // Act
377
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, 'Invalid data');
378
+        // Assert
379
+        $this->assertFalse($result);
380
+    }
381
+
382
+    public function testHandleImipRequestWithNoMethod(): void {
383
+        // construct mock user calendar
384
+        $userCalendar = $this->createMock(ITestCalendar::class);
385
+        // construct mock calendar manager and returns
386
+        /** @var Manager&MockObject $manager */
387
+        $manager = $this->getMockBuilder(Manager::class)
388
+            ->setConstructorArgs([
389
+                $this->coordinator,
390
+                $this->container,
391
+                $this->logger,
392
+                $this->time,
393
+                $this->secureRandom,
394
+                $this->userManager,
395
+                $this->serverFactory,
396
+            ])
397
+            ->onlyMethods(['getCalendarsForPrincipal'])
398
+            ->getMock();
399
+        $manager->expects(self::once())
400
+            ->method('getCalendarsForPrincipal')
401
+            ->willReturn([$userCalendar]);
402
+        // construct logger returns
403
+        $this->logger->expects(self::once())->method('warning')
404
+            ->with('iMip message contains an incorrect or invalid method');
405
+        // construct parameters
406
+        $principalUri = 'principals/user/attendee1';
407
+        $sender = '[email protected]';
408
+        $recipient = '[email protected]';
409
+        $calendar = $this->vCalendar1a;
410
+        // Act
411
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
412
+        // Assert
413
+        $this->assertFalse($result);
414
+    }
415
+
416
+    public function testHandleImipRequestWithInvalidMethod(): void {
417
+        // construct mock user calendar
418
+        $userCalendar = $this->createMock(ITestCalendar::class);
419
+        // construct mock calendar manager and returns
420
+        /** @var Manager&MockObject $manager */
421
+        $manager = $this->getMockBuilder(Manager::class)
422
+            ->setConstructorArgs([
423
+                $this->coordinator,
424
+                $this->container,
425
+                $this->logger,
426
+                $this->time,
427
+                $this->secureRandom,
428
+                $this->userManager,
429
+                $this->serverFactory,
430
+            ])
431
+            ->onlyMethods(['getCalendarsForPrincipal'])
432
+            ->getMock();
433
+        $manager->expects(self::once())
434
+            ->method('getCalendarsForPrincipal')
435
+            ->willReturn([$userCalendar]);
436
+        // construct logger returns
437
+        $this->logger->expects(self::once())->method('warning')
438
+            ->with('iMip message contains an incorrect or invalid method');
439
+        // construct parameters
440
+        $principalUri = 'principals/user/attendee1';
441
+        $sender = '[email protected]';
442
+        $recipient = '[email protected]';
443
+        $calendar = $this->vCalendar1a;
444
+        $calendar->add('METHOD', 'CANCEL');
445
+        // Act
446
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
447
+        // Assert
448
+        $this->assertFalse($result);
449
+    }
450
+
451
+    public function testHandleImipRequestWithNoEvent(): void {
452
+        // construct mock user calendar
453
+        $userCalendar = $this->createMock(ITestCalendar::class);
454
+        // construct mock calendar manager and returns
455
+        /** @var Manager&MockObject $manager */
456
+        $manager = $this->getMockBuilder(Manager::class)
457
+            ->setConstructorArgs([
458
+                $this->coordinator,
459
+                $this->container,
460
+                $this->logger,
461
+                $this->time,
462
+                $this->secureRandom,
463
+                $this->userManager,
464
+                $this->serverFactory,
465
+            ])
466
+            ->onlyMethods(['getCalendarsForPrincipal'])
467
+            ->getMock();
468
+        $manager->expects(self::once())
469
+            ->method('getCalendarsForPrincipal')
470
+            ->willReturn([$userCalendar]);
471
+        // construct logger returns
472
+        $this->logger->expects(self::once())->method('warning')
473
+            ->with('iMip message contains no event');
474
+        // construct parameters
475
+        $principalUri = 'principals/user/attendee1';
476
+        $sender = '[email protected]';
477
+        $recipient = '[email protected]';
478
+        $calendar = $this->vCalendar1a;
479
+        $calendar->add('METHOD', 'REQUEST');
480
+        $calendar->remove('VEVENT');
481
+        // Act
482
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
483
+        // Assert
484
+        $this->assertFalse($result);
485
+    }
486
+
487
+    public function testHandleImipRequestWithNoUid(): void {
488
+        // construct mock user calendar
489
+        $userCalendar = $this->createMock(ITestCalendar::class);
490
+        // construct mock calendar manager and returns
491
+        /** @var Manager&MockObject $manager */
492
+        $manager = $this->getMockBuilder(Manager::class)
493
+            ->setConstructorArgs([
494
+                $this->coordinator,
495
+                $this->container,
496
+                $this->logger,
497
+                $this->time,
498
+                $this->secureRandom,
499
+                $this->userManager,
500
+                $this->serverFactory,
501
+            ])
502
+            ->onlyMethods(['getCalendarsForPrincipal'])
503
+            ->getMock();
504
+        $manager->expects(self::once())
505
+            ->method('getCalendarsForPrincipal')
506
+            ->willReturn([$userCalendar]);
507
+        // construct logger returns
508
+        $this->logger->expects(self::once())->method('warning')
509
+            ->with('iMip message event dose not contains a UID');
510
+        // construct parameters
511
+        $principalUri = 'principals/user/attendee1';
512
+        $sender = '[email protected]';
513
+        $recipient = '[email protected]';
514
+        $calendar = $this->vCalendar1a;
515
+        $calendar->add('METHOD', 'REQUEST');
516
+        $calendar->VEVENT->remove('UID');
517
+        // Act
518
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
519
+        // Assert
520
+        $this->assertFalse($result);
521
+    }
522
+
523
+    public function testHandleImipRequestWithNoOrganizer(): void {
524
+        // construct mock user calendar
525
+        $userCalendar = $this->createMock(ITestCalendar::class);
526
+        // construct mock calendar manager and returns
527
+        /** @var Manager&MockObject $manager */
528
+        $manager = $this->getMockBuilder(Manager::class)
529
+            ->setConstructorArgs([
530
+                $this->coordinator,
531
+                $this->container,
532
+                $this->logger,
533
+                $this->time,
534
+                $this->secureRandom,
535
+                $this->userManager,
536
+                $this->serverFactory,
537
+            ])
538
+            ->onlyMethods(['getCalendarsForPrincipal'])
539
+            ->getMock();
540
+        $manager->expects(self::once())
541
+            ->method('getCalendarsForPrincipal')
542
+            ->willReturn([$userCalendar]);
543
+        // construct logger returns
544
+        $this->logger->expects(self::once())->method('warning')
545
+            ->with('iMip message event dose not contains an organizer');
546
+        // construct parameters
547
+        $principalUri = 'principals/user/attendee1';
548
+        $sender = '[email protected]';
549
+        $recipient = '[email protected]';
550
+        $calendar = $this->vCalendar1a;
551
+        $calendar->add('METHOD', 'REQUEST');
552
+        $calendar->VEVENT->remove('ORGANIZER');
553
+        // Act
554
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
555
+        // Assert
556
+        $this->assertFalse($result);
557
+    }
558
+
559
+    public function testHandleImipRequestWithNoAttendee(): void {
560
+        // construct mock user calendar
561
+        $userCalendar = $this->createMock(ITestCalendar::class);
562
+        // construct mock calendar manager and returns
563
+        /** @var Manager&MockObject $manager */
564
+        $manager = $this->getMockBuilder(Manager::class)
565
+            ->setConstructorArgs([
566
+                $this->coordinator,
567
+                $this->container,
568
+                $this->logger,
569
+                $this->time,
570
+                $this->secureRandom,
571
+                $this->userManager,
572
+                $this->serverFactory,
573
+            ])
574
+            ->onlyMethods(['getCalendarsForPrincipal'])
575
+            ->getMock();
576
+        $manager->expects(self::once())
577
+            ->method('getCalendarsForPrincipal')
578
+            ->willReturn([$userCalendar]);
579
+        // construct logger returns
580
+        $this->logger->expects(self::once())->method('warning')
581
+            ->with('iMip message event dose not contains any attendees');
582
+        // construct parameters
583
+        $principalUri = 'principals/user/attendee1';
584
+        $sender = '[email protected]';
585
+        $recipient = '[email protected]';
586
+        $calendar = $this->vCalendar1a;
587
+        $calendar->add('METHOD', 'REQUEST');
588
+        $calendar->VEVENT->remove('ATTENDEE');
589
+        // Act
590
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
591
+        // Assert
592
+        $this->assertFalse($result);
593
+    }
594
+
595
+    public function testHandleImipRequestWithInvalidAttendee(): void {
596
+        // construct mock user calendar
597
+        $userCalendar = $this->createMock(ITestCalendar::class);
598
+        // construct mock calendar manager and returns
599
+        /** @var Manager&MockObject $manager */
600
+        $manager = $this->getMockBuilder(Manager::class)
601
+            ->setConstructorArgs([
602
+                $this->coordinator,
603
+                $this->container,
604
+                $this->logger,
605
+                $this->time,
606
+                $this->secureRandom,
607
+                $this->userManager,
608
+                $this->serverFactory,
609
+            ])
610
+            ->onlyMethods(['getCalendarsForPrincipal'])
611
+            ->getMock();
612
+        $manager->expects(self::once())
613
+            ->method('getCalendarsForPrincipal')
614
+            ->willReturn([$userCalendar]);
615
+        // construct logger returns
616
+        $this->logger->expects(self::once())->method('warning')
617
+            ->with('iMip message event does not contain a attendee that matches the recipient');
618
+        // construct parameters
619
+        $principalUri = 'principals/user/attendee1';
620
+        $sender = '[email protected]';
621
+        $recipient = '[email protected]';
622
+        $calendar = $this->vCalendar1a;
623
+        $calendar->add('METHOD', 'REQUEST');
624
+        // Act
625
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
626
+        // Assert
627
+        $this->assertFalse($result);
628
+    }
629
+
630
+    public function testHandleImipRequestWithNoMatch(): void {
631
+        // construct mock user calendar
632
+        $userCalendar = $this->createMock(ITestCalendar::class);
633
+        $userCalendar->expects(self::once())
634
+            ->method('isDeleted')
635
+            ->willReturn(false);
636
+        $userCalendar->expects(self::once())
637
+            ->method('isWritable')
638
+            ->willReturn(true);
639
+        $userCalendar->expects(self::once())
640
+            ->method('isShared')
641
+            ->willReturn(false);
642
+        $userCalendar->expects(self::once())
643
+            ->method('search')
644
+            ->willReturn([]);
645
+        // construct mock calendar manager and returns
646
+        /** @var Manager&MockObject $manager */
647
+        $manager = $this->getMockBuilder(Manager::class)
648
+            ->setConstructorArgs([
649
+                $this->coordinator,
650
+                $this->container,
651
+                $this->logger,
652
+                $this->time,
653
+                $this->secureRandom,
654
+                $this->userManager,
655
+                $this->serverFactory,
656
+            ])
657
+            ->onlyMethods(['getCalendarsForPrincipal'])
658
+            ->getMock();
659
+        $manager->expects(self::once())
660
+            ->method('getCalendarsForPrincipal')
661
+            ->willReturn([$userCalendar]);
662
+        // construct logger returns
663
+        $this->logger->expects(self::once())->method('warning')
664
+            ->with('iMip message event could not be processed because no corresponding event was found in any calendar');
665
+        // construct parameters
666
+        $principalUri = 'principals/user/attendee1';
667
+        $sender = '[email protected]';
668
+        $recipient = '[email protected]';
669
+        $calendar = $this->vCalendar1a;
670
+        $calendar->add('METHOD', 'REQUEST');
671
+        // Act
672
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
673
+        // Assert
674
+        $this->assertFalse($result);
675
+    }
676
+
677
+    public function testHandleImipRequest(): void {
678
+        // construct mock user calendar
679
+        $userCalendar = $this->createMock(ITestCalendar::class);
680
+        $userCalendar->expects(self::once())
681
+            ->method('isDeleted')
682
+            ->willReturn(false);
683
+        $userCalendar->expects(self::once())
684
+            ->method('isWritable')
685
+            ->willReturn(true);
686
+        $userCalendar->expects(self::once())
687
+            ->method('isShared')
688
+            ->willReturn(false);
689
+        $userCalendar->expects(self::once())
690
+            ->method('search')
691
+            ->willReturn([['uri' => 'principals/user/attendee1/personal']]);
692
+        // construct mock calendar manager and returns
693
+        /** @var Manager&MockObject $manager */
694
+        $manager = $this->getMockBuilder(Manager::class)
695
+            ->setConstructorArgs([
696
+                $this->coordinator,
697
+                $this->container,
698
+                $this->logger,
699
+                $this->time,
700
+                $this->secureRandom,
701
+                $this->userManager,
702
+                $this->serverFactory,
703
+            ])
704
+            ->onlyMethods(['getCalendarsForPrincipal'])
705
+            ->getMock();
706
+        $manager->expects(self::once())
707
+            ->method('getCalendarsForPrincipal')
708
+            ->willReturn([$userCalendar]);
709
+        // construct parameters
710
+        $principalUri = 'principals/user/attendee1';
711
+        $sender = '[email protected]';
712
+        $recipient = '[email protected]';
713
+        $calendar = $this->vCalendar1a;
714
+        $calendar->add('METHOD', 'REQUEST');
715
+        // construct user calendar returns
716
+        $userCalendar->expects(self::once())
717
+            ->method('handleIMipMessage')
718
+            ->with('', $calendar->serialize());
719
+        // Act
720
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize());
721
+        // Assert
722
+        $this->assertTrue($result);
723
+    }
724
+
725
+    public function testHandleImipReplyWithNoCalendars(): void {
726
+        // construct calendar manager returns
727
+        /** @var Manager&MockObject $manager */
728
+        $manager = $this->getMockBuilder(Manager::class)
729
+            ->setConstructorArgs([
730
+                $this->coordinator,
731
+                $this->container,
732
+                $this->logger,
733
+                $this->time,
734
+                $this->secureRandom,
735
+                $this->userManager,
736
+                $this->serverFactory,
737
+            ])
738
+            ->onlyMethods(['getCalendarsForPrincipal'])
739
+            ->getMock();
740
+        $manager->expects(self::once())
741
+            ->method('getCalendarsForPrincipal')
742
+            ->willReturn([]);
743
+        // construct logger returns
744
+        $this->logger->expects(self::once())->method('warning')
745
+            ->with('iMip message could not be processed because user has no calendars');
746
+        // construct parameters
747
+        $principalUri = 'principals/user/linus';
748
+        $sender = '[email protected]';
749
+        $recipient = '[email protected]';
750
+        $calendar = $this->vCalendar2a;
751
+        $calendar->add('METHOD', 'REPLY');
752
+        // Act
753
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
754
+        // Assert
755
+        $this->assertFalse($result);
756
+    }
757
+
758
+    public function testHandleImipReplyWithInvalidData(): void {
759
+        // construct mock user calendar
760
+        $userCalendar = $this->createMock(ITestCalendar::class);
761
+        // construct mock calendar manager and returns
762
+        /** @var Manager&MockObject $manager */
763
+        $manager = $this->getMockBuilder(Manager::class)
764
+            ->setConstructorArgs([
765
+                $this->coordinator,
766
+                $this->container,
767
+                $this->logger,
768
+                $this->time,
769
+                $this->secureRandom,
770
+                $this->userManager,
771
+                $this->serverFactory,
772
+            ])
773
+            ->onlyMethods(['getCalendarsForPrincipal'])
774
+            ->getMock();
775
+        $manager->expects(self::once())
776
+            ->method('getCalendarsForPrincipal')
777
+            ->willReturn([$userCalendar]);
778
+        // construct logger returns
779
+        $this->logger->expects(self::once())->method('error')
780
+            ->with('iMip message could not be processed because an error occurred while parsing the iMip message');
781
+        // construct parameters
782
+        $principalUri = 'principals/user/attendee1';
783
+        $sender = '[email protected]';
784
+        $recipient = '[email protected]';
785
+        // Act
786
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, 'Invalid data');
787
+        // Assert
788
+        $this->assertFalse($result);
789
+    }
790
+
791
+    public function testHandleImipReplyWithNoMethod(): void {
792
+        // construct mock user calendar
793
+        $userCalendar = $this->createMock(ITestCalendar::class);
794
+        // construct mock calendar manager and returns
795
+        /** @var Manager&MockObject $manager */
796
+        $manager = $this->getMockBuilder(Manager::class)
797
+            ->setConstructorArgs([
798
+                $this->coordinator,
799
+                $this->container,
800
+                $this->logger,
801
+                $this->time,
802
+                $this->secureRandom,
803
+                $this->userManager,
804
+                $this->serverFactory,
805
+            ])
806
+            ->onlyMethods(['getCalendarsForPrincipal'])
807
+            ->getMock();
808
+        $manager->expects(self::once())
809
+            ->method('getCalendarsForPrincipal')
810
+            ->willReturn([$userCalendar]);
811
+        // construct logger returns
812
+        $this->logger->expects(self::once())->method('warning')
813
+            ->with('iMip message contains an incorrect or invalid method');
814
+        // construct parameters
815
+        $principalUri = 'principals/user/linus';
816
+        $sender = '[email protected]';
817
+        $recipient = '[email protected]';
818
+        $calendar = $this->vCalendar2a;
819
+        // Act
820
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
821
+        // Assert
822
+        $this->assertFalse($result);
823
+    }
824
+
825
+    public function testHandleImipReplyWithInvalidMethod(): void {
826
+        // construct mock user calendar
827
+        $userCalendar = $this->createMock(ITestCalendar::class);
828
+        // construct mock calendar manager and returns
829
+        /** @var Manager&MockObject $manager */
830
+        $manager = $this->getMockBuilder(Manager::class)
831
+            ->setConstructorArgs([
832
+                $this->coordinator,
833
+                $this->container,
834
+                $this->logger,
835
+                $this->time,
836
+                $this->secureRandom,
837
+                $this->userManager,
838
+                $this->serverFactory,
839
+            ])
840
+            ->onlyMethods(['getCalendarsForPrincipal'])
841
+            ->getMock();
842
+        $manager->expects(self::once())
843
+            ->method('getCalendarsForPrincipal')
844
+            ->willReturn([$userCalendar]);
845
+        // construct logger returns
846
+        $this->logger->expects(self::once())->method('warning')
847
+            ->with('iMip message contains an incorrect or invalid method');
848
+        // construct parameters
849
+        $principalUri = 'principals/user/linus';
850
+        $sender = '[email protected]';
851
+        $recipient = '[email protected]';
852
+        $calendar = $this->vCalendar2a;
853
+        $calendar->add('METHOD', 'UNKNOWN');
854
+        // Act
855
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
856
+        // Assert
857
+        $this->assertFalse($result);
858
+    }
859
+
860
+    public function testHandleImipReplyWithNoEvent(): void {
861
+        // construct mock user calendar
862
+        $userCalendar = $this->createMock(ITestCalendar::class);
863
+        // construct mock calendar manager and returns
864
+        /** @var Manager&MockObject $manager */
865
+        $manager = $this->getMockBuilder(Manager::class)
866
+            ->setConstructorArgs([
867
+                $this->coordinator,
868
+                $this->container,
869
+                $this->logger,
870
+                $this->time,
871
+                $this->secureRandom,
872
+                $this->userManager,
873
+                $this->serverFactory,
874
+            ])
875
+            ->onlyMethods(['getCalendarsForPrincipal'])
876
+            ->getMock();
877
+        $manager->expects(self::once())
878
+            ->method('getCalendarsForPrincipal')
879
+            ->willReturn([$userCalendar]);
880
+        // construct logger returns
881
+        $this->logger->expects(self::once())->method('warning')
882
+            ->with('iMip message contains no event');
883
+        // construct parameters
884
+        $principalUri = 'principals/user/linus';
885
+        $sender = '[email protected]';
886
+        $recipient = '[email protected]';
887
+        $calendar = $this->vCalendar2a;
888
+        $calendar->add('METHOD', 'REPLY');
889
+        $calendar->remove('VEVENT');
890
+        // Act
891
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
892
+        // Assert
893
+        $this->assertFalse($result);
894
+    }
895
+
896
+    public function testHandleImipReplyWithNoUid(): void {
897
+        // construct mock user calendar
898
+        $userCalendar = $this->createMock(ITestCalendar::class);
899
+        // construct mock calendar manager and returns
900
+        /** @var Manager&MockObject $manager */
901
+        $manager = $this->getMockBuilder(Manager::class)
902
+            ->setConstructorArgs([
903
+                $this->coordinator,
904
+                $this->container,
905
+                $this->logger,
906
+                $this->time,
907
+                $this->secureRandom,
908
+                $this->userManager,
909
+                $this->serverFactory,
910
+            ])
911
+            ->onlyMethods(['getCalendarsForPrincipal'])
912
+            ->getMock();
913
+        $manager->expects(self::once())
914
+            ->method('getCalendarsForPrincipal')
915
+            ->willReturn([$userCalendar]);
916
+        // construct logger returns
917
+        $this->logger->expects(self::once())->method('warning')
918
+            ->with('iMip message event dose not contains a UID');
919
+        // construct parameters
920
+        $principalUri = 'principals/user/linus';
921
+        $sender = '[email protected]';
922
+        $recipient = '[email protected]';
923
+        $calendar = $this->vCalendar2a;
924
+        $calendar->add('METHOD', 'REPLY');
925
+        $calendar->VEVENT->remove('UID');
926
+        // Act
927
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
928
+        // Assert
929
+        $this->assertFalse($result);
930
+    }
931
+
932
+    public function testHandleImipReplyWithNoOrganizer(): void {
933
+        // construct mock user calendar
934
+        $userCalendar = $this->createMock(ITestCalendar::class);
935
+        // construct mock calendar manager and returns
936
+        /** @var Manager&MockObject $manager */
937
+        $manager = $this->getMockBuilder(Manager::class)
938
+            ->setConstructorArgs([
939
+                $this->coordinator,
940
+                $this->container,
941
+                $this->logger,
942
+                $this->time,
943
+                $this->secureRandom,
944
+                $this->userManager,
945
+                $this->serverFactory,
946
+            ])
947
+            ->onlyMethods(['getCalendarsForPrincipal'])
948
+            ->getMock();
949
+        $manager->expects(self::once())
950
+            ->method('getCalendarsForPrincipal')
951
+            ->willReturn([$userCalendar]);
952
+        // construct logger returns
953
+        $this->logger->expects(self::once())->method('warning')
954
+            ->with('iMip message event dose not contains an organizer');
955
+        // construct parameters
956
+        $principalUri = 'principals/user/linus';
957
+        $sender = '[email protected]';
958
+        $recipient = '[email protected]';
959
+        $calendar = $this->vCalendar2a;
960
+        $calendar->add('METHOD', 'REPLY');
961
+        $calendar->VEVENT->remove('ORGANIZER');
962
+        // Act
963
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
964
+        // Assert
965
+        $this->assertFalse($result);
966
+    }
967
+
968
+    public function testHandleImipReplyWithNoAttendee(): void {
969
+        // construct mock user calendar
970
+        $userCalendar = $this->createMock(ITestCalendar::class);
971
+        // construct mock calendar manager and returns
972
+        /** @var Manager&MockObject $manager */
973
+        $manager = $this->getMockBuilder(Manager::class)
974
+            ->setConstructorArgs([
975
+                $this->coordinator,
976
+                $this->container,
977
+                $this->logger,
978
+                $this->time,
979
+                $this->secureRandom,
980
+                $this->userManager,
981
+                $this->serverFactory,
982
+            ])
983
+            ->onlyMethods(['getCalendarsForPrincipal'])
984
+            ->getMock();
985
+        $manager->expects(self::once())
986
+            ->method('getCalendarsForPrincipal')
987
+            ->willReturn([$userCalendar]);
988
+        // construct logger returns
989
+        $this->logger->expects(self::once())->method('warning')
990
+            ->with('iMip message event dose not contains any attendees');
991
+        // construct parameters
992
+        $principalUri = 'principals/user/linus';
993
+        $sender = '[email protected]';
994
+        $recipient = '[email protected]';
995
+        $calendar = $this->vCalendar2a;
996
+        $calendar->add('METHOD', 'REPLY');
997
+        $calendar->VEVENT->remove('ATTENDEE');
998
+        // Act
999
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize());
1000
+        // Assert
1001
+        $this->assertFalse($result);
1002
+    }
1003
+
1004
+    public function testHandleImipReplyDateInThePast(): void {
1005
+        // construct mock user calendar
1006
+        $userCalendar = $this->createMock(ITestCalendar::class);
1007
+        // construct mock calendar manager and returns
1008
+        /** @var Manager&MockObject $manager */
1009
+        $manager = $this->getMockBuilder(Manager::class)
1010
+            ->setConstructorArgs([
1011
+                $this->coordinator,
1012
+                $this->container,
1013
+                $this->logger,
1014
+                $this->time,
1015
+                $this->secureRandom,
1016
+                $this->userManager,
1017
+                $this->serverFactory,
1018
+            ])
1019
+            ->onlyMethods(['getCalendarsForPrincipal'])
1020
+            ->getMock();
1021
+        $manager->expects(self::once())
1022
+            ->method('getCalendarsForPrincipal')
1023
+            ->willReturn([$userCalendar]);
1024
+        // construct logger and time returns
1025
+        $this->logger->expects(self::once())->method('warning')
1026
+            ->with('iMip message event could not be processed because the event is in the past');
1027
+        $this->time->expects(self::once())
1028
+            ->method('getTime')
1029
+            ->willReturn(time());
1030
+        // construct parameters
1031
+        $principalUri = 'principals/user/linus';
1032
+        $sender = '[email protected]';
1033
+        $recipient = '[email protected]';
1034
+        $calendarData = clone $this->vCalendar2a;
1035
+        $calendarData->add('METHOD', 'REPLY');
1036
+        $calendarData->VEVENT->DTSTART = new \DateTime('2013-04-07'); // set to in the past
1037
+        // Act
1038
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
1039
+        // Assert
1040
+        $this->assertFalse($result);
1041
+    }
1042
+
1043
+    public function testHandleImipReplyEventNotFound(): void {
1044
+        // construct mock user calendar
1045
+        $userCalendar = $this->createMock(ITestCalendar::class);
1046
+        $userCalendar->expects(self::once())
1047
+            ->method('search')
1048
+            ->willReturn([]);
1049
+        // construct mock calendar manager and returns
1050
+        /** @var Manager&MockObject $manager */
1051
+        $manager = $this->getMockBuilder(Manager::class)
1052
+            ->setConstructorArgs([
1053
+                $this->coordinator,
1054
+                $this->container,
1055
+                $this->logger,
1056
+                $this->time,
1057
+                $this->secureRandom,
1058
+                $this->userManager,
1059
+                $this->serverFactory,
1060
+            ])
1061
+            ->onlyMethods(['getCalendarsForPrincipal'])
1062
+            ->getMock();
1063
+        $manager->expects(self::once())
1064
+            ->method('getCalendarsForPrincipal')
1065
+            ->willReturn([$userCalendar]);
1066
+        // construct time returns
1067
+        $this->time->expects(self::once())
1068
+            ->method('getTime')
1069
+            ->willReturn(1628374233);
1070
+        // construct parameters
1071
+        $principalUri = 'principals/user/linus';
1072
+        $sender = '[email protected]';
1073
+        $recipient = '[email protected]';
1074
+        $calendarData = clone $this->vCalendar2a;
1075
+        $calendarData->add('METHOD', 'REPLY');
1076
+        // construct logger return
1077
+        $this->logger->expects(self::once())->method('warning')
1078
+            ->with('iMip message event could not be processed because no corresponding event was found in any calendar', ['principalUri' => $principalUri, 'eventUid' => $calendarData->VEVENT->UID->getValue()]);
1079
+        // Act
1080
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
1081
+        // Assert
1082
+        $this->assertFalse($result);
1083
+    }
1084
+
1085
+    public function testHandleImipReply(): void {
1086
+        /** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */
1087
+        $manager = $this->getMockBuilder(Manager::class)
1088
+            ->setConstructorArgs([
1089
+                $this->coordinator,
1090
+                $this->container,
1091
+                $this->logger,
1092
+                $this->time,
1093
+                $this->secureRandom,
1094
+                $this->userManager,
1095
+                $this->serverFactory,
1096
+            ])
1097
+            ->onlyMethods([
1098
+                'getCalendarsForPrincipal'
1099
+            ])
1100
+            ->getMock();
1101
+        $calendar = $this->createMock(ITestCalendar::class);
1102
+        $principalUri = 'principals/user/linus';
1103
+        $sender = '[email protected]';
1104
+        $recipient = '[email protected]';
1105
+        $calendarData = clone $this->vCalendar2a;
1106
+        $calendarData->add('METHOD', 'REPLY');
1107
+
1108
+        $this->time->expects(self::once())
1109
+            ->method('getTime')
1110
+            ->willReturn(1628374233);
1111
+        $manager->expects(self::once())
1112
+            ->method('getCalendarsForPrincipal')
1113
+            ->willReturn([$calendar]);
1114
+        $calendar->expects(self::once())
1115
+            ->method('search')
1116
+            ->willReturn([['uri' => 'testname.ics']]);
1117
+        $calendar->expects(self::once())
1118
+            ->method('handleIMipMessage')
1119
+            ->with('testname.ics', $calendarData->serialize());
1120
+        // Act
1121
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
1122
+        // Assert
1123
+        $this->assertTrue($result);
1124
+    }
1125
+
1126
+    public function testHandleImipCancelWithNoCalendars(): void {
1127
+        // construct calendar manager returns
1128
+        /** @var Manager&MockObject $manager */
1129
+        $manager = $this->getMockBuilder(Manager::class)
1130
+            ->setConstructorArgs([
1131
+                $this->coordinator,
1132
+                $this->container,
1133
+                $this->logger,
1134
+                $this->time,
1135
+                $this->secureRandom,
1136
+                $this->userManager,
1137
+                $this->serverFactory,
1138
+            ])
1139
+            ->onlyMethods(['getCalendarsForPrincipal'])
1140
+            ->getMock();
1141
+        $manager->expects(self::once())
1142
+            ->method('getCalendarsForPrincipal')
1143
+            ->willReturn([]);
1144
+        // construct logger returns
1145
+        $this->logger->expects(self::once())->method('warning')
1146
+            ->with('iMip message could not be processed because user has no calendars');
1147
+        // construct parameters
1148
+        $principalUri = 'principals/user/pierre';
1149
+        $sender = '[email protected]';
1150
+        $recipient = '[email protected]';
1151
+        $replyTo = null;
1152
+        $calendar = $this->vCalendar3a;
1153
+        $calendar->add('METHOD', 'CANCEL');
1154
+        // Act
1155
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1156
+        // Assert
1157
+        $this->assertFalse($result);
1158
+    }
1159
+
1160
+    public function testHandleImipCancelWithInvalidData(): void {
1161
+        // construct mock user calendar
1162
+        $userCalendar = $this->createMock(ITestCalendar::class);
1163
+        // construct mock calendar manager and returns
1164
+        /** @var Manager&MockObject $manager */
1165
+        $manager = $this->getMockBuilder(Manager::class)
1166
+            ->setConstructorArgs([
1167
+                $this->coordinator,
1168
+                $this->container,
1169
+                $this->logger,
1170
+                $this->time,
1171
+                $this->secureRandom,
1172
+                $this->userManager,
1173
+                $this->serverFactory,
1174
+            ])
1175
+            ->onlyMethods(['getCalendarsForPrincipal'])
1176
+            ->getMock();
1177
+        $manager->expects(self::once())
1178
+            ->method('getCalendarsForPrincipal')
1179
+            ->willReturn([$userCalendar]);
1180
+        // construct logger returns
1181
+        $this->logger->expects(self::once())->method('error')
1182
+            ->with('iMip message could not be processed because an error occurred while parsing the iMip message');
1183
+        // construct parameters
1184
+        $principalUri = 'principals/user/attendee1';
1185
+        $sender = '[email protected]';
1186
+        $recipient = '[email protected]';
1187
+        $replyTo = null;
1188
+        // Act
1189
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, 'Invalid data');
1190
+        // Assert
1191
+        $this->assertFalse($result);
1192
+    }
1193
+
1194
+
1195
+    public function testHandleImipCancelWithNoMethod(): void {
1196
+        // construct mock user calendar
1197
+        $userCalendar = $this->createMock(ITestCalendar::class);
1198
+        // construct mock calendar manager and returns
1199
+        /** @var Manager&MockObject $manager */
1200
+        $manager = $this->getMockBuilder(Manager::class)
1201
+            ->setConstructorArgs([
1202
+                $this->coordinator,
1203
+                $this->container,
1204
+                $this->logger,
1205
+                $this->time,
1206
+                $this->secureRandom,
1207
+                $this->userManager,
1208
+                $this->serverFactory,
1209
+            ])
1210
+            ->onlyMethods(['getCalendarsForPrincipal'])
1211
+            ->getMock();
1212
+        $manager->expects(self::once())
1213
+            ->method('getCalendarsForPrincipal')
1214
+            ->willReturn([$userCalendar]);
1215
+        // construct logger returns
1216
+        $this->logger->expects(self::once())->method('warning')
1217
+            ->with('iMip message contains an incorrect or invalid method');
1218
+        // construct parameters
1219
+        $principalUri = 'principals/user/pierre';
1220
+        $sender = '[email protected]';
1221
+        $recipient = '[email protected]';
1222
+        $replyTo = null;
1223
+        $calendar = $this->vCalendar3a;
1224
+        // Act
1225
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1226
+        // Assert
1227
+        $this->assertFalse($result);
1228
+    }
1229
+
1230
+    public function testHandleImipCancelWithInvalidMethod(): void {
1231
+        // construct mock user calendar
1232
+        $userCalendar = $this->createMock(ITestCalendar::class);
1233
+        // construct mock calendar manager and returns
1234
+        /** @var Manager&MockObject $manager */
1235
+        $manager = $this->getMockBuilder(Manager::class)
1236
+            ->setConstructorArgs([
1237
+                $this->coordinator,
1238
+                $this->container,
1239
+                $this->logger,
1240
+                $this->time,
1241
+                $this->secureRandom,
1242
+                $this->userManager,
1243
+                $this->serverFactory,
1244
+            ])
1245
+            ->onlyMethods(['getCalendarsForPrincipal'])
1246
+            ->getMock();
1247
+        $manager->expects(self::once())
1248
+            ->method('getCalendarsForPrincipal')
1249
+            ->willReturn([$userCalendar]);
1250
+        // construct logger returns
1251
+        $this->logger->expects(self::once())->method('warning')
1252
+            ->with('iMip message contains an incorrect or invalid method');
1253
+        // construct parameters
1254
+        $principalUri = 'principals/user/pierre';
1255
+        $sender = '[email protected]';
1256
+        $recipient = '[email protected]';
1257
+        $replyTo = null;
1258
+        $calendar = $this->vCalendar3a;
1259
+        $calendar->add('METHOD', 'UNKNOWN');
1260
+        // Act
1261
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1262
+        // Assert
1263
+        $this->assertFalse($result);
1264
+    }
1265
+
1266
+    public function testHandleImipCancelWithNoEvent(): void {
1267
+        // construct mock user calendar
1268
+        $userCalendar = $this->createMock(ITestCalendar::class);
1269
+        // construct mock calendar manager and returns
1270
+        /** @var Manager&MockObject $manager */
1271
+        $manager = $this->getMockBuilder(Manager::class)
1272
+            ->setConstructorArgs([
1273
+                $this->coordinator,
1274
+                $this->container,
1275
+                $this->logger,
1276
+                $this->time,
1277
+                $this->secureRandom,
1278
+                $this->userManager,
1279
+                $this->serverFactory,
1280
+            ])
1281
+            ->onlyMethods(['getCalendarsForPrincipal'])
1282
+            ->getMock();
1283
+        $manager->expects(self::once())
1284
+            ->method('getCalendarsForPrincipal')
1285
+            ->willReturn([$userCalendar]);
1286
+        // construct logger returns
1287
+        $this->logger->expects(self::once())->method('warning')
1288
+            ->with('iMip message contains no event');
1289
+        // construct parameters
1290
+        $principalUri = 'principals/user/pierre';
1291
+        $sender = '[email protected]';
1292
+        $recipient = '[email protected]';
1293
+        $replyTo = null;
1294
+        $calendar = $this->vCalendar3a;
1295
+        $calendar->add('METHOD', 'CANCEL');
1296
+        $calendar->remove('VEVENT');
1297
+        // Act
1298
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1299
+        // Assert
1300
+        $this->assertFalse($result);
1301
+    }
1302
+
1303
+    public function testHandleImipCancelWithNoUid(): void {
1304
+        // construct mock user calendar
1305
+        $userCalendar = $this->createMock(ITestCalendar::class);
1306
+        // construct mock calendar manager and returns
1307
+        /** @var Manager&MockObject $manager */
1308
+        $manager = $this->getMockBuilder(Manager::class)
1309
+            ->setConstructorArgs([
1310
+                $this->coordinator,
1311
+                $this->container,
1312
+                $this->logger,
1313
+                $this->time,
1314
+                $this->secureRandom,
1315
+                $this->userManager,
1316
+                $this->serverFactory,
1317
+            ])
1318
+            ->onlyMethods(['getCalendarsForPrincipal'])
1319
+            ->getMock();
1320
+        $manager->expects(self::once())
1321
+            ->method('getCalendarsForPrincipal')
1322
+            ->willReturn([$userCalendar]);
1323
+        // construct logger returns
1324
+        $this->logger->expects(self::once())->method('warning')
1325
+            ->with('iMip message event dose not contains a UID');
1326
+        // construct parameters
1327
+        $principalUri = 'principals/user/pierre';
1328
+        $sender = '[email protected]';
1329
+        $recipient = '[email protected]';
1330
+        $replyTo = null;
1331
+        $calendar = $this->vCalendar3a;
1332
+        $calendar->add('METHOD', 'CANCEL');
1333
+        $calendar->VEVENT->remove('UID');
1334
+        // Act
1335
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1336
+        // Assert
1337
+        $this->assertFalse($result);
1338
+    }
1339
+
1340
+    public function testHandleImipCancelWithNoOrganizer(): void {
1341
+        // construct mock user calendar
1342
+        $userCalendar = $this->createMock(ITestCalendar::class);
1343
+        // construct mock calendar manager and returns
1344
+        /** @var Manager&MockObject $manager */
1345
+        $manager = $this->getMockBuilder(Manager::class)
1346
+            ->setConstructorArgs([
1347
+                $this->coordinator,
1348
+                $this->container,
1349
+                $this->logger,
1350
+                $this->time,
1351
+                $this->secureRandom,
1352
+                $this->userManager,
1353
+                $this->serverFactory,
1354
+            ])
1355
+            ->onlyMethods(['getCalendarsForPrincipal'])
1356
+            ->getMock();
1357
+        $manager->expects(self::once())
1358
+            ->method('getCalendarsForPrincipal')
1359
+            ->willReturn([$userCalendar]);
1360
+        // construct logger returns
1361
+        $this->logger->expects(self::once())->method('warning')
1362
+            ->with('iMip message event dose not contains an organizer');
1363
+        // construct parameters
1364
+        $principalUri = 'principals/user/pierre';
1365
+        $sender = '[email protected]';
1366
+        $recipient = '[email protected]';
1367
+        $replyTo = null;
1368
+        $calendar = $this->vCalendar3a;
1369
+        $calendar->add('METHOD', 'CANCEL');
1370
+        $calendar->VEVENT->remove('ORGANIZER');
1371
+        // Act
1372
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1373
+        // Assert
1374
+        $this->assertFalse($result);
1375
+    }
1376
+
1377
+    public function testHandleImipCancelWithNoAttendee(): void {
1378
+        // construct mock user calendar
1379
+        $userCalendar = $this->createMock(ITestCalendar::class);
1380
+        // construct mock calendar manager and returns
1381
+        /** @var Manager&MockObject $manager */
1382
+        $manager = $this->getMockBuilder(Manager::class)
1383
+            ->setConstructorArgs([
1384
+                $this->coordinator,
1385
+                $this->container,
1386
+                $this->logger,
1387
+                $this->time,
1388
+                $this->secureRandom,
1389
+                $this->userManager,
1390
+                $this->serverFactory,
1391
+            ])
1392
+            ->onlyMethods(['getCalendarsForPrincipal'])
1393
+            ->getMock();
1394
+        $manager->expects(self::once())
1395
+            ->method('getCalendarsForPrincipal')
1396
+            ->willReturn([$userCalendar]);
1397
+        // construct logger returns
1398
+        $this->logger->expects(self::once())->method('warning')
1399
+            ->with('iMip message event dose not contains any attendees');
1400
+        // construct parameters
1401
+        $principalUri = 'principals/user/pierre';
1402
+        $sender = '[email protected]';
1403
+        $recipient = '[email protected]';
1404
+        $replyTo = null;
1405
+        $calendar = $this->vCalendar3a;
1406
+        $calendar->add('METHOD', 'CANCEL');
1407
+        $calendar->VEVENT->remove('ATTENDEE');
1408
+        // Act
1409
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize());
1410
+        // Assert
1411
+        $this->assertFalse($result);
1412
+    }
1413
+
1414
+    public function testHandleImipCancelAttendeeNotRecipient(): void {
1415
+        // construct mock user calendar
1416
+        $userCalendar = $this->createMock(ITestCalendar::class);
1417
+        // construct mock calendar manager and returns
1418
+        /** @var Manager&MockObject $manager */
1419
+        $manager = $this->getMockBuilder(Manager::class)
1420
+            ->setConstructorArgs([
1421
+                $this->coordinator,
1422
+                $this->container,
1423
+                $this->logger,
1424
+                $this->time,
1425
+                $this->secureRandom,
1426
+                $this->userManager,
1427
+                $this->serverFactory,
1428
+            ])
1429
+            ->onlyMethods(['getCalendarsForPrincipal'])
1430
+            ->getMock();
1431
+        $manager->expects(self::once())
1432
+            ->method('getCalendarsForPrincipal')
1433
+            ->willReturn([$userCalendar]);
1434
+        // construct logger returns
1435
+        $this->logger->expects(self::once())->method('warning')
1436
+            ->with('iMip message event could not be processed because recipient must be an ATTENDEE of this event');
1437
+        // construct parameters
1438
+        $principalUri = 'principals/user/pierre';
1439
+        $sender = '[email protected]';
1440
+        $recipient = '[email protected]';
1441
+        $replyTo = null;
1442
+        $calendarData = clone $this->vCalendar3a;
1443
+        $calendarData->add('METHOD', 'CANCEL');
1444
+        // Act
1445
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
1446
+        // Assert
1447
+        $this->assertFalse($result);
1448
+    }
1449
+
1450
+    public function testHandleImipCancelDateInThePast(): void {
1451
+        // construct mock user calendar
1452
+        $userCalendar = $this->createMock(ITestCalendar::class);
1453
+        // construct mock calendar manager and returns
1454
+        /** @var Manager&MockObject $manager */
1455
+        $manager = $this->getMockBuilder(Manager::class)
1456
+            ->setConstructorArgs([
1457
+                $this->coordinator,
1458
+                $this->container,
1459
+                $this->logger,
1460
+                $this->time,
1461
+                $this->secureRandom,
1462
+                $this->userManager,
1463
+                $this->serverFactory,
1464
+            ])
1465
+            ->onlyMethods(['getCalendarsForPrincipal'])
1466
+            ->getMock();
1467
+        $manager->expects(self::once())
1468
+            ->method('getCalendarsForPrincipal')
1469
+            ->willReturn([$userCalendar]);
1470
+        // construct logger and time returns
1471
+        $this->logger->expects(self::once())->method('warning')
1472
+            ->with('iMip message event could not be processed because the event is in the past');
1473
+        $this->time->expects(self::once())
1474
+            ->method('getTime')
1475
+            ->willReturn(time());
1476
+        // construct parameters
1477
+        $principalUri = 'principals/user/pierre';
1478
+        $sender = '[email protected]';
1479
+        $recipient = '[email protected]';
1480
+        $replyTo = null;
1481
+        $calendarData = clone $this->vCalendar3a;
1482
+        $calendarData->add('METHOD', 'CANCEL');
1483
+        $calendarData->VEVENT->DTSTART = new \DateTime('2013-04-07'); // set to in the past
1484
+        // Act
1485
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
1486
+        // Assert
1487
+        $this->assertFalse($result);
1488
+    }
1489
+
1490
+    public function testHandleImipCancelEventNotFound(): void {
1491
+        // construct mock user calendar
1492
+        $userCalendar = $this->createMock(ITestCalendar::class);
1493
+        $userCalendar->expects(self::once())
1494
+            ->method('search')
1495
+            ->willReturn([]);
1496
+        // construct mock calendar manager and returns
1497
+        /** @var Manager&MockObject $manager */
1498
+        $manager = $this->getMockBuilder(Manager::class)
1499
+            ->setConstructorArgs([
1500
+                $this->coordinator,
1501
+                $this->container,
1502
+                $this->logger,
1503
+                $this->time,
1504
+                $this->secureRandom,
1505
+                $this->userManager,
1506
+                $this->serverFactory,
1507
+            ])
1508
+            ->onlyMethods(['getCalendarsForPrincipal'])
1509
+            ->getMock();
1510
+        $manager->expects(self::once())
1511
+            ->method('getCalendarsForPrincipal')
1512
+            ->willReturn([$userCalendar]);
1513
+        // construct time returns
1514
+        $this->time->expects(self::once())
1515
+            ->method('getTime')
1516
+            ->willReturn(1628374233);
1517
+        // construct parameters
1518
+        $principalUri = 'principals/user/pierre';
1519
+        $sender = '[email protected]';
1520
+        $recipient = '[email protected]';
1521
+        $replyTo = null;
1522
+        $calendarData = clone $this->vCalendar3a;
1523
+        $calendarData->add('METHOD', 'CANCEL');
1524
+        // construct logger return
1525
+        $this->logger->expects(self::once())->method('warning')
1526
+            ->with('iMip message event could not be processed because no corresponding event was found in any calendar', ['principalUri' => $principalUri, 'eventUid' => $calendarData->VEVENT->UID->getValue()]);
1527
+        // Act
1528
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
1529
+        // Assert
1530
+        $this->assertFalse($result);
1531
+    }
1532
+
1533
+    public function testHandleImipCancelOrganiserInReplyTo(): void {
1534
+        /** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */
1535
+        $manager = $this->getMockBuilder(Manager::class)
1536
+            ->setConstructorArgs([
1537
+                $this->coordinator,
1538
+                $this->container,
1539
+                $this->logger,
1540
+                $this->time,
1541
+                $this->secureRandom,
1542
+                $this->userManager,
1543
+                $this->serverFactory,
1544
+            ])
1545
+            ->onlyMethods([
1546
+                'getCalendarsForPrincipal'
1547
+            ])
1548
+            ->getMock();
1549
+
1550
+        $principalUri = 'principals/user/pierre';
1551
+        $sender = '[email protected]';
1552
+        $recipient = '[email protected]';
1553
+        $replyTo = '[email protected]';
1554
+        $calendar = $this->createMock(ITestCalendar::class);
1555
+        $calendarData = clone $this->vCalendar3a;
1556
+        $calendarData->add('METHOD', 'CANCEL');
1557
+
1558
+        $this->time->expects(self::once())
1559
+            ->method('getTime')
1560
+            ->willReturn(1628374233);
1561
+        $manager->expects(self::once())
1562
+            ->method('getCalendarsForPrincipal')
1563
+            ->with($principalUri)
1564
+            ->willReturn([$calendar]);
1565
+        $calendar->expects(self::once())
1566
+            ->method('search')
1567
+            ->willReturn([['uri' => 'testname.ics']]);
1568
+        $calendar->expects(self::once())
1569
+            ->method('handleIMipMessage')
1570
+            ->with('testname.ics', $calendarData->serialize());
1571
+        // Act
1572
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
1573
+        // Assert
1574
+        $this->assertTrue($result);
1575
+    }
1576
+
1577
+    public function testHandleImipCancel(): void {
1578
+        /** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */
1579
+        $manager = $this->getMockBuilder(Manager::class)
1580
+            ->setConstructorArgs([
1581
+                $this->coordinator,
1582
+                $this->container,
1583
+                $this->logger,
1584
+                $this->time,
1585
+                $this->secureRandom,
1586
+                $this->userManager,
1587
+                $this->serverFactory,
1588
+            ])
1589
+            ->onlyMethods([
1590
+                'getCalendarsForPrincipal'
1591
+            ])
1592
+            ->getMock();
1593
+        $principalUri = 'principals/user/pierre';
1594
+        $sender = '[email protected]';
1595
+        $recipient = '[email protected]';
1596
+        $replyTo = null;
1597
+        $calendar = $this->createMock(ITestCalendar::class);
1598
+        $calendarData = clone $this->vCalendar3a;
1599
+        $calendarData->add('METHOD', 'CANCEL');
1600
+
1601
+        $this->time->expects(self::once())
1602
+            ->method('getTime')
1603
+            ->willReturn(1628374233);
1604
+        $manager->expects(self::once())
1605
+            ->method('getCalendarsForPrincipal')
1606
+            ->with($principalUri)
1607
+            ->willReturn([$calendar]);
1608
+        $calendar->expects(self::once())
1609
+            ->method('search')
1610
+            ->willReturn([['uri' => 'testname.ics']]);
1611
+        $calendar->expects(self::once())
1612
+            ->method('handleIMipMessage')
1613
+            ->with('testname.ics', $calendarData->serialize());
1614
+        // Act
1615
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
1616
+        // Assert
1617
+        $this->assertTrue($result);
1618
+    }
1619
+
1620
+    private function getFreeBusyResponse(): string {
1621
+        return <<<EOF
1622 1622
 <?xml version="1.0" encoding="utf-8"?>
1623 1623
 <cal:schedule-response xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
1624 1624
   <cal:response>
@@ -1696,139 +1696,139 @@  discard block
 block discarded – undo
1696 1696
   </cal:response>
1697 1697
 </cal:schedule-response>
1698 1698
 EOF;
1699
-	}
1700
-
1701
-	public function testCheckAvailability(): void {
1702
-		$organizer = $this->createMock(IUser::class);
1703
-		$organizer->expects(self::once())
1704
-			->method('getUID')
1705
-			->willReturn('admin');
1706
-		$organizer->expects(self::once())
1707
-			->method('getEMailAddress')
1708
-			->willReturn('[email protected]');
1709
-
1710
-		$user1 = $this->createMock(IUser::class);
1711
-		$user2 = $this->createMock(IUser::class);
1712
-
1713
-		$this->userManager->expects(self::exactly(3))
1714
-			->method('getByEmail')
1715
-			->willReturnMap([
1716
-				['[email protected]', [$user1]],
1717
-				['[email protected]', [$user2]],
1718
-				['[email protected]', []],
1719
-			]);
1720
-
1721
-		$authPlugin = $this->createMock(CustomPrincipalPlugin::class);
1722
-		$authPlugin->expects(self::once())
1723
-			->method('setCurrentPrincipal')
1724
-			->with('principals/users/admin');
1725
-
1726
-		$server = $this->createMock(Server::class);
1727
-		$server->expects(self::once())
1728
-			->method('getPlugin')
1729
-			->with('auth')
1730
-			->willReturn($authPlugin);
1731
-		$server->expects(self::once())
1732
-			->method('invokeMethod')
1733
-			->willReturnCallback(function (
1734
-				RequestInterface $request,
1735
-				ResponseInterface $response,
1736
-				bool $sendResponse,
1737
-			): void {
1738
-				$requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
1739
-				$this->assertEquals('POST', $request->getMethod());
1740
-				$this->assertEquals('calendars/admin/outbox', $request->getPath());
1741
-				$this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
1742
-				$this->assertEquals('0', $request->getHeader('Depth'));
1743
-				$this->assertEquals($requestBody, $request->getBodyAsString());
1744
-				$this->assertFalse($sendResponse);
1745
-				$response->setStatus(200);
1746
-				$response->setBody($this->getFreeBusyResponse());
1747
-			});
1748
-
1749
-		$this->serverFactory->expects(self::once())
1750
-			->method('createAttendeeAvailabilityServer')
1751
-			->willReturn($server);
1752
-
1753
-		$start = new DateTimeImmutable('2025-01-16T06:00:00Z');
1754
-		$end = new DateTimeImmutable('2025-01-17T06:00:00Z');
1755
-		$actual = $this->manager->checkAvailability($start, $end, $organizer, [
1756
-			'[email protected]',
1757
-			'[email protected]',
1758
-			'[email protected]',
1759
-		]);
1760
-		$expected = [
1761
-			new AvailabilityResult('[email protected]', false),
1762
-			new AvailabilityResult('[email protected]', true),
1763
-			new AvailabilityResult('[email protected]', false),
1764
-		];
1765
-		$this->assertEquals($expected, $actual);
1766
-	}
1767
-
1768
-	public function testCheckAvailabilityWithMailtoPrefix(): void {
1769
-		$organizer = $this->createMock(IUser::class);
1770
-		$organizer->expects(self::once())
1771
-			->method('getUID')
1772
-			->willReturn('admin');
1773
-		$organizer->expects(self::once())
1774
-			->method('getEMailAddress')
1775
-			->willReturn('[email protected]');
1776
-
1777
-		$user1 = $this->createMock(IUser::class);
1778
-		$user2 = $this->createMock(IUser::class);
1779
-
1780
-		$this->userManager->expects(self::exactly(3))
1781
-			->method('getByEmail')
1782
-			->willReturnMap([
1783
-				['[email protected]', [$user1]],
1784
-				['[email protected]', [$user2]],
1785
-				['[email protected]', []],
1786
-			]);
1787
-
1788
-		$authPlugin = $this->createMock(CustomPrincipalPlugin::class);
1789
-		$authPlugin->expects(self::once())
1790
-			->method('setCurrentPrincipal')
1791
-			->with('principals/users/admin');
1792
-
1793
-		$server = $this->createMock(Server::class);
1794
-		$server->expects(self::once())
1795
-			->method('getPlugin')
1796
-			->with('auth')
1797
-			->willReturn($authPlugin);
1798
-		$server->expects(self::once())
1799
-			->method('invokeMethod')
1800
-			->willReturnCallback(function (
1801
-				RequestInterface $request,
1802
-				ResponseInterface $response,
1803
-				bool $sendResponse,
1804
-			): void {
1805
-				$requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
1806
-				$this->assertEquals('POST', $request->getMethod());
1807
-				$this->assertEquals('calendars/admin/outbox', $request->getPath());
1808
-				$this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
1809
-				$this->assertEquals('0', $request->getHeader('Depth'));
1810
-				$this->assertEquals($requestBody, $request->getBodyAsString());
1811
-				$this->assertFalse($sendResponse);
1812
-				$response->setStatus(200);
1813
-				$response->setBody($this->getFreeBusyResponse());
1814
-			});
1815
-
1816
-		$this->serverFactory->expects(self::once())
1817
-			->method('createAttendeeAvailabilityServer')
1818
-			->willReturn($server);
1819
-
1820
-		$start = new DateTimeImmutable('2025-01-16T06:00:00Z');
1821
-		$end = new DateTimeImmutable('2025-01-17T06:00:00Z');
1822
-		$actual = $this->manager->checkAvailability($start, $end, $organizer, [
1823
-			'mailto:[email protected]',
1824
-			'mailto:[email protected]',
1825
-			'mailto:[email protected]',
1826
-		]);
1827
-		$expected = [
1828
-			new AvailabilityResult('[email protected]', false),
1829
-			new AvailabilityResult('[email protected]', true),
1830
-			new AvailabilityResult('[email protected]', false),
1831
-		];
1832
-		$this->assertEquals($expected, $actual);
1833
-	}
1699
+    }
1700
+
1701
+    public function testCheckAvailability(): void {
1702
+        $organizer = $this->createMock(IUser::class);
1703
+        $organizer->expects(self::once())
1704
+            ->method('getUID')
1705
+            ->willReturn('admin');
1706
+        $organizer->expects(self::once())
1707
+            ->method('getEMailAddress')
1708
+            ->willReturn('[email protected]');
1709
+
1710
+        $user1 = $this->createMock(IUser::class);
1711
+        $user2 = $this->createMock(IUser::class);
1712
+
1713
+        $this->userManager->expects(self::exactly(3))
1714
+            ->method('getByEmail')
1715
+            ->willReturnMap([
1716
+                ['[email protected]', [$user1]],
1717
+                ['[email protected]', [$user2]],
1718
+                ['[email protected]', []],
1719
+            ]);
1720
+
1721
+        $authPlugin = $this->createMock(CustomPrincipalPlugin::class);
1722
+        $authPlugin->expects(self::once())
1723
+            ->method('setCurrentPrincipal')
1724
+            ->with('principals/users/admin');
1725
+
1726
+        $server = $this->createMock(Server::class);
1727
+        $server->expects(self::once())
1728
+            ->method('getPlugin')
1729
+            ->with('auth')
1730
+            ->willReturn($authPlugin);
1731
+        $server->expects(self::once())
1732
+            ->method('invokeMethod')
1733
+            ->willReturnCallback(function (
1734
+                RequestInterface $request,
1735
+                ResponseInterface $response,
1736
+                bool $sendResponse,
1737
+            ): void {
1738
+                $requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
1739
+                $this->assertEquals('POST', $request->getMethod());
1740
+                $this->assertEquals('calendars/admin/outbox', $request->getPath());
1741
+                $this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
1742
+                $this->assertEquals('0', $request->getHeader('Depth'));
1743
+                $this->assertEquals($requestBody, $request->getBodyAsString());
1744
+                $this->assertFalse($sendResponse);
1745
+                $response->setStatus(200);
1746
+                $response->setBody($this->getFreeBusyResponse());
1747
+            });
1748
+
1749
+        $this->serverFactory->expects(self::once())
1750
+            ->method('createAttendeeAvailabilityServer')
1751
+            ->willReturn($server);
1752
+
1753
+        $start = new DateTimeImmutable('2025-01-16T06:00:00Z');
1754
+        $end = new DateTimeImmutable('2025-01-17T06:00:00Z');
1755
+        $actual = $this->manager->checkAvailability($start, $end, $organizer, [
1756
+            '[email protected]',
1757
+            '[email protected]',
1758
+            '[email protected]',
1759
+        ]);
1760
+        $expected = [
1761
+            new AvailabilityResult('[email protected]', false),
1762
+            new AvailabilityResult('[email protected]', true),
1763
+            new AvailabilityResult('[email protected]', false),
1764
+        ];
1765
+        $this->assertEquals($expected, $actual);
1766
+    }
1767
+
1768
+    public function testCheckAvailabilityWithMailtoPrefix(): void {
1769
+        $organizer = $this->createMock(IUser::class);
1770
+        $organizer->expects(self::once())
1771
+            ->method('getUID')
1772
+            ->willReturn('admin');
1773
+        $organizer->expects(self::once())
1774
+            ->method('getEMailAddress')
1775
+            ->willReturn('[email protected]');
1776
+
1777
+        $user1 = $this->createMock(IUser::class);
1778
+        $user2 = $this->createMock(IUser::class);
1779
+
1780
+        $this->userManager->expects(self::exactly(3))
1781
+            ->method('getByEmail')
1782
+            ->willReturnMap([
1783
+                ['[email protected]', [$user1]],
1784
+                ['[email protected]', [$user2]],
1785
+                ['[email protected]', []],
1786
+            ]);
1787
+
1788
+        $authPlugin = $this->createMock(CustomPrincipalPlugin::class);
1789
+        $authPlugin->expects(self::once())
1790
+            ->method('setCurrentPrincipal')
1791
+            ->with('principals/users/admin');
1792
+
1793
+        $server = $this->createMock(Server::class);
1794
+        $server->expects(self::once())
1795
+            ->method('getPlugin')
1796
+            ->with('auth')
1797
+            ->willReturn($authPlugin);
1798
+        $server->expects(self::once())
1799
+            ->method('invokeMethod')
1800
+            ->willReturnCallback(function (
1801
+                RequestInterface $request,
1802
+                ResponseInterface $response,
1803
+                bool $sendResponse,
1804
+            ): void {
1805
+                $requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
1806
+                $this->assertEquals('POST', $request->getMethod());
1807
+                $this->assertEquals('calendars/admin/outbox', $request->getPath());
1808
+                $this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
1809
+                $this->assertEquals('0', $request->getHeader('Depth'));
1810
+                $this->assertEquals($requestBody, $request->getBodyAsString());
1811
+                $this->assertFalse($sendResponse);
1812
+                $response->setStatus(200);
1813
+                $response->setBody($this->getFreeBusyResponse());
1814
+            });
1815
+
1816
+        $this->serverFactory->expects(self::once())
1817
+            ->method('createAttendeeAvailabilityServer')
1818
+            ->willReturn($server);
1819
+
1820
+        $start = new DateTimeImmutable('2025-01-16T06:00:00Z');
1821
+        $end = new DateTimeImmutable('2025-01-17T06:00:00Z');
1822
+        $actual = $this->manager->checkAvailability($start, $end, $organizer, [
1823
+            'mailto:[email protected]',
1824
+            'mailto:[email protected]',
1825
+            'mailto:[email protected]',
1826
+        ]);
1827
+        $expected = [
1828
+            new AvailabilityResult('[email protected]', false),
1829
+            new AvailabilityResult('[email protected]', true),
1830
+            new AvailabilityResult('[email protected]', false),
1831
+        ];
1832
+        $this->assertEquals($expected, $actual);
1833
+    }
1834 1834
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -1730,12 +1730,12 @@  discard block
 block discarded – undo
1730 1730
 			->willReturn($authPlugin);
1731 1731
 		$server->expects(self::once())
1732 1732
 			->method('invokeMethod')
1733
-			->willReturnCallback(function (
1733
+			->willReturnCallback(function(
1734 1734
 				RequestInterface $request,
1735 1735
 				ResponseInterface $response,
1736 1736
 				bool $sendResponse,
1737 1737
 			): void {
1738
-				$requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
1738
+				$requestBody = file_get_contents(__DIR__.'/../../data/ics/free-busy-request.ics');
1739 1739
 				$this->assertEquals('POST', $request->getMethod());
1740 1740
 				$this->assertEquals('calendars/admin/outbox', $request->getPath());
1741 1741
 				$this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
@@ -1797,12 +1797,12 @@  discard block
 block discarded – undo
1797 1797
 			->willReturn($authPlugin);
1798 1798
 		$server->expects(self::once())
1799 1799
 			->method('invokeMethod')
1800
-			->willReturnCallback(function (
1800
+			->willReturnCallback(function(
1801 1801
 				RequestInterface $request,
1802 1802
 				ResponseInterface $response,
1803 1803
 				bool $sendResponse,
1804 1804
 			): void {
1805
-				$requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
1805
+				$requestBody = file_get_contents(__DIR__.'/../../data/ics/free-busy-request.ics');
1806 1806
 				$this->assertEquals('POST', $request->getMethod());
1807 1807
 				$this->assertEquals('calendars/admin/outbox', $request->getPath());
1808 1808
 				$this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
Please login to merge, or discard this patch.