Passed
Push — master ( 5e3a3c...fce87f )
by Christoph
15:52 queued 14s
created
apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php 1 patch
Indentation   +314 added lines, -314 removed lines patch added patch discarded remove patch
@@ -55,318 +55,318 @@
 block discarded – undo
55 55
 
56 56
 class RefreshWebcalService {
57 57
 
58
-	private CalDavBackend $calDavBackend;
59
-
60
-	private IClientService $clientService;
61
-
62
-	private IConfig $config;
63
-
64
-	/** @var LoggerInterface */
65
-	private LoggerInterface $logger;
66
-
67
-	public const REFRESH_RATE = '{http://apple.com/ns/ical/}refreshrate';
68
-	public const STRIP_ALARMS = '{http://calendarserver.org/ns/}subscribed-strip-alarms';
69
-	public const STRIP_ATTACHMENTS = '{http://calendarserver.org/ns/}subscribed-strip-attachments';
70
-	public const STRIP_TODOS = '{http://calendarserver.org/ns/}subscribed-strip-todos';
71
-
72
-	public function __construct(CalDavBackend $calDavBackend, IClientService $clientService, IConfig $config, LoggerInterface $logger) {
73
-		$this->calDavBackend = $calDavBackend;
74
-		$this->clientService = $clientService;
75
-		$this->config = $config;
76
-		$this->logger = $logger;
77
-	}
78
-
79
-	public function refreshSubscription(string $principalUri, string $uri) {
80
-		$subscription = $this->getSubscription($principalUri, $uri);
81
-		$mutations = [];
82
-		if (!$subscription) {
83
-			return;
84
-		}
85
-
86
-		$webcalData = $this->queryWebcalFeed($subscription, $mutations);
87
-		if (!$webcalData) {
88
-			return;
89
-		}
90
-
91
-		$stripTodos = ($subscription[self::STRIP_TODOS] ?? 1) === 1;
92
-		$stripAlarms = ($subscription[self::STRIP_ALARMS] ?? 1) === 1;
93
-		$stripAttachments = ($subscription[self::STRIP_ATTACHMENTS] ?? 1) === 1;
94
-
95
-		try {
96
-			$splitter = new ICalendar($webcalData, Reader::OPTION_FORGIVING);
97
-
98
-			// we wait with deleting all outdated events till we parsed the new ones
99
-			// in case the new calendar is broken and `new ICalendar` throws a ParseException
100
-			// the user will still see the old data
101
-			$this->calDavBackend->purgeAllCachedEventsForSubscription($subscription['id']);
102
-
103
-			while ($vObject = $splitter->getNext()) {
104
-				/** @var Component $vObject */
105
-				$compName = null;
106
-
107
-				foreach ($vObject->getComponents() as $component) {
108
-					if ($component->name === 'VTIMEZONE') {
109
-						continue;
110
-					}
111
-
112
-					$compName = $component->name;
113
-
114
-					if ($stripAlarms) {
115
-						unset($component->{'VALARM'});
116
-					}
117
-					if ($stripAttachments) {
118
-						unset($component->{'ATTACH'});
119
-					}
120
-				}
121
-
122
-				if ($stripTodos && $compName === 'VTODO') {
123
-					continue;
124
-				}
125
-
126
-				$objectUri = $this->getRandomCalendarObjectUri();
127
-				$calendarData = $vObject->serialize();
128
-				try {
129
-					$this->calDavBackend->createCalendarObject($subscription['id'], $objectUri, $calendarData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
130
-				} catch (NoInstancesException | BadRequest $ex) {
131
-					$this->logger->error('Unable to create calendar object from subscription {subscriptionId}', ['exception' => $ex, 'subscriptionId' => $subscription['id'], 'source' => $subscription['source']]);
132
-				}
133
-			}
134
-
135
-			$newRefreshRate = $this->checkWebcalDataForRefreshRate($subscription, $webcalData);
136
-			if ($newRefreshRate) {
137
-				$mutations[self::REFRESH_RATE] = $newRefreshRate;
138
-			}
139
-
140
-			$this->updateSubscription($subscription, $mutations);
141
-		} catch (ParseException $ex) {
142
-			$this->logger->error("Subscription {subscriptionId} could not be refreshed due to a parsing error", ['exception' => $ex, 'subscriptionId' => $subscription['id']]);
143
-		}
144
-	}
145
-
146
-	/**
147
-	 * loads subscription from backend
148
-	 */
149
-	public function getSubscription(string $principalUri, string $uri): ?array {
150
-		$subscriptions = array_values(array_filter(
151
-			$this->calDavBackend->getSubscriptionsForUser($principalUri),
152
-			function ($sub) use ($uri) {
153
-				return $sub['uri'] === $uri;
154
-			}
155
-		));
156
-
157
-		if (count($subscriptions) === 0) {
158
-			return null;
159
-		}
160
-
161
-		return $subscriptions[0];
162
-	}
163
-
164
-	/**
165
-	 * gets webcal feed from remote server
166
-	 */
167
-	private function queryWebcalFeed(array $subscription, array &$mutations): ?string {
168
-		$client = $this->clientService->newClient();
169
-
170
-		$didBreak301Chain = false;
171
-		$latestLocation = null;
172
-
173
-		$handlerStack = HandlerStack::create();
174
-		$handlerStack->push(Middleware::mapRequest(function (RequestInterface $request) {
175
-			return $request
176
-				->withHeader('Accept', 'text/calendar, application/calendar+json, application/calendar+xml')
177
-				->withHeader('User-Agent', 'Nextcloud Webcal Service');
178
-		}));
179
-		$handlerStack->push(Middleware::mapResponse(function (ResponseInterface $response) use (&$didBreak301Chain, &$latestLocation) {
180
-			if (!$didBreak301Chain) {
181
-				if ($response->getStatusCode() !== 301) {
182
-					$didBreak301Chain = true;
183
-				} else {
184
-					$latestLocation = $response->getHeader('Location');
185
-				}
186
-			}
187
-			return $response;
188
-		}));
189
-
190
-		$allowLocalAccess = $this->config->getAppValue('dav', 'webcalAllowLocalAccess', 'no');
191
-		$subscriptionId = $subscription['id'];
192
-		$url = $this->cleanURL($subscription['source']);
193
-		if ($url === null) {
194
-			return null;
195
-		}
196
-
197
-		try {
198
-			$params = [
199
-				'allow_redirects' => [
200
-					'redirects' => 10
201
-				],
202
-				'handler' => $handlerStack,
203
-				'nextcloud' => [
204
-					'allow_local_address' => $allowLocalAccess === 'yes',
205
-				]
206
-			];
207
-
208
-			$user = parse_url($subscription['source'], PHP_URL_USER);
209
-			$pass = parse_url($subscription['source'], PHP_URL_PASS);
210
-			if ($user !== null && $pass !== null) {
211
-				$params['auth'] = [$user, $pass];
212
-			}
213
-
214
-			$response = $client->get($url, $params);
215
-			$body = $response->getBody();
216
-
217
-			if ($latestLocation) {
218
-				$mutations['{http://calendarserver.org/ns/}source'] = new Href($latestLocation);
219
-			}
220
-
221
-			$contentType = $response->getHeader('Content-Type');
222
-			$contentType = explode(';', $contentType, 2)[0];
223
-			switch ($contentType) {
224
-				case 'application/calendar+json':
225
-					try {
226
-						$jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING);
227
-					} catch (Exception $ex) {
228
-						// In case of a parsing error return null
229
-						$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
230
-						return null;
231
-					}
232
-					return $jCalendar->serialize();
233
-
234
-				case 'application/calendar+xml':
235
-					try {
236
-						$xCalendar = Reader::readXML($body);
237
-					} catch (Exception $ex) {
238
-						// In case of a parsing error return null
239
-						$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
240
-						return null;
241
-					}
242
-					return $xCalendar->serialize();
243
-
244
-				case 'text/calendar':
245
-				default:
246
-					try {
247
-						$vCalendar = Reader::read($body);
248
-					} catch (Exception $ex) {
249
-						// In case of a parsing error return null
250
-						$this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
251
-						return null;
252
-					}
253
-					return $vCalendar->serialize();
254
-			}
255
-		} catch (LocalServerException $ex) {
256
-			$this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules", [
257
-				'exception' => $ex,
258
-			]);
259
-
260
-			return null;
261
-		} catch (Exception $ex) {
262
-			$this->logger->warning("Subscription $subscriptionId could not be refreshed due to a network error", [
263
-				'exception' => $ex,
264
-			]);
265
-
266
-			return null;
267
-		}
268
-	}
269
-
270
-	/**
271
-	 * check if:
272
-	 *  - current subscription stores a refreshrate
273
-	 *  - the webcal feed suggests a refreshrate
274
-	 *  - return suggested refreshrate if user didn't set a custom one
275
-	 *
276
-	 */
277
-	private function checkWebcalDataForRefreshRate(array $subscription, string $webcalData): ?string {
278
-		// if there is no refreshrate stored in the database, check the webcal feed
279
-		// whether it suggests any refresh rate and store that in the database
280
-		if (isset($subscription[self::REFRESH_RATE]) && $subscription[self::REFRESH_RATE] !== null) {
281
-			return null;
282
-		}
283
-
284
-		/** @var Component\VCalendar $vCalendar */
285
-		$vCalendar = Reader::read($webcalData);
286
-
287
-		$newRefreshRate = null;
288
-		if (isset($vCalendar->{'X-PUBLISHED-TTL'})) {
289
-			$newRefreshRate = $vCalendar->{'X-PUBLISHED-TTL'}->getValue();
290
-		}
291
-		if (isset($vCalendar->{'REFRESH-INTERVAL'})) {
292
-			$newRefreshRate = $vCalendar->{'REFRESH-INTERVAL'}->getValue();
293
-		}
294
-
295
-		if (!$newRefreshRate) {
296
-			return null;
297
-		}
298
-
299
-		// check if new refresh rate is even valid
300
-		try {
301
-			DateTimeParser::parseDuration($newRefreshRate);
302
-		} catch (InvalidDataException $ex) {
303
-			return null;
304
-		}
305
-
306
-		return $newRefreshRate;
307
-	}
308
-
309
-	/**
310
-	 * update subscription stored in database
311
-	 * used to set:
312
-	 *  - refreshrate
313
-	 *  - source
314
-	 *
315
-	 * @param array $subscription
316
-	 * @param array $mutations
317
-	 */
318
-	private function updateSubscription(array $subscription, array $mutations) {
319
-		if (empty($mutations)) {
320
-			return;
321
-		}
322
-
323
-		$propPatch = new PropPatch($mutations);
324
-		$this->calDavBackend->updateSubscription($subscription['id'], $propPatch);
325
-		$propPatch->commit();
326
-	}
327
-
328
-	/**
329
-	 * This method will strip authentication information and replace the
330
-	 * 'webcal' or 'webcals' protocol scheme
331
-	 *
332
-	 * @param string $url
333
-	 * @return string|null
334
-	 */
335
-	private function cleanURL(string $url): ?string {
336
-		$parsed = parse_url($url);
337
-		if ($parsed === false) {
338
-			return null;
339
-		}
340
-
341
-		if (isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
342
-			$scheme = 'http';
343
-		} else {
344
-			$scheme = 'https';
345
-		}
346
-
347
-		$host = $parsed['host'] ?? '';
348
-		$port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
349
-		$path = $parsed['path'] ?? '';
350
-		$query = isset($parsed['query']) ? '?' . $parsed['query'] : '';
351
-		$fragment = isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
352
-
353
-		$cleanURL = "$scheme://$host$port$path$query$fragment";
354
-		// parse_url is giving some weird results if no url and no :// is given,
355
-		// so let's test the url again
356
-		$parsedClean = parse_url($cleanURL);
357
-		if ($parsedClean === false || !isset($parsedClean['host'])) {
358
-			return null;
359
-		}
360
-
361
-		return $cleanURL;
362
-	}
363
-
364
-	/**
365
-	 * Returns a random uri for a calendar-object
366
-	 *
367
-	 * @return string
368
-	 */
369
-	public function getRandomCalendarObjectUri():string {
370
-		return UUIDUtil::getUUID() . '.ics';
371
-	}
58
+    private CalDavBackend $calDavBackend;
59
+
60
+    private IClientService $clientService;
61
+
62
+    private IConfig $config;
63
+
64
+    /** @var LoggerInterface */
65
+    private LoggerInterface $logger;
66
+
67
+    public const REFRESH_RATE = '{http://apple.com/ns/ical/}refreshrate';
68
+    public const STRIP_ALARMS = '{http://calendarserver.org/ns/}subscribed-strip-alarms';
69
+    public const STRIP_ATTACHMENTS = '{http://calendarserver.org/ns/}subscribed-strip-attachments';
70
+    public const STRIP_TODOS = '{http://calendarserver.org/ns/}subscribed-strip-todos';
71
+
72
+    public function __construct(CalDavBackend $calDavBackend, IClientService $clientService, IConfig $config, LoggerInterface $logger) {
73
+        $this->calDavBackend = $calDavBackend;
74
+        $this->clientService = $clientService;
75
+        $this->config = $config;
76
+        $this->logger = $logger;
77
+    }
78
+
79
+    public function refreshSubscription(string $principalUri, string $uri) {
80
+        $subscription = $this->getSubscription($principalUri, $uri);
81
+        $mutations = [];
82
+        if (!$subscription) {
83
+            return;
84
+        }
85
+
86
+        $webcalData = $this->queryWebcalFeed($subscription, $mutations);
87
+        if (!$webcalData) {
88
+            return;
89
+        }
90
+
91
+        $stripTodos = ($subscription[self::STRIP_TODOS] ?? 1) === 1;
92
+        $stripAlarms = ($subscription[self::STRIP_ALARMS] ?? 1) === 1;
93
+        $stripAttachments = ($subscription[self::STRIP_ATTACHMENTS] ?? 1) === 1;
94
+
95
+        try {
96
+            $splitter = new ICalendar($webcalData, Reader::OPTION_FORGIVING);
97
+
98
+            // we wait with deleting all outdated events till we parsed the new ones
99
+            // in case the new calendar is broken and `new ICalendar` throws a ParseException
100
+            // the user will still see the old data
101
+            $this->calDavBackend->purgeAllCachedEventsForSubscription($subscription['id']);
102
+
103
+            while ($vObject = $splitter->getNext()) {
104
+                /** @var Component $vObject */
105
+                $compName = null;
106
+
107
+                foreach ($vObject->getComponents() as $component) {
108
+                    if ($component->name === 'VTIMEZONE') {
109
+                        continue;
110
+                    }
111
+
112
+                    $compName = $component->name;
113
+
114
+                    if ($stripAlarms) {
115
+                        unset($component->{'VALARM'});
116
+                    }
117
+                    if ($stripAttachments) {
118
+                        unset($component->{'ATTACH'});
119
+                    }
120
+                }
121
+
122
+                if ($stripTodos && $compName === 'VTODO') {
123
+                    continue;
124
+                }
125
+
126
+                $objectUri = $this->getRandomCalendarObjectUri();
127
+                $calendarData = $vObject->serialize();
128
+                try {
129
+                    $this->calDavBackend->createCalendarObject($subscription['id'], $objectUri, $calendarData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
130
+                } catch (NoInstancesException | BadRequest $ex) {
131
+                    $this->logger->error('Unable to create calendar object from subscription {subscriptionId}', ['exception' => $ex, 'subscriptionId' => $subscription['id'], 'source' => $subscription['source']]);
132
+                }
133
+            }
134
+
135
+            $newRefreshRate = $this->checkWebcalDataForRefreshRate($subscription, $webcalData);
136
+            if ($newRefreshRate) {
137
+                $mutations[self::REFRESH_RATE] = $newRefreshRate;
138
+            }
139
+
140
+            $this->updateSubscription($subscription, $mutations);
141
+        } catch (ParseException $ex) {
142
+            $this->logger->error("Subscription {subscriptionId} could not be refreshed due to a parsing error", ['exception' => $ex, 'subscriptionId' => $subscription['id']]);
143
+        }
144
+    }
145
+
146
+    /**
147
+     * loads subscription from backend
148
+     */
149
+    public function getSubscription(string $principalUri, string $uri): ?array {
150
+        $subscriptions = array_values(array_filter(
151
+            $this->calDavBackend->getSubscriptionsForUser($principalUri),
152
+            function ($sub) use ($uri) {
153
+                return $sub['uri'] === $uri;
154
+            }
155
+        ));
156
+
157
+        if (count($subscriptions) === 0) {
158
+            return null;
159
+        }
160
+
161
+        return $subscriptions[0];
162
+    }
163
+
164
+    /**
165
+     * gets webcal feed from remote server
166
+     */
167
+    private function queryWebcalFeed(array $subscription, array &$mutations): ?string {
168
+        $client = $this->clientService->newClient();
169
+
170
+        $didBreak301Chain = false;
171
+        $latestLocation = null;
172
+
173
+        $handlerStack = HandlerStack::create();
174
+        $handlerStack->push(Middleware::mapRequest(function (RequestInterface $request) {
175
+            return $request
176
+                ->withHeader('Accept', 'text/calendar, application/calendar+json, application/calendar+xml')
177
+                ->withHeader('User-Agent', 'Nextcloud Webcal Service');
178
+        }));
179
+        $handlerStack->push(Middleware::mapResponse(function (ResponseInterface $response) use (&$didBreak301Chain, &$latestLocation) {
180
+            if (!$didBreak301Chain) {
181
+                if ($response->getStatusCode() !== 301) {
182
+                    $didBreak301Chain = true;
183
+                } else {
184
+                    $latestLocation = $response->getHeader('Location');
185
+                }
186
+            }
187
+            return $response;
188
+        }));
189
+
190
+        $allowLocalAccess = $this->config->getAppValue('dav', 'webcalAllowLocalAccess', 'no');
191
+        $subscriptionId = $subscription['id'];
192
+        $url = $this->cleanURL($subscription['source']);
193
+        if ($url === null) {
194
+            return null;
195
+        }
196
+
197
+        try {
198
+            $params = [
199
+                'allow_redirects' => [
200
+                    'redirects' => 10
201
+                ],
202
+                'handler' => $handlerStack,
203
+                'nextcloud' => [
204
+                    'allow_local_address' => $allowLocalAccess === 'yes',
205
+                ]
206
+            ];
207
+
208
+            $user = parse_url($subscription['source'], PHP_URL_USER);
209
+            $pass = parse_url($subscription['source'], PHP_URL_PASS);
210
+            if ($user !== null && $pass !== null) {
211
+                $params['auth'] = [$user, $pass];
212
+            }
213
+
214
+            $response = $client->get($url, $params);
215
+            $body = $response->getBody();
216
+
217
+            if ($latestLocation) {
218
+                $mutations['{http://calendarserver.org/ns/}source'] = new Href($latestLocation);
219
+            }
220
+
221
+            $contentType = $response->getHeader('Content-Type');
222
+            $contentType = explode(';', $contentType, 2)[0];
223
+            switch ($contentType) {
224
+                case 'application/calendar+json':
225
+                    try {
226
+                        $jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING);
227
+                    } catch (Exception $ex) {
228
+                        // In case of a parsing error return null
229
+                        $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
230
+                        return null;
231
+                    }
232
+                    return $jCalendar->serialize();
233
+
234
+                case 'application/calendar+xml':
235
+                    try {
236
+                        $xCalendar = Reader::readXML($body);
237
+                    } catch (Exception $ex) {
238
+                        // In case of a parsing error return null
239
+                        $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
240
+                        return null;
241
+                    }
242
+                    return $xCalendar->serialize();
243
+
244
+                case 'text/calendar':
245
+                default:
246
+                    try {
247
+                        $vCalendar = Reader::read($body);
248
+                    } catch (Exception $ex) {
249
+                        // In case of a parsing error return null
250
+                        $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
251
+                        return null;
252
+                    }
253
+                    return $vCalendar->serialize();
254
+            }
255
+        } catch (LocalServerException $ex) {
256
+            $this->logger->warning("Subscription $subscriptionId was not refreshed because it violates local access rules", [
257
+                'exception' => $ex,
258
+            ]);
259
+
260
+            return null;
261
+        } catch (Exception $ex) {
262
+            $this->logger->warning("Subscription $subscriptionId could not be refreshed due to a network error", [
263
+                'exception' => $ex,
264
+            ]);
265
+
266
+            return null;
267
+        }
268
+    }
269
+
270
+    /**
271
+     * check if:
272
+     *  - current subscription stores a refreshrate
273
+     *  - the webcal feed suggests a refreshrate
274
+     *  - return suggested refreshrate if user didn't set a custom one
275
+     *
276
+     */
277
+    private function checkWebcalDataForRefreshRate(array $subscription, string $webcalData): ?string {
278
+        // if there is no refreshrate stored in the database, check the webcal feed
279
+        // whether it suggests any refresh rate and store that in the database
280
+        if (isset($subscription[self::REFRESH_RATE]) && $subscription[self::REFRESH_RATE] !== null) {
281
+            return null;
282
+        }
283
+
284
+        /** @var Component\VCalendar $vCalendar */
285
+        $vCalendar = Reader::read($webcalData);
286
+
287
+        $newRefreshRate = null;
288
+        if (isset($vCalendar->{'X-PUBLISHED-TTL'})) {
289
+            $newRefreshRate = $vCalendar->{'X-PUBLISHED-TTL'}->getValue();
290
+        }
291
+        if (isset($vCalendar->{'REFRESH-INTERVAL'})) {
292
+            $newRefreshRate = $vCalendar->{'REFRESH-INTERVAL'}->getValue();
293
+        }
294
+
295
+        if (!$newRefreshRate) {
296
+            return null;
297
+        }
298
+
299
+        // check if new refresh rate is even valid
300
+        try {
301
+            DateTimeParser::parseDuration($newRefreshRate);
302
+        } catch (InvalidDataException $ex) {
303
+            return null;
304
+        }
305
+
306
+        return $newRefreshRate;
307
+    }
308
+
309
+    /**
310
+     * update subscription stored in database
311
+     * used to set:
312
+     *  - refreshrate
313
+     *  - source
314
+     *
315
+     * @param array $subscription
316
+     * @param array $mutations
317
+     */
318
+    private function updateSubscription(array $subscription, array $mutations) {
319
+        if (empty($mutations)) {
320
+            return;
321
+        }
322
+
323
+        $propPatch = new PropPatch($mutations);
324
+        $this->calDavBackend->updateSubscription($subscription['id'], $propPatch);
325
+        $propPatch->commit();
326
+    }
327
+
328
+    /**
329
+     * This method will strip authentication information and replace the
330
+     * 'webcal' or 'webcals' protocol scheme
331
+     *
332
+     * @param string $url
333
+     * @return string|null
334
+     */
335
+    private function cleanURL(string $url): ?string {
336
+        $parsed = parse_url($url);
337
+        if ($parsed === false) {
338
+            return null;
339
+        }
340
+
341
+        if (isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
342
+            $scheme = 'http';
343
+        } else {
344
+            $scheme = 'https';
345
+        }
346
+
347
+        $host = $parsed['host'] ?? '';
348
+        $port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
349
+        $path = $parsed['path'] ?? '';
350
+        $query = isset($parsed['query']) ? '?' . $parsed['query'] : '';
351
+        $fragment = isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
352
+
353
+        $cleanURL = "$scheme://$host$port$path$query$fragment";
354
+        // parse_url is giving some weird results if no url and no :// is given,
355
+        // so let's test the url again
356
+        $parsedClean = parse_url($cleanURL);
357
+        if ($parsedClean === false || !isset($parsedClean['host'])) {
358
+            return null;
359
+        }
360
+
361
+        return $cleanURL;
362
+    }
363
+
364
+    /**
365
+     * Returns a random uri for a calendar-object
366
+     *
367
+     * @return string
368
+     */
369
+    public function getRandomCalendarObjectUri():string {
370
+        return UUIDUtil::getUUID() . '.ics';
371
+    }
372 372
 }
Please login to merge, or discard this patch.