Passed
Push — master ( 8a2e52...3b084e )
by Christoph
21:24 queued 07:55
created
lib/public/Calendar/ICreateFromString.php 1 patch
Indentation   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -32,10 +32,10 @@
 block discarded – undo
32 32
  */
33 33
 interface ICreateFromString extends ICalendar {
34 34
 
35
-	/**
36
-	 * @since 23.0.0
37
-	 *
38
-	 * @throws CalendarException
39
-	 */
40
-	public function createFromString(string $name, string $calendarData): void;
35
+    /**
36
+     * @since 23.0.0
37
+     *
38
+     * @throws CalendarException
39
+     */
40
+    public function createFromString(string $name, string $calendarData): void;
41 41
 }
Please login to merge, or discard this patch.
lib/private/Calendar/Manager.php 1 patch
Indentation   +337 added lines, -337 removed lines patch added patch discarded remove patch
@@ -48,341 +48,341 @@
 block discarded – undo
48 48
 
49 49
 class Manager implements IManager {
50 50
 
51
-	/**
52
-	 * @var ICalendar[] holds all registered calendars
53
-	 */
54
-	private $calendars = [];
55
-
56
-	/**
57
-	 * @var \Closure[] to call to load/register calendar providers
58
-	 */
59
-	private $calendarLoaders = [];
60
-
61
-	/** @var Coordinator */
62
-	private $coordinator;
63
-
64
-	/** @var ContainerInterface */
65
-	private $container;
66
-
67
-	/** @var LoggerInterface */
68
-	private $logger;
69
-
70
-	private ITimeFactory $timeFactory;
71
-
72
-
73
-	public function __construct(Coordinator $coordinator,
74
-								ContainerInterface $container,
75
-								LoggerInterface $logger,
76
-								ITimeFactory $timeFactory) {
77
-		$this->coordinator = $coordinator;
78
-		$this->container = $container;
79
-		$this->logger = $logger;
80
-		$this->timeFactory = $timeFactory;
81
-	}
82
-
83
-	/**
84
-	 * This function is used to search and find objects within the user's calendars.
85
-	 * In case $pattern is empty all events/journals/todos will be returned.
86
-	 *
87
-	 * @param string $pattern which should match within the $searchProperties
88
-	 * @param array $searchProperties defines the properties within the query pattern should match
89
-	 * @param array $options - optional parameters:
90
-	 * 	['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]]
91
-	 * @param integer|null $limit - limit number of search results
92
-	 * @param integer|null $offset - offset for paging of search results
93
-	 * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs
94
-	 * @since 13.0.0
95
-	 */
96
-	public function search($pattern, array $searchProperties = [], array $options = [], $limit = null, $offset = null) {
97
-		$this->loadCalendars();
98
-		$result = [];
99
-		foreach ($this->calendars as $calendar) {
100
-			$r = $calendar->search($pattern, $searchProperties, $options, $limit, $offset);
101
-			foreach ($r as $o) {
102
-				$o['calendar-key'] = $calendar->getKey();
103
-				$result[] = $o;
104
-			}
105
-		}
106
-
107
-		return $result;
108
-	}
109
-
110
-	/**
111
-	 * Check if calendars are available
112
-	 *
113
-	 * @return bool true if enabled, false if not
114
-	 * @since 13.0.0
115
-	 */
116
-	public function isEnabled() {
117
-		return !empty($this->calendars) || !empty($this->calendarLoaders);
118
-	}
119
-
120
-	/**
121
-	 * Registers a calendar
122
-	 *
123
-	 * @param ICalendar $calendar
124
-	 * @return void
125
-	 * @since 13.0.0
126
-	 */
127
-	public function registerCalendar(ICalendar $calendar) {
128
-		$this->calendars[$calendar->getKey()] = $calendar;
129
-	}
130
-
131
-	/**
132
-	 * Unregisters a calendar
133
-	 *
134
-	 * @param ICalendar $calendar
135
-	 * @return void
136
-	 * @since 13.0.0
137
-	 */
138
-	public function unregisterCalendar(ICalendar $calendar) {
139
-		unset($this->calendars[$calendar->getKey()]);
140
-	}
141
-
142
-	/**
143
-	 * In order to improve lazy loading a closure can be registered which will be called in case
144
-	 * calendars are actually requested
145
-	 *
146
-	 * @param \Closure $callable
147
-	 * @return void
148
-	 * @since 13.0.0
149
-	 */
150
-	public function register(\Closure $callable) {
151
-		$this->calendarLoaders[] = $callable;
152
-	}
153
-
154
-	/**
155
-	 * @return ICalendar[]
156
-	 * @since 13.0.0
157
-	 */
158
-	public function getCalendars() {
159
-		$this->loadCalendars();
160
-
161
-		return array_values($this->calendars);
162
-	}
163
-
164
-	/**
165
-	 * removes all registered calendar instances
166
-	 * @return void
167
-	 * @since 13.0.0
168
-	 */
169
-	public function clear() {
170
-		$this->calendars = [];
171
-		$this->calendarLoaders = [];
172
-	}
173
-
174
-	/**
175
-	 * loads all calendars
176
-	 */
177
-	private function loadCalendars() {
178
-		foreach ($this->calendarLoaders as $callable) {
179
-			$callable($this);
180
-		}
181
-		$this->calendarLoaders = [];
182
-	}
183
-
184
-	/**
185
-	 * @param string $principalUri
186
-	 * @param array $calendarUris
187
-	 * @return ICreateFromString[]
188
-	 */
189
-	public function getCalendarsForPrincipal(string $principalUri, array $calendarUris = []): array {
190
-		$context = $this->coordinator->getRegistrationContext();
191
-		if ($context === null) {
192
-			return [];
193
-		}
194
-
195
-		return array_merge(
196
-			...array_map(function ($registration) use ($principalUri, $calendarUris) {
197
-				try {
198
-					/** @var ICalendarProvider $provider */
199
-					$provider = $this->container->get($registration->getService());
200
-				} catch (Throwable $e) {
201
-					$this->logger->error('Could not load calendar provider ' . $registration->getService() . ': ' . $e->getMessage(), [
202
-						'exception' => $e,
203
-					]);
204
-					return [];
205
-				}
206
-
207
-				return $provider->getCalendars($principalUri, $calendarUris);
208
-			}, $context->getCalendarProviders())
209
-		);
210
-	}
211
-
212
-	public function searchForPrincipal(ICalendarQuery $query): array {
213
-		/** @var CalendarQuery $query */
214
-		$calendars = $this->getCalendarsForPrincipal(
215
-			$query->getPrincipalUri(),
216
-			$query->getCalendarUris(),
217
-		);
218
-
219
-		$results = [];
220
-		foreach ($calendars as $calendar) {
221
-			$r = $calendar->search(
222
-				$query->getSearchPattern() ?? '',
223
-				$query->getSearchProperties(),
224
-				$query->getOptions(),
225
-				$query->getLimit(),
226
-				$query->getOffset()
227
-			);
228
-
229
-			foreach ($r as $o) {
230
-				$o['calendar-key'] = $calendar->getKey();
231
-				$results[] = $o;
232
-			}
233
-		}
234
-		return $results;
235
-	}
236
-
237
-	public function newQuery(string $principalUri): ICalendarQuery {
238
-		return new CalendarQuery($principalUri);
239
-	}
240
-
241
-	/**
242
-	 * @throws \OCP\DB\Exception
243
-	 */
244
-	public function handleIMipReply(string $principalUri, string $sender, string $recipient, string $calendarData): bool {
245
-		/** @var VCalendar $vObject */
246
-		$vObject = Reader::read($calendarData);
247
-		/** @var VEvent $vEvent */
248
-		$vEvent = $vObject->{'VEVENT'};
249
-
250
-		// First, we check if the correct method is passed to us
251
-		if (strcasecmp('REPLY', $vObject->{'METHOD'}->getValue()) !== 0) {
252
-			$this->logger->warning('Wrong method provided for processing');
253
-			return false;
254
-		}
255
-
256
-		// check if mail recipient and organizer are one and the same
257
-		$organizer = substr($vEvent->{'ORGANIZER'}->getValue(), 7);
258
-
259
-		if (strcasecmp($recipient, $organizer) !== 0) {
260
-			$this->logger->warning('Recipient and ORGANIZER must be identical');
261
-			return false;
262
-		}
263
-
264
-		//check if the event is in the future
265
-		/** @var DateTime $eventTime */
266
-		$eventTime = $vEvent->{'DTSTART'};
267
-		if ($eventTime->getDateTime()->getTimeStamp() < $this->timeFactory->getTime()) { // this might cause issues with recurrences
268
-			$this->logger->warning('Only events in the future are processed');
269
-			return false;
270
-		}
271
-
272
-		$calendars = $this->getCalendarsForPrincipal($principalUri);
273
-		if (empty($calendars)) {
274
-			$this->logger->warning('Could not find any calendars for principal ' . $principalUri);
275
-			return false;
276
-		}
277
-
278
-		$found = null;
279
-		// if the attendee has been found in at least one calendar event with the UID of the iMIP event
280
-		// we process it.
281
-		// Benefit: no attendee lost
282
-		// Drawback: attendees that have been deleted will still be able to update their partstat
283
-		foreach ($calendars as $calendar) {
284
-			// We should not search in writable calendars
285
-			if ($calendar instanceof IHandleImipMessage) {
286
-				$o = $calendar->search($sender, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
287
-				if (!empty($o)) {
288
-					$found = $calendar;
289
-					$name = $o[0]['uri'];
290
-					break;
291
-				}
292
-			}
293
-		}
294
-
295
-		if (empty($found)) {
296
-			$this->logger->info('Event not found in any calendar for principal ' . $principalUri . 'and UID' . $vEvent->{'UID'}->getValue());
297
-			return false;
298
-		}
299
-
300
-		try {
301
-			$found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes
302
-		} catch (CalendarException $e) {
303
-			$this->logger->error('Could not update calendar for iMIP processing', ['exception' => $e]);
304
-			return false;
305
-		}
306
-		return true;
307
-	}
308
-
309
-	/**
310
-	 * @since 25.0.0
311
-	 * @throws \OCP\DB\Exception
312
-	 */
313
-	public function handleIMipCancel(string $principalUri, string $sender, ?string $replyTo, string $recipient, string $calendarData): bool {
314
-		$vObject = Reader::read($calendarData);
315
-		/** @var VEvent $vEvent */
316
-		$vEvent = $vObject->{'VEVENT'};
317
-
318
-		// First, we check if the correct method is passed to us
319
-		if (strcasecmp('CANCEL', $vObject->{'METHOD'}->getValue()) !== 0) {
320
-			$this->logger->warning('Wrong method provided for processing');
321
-			return false;
322
-		}
323
-
324
-		$attendee = substr($vEvent->{'ATTENDEE'}->getValue(), 7);
325
-		if (strcasecmp($recipient, $attendee) !== 0) {
326
-			$this->logger->warning('Recipient must be an ATTENDEE of this event');
327
-			return false;
328
-		}
329
-
330
-		// Thirdly, we need to compare the email address the CANCEL is coming from (in Mail)
331
-		// or the Reply- To Address submitted with the CANCEL email
332
-		// to the email address in the ORGANIZER.
333
-		// We don't want to accept a CANCEL request from just anyone
334
-		$organizer = substr($vEvent->{'ORGANIZER'}->getValue(), 7);
335
-		$isNotOrganizer = ($replyTo !== null) ? (strcasecmp($sender, $organizer) !== 0 && strcasecmp($replyTo, $organizer) !== 0) : (strcasecmp($sender, $organizer) !== 0);
336
-		if ($isNotOrganizer) {
337
-			$this->logger->warning('Sender must be the ORGANIZER of this event');
338
-			return false;
339
-		}
340
-
341
-		//check if the event is in the future
342
-		/** @var DateTime $eventTime */
343
-		$eventTime = $vEvent->{'DTSTART'};
344
-		if ($eventTime->getDateTime()->getTimeStamp() < $this->timeFactory->getTime()) { // this might cause issues with recurrences
345
-			$this->logger->warning('Only events in the future are processed');
346
-			return false;
347
-		}
348
-
349
-		// Check if we have a calendar to work with
350
-		$calendars = $this->getCalendarsForPrincipal($principalUri);
351
-		if (empty($calendars)) {
352
-			$this->logger->warning('Could not find any calendars for principal ' . $principalUri);
353
-			return false;
354
-		}
355
-
356
-		$found = null;
357
-		// if the attendee has been found in at least one calendar event with the UID of the iMIP event
358
-		// we process it.
359
-		// Benefit: no attendee lost
360
-		// Drawback: attendees that have been deleted will still be able to update their partstat
361
-		foreach ($calendars as $calendar) {
362
-			// We should not search in writable calendars
363
-			if ($calendar instanceof IHandleImipMessage) {
364
-				$o = $calendar->search($recipient, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
365
-				if (!empty($o)) {
366
-					$found = $calendar;
367
-					$name = $o[0]['uri'];
368
-					break;
369
-				}
370
-			}
371
-		}
372
-
373
-		if (empty($found)) {
374
-			$this->logger->info('Event not found in any calendar for principal ' . $principalUri . 'and UID' . $vEvent->{'UID'}->getValue());
375
-			// this is a safe operation
376
-			// we can ignore events that have been cancelled but were not in the calendar anyway
377
-			return true;
378
-		}
379
-
380
-		try {
381
-			$found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes
382
-			return true;
383
-		} catch (CalendarException $e) {
384
-			$this->logger->error('Could not update calendar for iMIP processing', ['exception' => $e]);
385
-			return false;
386
-		}
387
-	}
51
+    /**
52
+     * @var ICalendar[] holds all registered calendars
53
+     */
54
+    private $calendars = [];
55
+
56
+    /**
57
+     * @var \Closure[] to call to load/register calendar providers
58
+     */
59
+    private $calendarLoaders = [];
60
+
61
+    /** @var Coordinator */
62
+    private $coordinator;
63
+
64
+    /** @var ContainerInterface */
65
+    private $container;
66
+
67
+    /** @var LoggerInterface */
68
+    private $logger;
69
+
70
+    private ITimeFactory $timeFactory;
71
+
72
+
73
+    public function __construct(Coordinator $coordinator,
74
+                                ContainerInterface $container,
75
+                                LoggerInterface $logger,
76
+                                ITimeFactory $timeFactory) {
77
+        $this->coordinator = $coordinator;
78
+        $this->container = $container;
79
+        $this->logger = $logger;
80
+        $this->timeFactory = $timeFactory;
81
+    }
82
+
83
+    /**
84
+     * This function is used to search and find objects within the user's calendars.
85
+     * In case $pattern is empty all events/journals/todos will be returned.
86
+     *
87
+     * @param string $pattern which should match within the $searchProperties
88
+     * @param array $searchProperties defines the properties within the query pattern should match
89
+     * @param array $options - optional parameters:
90
+     * 	['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]]
91
+     * @param integer|null $limit - limit number of search results
92
+     * @param integer|null $offset - offset for paging of search results
93
+     * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs
94
+     * @since 13.0.0
95
+     */
96
+    public function search($pattern, array $searchProperties = [], array $options = [], $limit = null, $offset = null) {
97
+        $this->loadCalendars();
98
+        $result = [];
99
+        foreach ($this->calendars as $calendar) {
100
+            $r = $calendar->search($pattern, $searchProperties, $options, $limit, $offset);
101
+            foreach ($r as $o) {
102
+                $o['calendar-key'] = $calendar->getKey();
103
+                $result[] = $o;
104
+            }
105
+        }
106
+
107
+        return $result;
108
+    }
109
+
110
+    /**
111
+     * Check if calendars are available
112
+     *
113
+     * @return bool true if enabled, false if not
114
+     * @since 13.0.0
115
+     */
116
+    public function isEnabled() {
117
+        return !empty($this->calendars) || !empty($this->calendarLoaders);
118
+    }
119
+
120
+    /**
121
+     * Registers a calendar
122
+     *
123
+     * @param ICalendar $calendar
124
+     * @return void
125
+     * @since 13.0.0
126
+     */
127
+    public function registerCalendar(ICalendar $calendar) {
128
+        $this->calendars[$calendar->getKey()] = $calendar;
129
+    }
130
+
131
+    /**
132
+     * Unregisters a calendar
133
+     *
134
+     * @param ICalendar $calendar
135
+     * @return void
136
+     * @since 13.0.0
137
+     */
138
+    public function unregisterCalendar(ICalendar $calendar) {
139
+        unset($this->calendars[$calendar->getKey()]);
140
+    }
141
+
142
+    /**
143
+     * In order to improve lazy loading a closure can be registered which will be called in case
144
+     * calendars are actually requested
145
+     *
146
+     * @param \Closure $callable
147
+     * @return void
148
+     * @since 13.0.0
149
+     */
150
+    public function register(\Closure $callable) {
151
+        $this->calendarLoaders[] = $callable;
152
+    }
153
+
154
+    /**
155
+     * @return ICalendar[]
156
+     * @since 13.0.0
157
+     */
158
+    public function getCalendars() {
159
+        $this->loadCalendars();
160
+
161
+        return array_values($this->calendars);
162
+    }
163
+
164
+    /**
165
+     * removes all registered calendar instances
166
+     * @return void
167
+     * @since 13.0.0
168
+     */
169
+    public function clear() {
170
+        $this->calendars = [];
171
+        $this->calendarLoaders = [];
172
+    }
173
+
174
+    /**
175
+     * loads all calendars
176
+     */
177
+    private function loadCalendars() {
178
+        foreach ($this->calendarLoaders as $callable) {
179
+            $callable($this);
180
+        }
181
+        $this->calendarLoaders = [];
182
+    }
183
+
184
+    /**
185
+     * @param string $principalUri
186
+     * @param array $calendarUris
187
+     * @return ICreateFromString[]
188
+     */
189
+    public function getCalendarsForPrincipal(string $principalUri, array $calendarUris = []): array {
190
+        $context = $this->coordinator->getRegistrationContext();
191
+        if ($context === null) {
192
+            return [];
193
+        }
194
+
195
+        return array_merge(
196
+            ...array_map(function ($registration) use ($principalUri, $calendarUris) {
197
+                try {
198
+                    /** @var ICalendarProvider $provider */
199
+                    $provider = $this->container->get($registration->getService());
200
+                } catch (Throwable $e) {
201
+                    $this->logger->error('Could not load calendar provider ' . $registration->getService() . ': ' . $e->getMessage(), [
202
+                        'exception' => $e,
203
+                    ]);
204
+                    return [];
205
+                }
206
+
207
+                return $provider->getCalendars($principalUri, $calendarUris);
208
+            }, $context->getCalendarProviders())
209
+        );
210
+    }
211
+
212
+    public function searchForPrincipal(ICalendarQuery $query): array {
213
+        /** @var CalendarQuery $query */
214
+        $calendars = $this->getCalendarsForPrincipal(
215
+            $query->getPrincipalUri(),
216
+            $query->getCalendarUris(),
217
+        );
218
+
219
+        $results = [];
220
+        foreach ($calendars as $calendar) {
221
+            $r = $calendar->search(
222
+                $query->getSearchPattern() ?? '',
223
+                $query->getSearchProperties(),
224
+                $query->getOptions(),
225
+                $query->getLimit(),
226
+                $query->getOffset()
227
+            );
228
+
229
+            foreach ($r as $o) {
230
+                $o['calendar-key'] = $calendar->getKey();
231
+                $results[] = $o;
232
+            }
233
+        }
234
+        return $results;
235
+    }
236
+
237
+    public function newQuery(string $principalUri): ICalendarQuery {
238
+        return new CalendarQuery($principalUri);
239
+    }
240
+
241
+    /**
242
+     * @throws \OCP\DB\Exception
243
+     */
244
+    public function handleIMipReply(string $principalUri, string $sender, string $recipient, string $calendarData): bool {
245
+        /** @var VCalendar $vObject */
246
+        $vObject = Reader::read($calendarData);
247
+        /** @var VEvent $vEvent */
248
+        $vEvent = $vObject->{'VEVENT'};
249
+
250
+        // First, we check if the correct method is passed to us
251
+        if (strcasecmp('REPLY', $vObject->{'METHOD'}->getValue()) !== 0) {
252
+            $this->logger->warning('Wrong method provided for processing');
253
+            return false;
254
+        }
255
+
256
+        // check if mail recipient and organizer are one and the same
257
+        $organizer = substr($vEvent->{'ORGANIZER'}->getValue(), 7);
258
+
259
+        if (strcasecmp($recipient, $organizer) !== 0) {
260
+            $this->logger->warning('Recipient and ORGANIZER must be identical');
261
+            return false;
262
+        }
263
+
264
+        //check if the event is in the future
265
+        /** @var DateTime $eventTime */
266
+        $eventTime = $vEvent->{'DTSTART'};
267
+        if ($eventTime->getDateTime()->getTimeStamp() < $this->timeFactory->getTime()) { // this might cause issues with recurrences
268
+            $this->logger->warning('Only events in the future are processed');
269
+            return false;
270
+        }
271
+
272
+        $calendars = $this->getCalendarsForPrincipal($principalUri);
273
+        if (empty($calendars)) {
274
+            $this->logger->warning('Could not find any calendars for principal ' . $principalUri);
275
+            return false;
276
+        }
277
+
278
+        $found = null;
279
+        // if the attendee has been found in at least one calendar event with the UID of the iMIP event
280
+        // we process it.
281
+        // Benefit: no attendee lost
282
+        // Drawback: attendees that have been deleted will still be able to update their partstat
283
+        foreach ($calendars as $calendar) {
284
+            // We should not search in writable calendars
285
+            if ($calendar instanceof IHandleImipMessage) {
286
+                $o = $calendar->search($sender, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
287
+                if (!empty($o)) {
288
+                    $found = $calendar;
289
+                    $name = $o[0]['uri'];
290
+                    break;
291
+                }
292
+            }
293
+        }
294
+
295
+        if (empty($found)) {
296
+            $this->logger->info('Event not found in any calendar for principal ' . $principalUri . 'and UID' . $vEvent->{'UID'}->getValue());
297
+            return false;
298
+        }
299
+
300
+        try {
301
+            $found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes
302
+        } catch (CalendarException $e) {
303
+            $this->logger->error('Could not update calendar for iMIP processing', ['exception' => $e]);
304
+            return false;
305
+        }
306
+        return true;
307
+    }
308
+
309
+    /**
310
+     * @since 25.0.0
311
+     * @throws \OCP\DB\Exception
312
+     */
313
+    public function handleIMipCancel(string $principalUri, string $sender, ?string $replyTo, string $recipient, string $calendarData): bool {
314
+        $vObject = Reader::read($calendarData);
315
+        /** @var VEvent $vEvent */
316
+        $vEvent = $vObject->{'VEVENT'};
317
+
318
+        // First, we check if the correct method is passed to us
319
+        if (strcasecmp('CANCEL', $vObject->{'METHOD'}->getValue()) !== 0) {
320
+            $this->logger->warning('Wrong method provided for processing');
321
+            return false;
322
+        }
323
+
324
+        $attendee = substr($vEvent->{'ATTENDEE'}->getValue(), 7);
325
+        if (strcasecmp($recipient, $attendee) !== 0) {
326
+            $this->logger->warning('Recipient must be an ATTENDEE of this event');
327
+            return false;
328
+        }
329
+
330
+        // Thirdly, we need to compare the email address the CANCEL is coming from (in Mail)
331
+        // or the Reply- To Address submitted with the CANCEL email
332
+        // to the email address in the ORGANIZER.
333
+        // We don't want to accept a CANCEL request from just anyone
334
+        $organizer = substr($vEvent->{'ORGANIZER'}->getValue(), 7);
335
+        $isNotOrganizer = ($replyTo !== null) ? (strcasecmp($sender, $organizer) !== 0 && strcasecmp($replyTo, $organizer) !== 0) : (strcasecmp($sender, $organizer) !== 0);
336
+        if ($isNotOrganizer) {
337
+            $this->logger->warning('Sender must be the ORGANIZER of this event');
338
+            return false;
339
+        }
340
+
341
+        //check if the event is in the future
342
+        /** @var DateTime $eventTime */
343
+        $eventTime = $vEvent->{'DTSTART'};
344
+        if ($eventTime->getDateTime()->getTimeStamp() < $this->timeFactory->getTime()) { // this might cause issues with recurrences
345
+            $this->logger->warning('Only events in the future are processed');
346
+            return false;
347
+        }
348
+
349
+        // Check if we have a calendar to work with
350
+        $calendars = $this->getCalendarsForPrincipal($principalUri);
351
+        if (empty($calendars)) {
352
+            $this->logger->warning('Could not find any calendars for principal ' . $principalUri);
353
+            return false;
354
+        }
355
+
356
+        $found = null;
357
+        // if the attendee has been found in at least one calendar event with the UID of the iMIP event
358
+        // we process it.
359
+        // Benefit: no attendee lost
360
+        // Drawback: attendees that have been deleted will still be able to update their partstat
361
+        foreach ($calendars as $calendar) {
362
+            // We should not search in writable calendars
363
+            if ($calendar instanceof IHandleImipMessage) {
364
+                $o = $calendar->search($recipient, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
365
+                if (!empty($o)) {
366
+                    $found = $calendar;
367
+                    $name = $o[0]['uri'];
368
+                    break;
369
+                }
370
+            }
371
+        }
372
+
373
+        if (empty($found)) {
374
+            $this->logger->info('Event not found in any calendar for principal ' . $principalUri . 'and UID' . $vEvent->{'UID'}->getValue());
375
+            // this is a safe operation
376
+            // we can ignore events that have been cancelled but were not in the calendar anyway
377
+            return true;
378
+        }
379
+
380
+        try {
381
+            $found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes
382
+            return true;
383
+        } catch (CalendarException $e) {
384
+            $this->logger->error('Could not update calendar for iMIP processing', ['exception' => $e]);
385
+            return false;
386
+        }
387
+    }
388 388
 }
Please login to merge, or discard this patch.
lib/public/Calendar/IHandleImipMessage.php 1 patch
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -37,12 +37,12 @@
 block discarded – undo
37 37
  */
38 38
 interface IHandleImipMessage extends ICalendar {
39 39
 
40
-	/**
41
-	 * Handle an iMIP VEvent for validation and processing
42
-	 *
43
-	 * @since 26.0.0
44
-	 *
45
-	 * @throws CalendarException  on validation failure or calendar write error
46
-	 */
47
-	public function handleIMipMessage(string $name, string $calendarData): void;
40
+    /**
41
+     * Handle an iMIP VEvent for validation and processing
42
+     *
43
+     * @since 26.0.0
44
+     *
45
+     * @throws CalendarException  on validation failure or calendar write error
46
+     */
47
+    public function handleIMipMessage(string $name, string $calendarData): void;
48 48
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/CalendarImpl.php 2 patches
Indentation   +189 added lines, -189 removed lines patch added patch discarded remove patch
@@ -43,193 +43,193 @@
 block discarded – undo
43 43
 
44 44
 class CalendarImpl implements ICreateFromString, IHandleImipMessage {
45 45
 
46
-	private CalDavBackend $backend;
47
-	private Calendar $calendar;
48
-	/** @var array<string, mixed> */
49
-	private array $calendarInfo;
50
-
51
-	public function __construct(Calendar $calendar,
52
-								array $calendarInfo,
53
-								CalDavBackend $backend) {
54
-		$this->calendar = $calendar;
55
-		$this->calendarInfo = $calendarInfo;
56
-		$this->backend = $backend;
57
-	}
58
-
59
-	/**
60
-	 * @return string defining the technical unique key
61
-	 * @since 13.0.0
62
-	 */
63
-	public function getKey(): string {
64
-		return (string) $this->calendarInfo['id'];
65
-	}
66
-
67
-	/**
68
-	 * {@inheritDoc}
69
-	 */
70
-	public function getUri(): string {
71
-		return $this->calendarInfo['uri'];
72
-	}
73
-
74
-	/**
75
-	 * In comparison to getKey() this function returns a human readable (maybe translated) name
76
-	 * @since 13.0.0
77
-	 */
78
-	public function getDisplayName(): ?string {
79
-		return $this->calendarInfo['{DAV:}displayname'];
80
-	}
81
-
82
-	/**
83
-	 * Calendar color
84
-	 * @since 13.0.0
85
-	 */
86
-	public function getDisplayColor(): ?string {
87
-		return $this->calendarInfo['{http://apple.com/ns/ical/}calendar-color'];
88
-	}
89
-
90
-	/**
91
-	 * @param string $pattern which should match within the $searchProperties
92
-	 * @param array $searchProperties defines the properties within the query pattern should match
93
-	 * @param array $options - optional parameters:
94
-	 * 	['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]]
95
-	 * @param int|null $limit - limit number of search results
96
-	 * @param int|null $offset - offset for paging of search results
97
-	 * @return array an array of events/journals/todos which are arrays of key-value-pairs
98
-	 * @since 13.0.0
99
-	 */
100
-	public function search(string $pattern, array $searchProperties = [], array $options = [], $limit = null, $offset = null): array {
101
-		return $this->backend->search($this->calendarInfo, $pattern,
102
-			$searchProperties, $options, $limit, $offset);
103
-	}
104
-
105
-	/**
106
-	 * @return int build up using \OCP\Constants
107
-	 * @since 13.0.0
108
-	 */
109
-	public function getPermissions(): int {
110
-		$permissions = $this->calendar->getACL();
111
-		$result = 0;
112
-		foreach ($permissions as $permission) {
113
-			switch ($permission['privilege']) {
114
-				case '{DAV:}read':
115
-					$result |= Constants::PERMISSION_READ;
116
-					break;
117
-				case '{DAV:}write':
118
-					$result |= Constants::PERMISSION_CREATE;
119
-					$result |= Constants::PERMISSION_UPDATE;
120
-					break;
121
-				case '{DAV:}all':
122
-					$result |= Constants::PERMISSION_ALL;
123
-					break;
124
-			}
125
-		}
126
-
127
-		return $result;
128
-	}
129
-
130
-	/**
131
-	 * @since 26.0.0
132
-	 */
133
-	public function isDeleted(): bool {
134
-		return $this->calendar->isDeleted();
135
-	}
136
-
137
-	/**
138
-	 * Create a new calendar event for this calendar
139
-	 * by way of an ICS string
140
-	 *
141
-	 * @param string $name the file name - needs to contain the .ics ending
142
-	 * @param string $calendarData a string containing a valid VEVENT ics
143
-	 *
144
-	 * @throws CalendarException
145
-	 */
146
-	public function createFromString(string $name, string $calendarData): void {
147
-		$server = new InvitationResponseServer(false);
148
-
149
-		/** @var CustomPrincipalPlugin $plugin */
150
-		$plugin = $server->server->getPlugin('auth');
151
-		// we're working around the previous implementation
152
-		// that only allowed the public system principal to be used
153
-		// so set the custom principal here
154
-		$plugin->setCurrentPrincipal($this->calendar->getPrincipalURI());
155
-
156
-		if (empty($this->calendarInfo['uri'])) {
157
-			throw new CalendarException('Could not write to calendar as URI parameter is missing');
158
-		}
159
-
160
-		// Build full calendar path
161
-		[, $user] = uriSplit($this->calendar->getPrincipalURI());
162
-		$fullCalendarFilename = sprintf('calendars/%s/%s/%s', $user, $this->calendarInfo['uri'], $name);
163
-
164
-		// Force calendar change URI
165
-		/** @var Schedule\Plugin $schedulingPlugin */
166
-		$schedulingPlugin = $server->server->getPlugin('caldav-schedule');
167
-		$schedulingPlugin->setPathOfCalendarObjectChange($fullCalendarFilename);
168
-
169
-		$stream = fopen('php://memory', 'rb+');
170
-		fwrite($stream, $calendarData);
171
-		rewind($stream);
172
-		try {
173
-			$server->server->createFile($fullCalendarFilename, $stream);
174
-		} catch (Conflict $e) {
175
-			throw new CalendarException('Could not create new calendar event: ' . $e->getMessage(), 0, $e);
176
-		} finally {
177
-			fclose($stream);
178
-		}
179
-	}
180
-
181
-	/**
182
-	 * @throws CalendarException
183
-	 */
184
-	public function handleIMipMessage(string $name, string $calendarData): void {
185
-		$server = new InvitationResponseServer(false);
186
-
187
-		/** @var CustomPrincipalPlugin $plugin */
188
-		$plugin = $server->server->getPlugin('auth');
189
-		// we're working around the previous implementation
190
-		// that only allowed the public system principal to be used
191
-		// so set the custom principal here
192
-		$plugin->setCurrentPrincipal($this->calendar->getPrincipalURI());
193
-
194
-		if (empty($this->calendarInfo['uri'])) {
195
-			throw new CalendarException('Could not write to calendar as URI parameter is missing');
196
-		}
197
-		// Force calendar change URI
198
-		/** @var Schedule\Plugin $schedulingPlugin */
199
-		$schedulingPlugin = $server->server->getPlugin('caldav-schedule');
200
-		// Let sabre handle the rest
201
-		$iTipMessage = new Message();
202
-		/** @var VCalendar $vObject */
203
-		$vObject = Reader::read($calendarData);
204
-		/** @var VEvent $vEvent */
205
-		$vEvent = $vObject->{'VEVENT'};
206
-
207
-		if($vObject->{'METHOD'} === null) {
208
-			throw new CalendarException('No Method provided for scheduling data. Could not process message');
209
-		}
210
-
211
-		if(!isset($vEvent->{'ORGANIZER'}) || !isset($vEvent->{'ATTENDEE'})) {
212
-			throw new CalendarException('Could not process scheduling data, neccessary data missing from ICAL');
213
-		}
214
-		$organizer = $vEvent->{'ORGANIZER'}->getValue();
215
-		$attendee = $vEvent->{'ATTENDEE'}->getValue();
216
-
217
-		$iTipMessage->method = $vObject->{'METHOD'}->getValue();
218
-		if($iTipMessage->method === 'REPLY') {
219
-			if ($server->isExternalAttendee($vEvent->{'ATTENDEE'}->getValue())) {
220
-				$iTipMessage->recipient = $organizer;
221
-			} else {
222
-				$iTipMessage->recipient = $attendee;
223
-			}
224
-			$iTipMessage->sender = $attendee;
225
-		} else if($iTipMessage->method === 'CANCEL') {
226
-			$iTipMessage->recipient = $attendee;
227
-			$iTipMessage->sender = $organizer;
228
-		}
229
-		$iTipMessage->uid = isset($vEvent->{'UID'}) ? $vEvent->{'UID'}->getValue() : '';
230
-		$iTipMessage->component = 'VEVENT';
231
-		$iTipMessage->sequence = isset($vEvent->{'SEQUENCE'}) ? (int)$vEvent->{'SEQUENCE'}->getValue() : 0;
232
-		$iTipMessage->message = $vObject;
233
-		$schedulingPlugin->scheduleLocalDelivery($iTipMessage);
234
-	}
46
+    private CalDavBackend $backend;
47
+    private Calendar $calendar;
48
+    /** @var array<string, mixed> */
49
+    private array $calendarInfo;
50
+
51
+    public function __construct(Calendar $calendar,
52
+                                array $calendarInfo,
53
+                                CalDavBackend $backend) {
54
+        $this->calendar = $calendar;
55
+        $this->calendarInfo = $calendarInfo;
56
+        $this->backend = $backend;
57
+    }
58
+
59
+    /**
60
+     * @return string defining the technical unique key
61
+     * @since 13.0.0
62
+     */
63
+    public function getKey(): string {
64
+        return (string) $this->calendarInfo['id'];
65
+    }
66
+
67
+    /**
68
+     * {@inheritDoc}
69
+     */
70
+    public function getUri(): string {
71
+        return $this->calendarInfo['uri'];
72
+    }
73
+
74
+    /**
75
+     * In comparison to getKey() this function returns a human readable (maybe translated) name
76
+     * @since 13.0.0
77
+     */
78
+    public function getDisplayName(): ?string {
79
+        return $this->calendarInfo['{DAV:}displayname'];
80
+    }
81
+
82
+    /**
83
+     * Calendar color
84
+     * @since 13.0.0
85
+     */
86
+    public function getDisplayColor(): ?string {
87
+        return $this->calendarInfo['{http://apple.com/ns/ical/}calendar-color'];
88
+    }
89
+
90
+    /**
91
+     * @param string $pattern which should match within the $searchProperties
92
+     * @param array $searchProperties defines the properties within the query pattern should match
93
+     * @param array $options - optional parameters:
94
+     * 	['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]]
95
+     * @param int|null $limit - limit number of search results
96
+     * @param int|null $offset - offset for paging of search results
97
+     * @return array an array of events/journals/todos which are arrays of key-value-pairs
98
+     * @since 13.0.0
99
+     */
100
+    public function search(string $pattern, array $searchProperties = [], array $options = [], $limit = null, $offset = null): array {
101
+        return $this->backend->search($this->calendarInfo, $pattern,
102
+            $searchProperties, $options, $limit, $offset);
103
+    }
104
+
105
+    /**
106
+     * @return int build up using \OCP\Constants
107
+     * @since 13.0.0
108
+     */
109
+    public function getPermissions(): int {
110
+        $permissions = $this->calendar->getACL();
111
+        $result = 0;
112
+        foreach ($permissions as $permission) {
113
+            switch ($permission['privilege']) {
114
+                case '{DAV:}read':
115
+                    $result |= Constants::PERMISSION_READ;
116
+                    break;
117
+                case '{DAV:}write':
118
+                    $result |= Constants::PERMISSION_CREATE;
119
+                    $result |= Constants::PERMISSION_UPDATE;
120
+                    break;
121
+                case '{DAV:}all':
122
+                    $result |= Constants::PERMISSION_ALL;
123
+                    break;
124
+            }
125
+        }
126
+
127
+        return $result;
128
+    }
129
+
130
+    /**
131
+     * @since 26.0.0
132
+     */
133
+    public function isDeleted(): bool {
134
+        return $this->calendar->isDeleted();
135
+    }
136
+
137
+    /**
138
+     * Create a new calendar event for this calendar
139
+     * by way of an ICS string
140
+     *
141
+     * @param string $name the file name - needs to contain the .ics ending
142
+     * @param string $calendarData a string containing a valid VEVENT ics
143
+     *
144
+     * @throws CalendarException
145
+     */
146
+    public function createFromString(string $name, string $calendarData): void {
147
+        $server = new InvitationResponseServer(false);
148
+
149
+        /** @var CustomPrincipalPlugin $plugin */
150
+        $plugin = $server->server->getPlugin('auth');
151
+        // we're working around the previous implementation
152
+        // that only allowed the public system principal to be used
153
+        // so set the custom principal here
154
+        $plugin->setCurrentPrincipal($this->calendar->getPrincipalURI());
155
+
156
+        if (empty($this->calendarInfo['uri'])) {
157
+            throw new CalendarException('Could not write to calendar as URI parameter is missing');
158
+        }
159
+
160
+        // Build full calendar path
161
+        [, $user] = uriSplit($this->calendar->getPrincipalURI());
162
+        $fullCalendarFilename = sprintf('calendars/%s/%s/%s', $user, $this->calendarInfo['uri'], $name);
163
+
164
+        // Force calendar change URI
165
+        /** @var Schedule\Plugin $schedulingPlugin */
166
+        $schedulingPlugin = $server->server->getPlugin('caldav-schedule');
167
+        $schedulingPlugin->setPathOfCalendarObjectChange($fullCalendarFilename);
168
+
169
+        $stream = fopen('php://memory', 'rb+');
170
+        fwrite($stream, $calendarData);
171
+        rewind($stream);
172
+        try {
173
+            $server->server->createFile($fullCalendarFilename, $stream);
174
+        } catch (Conflict $e) {
175
+            throw new CalendarException('Could not create new calendar event: ' . $e->getMessage(), 0, $e);
176
+        } finally {
177
+            fclose($stream);
178
+        }
179
+    }
180
+
181
+    /**
182
+     * @throws CalendarException
183
+     */
184
+    public function handleIMipMessage(string $name, string $calendarData): void {
185
+        $server = new InvitationResponseServer(false);
186
+
187
+        /** @var CustomPrincipalPlugin $plugin */
188
+        $plugin = $server->server->getPlugin('auth');
189
+        // we're working around the previous implementation
190
+        // that only allowed the public system principal to be used
191
+        // so set the custom principal here
192
+        $plugin->setCurrentPrincipal($this->calendar->getPrincipalURI());
193
+
194
+        if (empty($this->calendarInfo['uri'])) {
195
+            throw new CalendarException('Could not write to calendar as URI parameter is missing');
196
+        }
197
+        // Force calendar change URI
198
+        /** @var Schedule\Plugin $schedulingPlugin */
199
+        $schedulingPlugin = $server->server->getPlugin('caldav-schedule');
200
+        // Let sabre handle the rest
201
+        $iTipMessage = new Message();
202
+        /** @var VCalendar $vObject */
203
+        $vObject = Reader::read($calendarData);
204
+        /** @var VEvent $vEvent */
205
+        $vEvent = $vObject->{'VEVENT'};
206
+
207
+        if($vObject->{'METHOD'} === null) {
208
+            throw new CalendarException('No Method provided for scheduling data. Could not process message');
209
+        }
210
+
211
+        if(!isset($vEvent->{'ORGANIZER'}) || !isset($vEvent->{'ATTENDEE'})) {
212
+            throw new CalendarException('Could not process scheduling data, neccessary data missing from ICAL');
213
+        }
214
+        $organizer = $vEvent->{'ORGANIZER'}->getValue();
215
+        $attendee = $vEvent->{'ATTENDEE'}->getValue();
216
+
217
+        $iTipMessage->method = $vObject->{'METHOD'}->getValue();
218
+        if($iTipMessage->method === 'REPLY') {
219
+            if ($server->isExternalAttendee($vEvent->{'ATTENDEE'}->getValue())) {
220
+                $iTipMessage->recipient = $organizer;
221
+            } else {
222
+                $iTipMessage->recipient = $attendee;
223
+            }
224
+            $iTipMessage->sender = $attendee;
225
+        } else if($iTipMessage->method === 'CANCEL') {
226
+            $iTipMessage->recipient = $attendee;
227
+            $iTipMessage->sender = $organizer;
228
+        }
229
+        $iTipMessage->uid = isset($vEvent->{'UID'}) ? $vEvent->{'UID'}->getValue() : '';
230
+        $iTipMessage->component = 'VEVENT';
231
+        $iTipMessage->sequence = isset($vEvent->{'SEQUENCE'}) ? (int)$vEvent->{'SEQUENCE'}->getValue() : 0;
232
+        $iTipMessage->message = $vObject;
233
+        $schedulingPlugin->scheduleLocalDelivery($iTipMessage);
234
+    }
235 235
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -172,7 +172,7 @@  discard block
 block discarded – undo
172 172
 		try {
173 173
 			$server->server->createFile($fullCalendarFilename, $stream);
174 174
 		} catch (Conflict $e) {
175
-			throw new CalendarException('Could not create new calendar event: ' . $e->getMessage(), 0, $e);
175
+			throw new CalendarException('Could not create new calendar event: '.$e->getMessage(), 0, $e);
176 176
 		} finally {
177 177
 			fclose($stream);
178 178
 		}
@@ -204,31 +204,31 @@  discard block
 block discarded – undo
204 204
 		/** @var VEvent $vEvent */
205 205
 		$vEvent = $vObject->{'VEVENT'};
206 206
 
207
-		if($vObject->{'METHOD'} === null) {
207
+		if ($vObject->{'METHOD'} === null) {
208 208
 			throw new CalendarException('No Method provided for scheduling data. Could not process message');
209 209
 		}
210 210
 
211
-		if(!isset($vEvent->{'ORGANIZER'}) || !isset($vEvent->{'ATTENDEE'})) {
211
+		if (!isset($vEvent->{'ORGANIZER'}) || !isset($vEvent->{'ATTENDEE'})) {
212 212
 			throw new CalendarException('Could not process scheduling data, neccessary data missing from ICAL');
213 213
 		}
214 214
 		$organizer = $vEvent->{'ORGANIZER'}->getValue();
215 215
 		$attendee = $vEvent->{'ATTENDEE'}->getValue();
216 216
 
217 217
 		$iTipMessage->method = $vObject->{'METHOD'}->getValue();
218
-		if($iTipMessage->method === 'REPLY') {
218
+		if ($iTipMessage->method === 'REPLY') {
219 219
 			if ($server->isExternalAttendee($vEvent->{'ATTENDEE'}->getValue())) {
220 220
 				$iTipMessage->recipient = $organizer;
221 221
 			} else {
222 222
 				$iTipMessage->recipient = $attendee;
223 223
 			}
224 224
 			$iTipMessage->sender = $attendee;
225
-		} else if($iTipMessage->method === 'CANCEL') {
225
+		} else if ($iTipMessage->method === 'CANCEL') {
226 226
 			$iTipMessage->recipient = $attendee;
227 227
 			$iTipMessage->sender = $organizer;
228 228
 		}
229 229
 		$iTipMessage->uid = isset($vEvent->{'UID'}) ? $vEvent->{'UID'}->getValue() : '';
230 230
 		$iTipMessage->component = 'VEVENT';
231
-		$iTipMessage->sequence = isset($vEvent->{'SEQUENCE'}) ? (int)$vEvent->{'SEQUENCE'}->getValue() : 0;
231
+		$iTipMessage->sequence = isset($vEvent->{'SEQUENCE'}) ? (int) $vEvent->{'SEQUENCE'}->getValue() : 0;
232 232
 		$iTipMessage->message = $vObject;
233 233
 		$schedulingPlugin->scheduleLocalDelivery($iTipMessage);
234 234
 	}
Please login to merge, or discard this patch.