Completed
Push — master ( ef2e71...d39ae9 )
by Daniel
27:45 queued 10s
created
lib/public/Calendar/IManager.php 1 patch
Indentation   +153 added lines, -153 removed lines patch added patch discarded remove patch
@@ -42,157 +42,157 @@
 block discarded – undo
42 42
  * @since 13.0.0
43 43
  */
44 44
 interface IManager {
45
-	/**
46
-	 * This function is used to search and find objects within the user's calendars.
47
-	 * In case $pattern is empty all events/journals/todos will be returned.
48
-	 *
49
-	 * @param string $pattern which should match within the $searchProperties
50
-	 * @param array $searchProperties defines the properties within the query pattern should match
51
-	 * @param array $options - optional parameters:
52
-	 *                       ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]]
53
-	 * @param integer|null $limit - limit number of search results
54
-	 * @param integer|null $offset - offset for paging of search results
55
-	 * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs
56
-	 * @since 13.0.0
57
-	 * @deprecated 23.0.0 use \OCP\Calendar\IManager::searchForPrincipal
58
-	 */
59
-	public function search($pattern, array $searchProperties = [], array $options = [], $limit = null, $offset = null);
60
-
61
-	/**
62
-	 * Check if calendars are available
63
-	 *
64
-	 * @return bool true if enabled, false if not
65
-	 * @since 13.0.0
66
-	 * @deprecated 23.0.0
67
-	 */
68
-	public function isEnabled();
69
-
70
-	/**
71
-	 * Registers a calendar
72
-	 *
73
-	 * @param ICalendar $calendar
74
-	 * @return void
75
-	 * @since 13.0.0
76
-	 * @deprecated 23.0.0 use \OCP\AppFramework\Bootstrap\IRegistrationContext::registerCalendarProvider
77
-	 */
78
-	public function registerCalendar(ICalendar $calendar);
79
-
80
-	/**
81
-	 * Unregisters a calendar
82
-	 *
83
-	 * @param ICalendar $calendar
84
-	 * @return void
85
-	 * @since 13.0.0
86
-	 * @deprecated 23.0.0
87
-	 */
88
-	public function unregisterCalendar(ICalendar $calendar);
89
-
90
-	/**
91
-	 * In order to improve lazy loading a closure can be registered which will be called in case
92
-	 * calendars are actually requested
93
-	 *
94
-	 * @param \Closure $callable
95
-	 * @return void
96
-	 * @since 13.0.0
97
-	 * @deprecated 23.0.0 use \OCP\AppFramework\Bootstrap\IRegistrationContext::registerCalendarProvider
98
-	 */
99
-	public function register(\Closure $callable);
100
-
101
-	/**
102
-	 * @return ICalendar[]
103
-	 * @since 13.0.0
104
-	 * @deprecated 23.0.0 use \OCP\Calendar\IManager::getCalendarsForPrincipal
105
-	 */
106
-	public function getCalendars();
107
-
108
-	/**
109
-	 * removes all registered calendar instances
110
-	 *
111
-	 * @return void
112
-	 * @since 13.0.0
113
-	 * @deprecated 23.0.0
114
-	 */
115
-	public function clear();
116
-
117
-	/**
118
-	 * @param string $principalUri URI of the principal
119
-	 * @param string[] $calendarUris optionally specify which calendars to load, or all if this array is empty
120
-	 *
121
-	 * @return ICalendar[]
122
-	 * @since 23.0.0
123
-	 */
124
-	public function getCalendarsForPrincipal(string $principalUri, array $calendarUris = []): array;
125
-
126
-	/**
127
-	 * Query a principals calendar(s)
128
-	 *
129
-	 * @param ICalendarQuery $query
130
-	 * @return array[]
131
-	 * @since 23.0.0
132
-	 */
133
-	public function searchForPrincipal(ICalendarQuery $query): array;
134
-
135
-	/**
136
-	 * Build a new query for searchForPrincipal
137
-	 *
138
-	 * @return ICalendarQuery
139
-	 * @since 23.0.0
140
-	 */
141
-	public function newQuery(string $principalUri) : ICalendarQuery;
142
-
143
-	/**
144
-	 * Handles a iMip message
145
-	 *
146
-	 * @param array{absent?: "create", recipient?: string} $options
147
-	 *
148
-	 * @throws \OCP\DB\Exception
149
-	 *
150
-	 * @since 32.0.0
151
-	 */
152
-	public function handleIMip(string $userId, string $message, array $options = []): bool;
153
-
154
-	/**
155
-	 * Handle a iMip REQUEST message
156
-	 *
157
-	 * @since 31.0.0
158
-	 */
159
-	public function handleIMipRequest(string $principalUri, string $sender, string $recipient, string $calendarData): bool;
160
-
161
-	/**
162
-	 * Handle a iMip REPLY message
163
-	 *
164
-	 * @since 25.0.0
165
-	 */
166
-	public function handleIMipReply(string $principalUri, string $sender, string $recipient, string $calendarData): bool;
167
-
168
-	/**
169
-	 * Handle a iMip CANCEL message
170
-	 *
171
-	 * @since 25.0.0
172
-	 */
173
-	public function handleIMipCancel(string $principalUri, string $sender, ?string $replyTo, string $recipient, string $calendarData): bool;
174
-
175
-	/**
176
-	 * Create a new event builder instance. Please have a look at its documentation and the
177
-	 * \OCP\Calendar\ICreateFromString interface on how to use it.
178
-	 *
179
-	 * @since 31.0.0
180
-	 */
181
-	public function createEventBuilder(): ICalendarEventBuilder;
182
-
183
-	/**
184
-	 * Check the availability of the given organizer and attendees in the given time range.
185
-	 *
186
-	 * @since 31.0.0
187
-	 *
188
-	 * @param IUser $organizer The organizing user from whose perspective to do the availability check.
189
-	 * @param string[] $attendees Email addresses of attendees to check for (with or without a "mailto:" prefix). Only users on this instance can be checked. The rest will be silently ignored.
190
-	 * @return IAvailabilityResult[] Availabilities of the organizer and all attendees which are also users on this instance. As such, the array might not contain an entry for each given attendee.
191
-	 */
192
-	public function checkAvailability(
193
-		DateTimeInterface $start,
194
-		DateTimeInterface $end,
195
-		IUser $organizer,
196
-		array $attendees,
197
-	): array;
45
+    /**
46
+     * This function is used to search and find objects within the user's calendars.
47
+     * In case $pattern is empty all events/journals/todos will be returned.
48
+     *
49
+     * @param string $pattern which should match within the $searchProperties
50
+     * @param array $searchProperties defines the properties within the query pattern should match
51
+     * @param array $options - optional parameters:
52
+     *                       ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]]
53
+     * @param integer|null $limit - limit number of search results
54
+     * @param integer|null $offset - offset for paging of search results
55
+     * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs
56
+     * @since 13.0.0
57
+     * @deprecated 23.0.0 use \OCP\Calendar\IManager::searchForPrincipal
58
+     */
59
+    public function search($pattern, array $searchProperties = [], array $options = [], $limit = null, $offset = null);
60
+
61
+    /**
62
+     * Check if calendars are available
63
+     *
64
+     * @return bool true if enabled, false if not
65
+     * @since 13.0.0
66
+     * @deprecated 23.0.0
67
+     */
68
+    public function isEnabled();
69
+
70
+    /**
71
+     * Registers a calendar
72
+     *
73
+     * @param ICalendar $calendar
74
+     * @return void
75
+     * @since 13.0.0
76
+     * @deprecated 23.0.0 use \OCP\AppFramework\Bootstrap\IRegistrationContext::registerCalendarProvider
77
+     */
78
+    public function registerCalendar(ICalendar $calendar);
79
+
80
+    /**
81
+     * Unregisters a calendar
82
+     *
83
+     * @param ICalendar $calendar
84
+     * @return void
85
+     * @since 13.0.0
86
+     * @deprecated 23.0.0
87
+     */
88
+    public function unregisterCalendar(ICalendar $calendar);
89
+
90
+    /**
91
+     * In order to improve lazy loading a closure can be registered which will be called in case
92
+     * calendars are actually requested
93
+     *
94
+     * @param \Closure $callable
95
+     * @return void
96
+     * @since 13.0.0
97
+     * @deprecated 23.0.0 use \OCP\AppFramework\Bootstrap\IRegistrationContext::registerCalendarProvider
98
+     */
99
+    public function register(\Closure $callable);
100
+
101
+    /**
102
+     * @return ICalendar[]
103
+     * @since 13.0.0
104
+     * @deprecated 23.0.0 use \OCP\Calendar\IManager::getCalendarsForPrincipal
105
+     */
106
+    public function getCalendars();
107
+
108
+    /**
109
+     * removes all registered calendar instances
110
+     *
111
+     * @return void
112
+     * @since 13.0.0
113
+     * @deprecated 23.0.0
114
+     */
115
+    public function clear();
116
+
117
+    /**
118
+     * @param string $principalUri URI of the principal
119
+     * @param string[] $calendarUris optionally specify which calendars to load, or all if this array is empty
120
+     *
121
+     * @return ICalendar[]
122
+     * @since 23.0.0
123
+     */
124
+    public function getCalendarsForPrincipal(string $principalUri, array $calendarUris = []): array;
125
+
126
+    /**
127
+     * Query a principals calendar(s)
128
+     *
129
+     * @param ICalendarQuery $query
130
+     * @return array[]
131
+     * @since 23.0.0
132
+     */
133
+    public function searchForPrincipal(ICalendarQuery $query): array;
134
+
135
+    /**
136
+     * Build a new query for searchForPrincipal
137
+     *
138
+     * @return ICalendarQuery
139
+     * @since 23.0.0
140
+     */
141
+    public function newQuery(string $principalUri) : ICalendarQuery;
142
+
143
+    /**
144
+     * Handles a iMip message
145
+     *
146
+     * @param array{absent?: "create", recipient?: string} $options
147
+     *
148
+     * @throws \OCP\DB\Exception
149
+     *
150
+     * @since 32.0.0
151
+     */
152
+    public function handleIMip(string $userId, string $message, array $options = []): bool;
153
+
154
+    /**
155
+     * Handle a iMip REQUEST message
156
+     *
157
+     * @since 31.0.0
158
+     */
159
+    public function handleIMipRequest(string $principalUri, string $sender, string $recipient, string $calendarData): bool;
160
+
161
+    /**
162
+     * Handle a iMip REPLY message
163
+     *
164
+     * @since 25.0.0
165
+     */
166
+    public function handleIMipReply(string $principalUri, string $sender, string $recipient, string $calendarData): bool;
167
+
168
+    /**
169
+     * Handle a iMip CANCEL message
170
+     *
171
+     * @since 25.0.0
172
+     */
173
+    public function handleIMipCancel(string $principalUri, string $sender, ?string $replyTo, string $recipient, string $calendarData): bool;
174
+
175
+    /**
176
+     * Create a new event builder instance. Please have a look at its documentation and the
177
+     * \OCP\Calendar\ICreateFromString interface on how to use it.
178
+     *
179
+     * @since 31.0.0
180
+     */
181
+    public function createEventBuilder(): ICalendarEventBuilder;
182
+
183
+    /**
184
+     * Check the availability of the given organizer and attendees in the given time range.
185
+     *
186
+     * @since 31.0.0
187
+     *
188
+     * @param IUser $organizer The organizing user from whose perspective to do the availability check.
189
+     * @param string[] $attendees Email addresses of attendees to check for (with or without a "mailto:" prefix). Only users on this instance can be checked. The rest will be silently ignored.
190
+     * @return IAvailabilityResult[] Availabilities of the organizer and all attendees which are also users on this instance. As such, the array might not contain an entry for each given attendee.
191
+     */
192
+    public function checkAvailability(
193
+        DateTimeInterface $start,
194
+        DateTimeInterface $end,
195
+        IUser $organizer,
196
+        array $attendees,
197
+    ): array;
198 198
 }
Please login to merge, or discard this patch.
lib/private/Calendar/Manager.php 2 patches
Indentation   +458 added lines, -458 removed lines patch added patch discarded remove patch
@@ -41,463 +41,463 @@
 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
-		private PropertyMapper $propertyMapper,
63
-	) {
64
-	}
65
-
66
-	/**
67
-	 * This function is used to search and find objects within the user's calendars.
68
-	 * In case $pattern is empty all events/journals/todos will be returned.
69
-	 *
70
-	 * @param string $pattern which should match within the $searchProperties
71
-	 * @param array $searchProperties defines the properties within the query pattern should match
72
-	 * @param array $options - optional parameters:
73
-	 *                       ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]]
74
-	 * @param integer|null $limit - limit number of search results
75
-	 * @param integer|null $offset - offset for paging of search results
76
-	 * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs
77
-	 * @since 13.0.0
78
-	 */
79
-	public function search(
80
-		$pattern,
81
-		array $searchProperties = [],
82
-		array $options = [],
83
-		$limit = null,
84
-		$offset = null,
85
-	): array {
86
-		$this->loadCalendars();
87
-		$result = [];
88
-		foreach ($this->calendars as $calendar) {
89
-			$r = $calendar->search($pattern, $searchProperties, $options, $limit, $offset);
90
-			foreach ($r as $o) {
91
-				$o['calendar-key'] = $calendar->getKey();
92
-				$result[] = $o;
93
-			}
94
-		}
95
-
96
-		return $result;
97
-	}
98
-
99
-	/**
100
-	 * Check if calendars are available
101
-	 *
102
-	 * @return bool true if enabled, false if not
103
-	 * @since 13.0.0
104
-	 */
105
-	public function isEnabled(): bool {
106
-		return !empty($this->calendars) || !empty($this->calendarLoaders);
107
-	}
108
-
109
-	/**
110
-	 * Registers a calendar
111
-	 *
112
-	 * @since 13.0.0
113
-	 */
114
-	public function registerCalendar(ICalendar $calendar): void {
115
-		$this->calendars[$calendar->getKey()] = $calendar;
116
-	}
117
-
118
-	/**
119
-	 * Unregisters a calendar
120
-	 *
121
-	 * @since 13.0.0
122
-	 */
123
-	public function unregisterCalendar(ICalendar $calendar): void {
124
-		unset($this->calendars[$calendar->getKey()]);
125
-	}
126
-
127
-	/**
128
-	 * In order to improve lazy loading a closure can be registered which will be called in case
129
-	 * calendars are actually requested
130
-	 *
131
-	 * @since 13.0.0
132
-	 */
133
-	public function register(\Closure $callable): void {
134
-		$this->calendarLoaders[] = $callable;
135
-	}
136
-
137
-	/**
138
-	 * @return ICalendar[]
139
-	 *
140
-	 * @since 13.0.0
141
-	 */
142
-	public function getCalendars(): array {
143
-		$this->loadCalendars();
144
-
145
-		return array_values($this->calendars);
146
-	}
147
-
148
-	/**
149
-	 * removes all registered calendar instances
150
-	 *
151
-	 * @since 13.0.0
152
-	 */
153
-	public function clear(): void {
154
-		$this->calendars = [];
155
-		$this->calendarLoaders = [];
156
-	}
157
-
158
-	/**
159
-	 * loads all calendars
160
-	 */
161
-	private function loadCalendars(): void {
162
-		foreach ($this->calendarLoaders as $callable) {
163
-			$callable($this);
164
-		}
165
-		$this->calendarLoaders = [];
166
-	}
167
-
168
-	/**
169
-	 * @return ICreateFromString[]
170
-	 */
171
-	public function getCalendarsForPrincipal(string $principalUri, array $calendarUris = []): array {
172
-		$context = $this->coordinator->getRegistrationContext();
173
-		if ($context === null) {
174
-			return [];
175
-		}
176
-
177
-		return array_merge(
178
-			...array_map(function ($registration) use ($principalUri, $calendarUris) {
179
-				try {
180
-					/** @var ICalendarProvider $provider */
181
-					$provider = $this->container->get($registration->getService());
182
-				} catch (Throwable $e) {
183
-					$this->logger->error('Could not load calendar provider ' . $registration->getService() . ': ' . $e->getMessage(), [
184
-						'exception' => $e,
185
-					]);
186
-					return [];
187
-				}
188
-
189
-				return $provider->getCalendars($principalUri, $calendarUris);
190
-			}, $context->getCalendarProviders())
191
-		);
192
-	}
193
-
194
-	public function searchForPrincipal(ICalendarQuery $query): array {
195
-		/** @var CalendarQuery $query */
196
-		$calendars = $this->getCalendarsForPrincipal(
197
-			$query->getPrincipalUri(),
198
-			$query->getCalendarUris(),
199
-		);
200
-
201
-		$results = [];
202
-		foreach ($calendars as $calendar) {
203
-			$r = $calendar->search(
204
-				$query->getSearchPattern() ?? '',
205
-				$query->getSearchProperties(),
206
-				$query->getOptions(),
207
-				$query->getLimit(),
208
-				$query->getOffset()
209
-			);
210
-
211
-			foreach ($r as $o) {
212
-				$o['calendar-key'] = $calendar->getKey();
213
-				$o['calendar-uri'] = $calendar->getUri();
214
-				$results[] = $o;
215
-			}
216
-		}
217
-		return $results;
218
-	}
219
-
220
-	public function newQuery(string $principalUri): ICalendarQuery {
221
-		return new CalendarQuery($principalUri);
222
-	}
223
-
224
-	/**
225
-	 * @since 32.0.0
226
-	 *
227
-	 * @throws \OCP\DB\Exception
228
-	 */
229
-	public function handleIMip(
230
-		string $userId,
231
-		string $message,
232
-		array $options = [],
233
-	): bool {
234
-
235
-		$userUri = 'principals/users/' . $userId;
236
-
237
-		$userCalendars = $this->getCalendarsForPrincipal($userUri);
238
-		if (empty($userCalendars)) {
239
-			$this->logger->warning('iMip message could not be processed because user has no calendars');
240
-			return false;
241
-		}
242
-
243
-		try {
244
-			/** @var VCalendar $vObject|null */
245
-			$vObject = Reader::read($message);
246
-		} catch (ParseException $e) {
247
-			$this->logger->error('iMip message could not be processed because an error occurred while parsing the iMip message', ['exception' => $e]);
248
-			return false;
249
-		}
250
-
251
-		if (!isset($vObject->VEVENT)) {
252
-			$this->logger->warning('iMip message does not contain any event(s)');
253
-			return false;
254
-		}
255
-		/** @var VEvent $vEvent */
256
-		$vEvent = $vObject->VEVENT;
257
-
258
-		if (!isset($vEvent->UID)) {
259
-			$this->logger->warning('iMip message event dose not contains a UID');
260
-			return false;
261
-		}
262
-
263
-		if (!isset($vEvent->ORGANIZER)) {
264
-			// quirks mode: for Microsoft Exchange Servers use recipient as organizer if no organizer is set
265
-			if (isset($options['recipient']) && $options['recipient'] !== '') {
266
-				$vEvent->add('ORGANIZER', 'mailto:' . $options['recipient']);
267
-			} else {
268
-				$this->logger->warning('iMip message event does not contain an organizer and no recipient was provided');
269
-				return false;
270
-			}
271
-		}
272
-
273
-		if (!isset($vEvent->ATTENDEE)) {
274
-			$this->logger->warning('iMip message event dose not contains any attendees');
275
-			return false;
276
-		}
277
-
278
-		foreach ($userCalendars as $calendar) {
279
-			if (!$calendar instanceof ICalendarIsWritable) {
280
-				continue;
281
-			}
282
-			if ($calendar->isDeleted() || !$calendar->isWritable()) {
283
-				continue;
284
-			}
285
-			if (!empty($calendar->search('', [], ['uid' => $vEvent->UID->getValue()]))) {
286
-				try {
287
-					if ($calendar instanceof IHandleImipMessage) {
288
-						$calendar->handleIMipMessage($userId, $vObject->serialize());
289
-					}
290
-					return true;
291
-				} catch (CalendarException $e) {
292
-					$this->logger->error('iMip message could not be processed because an error occurred', ['exception' => $e]);
293
-					return false;
294
-				}
295
-			}
296
-		}
297
-
298
-		if (isset($options['absent']) && $options['absent'] === 'create') {
299
-			// retrieve the primary calendar for the user
300
-			$calendar = $this->getPrimaryCalendar($userId);
301
-			if ($calendar !== null && (
302
-				!$calendar instanceof IHandleImipMessage || !$calendar instanceof ICalendarIsWritable || $calendar->isDeleted() || !$calendar->isWritable()
303
-			)) {
304
-				$calendar = null;
305
-			}
306
-			// if no primary calendar is set, use the first writable calendar
307
-			if ($calendar === null) {
308
-				foreach ($userCalendars as $userCalendar) {
309
-					if ($userCalendar instanceof IHandleImipMessage && $userCalendar instanceof ICalendarIsWritable && !$userCalendar->isDeleted() && $userCalendar->isWritable()) {
310
-						$calendar = $userCalendar;
311
-						break;
312
-					}
313
-				}
314
-			}
315
-			if ($calendar === null) {
316
-				$this->logger->warning('iMip message could not be processed because no writable calendar was found');
317
-				return false;
318
-			}
319
-			$calendar->handleIMipMessage($userId, $vObject->serialize());
320
-		}
321
-
322
-		$this->logger->warning('iMip message could not be processed because no corresponding event was found in any calendar');
323
-
324
-		return false;
325
-	}
326
-
327
-	/**
328
-	 * @since 31.0.0
329
-	 *
330
-	 * @throws \OCP\DB\Exception
331
-	 */
332
-	public function handleIMipRequest(
333
-		string $principalUri,
334
-		string $sender,
335
-		string $recipient,
336
-		string $calendarData,
337
-	): bool {
338
-		if (empty($principalUri) || !str_starts_with($principalUri, 'principals/users/')) {
339
-			$this->logger->error('Invalid principal URI provided for iMip request');
340
-			return false;
341
-		}
342
-		$userId = substr($principalUri, 17);
343
-		$options = ['recipient' => $recipient];
344
-		return $this->handleIMip($userId, $calendarData, $options);
345
-	}
346
-
347
-	/**
348
-	 * @since 25.0.0
349
-	 *
350
-	 * @throws \OCP\DB\Exception
351
-	 */
352
-	public function handleIMipReply(
353
-		string $principalUri,
354
-		string $sender,
355
-		string $recipient,
356
-		string $calendarData,
357
-	): bool {
358
-		if (empty($principalUri) || !str_starts_with($principalUri, 'principals/users/')) {
359
-			$this->logger->error('Invalid principal URI provided for iMip reply');
360
-			return false;
361
-		}
362
-		$userId = substr($principalUri, 17);
363
-		$options = ['recipient' => $recipient];
364
-		return $this->handleIMip($userId, $calendarData, $options);
365
-	}
366
-
367
-	/**
368
-	 * @since 25.0.0
369
-	 *
370
-	 * @throws \OCP\DB\Exception
371
-	 */
372
-	public function handleIMipCancel(
373
-		string $principalUri,
374
-		string $sender,
375
-		?string $replyTo,
376
-		string $recipient,
377
-		string $calendarData,
378
-	): bool {
379
-		if (empty($principalUri) || !str_starts_with($principalUri, 'principals/users/')) {
380
-			$this->logger->error('Invalid principal URI provided for iMip cancel');
381
-			return false;
382
-		}
383
-		$userId = substr($principalUri, 17);
384
-		$options = ['recipient' => $recipient];
385
-		return $this->handleIMip($userId, $calendarData, $options);
386
-	}
387
-
388
-	public function createEventBuilder(): ICalendarEventBuilder {
389
-		$uid = $this->random->generate(32, ISecureRandom::CHAR_ALPHANUMERIC);
390
-		return new CalendarEventBuilder($uid, $this->timeFactory);
391
-	}
392
-
393
-	public function checkAvailability(
394
-		DateTimeInterface $start,
395
-		DateTimeInterface $end,
396
-		IUser $organizer,
397
-		array $attendees,
398
-	): array {
399
-		$organizerMailto = 'mailto:' . $organizer->getEMailAddress();
400
-		$request = new VCalendar();
401
-		$request->METHOD = 'REQUEST';
402
-		$request->add('VFREEBUSY', [
403
-			'DTSTART' => $start,
404
-			'DTEND' => $end,
405
-			'ORGANIZER' => $organizerMailto,
406
-			'ATTENDEE' => $organizerMailto,
407
-		]);
408
-
409
-		$mailtoLen = strlen('mailto:');
410
-		foreach ($attendees as $attendee) {
411
-			if (str_starts_with($attendee, 'mailto:')) {
412
-				$attendee = substr($attendee, $mailtoLen);
413
-			}
414
-
415
-			$attendeeUsers = $this->userManager->getByEmail($attendee);
416
-			if ($attendeeUsers === []) {
417
-				continue;
418
-			}
419
-
420
-			$request->VFREEBUSY->add('ATTENDEE', "mailto:$attendee");
421
-		}
422
-
423
-		$organizerUid = $organizer->getUID();
424
-		$server = $this->serverFactory->createAttendeeAvailabilityServer();
425
-		/** @var CustomPrincipalPlugin $plugin */
426
-		$plugin = $server->getPlugin('auth');
427
-		$plugin->setCurrentPrincipal("principals/users/$organizerUid");
428
-
429
-		$request = new Request(
430
-			'POST',
431
-			"/calendars/$organizerUid/outbox/",
432
-			[
433
-				'Content-Type' => 'text/calendar',
434
-				'Depth' => 0,
435
-			],
436
-			$request->serialize(),
437
-		);
438
-		$response = new Response();
439
-		$server->invokeMethod($request, $response, false);
440
-
441
-		$xmlService = new \Sabre\Xml\Service();
442
-		$xmlService->elementMap = [
443
-			'{urn:ietf:params:xml:ns:caldav}response' => 'Sabre\Xml\Deserializer\keyValue',
444
-			'{urn:ietf:params:xml:ns:caldav}recipient' => 'Sabre\Xml\Deserializer\keyValue',
445
-		];
446
-		$parsedResponse = $xmlService->parse($response->getBodyAsString());
447
-
448
-		$result = [];
449
-		foreach ($parsedResponse as $freeBusyResponse) {
450
-			$freeBusyResponse = $freeBusyResponse['value'];
451
-			if ($freeBusyResponse['{urn:ietf:params:xml:ns:caldav}request-status'] !== '2.0;Success') {
452
-				continue;
453
-			}
454
-
455
-			$freeBusyResponseData = \Sabre\VObject\Reader::read(
456
-				$freeBusyResponse['{urn:ietf:params:xml:ns:caldav}calendar-data']
457
-			);
458
-
459
-			$attendee = substr(
460
-				$freeBusyResponse['{urn:ietf:params:xml:ns:caldav}recipient']['{DAV:}href'],
461
-				$mailtoLen,
462
-			);
463
-
464
-			$vFreeBusy = $freeBusyResponseData->VFREEBUSY;
465
-			if (!($vFreeBusy instanceof VFreeBusy)) {
466
-				continue;
467
-			}
468
-
469
-			// TODO: actually check values of FREEBUSY properties to find a free slot
470
-			$result[] = new AvailabilityResult($attendee, $vFreeBusy->isFree($start, $end));
471
-		}
472
-
473
-		return $result;
474
-	}
475
-
476
-	public function getPrimaryCalendar(string $userId): ?ICalendar {
477
-		// determine if the principal has a default calendar configured
478
-		$properties = $this->propertyMapper->findPropertyByPathAndName(
479
-			$userId,
480
-			'principals/users/' . $userId,
481
-			'{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'
482
-		);
483
-		if ($properties === []) {
484
-			return null;
485
-		}
486
-		// extract the calendar URI from the property value
487
-		$propertyValue = $properties[0]->getPropertyvalue() ?? null;
488
-		if (str_starts_with($propertyValue, 'calendars/' . $userId)) {
489
-			$calendarUri = rtrim(str_replace('calendars/' . $userId . '/', '', $propertyValue), '/');
490
-		}
491
-		if (empty($calendarUri)) {
492
-			return null;
493
-		}
494
-		// retrieve the calendar by URI
495
-		$calendars = $this->getCalendarsForPrincipal('principals/users/' . $userId, [$calendarUri]);
496
-		if ($calendars === []) {
497
-			return null;
498
-		}
499
-
500
-		return $calendars[0];
501
-	}
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
+        private PropertyMapper $propertyMapper,
63
+    ) {
64
+    }
65
+
66
+    /**
67
+     * This function is used to search and find objects within the user's calendars.
68
+     * In case $pattern is empty all events/journals/todos will be returned.
69
+     *
70
+     * @param string $pattern which should match within the $searchProperties
71
+     * @param array $searchProperties defines the properties within the query pattern should match
72
+     * @param array $options - optional parameters:
73
+     *                       ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]]
74
+     * @param integer|null $limit - limit number of search results
75
+     * @param integer|null $offset - offset for paging of search results
76
+     * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs
77
+     * @since 13.0.0
78
+     */
79
+    public function search(
80
+        $pattern,
81
+        array $searchProperties = [],
82
+        array $options = [],
83
+        $limit = null,
84
+        $offset = null,
85
+    ): array {
86
+        $this->loadCalendars();
87
+        $result = [];
88
+        foreach ($this->calendars as $calendar) {
89
+            $r = $calendar->search($pattern, $searchProperties, $options, $limit, $offset);
90
+            foreach ($r as $o) {
91
+                $o['calendar-key'] = $calendar->getKey();
92
+                $result[] = $o;
93
+            }
94
+        }
95
+
96
+        return $result;
97
+    }
98
+
99
+    /**
100
+     * Check if calendars are available
101
+     *
102
+     * @return bool true if enabled, false if not
103
+     * @since 13.0.0
104
+     */
105
+    public function isEnabled(): bool {
106
+        return !empty($this->calendars) || !empty($this->calendarLoaders);
107
+    }
108
+
109
+    /**
110
+     * Registers a calendar
111
+     *
112
+     * @since 13.0.0
113
+     */
114
+    public function registerCalendar(ICalendar $calendar): void {
115
+        $this->calendars[$calendar->getKey()] = $calendar;
116
+    }
117
+
118
+    /**
119
+     * Unregisters a calendar
120
+     *
121
+     * @since 13.0.0
122
+     */
123
+    public function unregisterCalendar(ICalendar $calendar): void {
124
+        unset($this->calendars[$calendar->getKey()]);
125
+    }
126
+
127
+    /**
128
+     * In order to improve lazy loading a closure can be registered which will be called in case
129
+     * calendars are actually requested
130
+     *
131
+     * @since 13.0.0
132
+     */
133
+    public function register(\Closure $callable): void {
134
+        $this->calendarLoaders[] = $callable;
135
+    }
136
+
137
+    /**
138
+     * @return ICalendar[]
139
+     *
140
+     * @since 13.0.0
141
+     */
142
+    public function getCalendars(): array {
143
+        $this->loadCalendars();
144
+
145
+        return array_values($this->calendars);
146
+    }
147
+
148
+    /**
149
+     * removes all registered calendar instances
150
+     *
151
+     * @since 13.0.0
152
+     */
153
+    public function clear(): void {
154
+        $this->calendars = [];
155
+        $this->calendarLoaders = [];
156
+    }
157
+
158
+    /**
159
+     * loads all calendars
160
+     */
161
+    private function loadCalendars(): void {
162
+        foreach ($this->calendarLoaders as $callable) {
163
+            $callable($this);
164
+        }
165
+        $this->calendarLoaders = [];
166
+    }
167
+
168
+    /**
169
+     * @return ICreateFromString[]
170
+     */
171
+    public function getCalendarsForPrincipal(string $principalUri, array $calendarUris = []): array {
172
+        $context = $this->coordinator->getRegistrationContext();
173
+        if ($context === null) {
174
+            return [];
175
+        }
176
+
177
+        return array_merge(
178
+            ...array_map(function ($registration) use ($principalUri, $calendarUris) {
179
+                try {
180
+                    /** @var ICalendarProvider $provider */
181
+                    $provider = $this->container->get($registration->getService());
182
+                } catch (Throwable $e) {
183
+                    $this->logger->error('Could not load calendar provider ' . $registration->getService() . ': ' . $e->getMessage(), [
184
+                        'exception' => $e,
185
+                    ]);
186
+                    return [];
187
+                }
188
+
189
+                return $provider->getCalendars($principalUri, $calendarUris);
190
+            }, $context->getCalendarProviders())
191
+        );
192
+    }
193
+
194
+    public function searchForPrincipal(ICalendarQuery $query): array {
195
+        /** @var CalendarQuery $query */
196
+        $calendars = $this->getCalendarsForPrincipal(
197
+            $query->getPrincipalUri(),
198
+            $query->getCalendarUris(),
199
+        );
200
+
201
+        $results = [];
202
+        foreach ($calendars as $calendar) {
203
+            $r = $calendar->search(
204
+                $query->getSearchPattern() ?? '',
205
+                $query->getSearchProperties(),
206
+                $query->getOptions(),
207
+                $query->getLimit(),
208
+                $query->getOffset()
209
+            );
210
+
211
+            foreach ($r as $o) {
212
+                $o['calendar-key'] = $calendar->getKey();
213
+                $o['calendar-uri'] = $calendar->getUri();
214
+                $results[] = $o;
215
+            }
216
+        }
217
+        return $results;
218
+    }
219
+
220
+    public function newQuery(string $principalUri): ICalendarQuery {
221
+        return new CalendarQuery($principalUri);
222
+    }
223
+
224
+    /**
225
+     * @since 32.0.0
226
+     *
227
+     * @throws \OCP\DB\Exception
228
+     */
229
+    public function handleIMip(
230
+        string $userId,
231
+        string $message,
232
+        array $options = [],
233
+    ): bool {
234
+
235
+        $userUri = 'principals/users/' . $userId;
236
+
237
+        $userCalendars = $this->getCalendarsForPrincipal($userUri);
238
+        if (empty($userCalendars)) {
239
+            $this->logger->warning('iMip message could not be processed because user has no calendars');
240
+            return false;
241
+        }
242
+
243
+        try {
244
+            /** @var VCalendar $vObject|null */
245
+            $vObject = Reader::read($message);
246
+        } catch (ParseException $e) {
247
+            $this->logger->error('iMip message could not be processed because an error occurred while parsing the iMip message', ['exception' => $e]);
248
+            return false;
249
+        }
250
+
251
+        if (!isset($vObject->VEVENT)) {
252
+            $this->logger->warning('iMip message does not contain any event(s)');
253
+            return false;
254
+        }
255
+        /** @var VEvent $vEvent */
256
+        $vEvent = $vObject->VEVENT;
257
+
258
+        if (!isset($vEvent->UID)) {
259
+            $this->logger->warning('iMip message event dose not contains a UID');
260
+            return false;
261
+        }
262
+
263
+        if (!isset($vEvent->ORGANIZER)) {
264
+            // quirks mode: for Microsoft Exchange Servers use recipient as organizer if no organizer is set
265
+            if (isset($options['recipient']) && $options['recipient'] !== '') {
266
+                $vEvent->add('ORGANIZER', 'mailto:' . $options['recipient']);
267
+            } else {
268
+                $this->logger->warning('iMip message event does not contain an organizer and no recipient was provided');
269
+                return false;
270
+            }
271
+        }
272
+
273
+        if (!isset($vEvent->ATTENDEE)) {
274
+            $this->logger->warning('iMip message event dose not contains any attendees');
275
+            return false;
276
+        }
277
+
278
+        foreach ($userCalendars as $calendar) {
279
+            if (!$calendar instanceof ICalendarIsWritable) {
280
+                continue;
281
+            }
282
+            if ($calendar->isDeleted() || !$calendar->isWritable()) {
283
+                continue;
284
+            }
285
+            if (!empty($calendar->search('', [], ['uid' => $vEvent->UID->getValue()]))) {
286
+                try {
287
+                    if ($calendar instanceof IHandleImipMessage) {
288
+                        $calendar->handleIMipMessage($userId, $vObject->serialize());
289
+                    }
290
+                    return true;
291
+                } catch (CalendarException $e) {
292
+                    $this->logger->error('iMip message could not be processed because an error occurred', ['exception' => $e]);
293
+                    return false;
294
+                }
295
+            }
296
+        }
297
+
298
+        if (isset($options['absent']) && $options['absent'] === 'create') {
299
+            // retrieve the primary calendar for the user
300
+            $calendar = $this->getPrimaryCalendar($userId);
301
+            if ($calendar !== null && (
302
+                !$calendar instanceof IHandleImipMessage || !$calendar instanceof ICalendarIsWritable || $calendar->isDeleted() || !$calendar->isWritable()
303
+            )) {
304
+                $calendar = null;
305
+            }
306
+            // if no primary calendar is set, use the first writable calendar
307
+            if ($calendar === null) {
308
+                foreach ($userCalendars as $userCalendar) {
309
+                    if ($userCalendar instanceof IHandleImipMessage && $userCalendar instanceof ICalendarIsWritable && !$userCalendar->isDeleted() && $userCalendar->isWritable()) {
310
+                        $calendar = $userCalendar;
311
+                        break;
312
+                    }
313
+                }
314
+            }
315
+            if ($calendar === null) {
316
+                $this->logger->warning('iMip message could not be processed because no writable calendar was found');
317
+                return false;
318
+            }
319
+            $calendar->handleIMipMessage($userId, $vObject->serialize());
320
+        }
321
+
322
+        $this->logger->warning('iMip message could not be processed because no corresponding event was found in any calendar');
323
+
324
+        return false;
325
+    }
326
+
327
+    /**
328
+     * @since 31.0.0
329
+     *
330
+     * @throws \OCP\DB\Exception
331
+     */
332
+    public function handleIMipRequest(
333
+        string $principalUri,
334
+        string $sender,
335
+        string $recipient,
336
+        string $calendarData,
337
+    ): bool {
338
+        if (empty($principalUri) || !str_starts_with($principalUri, 'principals/users/')) {
339
+            $this->logger->error('Invalid principal URI provided for iMip request');
340
+            return false;
341
+        }
342
+        $userId = substr($principalUri, 17);
343
+        $options = ['recipient' => $recipient];
344
+        return $this->handleIMip($userId, $calendarData, $options);
345
+    }
346
+
347
+    /**
348
+     * @since 25.0.0
349
+     *
350
+     * @throws \OCP\DB\Exception
351
+     */
352
+    public function handleIMipReply(
353
+        string $principalUri,
354
+        string $sender,
355
+        string $recipient,
356
+        string $calendarData,
357
+    ): bool {
358
+        if (empty($principalUri) || !str_starts_with($principalUri, 'principals/users/')) {
359
+            $this->logger->error('Invalid principal URI provided for iMip reply');
360
+            return false;
361
+        }
362
+        $userId = substr($principalUri, 17);
363
+        $options = ['recipient' => $recipient];
364
+        return $this->handleIMip($userId, $calendarData, $options);
365
+    }
366
+
367
+    /**
368
+     * @since 25.0.0
369
+     *
370
+     * @throws \OCP\DB\Exception
371
+     */
372
+    public function handleIMipCancel(
373
+        string $principalUri,
374
+        string $sender,
375
+        ?string $replyTo,
376
+        string $recipient,
377
+        string $calendarData,
378
+    ): bool {
379
+        if (empty($principalUri) || !str_starts_with($principalUri, 'principals/users/')) {
380
+            $this->logger->error('Invalid principal URI provided for iMip cancel');
381
+            return false;
382
+        }
383
+        $userId = substr($principalUri, 17);
384
+        $options = ['recipient' => $recipient];
385
+        return $this->handleIMip($userId, $calendarData, $options);
386
+    }
387
+
388
+    public function createEventBuilder(): ICalendarEventBuilder {
389
+        $uid = $this->random->generate(32, ISecureRandom::CHAR_ALPHANUMERIC);
390
+        return new CalendarEventBuilder($uid, $this->timeFactory);
391
+    }
392
+
393
+    public function checkAvailability(
394
+        DateTimeInterface $start,
395
+        DateTimeInterface $end,
396
+        IUser $organizer,
397
+        array $attendees,
398
+    ): array {
399
+        $organizerMailto = 'mailto:' . $organizer->getEMailAddress();
400
+        $request = new VCalendar();
401
+        $request->METHOD = 'REQUEST';
402
+        $request->add('VFREEBUSY', [
403
+            'DTSTART' => $start,
404
+            'DTEND' => $end,
405
+            'ORGANIZER' => $organizerMailto,
406
+            'ATTENDEE' => $organizerMailto,
407
+        ]);
408
+
409
+        $mailtoLen = strlen('mailto:');
410
+        foreach ($attendees as $attendee) {
411
+            if (str_starts_with($attendee, 'mailto:')) {
412
+                $attendee = substr($attendee, $mailtoLen);
413
+            }
414
+
415
+            $attendeeUsers = $this->userManager->getByEmail($attendee);
416
+            if ($attendeeUsers === []) {
417
+                continue;
418
+            }
419
+
420
+            $request->VFREEBUSY->add('ATTENDEE', "mailto:$attendee");
421
+        }
422
+
423
+        $organizerUid = $organizer->getUID();
424
+        $server = $this->serverFactory->createAttendeeAvailabilityServer();
425
+        /** @var CustomPrincipalPlugin $plugin */
426
+        $plugin = $server->getPlugin('auth');
427
+        $plugin->setCurrentPrincipal("principals/users/$organizerUid");
428
+
429
+        $request = new Request(
430
+            'POST',
431
+            "/calendars/$organizerUid/outbox/",
432
+            [
433
+                'Content-Type' => 'text/calendar',
434
+                'Depth' => 0,
435
+            ],
436
+            $request->serialize(),
437
+        );
438
+        $response = new Response();
439
+        $server->invokeMethod($request, $response, false);
440
+
441
+        $xmlService = new \Sabre\Xml\Service();
442
+        $xmlService->elementMap = [
443
+            '{urn:ietf:params:xml:ns:caldav}response' => 'Sabre\Xml\Deserializer\keyValue',
444
+            '{urn:ietf:params:xml:ns:caldav}recipient' => 'Sabre\Xml\Deserializer\keyValue',
445
+        ];
446
+        $parsedResponse = $xmlService->parse($response->getBodyAsString());
447
+
448
+        $result = [];
449
+        foreach ($parsedResponse as $freeBusyResponse) {
450
+            $freeBusyResponse = $freeBusyResponse['value'];
451
+            if ($freeBusyResponse['{urn:ietf:params:xml:ns:caldav}request-status'] !== '2.0;Success') {
452
+                continue;
453
+            }
454
+
455
+            $freeBusyResponseData = \Sabre\VObject\Reader::read(
456
+                $freeBusyResponse['{urn:ietf:params:xml:ns:caldav}calendar-data']
457
+            );
458
+
459
+            $attendee = substr(
460
+                $freeBusyResponse['{urn:ietf:params:xml:ns:caldav}recipient']['{DAV:}href'],
461
+                $mailtoLen,
462
+            );
463
+
464
+            $vFreeBusy = $freeBusyResponseData->VFREEBUSY;
465
+            if (!($vFreeBusy instanceof VFreeBusy)) {
466
+                continue;
467
+            }
468
+
469
+            // TODO: actually check values of FREEBUSY properties to find a free slot
470
+            $result[] = new AvailabilityResult($attendee, $vFreeBusy->isFree($start, $end));
471
+        }
472
+
473
+        return $result;
474
+    }
475
+
476
+    public function getPrimaryCalendar(string $userId): ?ICalendar {
477
+        // determine if the principal has a default calendar configured
478
+        $properties = $this->propertyMapper->findPropertyByPathAndName(
479
+            $userId,
480
+            'principals/users/' . $userId,
481
+            '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'
482
+        );
483
+        if ($properties === []) {
484
+            return null;
485
+        }
486
+        // extract the calendar URI from the property value
487
+        $propertyValue = $properties[0]->getPropertyvalue() ?? null;
488
+        if (str_starts_with($propertyValue, 'calendars/' . $userId)) {
489
+            $calendarUri = rtrim(str_replace('calendars/' . $userId . '/', '', $propertyValue), '/');
490
+        }
491
+        if (empty($calendarUri)) {
492
+            return null;
493
+        }
494
+        // retrieve the calendar by URI
495
+        $calendars = $this->getCalendarsForPrincipal('principals/users/' . $userId, [$calendarUri]);
496
+        if ($calendars === []) {
497
+            return null;
498
+        }
499
+
500
+        return $calendars[0];
501
+    }
502 502
 
503 503
 }
Please login to merge, or discard this patch.
Spacing   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -175,12 +175,12 @@  discard block
 block discarded – undo
175 175
 		}
176 176
 
177 177
 		return array_merge(
178
-			...array_map(function ($registration) use ($principalUri, $calendarUris) {
178
+			...array_map(function($registration) use ($principalUri, $calendarUris) {
179 179
 				try {
180 180
 					/** @var ICalendarProvider $provider */
181 181
 					$provider = $this->container->get($registration->getService());
182 182
 				} catch (Throwable $e) {
183
-					$this->logger->error('Could not load calendar provider ' . $registration->getService() . ': ' . $e->getMessage(), [
183
+					$this->logger->error('Could not load calendar provider '.$registration->getService().': '.$e->getMessage(), [
184 184
 						'exception' => $e,
185 185
 					]);
186 186
 					return [];
@@ -232,7 +232,7 @@  discard block
 block discarded – undo
232 232
 		array $options = [],
233 233
 	): bool {
234 234
 
235
-		$userUri = 'principals/users/' . $userId;
235
+		$userUri = 'principals/users/'.$userId;
236 236
 
237 237
 		$userCalendars = $this->getCalendarsForPrincipal($userUri);
238 238
 		if (empty($userCalendars)) {
@@ -263,7 +263,7 @@  discard block
 block discarded – undo
263 263
 		if (!isset($vEvent->ORGANIZER)) {
264 264
 			// quirks mode: for Microsoft Exchange Servers use recipient as organizer if no organizer is set
265 265
 			if (isset($options['recipient']) && $options['recipient'] !== '') {
266
-				$vEvent->add('ORGANIZER', 'mailto:' . $options['recipient']);
266
+				$vEvent->add('ORGANIZER', 'mailto:'.$options['recipient']);
267 267
 			} else {
268 268
 				$this->logger->warning('iMip message event does not contain an organizer and no recipient was provided');
269 269
 				return false;
@@ -396,7 +396,7 @@  discard block
 block discarded – undo
396 396
 		IUser $organizer,
397 397
 		array $attendees,
398 398
 	): array {
399
-		$organizerMailto = 'mailto:' . $organizer->getEMailAddress();
399
+		$organizerMailto = 'mailto:'.$organizer->getEMailAddress();
400 400
 		$request = new VCalendar();
401 401
 		$request->METHOD = 'REQUEST';
402 402
 		$request->add('VFREEBUSY', [
@@ -477,7 +477,7 @@  discard block
 block discarded – undo
477 477
 		// determine if the principal has a default calendar configured
478 478
 		$properties = $this->propertyMapper->findPropertyByPathAndName(
479 479
 			$userId,
480
-			'principals/users/' . $userId,
480
+			'principals/users/'.$userId,
481 481
 			'{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'
482 482
 		);
483 483
 		if ($properties === []) {
@@ -485,14 +485,14 @@  discard block
 block discarded – undo
485 485
 		}
486 486
 		// extract the calendar URI from the property value
487 487
 		$propertyValue = $properties[0]->getPropertyvalue() ?? null;
488
-		if (str_starts_with($propertyValue, 'calendars/' . $userId)) {
489
-			$calendarUri = rtrim(str_replace('calendars/' . $userId . '/', '', $propertyValue), '/');
488
+		if (str_starts_with($propertyValue, 'calendars/'.$userId)) {
489
+			$calendarUri = rtrim(str_replace('calendars/'.$userId.'/', '', $propertyValue), '/');
490 490
 		}
491 491
 		if (empty($calendarUri)) {
492 492
 			return null;
493 493
 		}
494 494
 		// retrieve the calendar by URI
495
-		$calendars = $this->getCalendarsForPrincipal('principals/users/' . $userId, [$calendarUri]);
495
+		$calendars = $this->getCalendarsForPrincipal('principals/users/'.$userId, [$calendarUri]);
496 496
 		if ($calendars === []) {
497 497
 			return null;
498 498
 		}
Please login to merge, or discard this patch.
tests/lib/Calendar/ManagerTest.php 1 patch
Indentation   +808 added lines, -808 removed lines patch added patch discarded remove patch
@@ -42,679 +42,679 @@  discard block
 block discarded – undo
42 42
 }
43 43
 
44 44
 class ManagerTest extends TestCase {
45
-	/** @var Coordinator&MockObject */
46
-	private $coordinator;
47
-
48
-	/** @var ContainerInterface&MockObject */
49
-	private $container;
50
-
51
-	/** @var LoggerInterface&MockObject */
52
-	private $logger;
53
-
54
-	/** @var Manager */
55
-	private $manager;
56
-
57
-	/** @var ITimeFactory&MockObject */
58
-	private $time;
59
-
60
-	/** @var ISecureRandom&MockObject */
61
-	private ISecureRandom $secureRandom;
62
-
63
-	private IUserManager&MockObject $userManager;
64
-	private ServerFactory&MockObject $serverFactory;
65
-	private PropertyMapper&MockObject $propertyMapper;
66
-
67
-	private VCalendar $vCalendar1a;
68
-	private VCalendar $vCalendar2a;
69
-	private VCalendar $vCalendar3a;
70
-
71
-	protected function setUp(): void {
72
-		parent::setUp();
73
-
74
-		$this->coordinator = $this->createMock(Coordinator::class);
75
-		$this->container = $this->createMock(ContainerInterface::class);
76
-		$this->logger = $this->createMock(LoggerInterface::class);
77
-		$this->time = $this->createMock(ITimeFactory::class);
78
-		$this->secureRandom = $this->createMock(ISecureRandom::class);
79
-		$this->userManager = $this->createMock(IUserManager::class);
80
-		$this->serverFactory = $this->createMock(ServerFactory::class);
81
-		$this->propertyMapper = $this->createMock(PropertyMapper::class);
82
-
83
-		$this->manager = new Manager(
84
-			$this->coordinator,
85
-			$this->container,
86
-			$this->logger,
87
-			$this->time,
88
-			$this->secureRandom,
89
-			$this->userManager,
90
-			$this->serverFactory,
91
-			$this->propertyMapper,
92
-		);
93
-
94
-		// construct calendar with a 1 hour event and same start/end time zones
95
-		$this->vCalendar1a = new VCalendar();
96
-		/** @var VEvent $vEvent */
97
-		$vEvent = $this->vCalendar1a->add('VEVENT', []);
98
-		$vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
99
-		$vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
100
-		$vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
101
-		$vEvent->add('SUMMARY', 'Test Event');
102
-		$vEvent->add('SEQUENCE', 3);
103
-		$vEvent->add('STATUS', 'CONFIRMED');
104
-		$vEvent->add('TRANSP', 'OPAQUE');
105
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
106
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
107
-			'CN' => 'Attendee One',
108
-			'CUTYPE' => 'INDIVIDUAL',
109
-			'PARTSTAT' => 'NEEDS-ACTION',
110
-			'ROLE' => 'REQ-PARTICIPANT',
111
-			'RSVP' => 'TRUE'
112
-		]);
113
-
114
-		// construct calendar with a event for reply
115
-		$this->vCalendar2a = new VCalendar();
116
-		/** @var VEvent $vEvent */
117
-		$vEvent = $this->vCalendar2a->add('VEVENT', []);
118
-		$vEvent->UID->setValue('dcc733bf-b2b2-41f2-a8cf-550ae4b67aff');
119
-		$vEvent->add('DTSTART', '20210820');
120
-		$vEvent->add('DTEND', '20220821');
121
-		$vEvent->add('SUMMARY', 'berry basket');
122
-		$vEvent->add('SEQUENCE', 3);
123
-		$vEvent->add('STATUS', 'CONFIRMED');
124
-		$vEvent->add('TRANSP', 'OPAQUE');
125
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'admin']);
126
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
127
-			'CN' => '[email protected]',
128
-			'CUTYPE' => 'INDIVIDUAL',
129
-			'ROLE' => 'REQ-PARTICIPANT',
130
-			'PARTSTAT' => 'ACCEPTED',
131
-		]);
132
-
133
-		// construct calendar with a event for reply
134
-		$this->vCalendar3a = new VCalendar();
135
-		/** @var VEvent $vEvent */
136
-		$vEvent = $this->vCalendar3a->add('VEVENT', []);
137
-		$vEvent->UID->setValue('dcc733bf-b2b2-41f2-a8cf-550ae4b67aff');
138
-		$vEvent->add('DTSTART', '20210820');
139
-		$vEvent->add('DTEND', '20220821');
140
-		$vEvent->add('SUMMARY', 'berry basket');
141
-		$vEvent->add('SEQUENCE', 3);
142
-		$vEvent->add('STATUS', 'CANCELLED');
143
-		$vEvent->add('TRANSP', 'OPAQUE');
144
-		$vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'admin']);
145
-		$vEvent->add('ATTENDEE', 'mailto:[email protected]', [
146
-			'CN' => '[email protected]',
147
-			'CUTYPE' => 'INDIVIDUAL',
148
-			'ROLE' => 'REQ-PARTICIPANT',
149
-			'PARTSTAT' => 'ACCEPTED',
150
-		]);
151
-
152
-	}
153
-
154
-	#[\PHPUnit\Framework\Attributes\DataProvider('searchProvider')]
155
-	public function testSearch($search1, $search2, $expected): void {
156
-		/** @var ICalendar | MockObject $calendar1 */
157
-		$calendar1 = $this->createMock(ICalendar::class);
158
-		$calendar1->method('getKey')->willReturn('simple:1');
159
-		$calendar1->expects($this->once())
160
-			->method('search')
161
-			->with('', [], [], null, null)
162
-			->willReturn($search1);
163
-
164
-		/** @var ICalendar | MockObject $calendar2 */
165
-		$calendar2 = $this->createMock(ICalendar::class);
166
-		$calendar2->method('getKey')->willReturn('simple:2');
167
-		$calendar2->expects($this->once())
168
-			->method('search')
169
-			->with('', [], [], null, null)
170
-			->willReturn($search2);
171
-
172
-		$this->manager->registerCalendar($calendar1);
173
-		$this->manager->registerCalendar($calendar2);
174
-
175
-		$result = $this->manager->search('');
176
-		$this->assertEquals($expected, $result);
177
-	}
178
-
179
-	#[\PHPUnit\Framework\Attributes\DataProvider('searchProvider')]
180
-	public function testSearchOptions($search1, $search2, $expected): void {
181
-		/** @var ICalendar | MockObject $calendar1 */
182
-		$calendar1 = $this->createMock(ICalendar::class);
183
-		$calendar1->method('getKey')->willReturn('simple:1');
184
-		$calendar1->expects($this->once())
185
-			->method('search')
186
-			->with('searchTerm', ['SUMMARY', 'DESCRIPTION'],
187
-				['timerange' => ['start' => null, 'end' => null]], 5, 20)
188
-			->willReturn($search1);
189
-
190
-		/** @var ICalendar | MockObject $calendar2 */
191
-		$calendar2 = $this->createMock(ICalendar::class);
192
-		$calendar2->method('getKey')->willReturn('simple:2');
193
-		$calendar2->expects($this->once())
194
-			->method('search')
195
-			->with('searchTerm', ['SUMMARY', 'DESCRIPTION'],
196
-				['timerange' => ['start' => null, 'end' => null]], 5, 20)
197
-			->willReturn($search2);
198
-
199
-		$this->manager->registerCalendar($calendar1);
200
-		$this->manager->registerCalendar($calendar2);
201
-
202
-		$result = $this->manager->search('searchTerm', ['SUMMARY', 'DESCRIPTION'],
203
-			['timerange' => ['start' => null, 'end' => null]], 5, 20);
204
-		$this->assertEquals($expected, $result);
205
-	}
206
-
207
-	public static function searchProvider(): array {
208
-		$search1 = [
209
-			[
210
-				'id' => 1,
211
-				'data' => 'foobar',
212
-			],
213
-			[
214
-				'id' => 2,
215
-				'data' => 'barfoo',
216
-			]
217
-		];
218
-		$search2 = [
219
-			[
220
-				'id' => 3,
221
-				'data' => 'blablub',
222
-			],
223
-			[
224
-				'id' => 4,
225
-				'data' => 'blubbla',
226
-			]
227
-		];
228
-
229
-		$expected = [
230
-			[
231
-				'id' => 1,
232
-				'data' => 'foobar',
233
-				'calendar-key' => 'simple:1',
234
-			],
235
-			[
236
-				'id' => 2,
237
-				'data' => 'barfoo',
238
-				'calendar-key' => 'simple:1',
239
-			],
240
-			[
241
-				'id' => 3,
242
-				'data' => 'blablub',
243
-				'calendar-key' => 'simple:2',
244
-			],
245
-			[
246
-				'id' => 4,
247
-				'data' => 'blubbla',
248
-				'calendar-key' => 'simple:2',
249
-			]
250
-		];
251
-
252
-		return [
253
-			[
254
-				$search1,
255
-				$search2,
256
-				$expected
257
-			]
258
-		];
259
-	}
260
-
261
-	public function testRegisterUnregister(): void {
262
-		/** @var ICalendar | MockObject $calendar1 */
263
-		$calendar1 = $this->createMock(ICalendar::class);
264
-		$calendar1->method('getKey')->willReturn('key1');
265
-
266
-		/** @var ICalendar | MockObject $calendar2 */
267
-		$calendar2 = $this->createMock(ICalendar::class);
268
-		$calendar2->method('getKey')->willReturn('key2');
269
-
270
-		$this->manager->registerCalendar($calendar1);
271
-		$this->manager->registerCalendar($calendar2);
272
-
273
-		$result = $this->manager->getCalendars();
274
-		$this->assertCount(2, $result);
275
-		$this->assertContains($calendar1, $result);
276
-		$this->assertContains($calendar2, $result);
277
-
278
-		$this->manager->unregisterCalendar($calendar1);
279
-
280
-		$result = $this->manager->getCalendars();
281
-		$this->assertCount(1, $result);
282
-		$this->assertContains($calendar2, $result);
283
-	}
284
-
285
-	public function testGetCalendars(): void {
286
-		/** @var ICalendar | MockObject $calendar1 */
287
-		$calendar1 = $this->createMock(ICalendar::class);
288
-		$calendar1->method('getKey')->willReturn('key1');
289
-
290
-		/** @var ICalendar | MockObject $calendar2 */
291
-		$calendar2 = $this->createMock(ICalendar::class);
292
-		$calendar2->method('getKey')->willReturn('key2');
293
-
294
-		$this->manager->registerCalendar($calendar1);
295
-		$this->manager->registerCalendar($calendar2);
296
-
297
-		$result = $this->manager->getCalendars();
298
-		$this->assertCount(2, $result);
299
-		$this->assertContains($calendar1, $result);
300
-		$this->assertContains($calendar2, $result);
301
-
302
-		$this->manager->clear();
303
-
304
-		$result = $this->manager->getCalendars();
305
-
306
-		$this->assertCount(0, $result);
307
-	}
308
-
309
-	public function testEnabledIfNot(): void {
310
-		$isEnabled = $this->manager->isEnabled();
311
-		$this->assertFalse($isEnabled);
312
-	}
313
-
314
-	public function testIfEnabledIfSo(): void {
315
-		/** @var ICalendar | MockObject $calendar */
316
-		$calendar = $this->createMock(ICalendar::class);
317
-		$this->manager->registerCalendar($calendar);
318
-
319
-		$isEnabled = $this->manager->isEnabled();
320
-		$this->assertTrue($isEnabled);
321
-	}
322
-
323
-	public function testHandleImipWithNoCalendars(): void {
324
-		// construct calendar manager returns
325
-		/** @var Manager&MockObject $manager */
326
-		$manager = $this->getMockBuilder(Manager::class)
327
-			->setConstructorArgs([
328
-				$this->coordinator,
329
-				$this->container,
330
-				$this->logger,
331
-				$this->time,
332
-				$this->secureRandom,
333
-				$this->userManager,
334
-				$this->serverFactory,
335
-				$this->propertyMapper,
336
-			])
337
-			->onlyMethods(['getCalendarsForPrincipal'])
338
-			->getMock();
339
-		$manager->expects(self::once())
340
-			->method('getCalendarsForPrincipal')
341
-			->willReturn([]);
342
-		// construct logger returns
343
-		$this->logger->expects(self::once())->method('warning')
344
-			->with('iMip message could not be processed because user has no calendars');
345
-		// construct parameters
346
-		$userId = 'attendee1';
347
-		$calendar = $this->vCalendar1a;
348
-		$calendar->add('METHOD', 'REQUEST');
349
-		// test method
350
-		$result = $manager->handleIMip($userId, $calendar->serialize());
351
-		// Assert
352
-		$this->assertFalse($result);
353
-	}
354
-
355
-	public function testHandleImipWithNoEvent(): void {
356
-		// construct mock user calendar
357
-		$userCalendar = $this->createMock(ITestCalendar::class);
358
-		// construct mock calendar manager and returns
359
-		/** @var Manager&MockObject $manager */
360
-		$manager = $this->getMockBuilder(Manager::class)
361
-			->setConstructorArgs([
362
-				$this->coordinator,
363
-				$this->container,
364
-				$this->logger,
365
-				$this->time,
366
-				$this->secureRandom,
367
-				$this->userManager,
368
-				$this->serverFactory,
369
-				$this->propertyMapper,
370
-			])
371
-			->onlyMethods(['getCalendarsForPrincipal'])
372
-			->getMock();
373
-		$manager->expects(self::once())
374
-			->method('getCalendarsForPrincipal')
375
-			->willReturn([$userCalendar]);
376
-		// construct logger returns
377
-		$this->logger->expects(self::once())->method('warning')
378
-			->with('iMip message does not contain any event(s)');
379
-		// construct parameters
380
-		$userId = 'attendee1';
381
-		$calendar = $this->vCalendar1a;
382
-		$calendar->add('METHOD', 'REQUEST');
383
-		$calendar->remove('VEVENT');
384
-		// Act
385
-		$result = $manager->handleIMip($userId, $calendar->serialize());
386
-		// Assert
387
-		$this->assertFalse($result);
388
-	}
389
-
390
-	public function testHandleImipMissingOrganizerWithRecipient(): void {
391
-		// construct mock user calendar
392
-		$userCalendar = $this->createMock(ITestCalendar::class);
393
-		$userCalendar->expects(self::once())
394
-			->method('isDeleted')
395
-			->willReturn(false);
396
-		$userCalendar->expects(self::once())
397
-			->method('isWritable')
398
-			->willReturn(true);
399
-		$userCalendar->expects(self::once())
400
-			->method('search')
401
-			->willReturn([['uri' => 'principals/user/attendee1/personal']]);
402
-		// construct mock calendar manager and returns
403
-		/** @var Manager&MockObject $manager */
404
-		$manager = $this->getMockBuilder(Manager::class)
405
-			->setConstructorArgs([
406
-				$this->coordinator,
407
-				$this->container,
408
-				$this->logger,
409
-				$this->time,
410
-				$this->secureRandom,
411
-				$this->userManager,
412
-				$this->serverFactory,
413
-				$this->propertyMapper,
414
-			])
415
-			->onlyMethods(['getCalendarsForPrincipal'])
416
-			->getMock();
417
-		$manager->expects(self::once())
418
-			->method('getCalendarsForPrincipal')
419
-			->willReturn([$userCalendar]);
420
-		// construct parameters
421
-		$userId = 'attendee1';
422
-		$calendar = $this->vCalendar1a;
423
-		$calendar->add('METHOD', 'REQUEST');
424
-		$calendar->VEVENT->remove('ORGANIZER');
425
-		// construct user calendar returns
426
-		$userCalendar->expects(self::once())
427
-			->method('handleIMipMessage');
428
-		// test method
429
-		$result = $manager->handleIMip($userId, $calendar->serialize(), ['recipient' => '[email protected]']);
430
-	}
431
-
432
-	public function testHandleImipMissingOrganizerNoRecipient(): void {
433
-		// construct mock user calendar
434
-		$userCalendar = $this->createMock(ITestCalendar::class);
435
-		// construct mock calendar manager and returns
436
-		/** @var Manager&MockObject $manager */
437
-		$manager = $this->getMockBuilder(Manager::class)
438
-			->setConstructorArgs([
439
-				$this->coordinator,
440
-				$this->container,
441
-				$this->logger,
442
-				$this->time,
443
-				$this->secureRandom,
444
-				$this->userManager,
445
-				$this->serverFactory,
446
-				$this->propertyMapper,
447
-			])
448
-			->onlyMethods(['getCalendarsForPrincipal'])
449
-			->getMock();
450
-		$manager->expects(self::once())
451
-			->method('getCalendarsForPrincipal')
452
-			->willReturn([$userCalendar]);
453
-		// construct parameters
454
-		$userId = 'attendee1';
455
-		$calendar = $this->vCalendar1a;
456
-		$calendar->add('METHOD', 'REQUEST');
457
-		$calendar->VEVENT->remove('ORGANIZER');
458
-		// Logger expects warning
459
-		$this->logger->expects($this->once())
460
-			->method('warning')
461
-			->with('iMip message event does not contain an organizer and no recipient was provided');
462
-
463
-		$result = $manager->handleIMip($userId, $calendar->serialize(), []);
464
-	}
465
-
466
-	public function testHandleImipWithNoUid(): void {
467
-		// construct mock user calendar
468
-		$userCalendar = $this->createMock(ITestCalendar::class);
469
-		// construct mock calendar manager and returns
470
-		/** @var Manager&MockObject $manager */
471
-		$manager = $this->getMockBuilder(Manager::class)
472
-			->setConstructorArgs([
473
-				$this->coordinator,
474
-				$this->container,
475
-				$this->logger,
476
-				$this->time,
477
-				$this->secureRandom,
478
-				$this->userManager,
479
-				$this->serverFactory,
480
-				$this->propertyMapper,
481
-			])
482
-			->onlyMethods(['getCalendarsForPrincipal'])
483
-			->getMock();
484
-		$manager->expects(self::once())
485
-			->method('getCalendarsForPrincipal')
486
-			->willReturn([$userCalendar]);
487
-		// construct logger returns
488
-		$this->logger->expects(self::once())->method('warning')
489
-			->with('iMip message event dose not contains a UID');
490
-		// construct parameters
491
-		$userId = 'attendee1';
492
-		$calendar = $this->vCalendar1a;
493
-		$calendar->add('METHOD', 'REQUEST');
494
-		$calendar->VEVENT->remove('UID');
495
-		// test method
496
-		$result = $manager->handleIMip($userId, $calendar->serialize());
497
-		// Assert
498
-		$this->assertFalse($result);
499
-	}
500
-
501
-	public function testHandleImipWithNoMatch(): void {
502
-		// construct mock user calendar
503
-		$userCalendar = $this->createMock(ITestCalendar::class);
504
-		$userCalendar->expects(self::once())
505
-			->method('isDeleted')
506
-			->willReturn(false);
507
-		$userCalendar->expects(self::once())
508
-			->method('isWritable')
509
-			->willReturn(true);
510
-		$userCalendar->expects(self::once())
511
-			->method('search')
512
-			->willReturn([]);
513
-		// construct mock calendar manager and returns
514
-		/** @var Manager&MockObject $manager */
515
-		$manager = $this->getMockBuilder(Manager::class)
516
-			->setConstructorArgs([
517
-				$this->coordinator,
518
-				$this->container,
519
-				$this->logger,
520
-				$this->time,
521
-				$this->secureRandom,
522
-				$this->userManager,
523
-				$this->serverFactory,
524
-				$this->propertyMapper,
525
-			])
526
-			->onlyMethods(['getCalendarsForPrincipal'])
527
-			->getMock();
528
-		$manager->expects(self::once())
529
-			->method('getCalendarsForPrincipal')
530
-			->willReturn([$userCalendar]);
531
-		// construct logger returns
532
-		$this->logger->expects(self::once())->method('warning')
533
-			->with('iMip message could not be processed because no corresponding event was found in any calendar');
534
-		// construct parameters
535
-		$userId = 'attendee1';
536
-		$calendar = $this->vCalendar1a;
537
-		$calendar->add('METHOD', 'REQUEST');
538
-		// test method
539
-		$result = $manager->handleIMip($userId, $calendar->serialize());
540
-		// Assert
541
-		$this->assertFalse($result);
542
-	}
543
-
544
-	public function testHandleImip(): void {
545
-		// construct mock user calendar
546
-		$userCalendar = $this->createMock(ITestCalendar::class);
547
-		$userCalendar->expects(self::once())
548
-			->method('isDeleted')
549
-			->willReturn(false);
550
-		$userCalendar->expects(self::once())
551
-			->method('isWritable')
552
-			->willReturn(true);
553
-		$userCalendar->expects(self::once())
554
-			->method('search')
555
-			->willReturn([['uri' => 'principals/user/attendee1/personal']]);
556
-		// construct mock calendar manager and returns
557
-		/** @var Manager&MockObject $manager */
558
-		$manager = $this->getMockBuilder(Manager::class)
559
-			->setConstructorArgs([
560
-				$this->coordinator,
561
-				$this->container,
562
-				$this->logger,
563
-				$this->time,
564
-				$this->secureRandom,
565
-				$this->userManager,
566
-				$this->serverFactory,
567
-				$this->propertyMapper,
568
-			])
569
-			->onlyMethods(['getCalendarsForPrincipal'])
570
-			->getMock();
571
-		$manager->expects(self::once())
572
-			->method('getCalendarsForPrincipal')
573
-			->willReturn([$userCalendar]);
574
-		// construct parameters
575
-		$userId = 'attendee1';
576
-		$calendar = $this->vCalendar1a;
577
-		$calendar->add('METHOD', 'REQUEST');
578
-		// construct user calendar returns
579
-		$userCalendar->expects(self::once())
580
-			->method('handleIMipMessage');
581
-		// test method
582
-		$result = $manager->handleIMip($userId, $calendar->serialize());
583
-	}
584
-
585
-	public function testhandleIMipRequestWithInvalidPrincipal() {
586
-		$invalidPrincipal = 'invalid-principal-uri';
587
-		$sender = '[email protected]';
588
-		$recipient = '[email protected]';
589
-		$calendarData = $this->vCalendar1a->serialize();
590
-
591
-		$this->logger->expects(self::once())
592
-			->method('error')
593
-			->with('Invalid principal URI provided for iMip request');
594
-
595
-		$result = $this->manager->handleIMipRequest($invalidPrincipal, $sender, $recipient, $calendarData);
596
-		$this->assertFalse($result);
597
-	}
598
-
599
-	public function testhandleIMipRequest() {
600
-		$principalUri = 'principals/users/attendee1';
601
-		$sender = '[email protected]';
602
-		$recipient = '[email protected]';
603
-		$calendarData = $this->vCalendar1a->serialize();
604
-
605
-		/** @var Manager&MockObject $manager */
606
-		$manager = $this->getMockBuilder(Manager::class)
607
-			->setConstructorArgs([
608
-				$this->coordinator,
609
-				$this->container,
610
-				$this->logger,
611
-				$this->time,
612
-				$this->secureRandom,
613
-				$this->userManager,
614
-				$this->serverFactory,
615
-				$this->propertyMapper,
616
-			])
617
-			->onlyMethods(['handleIMip'])
618
-			->getMock();
619
-		$manager->expects(self::once())
620
-			->method('handleIMip')
621
-			->with('attendee1', $calendarData)
622
-			->willReturn(true);
623
-
624
-		$result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendarData);
625
-		$this->assertTrue($result);
626
-	}
627
-
628
-	public function testhandleIMipReplyWithInvalidPrincipal() {
629
-		$invalidPrincipal = 'invalid-principal-uri';
630
-		$sender = '[email protected]';
631
-		$recipient = '[email protected]';
632
-		$calendarData = $this->vCalendar2a->serialize();
633
-
634
-		$this->logger->expects(self::once())
635
-			->method('error')
636
-			->with('Invalid principal URI provided for iMip reply');
637
-
638
-		$result = $this->manager->handleIMipReply($invalidPrincipal, $sender, $recipient, $calendarData);
639
-		$this->assertFalse($result);
640
-	}
641
-
642
-	public function testhandleIMipReply() {
643
-		$principalUri = 'principals/users/attendee2';
644
-		$sender = '[email protected]';
645
-		$recipient = '[email protected]';
646
-		$calendarData = $this->vCalendar2a->serialize();
647
-
648
-		/** @var Manager&MockObject $manager */
649
-		$manager = $this->getMockBuilder(Manager::class)
650
-			->setConstructorArgs([
651
-				$this->coordinator,
652
-				$this->container,
653
-				$this->logger,
654
-				$this->time,
655
-				$this->secureRandom,
656
-				$this->userManager,
657
-				$this->serverFactory,
658
-				$this->propertyMapper,
659
-			])
660
-			->onlyMethods(['handleIMip'])
661
-			->getMock();
662
-		$manager->expects(self::once())
663
-			->method('handleIMip')
664
-			->with('attendee2', $calendarData)
665
-			->willReturn(true);
666
-
667
-		$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData);
668
-		$this->assertTrue($result);
669
-	}
670
-
671
-	public function testhandleIMipCancelWithInvalidPrincipal() {
672
-		$invalidPrincipal = 'invalid-principal-uri';
673
-		$sender = '[email protected]';
674
-		$replyTo = null;
675
-		$recipient = '[email protected]';
676
-		$calendarData = $this->vCalendar3a->serialize();
677
-
678
-		$this->logger->expects(self::once())
679
-			->method('error')
680
-			->with('Invalid principal URI provided for iMip cancel');
681
-
682
-		$result = $this->manager->handleIMipCancel($invalidPrincipal, $sender, $replyTo, $recipient, $calendarData);
683
-		$this->assertFalse($result);
684
-	}
685
-
686
-	public function testhandleIMipCancel() {
687
-		$principalUri = 'principals/users/attendee3';
688
-		$sender = '[email protected]';
689
-		$replyTo = null;
690
-		$recipient = '[email protected]';
691
-		$calendarData = $this->vCalendar3a->serialize();
692
-
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
-				$this->propertyMapper,
704
-			])
705
-			->onlyMethods(['handleIMip'])
706
-			->getMock();
707
-		$manager->expects(self::once())
708
-			->method('handleIMip')
709
-			->with('attendee3', $calendarData)
710
-			->willReturn(true);
711
-
712
-		$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData);
713
-		$this->assertTrue($result);
714
-	}
715
-
716
-	private function getFreeBusyResponse(): string {
717
-		return <<<EOF
45
+    /** @var Coordinator&MockObject */
46
+    private $coordinator;
47
+
48
+    /** @var ContainerInterface&MockObject */
49
+    private $container;
50
+
51
+    /** @var LoggerInterface&MockObject */
52
+    private $logger;
53
+
54
+    /** @var Manager */
55
+    private $manager;
56
+
57
+    /** @var ITimeFactory&MockObject */
58
+    private $time;
59
+
60
+    /** @var ISecureRandom&MockObject */
61
+    private ISecureRandom $secureRandom;
62
+
63
+    private IUserManager&MockObject $userManager;
64
+    private ServerFactory&MockObject $serverFactory;
65
+    private PropertyMapper&MockObject $propertyMapper;
66
+
67
+    private VCalendar $vCalendar1a;
68
+    private VCalendar $vCalendar2a;
69
+    private VCalendar $vCalendar3a;
70
+
71
+    protected function setUp(): void {
72
+        parent::setUp();
73
+
74
+        $this->coordinator = $this->createMock(Coordinator::class);
75
+        $this->container = $this->createMock(ContainerInterface::class);
76
+        $this->logger = $this->createMock(LoggerInterface::class);
77
+        $this->time = $this->createMock(ITimeFactory::class);
78
+        $this->secureRandom = $this->createMock(ISecureRandom::class);
79
+        $this->userManager = $this->createMock(IUserManager::class);
80
+        $this->serverFactory = $this->createMock(ServerFactory::class);
81
+        $this->propertyMapper = $this->createMock(PropertyMapper::class);
82
+
83
+        $this->manager = new Manager(
84
+            $this->coordinator,
85
+            $this->container,
86
+            $this->logger,
87
+            $this->time,
88
+            $this->secureRandom,
89
+            $this->userManager,
90
+            $this->serverFactory,
91
+            $this->propertyMapper,
92
+        );
93
+
94
+        // construct calendar with a 1 hour event and same start/end time zones
95
+        $this->vCalendar1a = new VCalendar();
96
+        /** @var VEvent $vEvent */
97
+        $vEvent = $this->vCalendar1a->add('VEVENT', []);
98
+        $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc');
99
+        $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']);
100
+        $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']);
101
+        $vEvent->add('SUMMARY', 'Test Event');
102
+        $vEvent->add('SEQUENCE', 3);
103
+        $vEvent->add('STATUS', 'CONFIRMED');
104
+        $vEvent->add('TRANSP', 'OPAQUE');
105
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'Organizer']);
106
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
107
+            'CN' => 'Attendee One',
108
+            'CUTYPE' => 'INDIVIDUAL',
109
+            'PARTSTAT' => 'NEEDS-ACTION',
110
+            'ROLE' => 'REQ-PARTICIPANT',
111
+            'RSVP' => 'TRUE'
112
+        ]);
113
+
114
+        // construct calendar with a event for reply
115
+        $this->vCalendar2a = new VCalendar();
116
+        /** @var VEvent $vEvent */
117
+        $vEvent = $this->vCalendar2a->add('VEVENT', []);
118
+        $vEvent->UID->setValue('dcc733bf-b2b2-41f2-a8cf-550ae4b67aff');
119
+        $vEvent->add('DTSTART', '20210820');
120
+        $vEvent->add('DTEND', '20220821');
121
+        $vEvent->add('SUMMARY', 'berry basket');
122
+        $vEvent->add('SEQUENCE', 3);
123
+        $vEvent->add('STATUS', 'CONFIRMED');
124
+        $vEvent->add('TRANSP', 'OPAQUE');
125
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'admin']);
126
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
127
+            'CN' => '[email protected]',
128
+            'CUTYPE' => 'INDIVIDUAL',
129
+            'ROLE' => 'REQ-PARTICIPANT',
130
+            'PARTSTAT' => 'ACCEPTED',
131
+        ]);
132
+
133
+        // construct calendar with a event for reply
134
+        $this->vCalendar3a = new VCalendar();
135
+        /** @var VEvent $vEvent */
136
+        $vEvent = $this->vCalendar3a->add('VEVENT', []);
137
+        $vEvent->UID->setValue('dcc733bf-b2b2-41f2-a8cf-550ae4b67aff');
138
+        $vEvent->add('DTSTART', '20210820');
139
+        $vEvent->add('DTEND', '20220821');
140
+        $vEvent->add('SUMMARY', 'berry basket');
141
+        $vEvent->add('SEQUENCE', 3);
142
+        $vEvent->add('STATUS', 'CANCELLED');
143
+        $vEvent->add('TRANSP', 'OPAQUE');
144
+        $vEvent->add('ORGANIZER', 'mailto:[email protected]', ['CN' => 'admin']);
145
+        $vEvent->add('ATTENDEE', 'mailto:[email protected]', [
146
+            'CN' => '[email protected]',
147
+            'CUTYPE' => 'INDIVIDUAL',
148
+            'ROLE' => 'REQ-PARTICIPANT',
149
+            'PARTSTAT' => 'ACCEPTED',
150
+        ]);
151
+
152
+    }
153
+
154
+    #[\PHPUnit\Framework\Attributes\DataProvider('searchProvider')]
155
+    public function testSearch($search1, $search2, $expected): void {
156
+        /** @var ICalendar | MockObject $calendar1 */
157
+        $calendar1 = $this->createMock(ICalendar::class);
158
+        $calendar1->method('getKey')->willReturn('simple:1');
159
+        $calendar1->expects($this->once())
160
+            ->method('search')
161
+            ->with('', [], [], null, null)
162
+            ->willReturn($search1);
163
+
164
+        /** @var ICalendar | MockObject $calendar2 */
165
+        $calendar2 = $this->createMock(ICalendar::class);
166
+        $calendar2->method('getKey')->willReturn('simple:2');
167
+        $calendar2->expects($this->once())
168
+            ->method('search')
169
+            ->with('', [], [], null, null)
170
+            ->willReturn($search2);
171
+
172
+        $this->manager->registerCalendar($calendar1);
173
+        $this->manager->registerCalendar($calendar2);
174
+
175
+        $result = $this->manager->search('');
176
+        $this->assertEquals($expected, $result);
177
+    }
178
+
179
+    #[\PHPUnit\Framework\Attributes\DataProvider('searchProvider')]
180
+    public function testSearchOptions($search1, $search2, $expected): void {
181
+        /** @var ICalendar | MockObject $calendar1 */
182
+        $calendar1 = $this->createMock(ICalendar::class);
183
+        $calendar1->method('getKey')->willReturn('simple:1');
184
+        $calendar1->expects($this->once())
185
+            ->method('search')
186
+            ->with('searchTerm', ['SUMMARY', 'DESCRIPTION'],
187
+                ['timerange' => ['start' => null, 'end' => null]], 5, 20)
188
+            ->willReturn($search1);
189
+
190
+        /** @var ICalendar | MockObject $calendar2 */
191
+        $calendar2 = $this->createMock(ICalendar::class);
192
+        $calendar2->method('getKey')->willReturn('simple:2');
193
+        $calendar2->expects($this->once())
194
+            ->method('search')
195
+            ->with('searchTerm', ['SUMMARY', 'DESCRIPTION'],
196
+                ['timerange' => ['start' => null, 'end' => null]], 5, 20)
197
+            ->willReturn($search2);
198
+
199
+        $this->manager->registerCalendar($calendar1);
200
+        $this->manager->registerCalendar($calendar2);
201
+
202
+        $result = $this->manager->search('searchTerm', ['SUMMARY', 'DESCRIPTION'],
203
+            ['timerange' => ['start' => null, 'end' => null]], 5, 20);
204
+        $this->assertEquals($expected, $result);
205
+    }
206
+
207
+    public static function searchProvider(): array {
208
+        $search1 = [
209
+            [
210
+                'id' => 1,
211
+                'data' => 'foobar',
212
+            ],
213
+            [
214
+                'id' => 2,
215
+                'data' => 'barfoo',
216
+            ]
217
+        ];
218
+        $search2 = [
219
+            [
220
+                'id' => 3,
221
+                'data' => 'blablub',
222
+            ],
223
+            [
224
+                'id' => 4,
225
+                'data' => 'blubbla',
226
+            ]
227
+        ];
228
+
229
+        $expected = [
230
+            [
231
+                'id' => 1,
232
+                'data' => 'foobar',
233
+                'calendar-key' => 'simple:1',
234
+            ],
235
+            [
236
+                'id' => 2,
237
+                'data' => 'barfoo',
238
+                'calendar-key' => 'simple:1',
239
+            ],
240
+            [
241
+                'id' => 3,
242
+                'data' => 'blablub',
243
+                'calendar-key' => 'simple:2',
244
+            ],
245
+            [
246
+                'id' => 4,
247
+                'data' => 'blubbla',
248
+                'calendar-key' => 'simple:2',
249
+            ]
250
+        ];
251
+
252
+        return [
253
+            [
254
+                $search1,
255
+                $search2,
256
+                $expected
257
+            ]
258
+        ];
259
+    }
260
+
261
+    public function testRegisterUnregister(): void {
262
+        /** @var ICalendar | MockObject $calendar1 */
263
+        $calendar1 = $this->createMock(ICalendar::class);
264
+        $calendar1->method('getKey')->willReturn('key1');
265
+
266
+        /** @var ICalendar | MockObject $calendar2 */
267
+        $calendar2 = $this->createMock(ICalendar::class);
268
+        $calendar2->method('getKey')->willReturn('key2');
269
+
270
+        $this->manager->registerCalendar($calendar1);
271
+        $this->manager->registerCalendar($calendar2);
272
+
273
+        $result = $this->manager->getCalendars();
274
+        $this->assertCount(2, $result);
275
+        $this->assertContains($calendar1, $result);
276
+        $this->assertContains($calendar2, $result);
277
+
278
+        $this->manager->unregisterCalendar($calendar1);
279
+
280
+        $result = $this->manager->getCalendars();
281
+        $this->assertCount(1, $result);
282
+        $this->assertContains($calendar2, $result);
283
+    }
284
+
285
+    public function testGetCalendars(): void {
286
+        /** @var ICalendar | MockObject $calendar1 */
287
+        $calendar1 = $this->createMock(ICalendar::class);
288
+        $calendar1->method('getKey')->willReturn('key1');
289
+
290
+        /** @var ICalendar | MockObject $calendar2 */
291
+        $calendar2 = $this->createMock(ICalendar::class);
292
+        $calendar2->method('getKey')->willReturn('key2');
293
+
294
+        $this->manager->registerCalendar($calendar1);
295
+        $this->manager->registerCalendar($calendar2);
296
+
297
+        $result = $this->manager->getCalendars();
298
+        $this->assertCount(2, $result);
299
+        $this->assertContains($calendar1, $result);
300
+        $this->assertContains($calendar2, $result);
301
+
302
+        $this->manager->clear();
303
+
304
+        $result = $this->manager->getCalendars();
305
+
306
+        $this->assertCount(0, $result);
307
+    }
308
+
309
+    public function testEnabledIfNot(): void {
310
+        $isEnabled = $this->manager->isEnabled();
311
+        $this->assertFalse($isEnabled);
312
+    }
313
+
314
+    public function testIfEnabledIfSo(): void {
315
+        /** @var ICalendar | MockObject $calendar */
316
+        $calendar = $this->createMock(ICalendar::class);
317
+        $this->manager->registerCalendar($calendar);
318
+
319
+        $isEnabled = $this->manager->isEnabled();
320
+        $this->assertTrue($isEnabled);
321
+    }
322
+
323
+    public function testHandleImipWithNoCalendars(): void {
324
+        // construct calendar manager returns
325
+        /** @var Manager&MockObject $manager */
326
+        $manager = $this->getMockBuilder(Manager::class)
327
+            ->setConstructorArgs([
328
+                $this->coordinator,
329
+                $this->container,
330
+                $this->logger,
331
+                $this->time,
332
+                $this->secureRandom,
333
+                $this->userManager,
334
+                $this->serverFactory,
335
+                $this->propertyMapper,
336
+            ])
337
+            ->onlyMethods(['getCalendarsForPrincipal'])
338
+            ->getMock();
339
+        $manager->expects(self::once())
340
+            ->method('getCalendarsForPrincipal')
341
+            ->willReturn([]);
342
+        // construct logger returns
343
+        $this->logger->expects(self::once())->method('warning')
344
+            ->with('iMip message could not be processed because user has no calendars');
345
+        // construct parameters
346
+        $userId = 'attendee1';
347
+        $calendar = $this->vCalendar1a;
348
+        $calendar->add('METHOD', 'REQUEST');
349
+        // test method
350
+        $result = $manager->handleIMip($userId, $calendar->serialize());
351
+        // Assert
352
+        $this->assertFalse($result);
353
+    }
354
+
355
+    public function testHandleImipWithNoEvent(): void {
356
+        // construct mock user calendar
357
+        $userCalendar = $this->createMock(ITestCalendar::class);
358
+        // construct mock calendar manager and returns
359
+        /** @var Manager&MockObject $manager */
360
+        $manager = $this->getMockBuilder(Manager::class)
361
+            ->setConstructorArgs([
362
+                $this->coordinator,
363
+                $this->container,
364
+                $this->logger,
365
+                $this->time,
366
+                $this->secureRandom,
367
+                $this->userManager,
368
+                $this->serverFactory,
369
+                $this->propertyMapper,
370
+            ])
371
+            ->onlyMethods(['getCalendarsForPrincipal'])
372
+            ->getMock();
373
+        $manager->expects(self::once())
374
+            ->method('getCalendarsForPrincipal')
375
+            ->willReturn([$userCalendar]);
376
+        // construct logger returns
377
+        $this->logger->expects(self::once())->method('warning')
378
+            ->with('iMip message does not contain any event(s)');
379
+        // construct parameters
380
+        $userId = 'attendee1';
381
+        $calendar = $this->vCalendar1a;
382
+        $calendar->add('METHOD', 'REQUEST');
383
+        $calendar->remove('VEVENT');
384
+        // Act
385
+        $result = $manager->handleIMip($userId, $calendar->serialize());
386
+        // Assert
387
+        $this->assertFalse($result);
388
+    }
389
+
390
+    public function testHandleImipMissingOrganizerWithRecipient(): void {
391
+        // construct mock user calendar
392
+        $userCalendar = $this->createMock(ITestCalendar::class);
393
+        $userCalendar->expects(self::once())
394
+            ->method('isDeleted')
395
+            ->willReturn(false);
396
+        $userCalendar->expects(self::once())
397
+            ->method('isWritable')
398
+            ->willReturn(true);
399
+        $userCalendar->expects(self::once())
400
+            ->method('search')
401
+            ->willReturn([['uri' => 'principals/user/attendee1/personal']]);
402
+        // construct mock calendar manager and returns
403
+        /** @var Manager&MockObject $manager */
404
+        $manager = $this->getMockBuilder(Manager::class)
405
+            ->setConstructorArgs([
406
+                $this->coordinator,
407
+                $this->container,
408
+                $this->logger,
409
+                $this->time,
410
+                $this->secureRandom,
411
+                $this->userManager,
412
+                $this->serverFactory,
413
+                $this->propertyMapper,
414
+            ])
415
+            ->onlyMethods(['getCalendarsForPrincipal'])
416
+            ->getMock();
417
+        $manager->expects(self::once())
418
+            ->method('getCalendarsForPrincipal')
419
+            ->willReturn([$userCalendar]);
420
+        // construct parameters
421
+        $userId = 'attendee1';
422
+        $calendar = $this->vCalendar1a;
423
+        $calendar->add('METHOD', 'REQUEST');
424
+        $calendar->VEVENT->remove('ORGANIZER');
425
+        // construct user calendar returns
426
+        $userCalendar->expects(self::once())
427
+            ->method('handleIMipMessage');
428
+        // test method
429
+        $result = $manager->handleIMip($userId, $calendar->serialize(), ['recipient' => '[email protected]']);
430
+    }
431
+
432
+    public function testHandleImipMissingOrganizerNoRecipient(): void {
433
+        // construct mock user calendar
434
+        $userCalendar = $this->createMock(ITestCalendar::class);
435
+        // construct mock calendar manager and returns
436
+        /** @var Manager&MockObject $manager */
437
+        $manager = $this->getMockBuilder(Manager::class)
438
+            ->setConstructorArgs([
439
+                $this->coordinator,
440
+                $this->container,
441
+                $this->logger,
442
+                $this->time,
443
+                $this->secureRandom,
444
+                $this->userManager,
445
+                $this->serverFactory,
446
+                $this->propertyMapper,
447
+            ])
448
+            ->onlyMethods(['getCalendarsForPrincipal'])
449
+            ->getMock();
450
+        $manager->expects(self::once())
451
+            ->method('getCalendarsForPrincipal')
452
+            ->willReturn([$userCalendar]);
453
+        // construct parameters
454
+        $userId = 'attendee1';
455
+        $calendar = $this->vCalendar1a;
456
+        $calendar->add('METHOD', 'REQUEST');
457
+        $calendar->VEVENT->remove('ORGANIZER');
458
+        // Logger expects warning
459
+        $this->logger->expects($this->once())
460
+            ->method('warning')
461
+            ->with('iMip message event does not contain an organizer and no recipient was provided');
462
+
463
+        $result = $manager->handleIMip($userId, $calendar->serialize(), []);
464
+    }
465
+
466
+    public function testHandleImipWithNoUid(): void {
467
+        // construct mock user calendar
468
+        $userCalendar = $this->createMock(ITestCalendar::class);
469
+        // construct mock calendar manager and returns
470
+        /** @var Manager&MockObject $manager */
471
+        $manager = $this->getMockBuilder(Manager::class)
472
+            ->setConstructorArgs([
473
+                $this->coordinator,
474
+                $this->container,
475
+                $this->logger,
476
+                $this->time,
477
+                $this->secureRandom,
478
+                $this->userManager,
479
+                $this->serverFactory,
480
+                $this->propertyMapper,
481
+            ])
482
+            ->onlyMethods(['getCalendarsForPrincipal'])
483
+            ->getMock();
484
+        $manager->expects(self::once())
485
+            ->method('getCalendarsForPrincipal')
486
+            ->willReturn([$userCalendar]);
487
+        // construct logger returns
488
+        $this->logger->expects(self::once())->method('warning')
489
+            ->with('iMip message event dose not contains a UID');
490
+        // construct parameters
491
+        $userId = 'attendee1';
492
+        $calendar = $this->vCalendar1a;
493
+        $calendar->add('METHOD', 'REQUEST');
494
+        $calendar->VEVENT->remove('UID');
495
+        // test method
496
+        $result = $manager->handleIMip($userId, $calendar->serialize());
497
+        // Assert
498
+        $this->assertFalse($result);
499
+    }
500
+
501
+    public function testHandleImipWithNoMatch(): void {
502
+        // construct mock user calendar
503
+        $userCalendar = $this->createMock(ITestCalendar::class);
504
+        $userCalendar->expects(self::once())
505
+            ->method('isDeleted')
506
+            ->willReturn(false);
507
+        $userCalendar->expects(self::once())
508
+            ->method('isWritable')
509
+            ->willReturn(true);
510
+        $userCalendar->expects(self::once())
511
+            ->method('search')
512
+            ->willReturn([]);
513
+        // construct mock calendar manager and returns
514
+        /** @var Manager&MockObject $manager */
515
+        $manager = $this->getMockBuilder(Manager::class)
516
+            ->setConstructorArgs([
517
+                $this->coordinator,
518
+                $this->container,
519
+                $this->logger,
520
+                $this->time,
521
+                $this->secureRandom,
522
+                $this->userManager,
523
+                $this->serverFactory,
524
+                $this->propertyMapper,
525
+            ])
526
+            ->onlyMethods(['getCalendarsForPrincipal'])
527
+            ->getMock();
528
+        $manager->expects(self::once())
529
+            ->method('getCalendarsForPrincipal')
530
+            ->willReturn([$userCalendar]);
531
+        // construct logger returns
532
+        $this->logger->expects(self::once())->method('warning')
533
+            ->with('iMip message could not be processed because no corresponding event was found in any calendar');
534
+        // construct parameters
535
+        $userId = 'attendee1';
536
+        $calendar = $this->vCalendar1a;
537
+        $calendar->add('METHOD', 'REQUEST');
538
+        // test method
539
+        $result = $manager->handleIMip($userId, $calendar->serialize());
540
+        // Assert
541
+        $this->assertFalse($result);
542
+    }
543
+
544
+    public function testHandleImip(): void {
545
+        // construct mock user calendar
546
+        $userCalendar = $this->createMock(ITestCalendar::class);
547
+        $userCalendar->expects(self::once())
548
+            ->method('isDeleted')
549
+            ->willReturn(false);
550
+        $userCalendar->expects(self::once())
551
+            ->method('isWritable')
552
+            ->willReturn(true);
553
+        $userCalendar->expects(self::once())
554
+            ->method('search')
555
+            ->willReturn([['uri' => 'principals/user/attendee1/personal']]);
556
+        // construct mock calendar manager and returns
557
+        /** @var Manager&MockObject $manager */
558
+        $manager = $this->getMockBuilder(Manager::class)
559
+            ->setConstructorArgs([
560
+                $this->coordinator,
561
+                $this->container,
562
+                $this->logger,
563
+                $this->time,
564
+                $this->secureRandom,
565
+                $this->userManager,
566
+                $this->serverFactory,
567
+                $this->propertyMapper,
568
+            ])
569
+            ->onlyMethods(['getCalendarsForPrincipal'])
570
+            ->getMock();
571
+        $manager->expects(self::once())
572
+            ->method('getCalendarsForPrincipal')
573
+            ->willReturn([$userCalendar]);
574
+        // construct parameters
575
+        $userId = 'attendee1';
576
+        $calendar = $this->vCalendar1a;
577
+        $calendar->add('METHOD', 'REQUEST');
578
+        // construct user calendar returns
579
+        $userCalendar->expects(self::once())
580
+            ->method('handleIMipMessage');
581
+        // test method
582
+        $result = $manager->handleIMip($userId, $calendar->serialize());
583
+    }
584
+
585
+    public function testhandleIMipRequestWithInvalidPrincipal() {
586
+        $invalidPrincipal = 'invalid-principal-uri';
587
+        $sender = '[email protected]';
588
+        $recipient = '[email protected]';
589
+        $calendarData = $this->vCalendar1a->serialize();
590
+
591
+        $this->logger->expects(self::once())
592
+            ->method('error')
593
+            ->with('Invalid principal URI provided for iMip request');
594
+
595
+        $result = $this->manager->handleIMipRequest($invalidPrincipal, $sender, $recipient, $calendarData);
596
+        $this->assertFalse($result);
597
+    }
598
+
599
+    public function testhandleIMipRequest() {
600
+        $principalUri = 'principals/users/attendee1';
601
+        $sender = '[email protected]';
602
+        $recipient = '[email protected]';
603
+        $calendarData = $this->vCalendar1a->serialize();
604
+
605
+        /** @var Manager&MockObject $manager */
606
+        $manager = $this->getMockBuilder(Manager::class)
607
+            ->setConstructorArgs([
608
+                $this->coordinator,
609
+                $this->container,
610
+                $this->logger,
611
+                $this->time,
612
+                $this->secureRandom,
613
+                $this->userManager,
614
+                $this->serverFactory,
615
+                $this->propertyMapper,
616
+            ])
617
+            ->onlyMethods(['handleIMip'])
618
+            ->getMock();
619
+        $manager->expects(self::once())
620
+            ->method('handleIMip')
621
+            ->with('attendee1', $calendarData)
622
+            ->willReturn(true);
623
+
624
+        $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendarData);
625
+        $this->assertTrue($result);
626
+    }
627
+
628
+    public function testhandleIMipReplyWithInvalidPrincipal() {
629
+        $invalidPrincipal = 'invalid-principal-uri';
630
+        $sender = '[email protected]';
631
+        $recipient = '[email protected]';
632
+        $calendarData = $this->vCalendar2a->serialize();
633
+
634
+        $this->logger->expects(self::once())
635
+            ->method('error')
636
+            ->with('Invalid principal URI provided for iMip reply');
637
+
638
+        $result = $this->manager->handleIMipReply($invalidPrincipal, $sender, $recipient, $calendarData);
639
+        $this->assertFalse($result);
640
+    }
641
+
642
+    public function testhandleIMipReply() {
643
+        $principalUri = 'principals/users/attendee2';
644
+        $sender = '[email protected]';
645
+        $recipient = '[email protected]';
646
+        $calendarData = $this->vCalendar2a->serialize();
647
+
648
+        /** @var Manager&MockObject $manager */
649
+        $manager = $this->getMockBuilder(Manager::class)
650
+            ->setConstructorArgs([
651
+                $this->coordinator,
652
+                $this->container,
653
+                $this->logger,
654
+                $this->time,
655
+                $this->secureRandom,
656
+                $this->userManager,
657
+                $this->serverFactory,
658
+                $this->propertyMapper,
659
+            ])
660
+            ->onlyMethods(['handleIMip'])
661
+            ->getMock();
662
+        $manager->expects(self::once())
663
+            ->method('handleIMip')
664
+            ->with('attendee2', $calendarData)
665
+            ->willReturn(true);
666
+
667
+        $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData);
668
+        $this->assertTrue($result);
669
+    }
670
+
671
+    public function testhandleIMipCancelWithInvalidPrincipal() {
672
+        $invalidPrincipal = 'invalid-principal-uri';
673
+        $sender = '[email protected]';
674
+        $replyTo = null;
675
+        $recipient = '[email protected]';
676
+        $calendarData = $this->vCalendar3a->serialize();
677
+
678
+        $this->logger->expects(self::once())
679
+            ->method('error')
680
+            ->with('Invalid principal URI provided for iMip cancel');
681
+
682
+        $result = $this->manager->handleIMipCancel($invalidPrincipal, $sender, $replyTo, $recipient, $calendarData);
683
+        $this->assertFalse($result);
684
+    }
685
+
686
+    public function testhandleIMipCancel() {
687
+        $principalUri = 'principals/users/attendee3';
688
+        $sender = '[email protected]';
689
+        $replyTo = null;
690
+        $recipient = '[email protected]';
691
+        $calendarData = $this->vCalendar3a->serialize();
692
+
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
+                $this->propertyMapper,
704
+            ])
705
+            ->onlyMethods(['handleIMip'])
706
+            ->getMock();
707
+        $manager->expects(self::once())
708
+            ->method('handleIMip')
709
+            ->with('attendee3', $calendarData)
710
+            ->willReturn(true);
711
+
712
+        $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData);
713
+        $this->assertTrue($result);
714
+    }
715
+
716
+    private function getFreeBusyResponse(): string {
717
+        return <<<EOF
718 718
 <?xml version="1.0" encoding="utf-8"?>
719 719
 <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">
720 720
   <cal:response>
@@ -792,139 +792,139 @@  discard block
 block discarded – undo
792 792
   </cal:response>
793 793
 </cal:schedule-response>
794 794
 EOF;
795
-	}
796
-
797
-	public function testCheckAvailability(): void {
798
-		$organizer = $this->createMock(IUser::class);
799
-		$organizer->expects(self::once())
800
-			->method('getUID')
801
-			->willReturn('admin');
802
-		$organizer->expects(self::once())
803
-			->method('getEMailAddress')
804
-			->willReturn('[email protected]');
805
-
806
-		$user1 = $this->createMock(IUser::class);
807
-		$user2 = $this->createMock(IUser::class);
808
-
809
-		$this->userManager->expects(self::exactly(3))
810
-			->method('getByEmail')
811
-			->willReturnMap([
812
-				['[email protected]', [$user1]],
813
-				['[email protected]', [$user2]],
814
-				['[email protected]', []],
815
-			]);
816
-
817
-		$authPlugin = $this->createMock(CustomPrincipalPlugin::class);
818
-		$authPlugin->expects(self::once())
819
-			->method('setCurrentPrincipal')
820
-			->with('principals/users/admin');
821
-
822
-		$server = $this->createMock(Server::class);
823
-		$server->expects(self::once())
824
-			->method('getPlugin')
825
-			->with('auth')
826
-			->willReturn($authPlugin);
827
-		$server->expects(self::once())
828
-			->method('invokeMethod')
829
-			->willReturnCallback(function (
830
-				RequestInterface $request,
831
-				ResponseInterface $response,
832
-				bool $sendResponse,
833
-			): void {
834
-				$requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
835
-				$this->assertEquals('POST', $request->getMethod());
836
-				$this->assertEquals('calendars/admin/outbox', $request->getPath());
837
-				$this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
838
-				$this->assertEquals('0', $request->getHeader('Depth'));
839
-				$this->assertEquals($requestBody, $request->getBodyAsString());
840
-				$this->assertFalse($sendResponse);
841
-				$response->setStatus(200);
842
-				$response->setBody($this->getFreeBusyResponse());
843
-			});
844
-
845
-		$this->serverFactory->expects(self::once())
846
-			->method('createAttendeeAvailabilityServer')
847
-			->willReturn($server);
848
-
849
-		$start = new DateTimeImmutable('2025-01-16T06:00:00Z');
850
-		$end = new DateTimeImmutable('2025-01-17T06:00:00Z');
851
-		$actual = $this->manager->checkAvailability($start, $end, $organizer, [
852
-			'[email protected]',
853
-			'[email protected]',
854
-			'[email protected]',
855
-		]);
856
-		$expected = [
857
-			new AvailabilityResult('[email protected]', false),
858
-			new AvailabilityResult('[email protected]', true),
859
-			new AvailabilityResult('[email protected]', false),
860
-		];
861
-		$this->assertEquals($expected, $actual);
862
-	}
863
-
864
-	public function testCheckAvailabilityWithMailtoPrefix(): void {
865
-		$organizer = $this->createMock(IUser::class);
866
-		$organizer->expects(self::once())
867
-			->method('getUID')
868
-			->willReturn('admin');
869
-		$organizer->expects(self::once())
870
-			->method('getEMailAddress')
871
-			->willReturn('[email protected]');
872
-
873
-		$user1 = $this->createMock(IUser::class);
874
-		$user2 = $this->createMock(IUser::class);
875
-
876
-		$this->userManager->expects(self::exactly(3))
877
-			->method('getByEmail')
878
-			->willReturnMap([
879
-				['[email protected]', [$user1]],
880
-				['[email protected]', [$user2]],
881
-				['[email protected]', []],
882
-			]);
883
-
884
-		$authPlugin = $this->createMock(CustomPrincipalPlugin::class);
885
-		$authPlugin->expects(self::once())
886
-			->method('setCurrentPrincipal')
887
-			->with('principals/users/admin');
888
-
889
-		$server = $this->createMock(Server::class);
890
-		$server->expects(self::once())
891
-			->method('getPlugin')
892
-			->with('auth')
893
-			->willReturn($authPlugin);
894
-		$server->expects(self::once())
895
-			->method('invokeMethod')
896
-			->willReturnCallback(function (
897
-				RequestInterface $request,
898
-				ResponseInterface $response,
899
-				bool $sendResponse,
900
-			): void {
901
-				$requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
902
-				$this->assertEquals('POST', $request->getMethod());
903
-				$this->assertEquals('calendars/admin/outbox', $request->getPath());
904
-				$this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
905
-				$this->assertEquals('0', $request->getHeader('Depth'));
906
-				$this->assertEquals($requestBody, $request->getBodyAsString());
907
-				$this->assertFalse($sendResponse);
908
-				$response->setStatus(200);
909
-				$response->setBody($this->getFreeBusyResponse());
910
-			});
911
-
912
-		$this->serverFactory->expects(self::once())
913
-			->method('createAttendeeAvailabilityServer')
914
-			->willReturn($server);
915
-
916
-		$start = new DateTimeImmutable('2025-01-16T06:00:00Z');
917
-		$end = new DateTimeImmutable('2025-01-17T06:00:00Z');
918
-		$actual = $this->manager->checkAvailability($start, $end, $organizer, [
919
-			'mailto:[email protected]',
920
-			'mailto:[email protected]',
921
-			'mailto:[email protected]',
922
-		]);
923
-		$expected = [
924
-			new AvailabilityResult('[email protected]', false),
925
-			new AvailabilityResult('[email protected]', true),
926
-			new AvailabilityResult('[email protected]', false),
927
-		];
928
-		$this->assertEquals($expected, $actual);
929
-	}
795
+    }
796
+
797
+    public function testCheckAvailability(): void {
798
+        $organizer = $this->createMock(IUser::class);
799
+        $organizer->expects(self::once())
800
+            ->method('getUID')
801
+            ->willReturn('admin');
802
+        $organizer->expects(self::once())
803
+            ->method('getEMailAddress')
804
+            ->willReturn('[email protected]');
805
+
806
+        $user1 = $this->createMock(IUser::class);
807
+        $user2 = $this->createMock(IUser::class);
808
+
809
+        $this->userManager->expects(self::exactly(3))
810
+            ->method('getByEmail')
811
+            ->willReturnMap([
812
+                ['[email protected]', [$user1]],
813
+                ['[email protected]', [$user2]],
814
+                ['[email protected]', []],
815
+            ]);
816
+
817
+        $authPlugin = $this->createMock(CustomPrincipalPlugin::class);
818
+        $authPlugin->expects(self::once())
819
+            ->method('setCurrentPrincipal')
820
+            ->with('principals/users/admin');
821
+
822
+        $server = $this->createMock(Server::class);
823
+        $server->expects(self::once())
824
+            ->method('getPlugin')
825
+            ->with('auth')
826
+            ->willReturn($authPlugin);
827
+        $server->expects(self::once())
828
+            ->method('invokeMethod')
829
+            ->willReturnCallback(function (
830
+                RequestInterface $request,
831
+                ResponseInterface $response,
832
+                bool $sendResponse,
833
+            ): void {
834
+                $requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
835
+                $this->assertEquals('POST', $request->getMethod());
836
+                $this->assertEquals('calendars/admin/outbox', $request->getPath());
837
+                $this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
838
+                $this->assertEquals('0', $request->getHeader('Depth'));
839
+                $this->assertEquals($requestBody, $request->getBodyAsString());
840
+                $this->assertFalse($sendResponse);
841
+                $response->setStatus(200);
842
+                $response->setBody($this->getFreeBusyResponse());
843
+            });
844
+
845
+        $this->serverFactory->expects(self::once())
846
+            ->method('createAttendeeAvailabilityServer')
847
+            ->willReturn($server);
848
+
849
+        $start = new DateTimeImmutable('2025-01-16T06:00:00Z');
850
+        $end = new DateTimeImmutable('2025-01-17T06:00:00Z');
851
+        $actual = $this->manager->checkAvailability($start, $end, $organizer, [
852
+            '[email protected]',
853
+            '[email protected]',
854
+            '[email protected]',
855
+        ]);
856
+        $expected = [
857
+            new AvailabilityResult('[email protected]', false),
858
+            new AvailabilityResult('[email protected]', true),
859
+            new AvailabilityResult('[email protected]', false),
860
+        ];
861
+        $this->assertEquals($expected, $actual);
862
+    }
863
+
864
+    public function testCheckAvailabilityWithMailtoPrefix(): void {
865
+        $organizer = $this->createMock(IUser::class);
866
+        $organizer->expects(self::once())
867
+            ->method('getUID')
868
+            ->willReturn('admin');
869
+        $organizer->expects(self::once())
870
+            ->method('getEMailAddress')
871
+            ->willReturn('[email protected]');
872
+
873
+        $user1 = $this->createMock(IUser::class);
874
+        $user2 = $this->createMock(IUser::class);
875
+
876
+        $this->userManager->expects(self::exactly(3))
877
+            ->method('getByEmail')
878
+            ->willReturnMap([
879
+                ['[email protected]', [$user1]],
880
+                ['[email protected]', [$user2]],
881
+                ['[email protected]', []],
882
+            ]);
883
+
884
+        $authPlugin = $this->createMock(CustomPrincipalPlugin::class);
885
+        $authPlugin->expects(self::once())
886
+            ->method('setCurrentPrincipal')
887
+            ->with('principals/users/admin');
888
+
889
+        $server = $this->createMock(Server::class);
890
+        $server->expects(self::once())
891
+            ->method('getPlugin')
892
+            ->with('auth')
893
+            ->willReturn($authPlugin);
894
+        $server->expects(self::once())
895
+            ->method('invokeMethod')
896
+            ->willReturnCallback(function (
897
+                RequestInterface $request,
898
+                ResponseInterface $response,
899
+                bool $sendResponse,
900
+            ): void {
901
+                $requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
902
+                $this->assertEquals('POST', $request->getMethod());
903
+                $this->assertEquals('calendars/admin/outbox', $request->getPath());
904
+                $this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
905
+                $this->assertEquals('0', $request->getHeader('Depth'));
906
+                $this->assertEquals($requestBody, $request->getBodyAsString());
907
+                $this->assertFalse($sendResponse);
908
+                $response->setStatus(200);
909
+                $response->setBody($this->getFreeBusyResponse());
910
+            });
911
+
912
+        $this->serverFactory->expects(self::once())
913
+            ->method('createAttendeeAvailabilityServer')
914
+            ->willReturn($server);
915
+
916
+        $start = new DateTimeImmutable('2025-01-16T06:00:00Z');
917
+        $end = new DateTimeImmutable('2025-01-17T06:00:00Z');
918
+        $actual = $this->manager->checkAvailability($start, $end, $organizer, [
919
+            'mailto:[email protected]',
920
+            'mailto:[email protected]',
921
+            'mailto:[email protected]',
922
+        ]);
923
+        $expected = [
924
+            new AvailabilityResult('[email protected]', false),
925
+            new AvailabilityResult('[email protected]', true),
926
+            new AvailabilityResult('[email protected]', false),
927
+        ];
928
+        $this->assertEquals($expected, $actual);
929
+    }
930 930
 }
Please login to merge, or discard this patch.