aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/settings/lib/Controller/LogSettingsController.php18
-rw-r--r--apps/settings/openapi-administration.json5
-rw-r--r--apps/settings/openapi-full.json5
-rw-r--r--lib/composer/composer/autoload_classmap.php2
-rw-r--r--lib/composer/composer/autoload_static.php2
-rw-r--r--lib/private/AppFramework/OCS/BaseResponse.php4
-rw-r--r--lib/private/AppFramework/OCS/V1Response.php6
-rw-r--r--lib/private/AppFramework/OCS/V2Response.php6
-rw-r--r--lib/private/Calendar/CalendarEventBuilder.php132
-rw-r--r--lib/private/Calendar/Manager.php30
-rw-r--r--lib/public/AppFramework/Http/DataDisplayResponse.php4
-rw-r--r--lib/public/AppFramework/Http/DataDownloadResponse.php4
-rw-r--r--lib/public/AppFramework/Http/DataResponse.php4
-rw-r--r--lib/public/AppFramework/Http/DownloadResponse.php4
-rw-r--r--lib/public/AppFramework/Http/FileDisplayResponse.php4
-rw-r--r--lib/public/AppFramework/Http/JSONResponse.php4
-rw-r--r--lib/public/AppFramework/Http/NotFoundResponse.php4
-rw-r--r--lib/public/AppFramework/Http/RedirectResponse.php4
-rw-r--r--lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php4
-rw-r--r--lib/public/AppFramework/Http/Response.php2
-rw-r--r--lib/public/AppFramework/Http/StandaloneTemplateResponse.php6
-rw-r--r--lib/public/AppFramework/Http/StreamResponse.php4
-rw-r--r--lib/public/AppFramework/Http/Template/PublicTemplateResponse.php4
-rw-r--r--lib/public/AppFramework/Http/TemplateResponse.php4
-rw-r--r--lib/public/AppFramework/Http/TextPlainResponse.php4
-rw-r--r--lib/public/AppFramework/Http/TooManyRequestsResponse.php4
-rw-r--r--lib/public/AppFramework/Http/ZipResponse.php4
-rw-r--r--lib/public/Calendar/ICalendarEventBuilder.php110
-rw-r--r--lib/public/Calendar/IManager.php8
-rw-r--r--tests/data/ics/event-builder-complete.ics16
-rw-r--r--tests/data/ics/event-builder-complete.ics.license2
-rw-r--r--tests/data/ics/event-builder-without-attendees.ics13
-rw-r--r--tests/data/ics/event-builder-without-attendees.ics.license2
-rw-r--r--tests/lib/Calendar/CalendarEventBuilderTest.php146
-rw-r--r--tests/lib/Calendar/ManagerTest.php53
35 files changed, 549 insertions, 79 deletions
diff --git a/apps/settings/lib/Controller/LogSettingsController.php b/apps/settings/lib/Controller/LogSettingsController.php
index aa5ac9b2cc9..7cf8d631c8e 100644
--- a/apps/settings/lib/Controller/LogSettingsController.php
+++ b/apps/settings/lib/Controller/LogSettingsController.php
@@ -27,9 +27,7 @@ class LogSettingsController extends Controller {
/**
* download logfile
*
- * @psalm-suppress MoreSpecificReturnType The value of Content-Disposition is not relevant
- * @psalm-suppress LessSpecificReturnStatement The value of Content-Disposition is not relevant
- * @return StreamResponse<Http::STATUS_OK, array{Content-Type: 'application/octet-stream', 'Content-Disposition': string}>
+ * @return StreamResponse<Http::STATUS_OK, array{Content-Type: 'application/octet-stream', 'Content-Disposition': 'attachment; filename="nextcloud.log"'}>
*
* 200: Logfile returned
*/
@@ -38,11 +36,13 @@ class LogSettingsController extends Controller {
if (!$this->log instanceof Log) {
throw new \UnexpectedValueException('Log file not available');
}
- $resp = new StreamResponse($this->log->getLogPath());
- $resp->setHeaders([
- 'Content-Type' => 'application/octet-stream',
- 'Content-Disposition' => 'attachment; filename="nextcloud.log"',
- ]);
- return $resp;
+ return new StreamResponse(
+ $this->log->getLogPath(),
+ Http::STATUS_OK,
+ [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Disposition' => 'attachment; filename="nextcloud.log"',
+ ],
+ );
}
}
diff --git a/apps/settings/openapi-administration.json b/apps/settings/openapi-administration.json
index 090ef865371..1f6eb0bc652 100644
--- a/apps/settings/openapi-administration.json
+++ b/apps/settings/openapi-administration.json
@@ -44,7 +44,10 @@
"headers": {
"Content-Disposition": {
"schema": {
- "type": "string"
+ "type": "string",
+ "enum": [
+ "attachment; filename=\"nextcloud.log\""
+ ]
}
}
},
diff --git a/apps/settings/openapi-full.json b/apps/settings/openapi-full.json
index b5cbfda7096..e12598a2584 100644
--- a/apps/settings/openapi-full.json
+++ b/apps/settings/openapi-full.json
@@ -221,7 +221,10 @@
"headers": {
"Content-Disposition": {
"schema": {
- "type": "string"
+ "type": "string",
+ "enum": [
+ "attachment; filename=\"nextcloud.log\""
+ ]
}
}
},
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index fd5d9e62ba6..200e2e75612 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -192,6 +192,7 @@ return array(
'OCP\\Calendar\\BackendTemporarilyUnavailableException' => $baseDir . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php',
'OCP\\Calendar\\Exceptions\\CalendarException' => $baseDir . '/lib/public/Calendar/Exceptions/CalendarException.php',
'OCP\\Calendar\\ICalendar' => $baseDir . '/lib/public/Calendar/ICalendar.php',
+ 'OCP\\Calendar\\ICalendarEventBuilder' => $baseDir . '/lib/public/Calendar/ICalendarEventBuilder.php',
'OCP\\Calendar\\ICalendarIsShared' => $baseDir . '/lib/public/Calendar/ICalendarIsShared.php',
'OCP\\Calendar\\ICalendarIsWritable' => $baseDir . '/lib/public/Calendar/ICalendarIsWritable.php',
'OCP\\Calendar\\ICalendarProvider' => $baseDir . '/lib/public/Calendar/ICalendarProvider.php',
@@ -1116,6 +1117,7 @@ return array(
'OC\\Broadcast\\Events\\BroadcastEvent' => $baseDir . '/lib/private/Broadcast/Events/BroadcastEvent.php',
'OC\\Cache\\CappedMemoryCache' => $baseDir . '/lib/private/Cache/CappedMemoryCache.php',
'OC\\Cache\\File' => $baseDir . '/lib/private/Cache/File.php',
+ 'OC\\Calendar\\CalendarEventBuilder' => $baseDir . '/lib/private/Calendar/CalendarEventBuilder.php',
'OC\\Calendar\\CalendarQuery' => $baseDir . '/lib/private/Calendar/CalendarQuery.php',
'OC\\Calendar\\Manager' => $baseDir . '/lib/private/Calendar/Manager.php',
'OC\\Calendar\\Resource\\Manager' => $baseDir . '/lib/private/Calendar/Resource/Manager.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 092b03d7641..bf9385c1741 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -233,6 +233,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Calendar\\BackendTemporarilyUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php',
'OCP\\Calendar\\Exceptions\\CalendarException' => __DIR__ . '/../../..' . '/lib/public/Calendar/Exceptions/CalendarException.php',
'OCP\\Calendar\\ICalendar' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendar.php',
+ 'OCP\\Calendar\\ICalendarEventBuilder' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarEventBuilder.php',
'OCP\\Calendar\\ICalendarIsShared' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarIsShared.php',
'OCP\\Calendar\\ICalendarIsWritable' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarIsWritable.php',
'OCP\\Calendar\\ICalendarProvider' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarProvider.php',
@@ -1157,6 +1158,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Broadcast\\Events\\BroadcastEvent' => __DIR__ . '/../../..' . '/lib/private/Broadcast/Events/BroadcastEvent.php',
'OC\\Cache\\CappedMemoryCache' => __DIR__ . '/../../..' . '/lib/private/Cache/CappedMemoryCache.php',
'OC\\Cache\\File' => __DIR__ . '/../../..' . '/lib/private/Cache/File.php',
+ 'OC\\Calendar\\CalendarEventBuilder' => __DIR__ . '/../../..' . '/lib/private/Calendar/CalendarEventBuilder.php',
'OC\\Calendar\\CalendarQuery' => __DIR__ . '/../../..' . '/lib/private/Calendar/CalendarQuery.php',
'OC\\Calendar\\Manager' => __DIR__ . '/../../..' . '/lib/private/Calendar/Manager.php',
'OC\\Calendar\\Resource\\Manager' => __DIR__ . '/../../..' . '/lib/private/Calendar/Resource/Manager.php',
diff --git a/lib/private/AppFramework/OCS/BaseResponse.php b/lib/private/AppFramework/OCS/BaseResponse.php
index 3b0a28fe89c..cc7f7845760 100644
--- a/lib/private/AppFramework/OCS/BaseResponse.php
+++ b/lib/private/AppFramework/OCS/BaseResponse.php
@@ -11,10 +11,10 @@ use OCP\AppFramework\Http\Response;
/**
* @psalm-import-type DataResponseType from DataResponse
- * @template S of int
+ * @template S of Http::STATUS_*
* @template-covariant T of DataResponseType
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
abstract class BaseResponse extends Response {
/** @var array */
diff --git a/lib/private/AppFramework/OCS/V1Response.php b/lib/private/AppFramework/OCS/V1Response.php
index c56aa9cf478..131ca22ff24 100644
--- a/lib/private/AppFramework/OCS/V1Response.php
+++ b/lib/private/AppFramework/OCS/V1Response.php
@@ -11,17 +11,17 @@ use OCP\AppFramework\OCSController;
/**
* @psalm-import-type DataResponseType from DataResponse
- * @template S of int
+ * @template S of Http::STATUS_*
* @template-covariant T of DataResponseType
* @template H of array<string, mixed>
- * @template-extends BaseResponse<int, DataResponseType, array<string, mixed>>
+ * @template-extends BaseResponse<Http::STATUS_*, DataResponseType, array<string, mixed>>
*/
class V1Response extends BaseResponse {
/**
* The V1 endpoint has very limited http status codes basically everything
* is status 200 except 401
*
- * @return int
+ * @return Http::STATUS_*
*/
public function getStatus() {
$status = parent::getStatus();
diff --git a/lib/private/AppFramework/OCS/V2Response.php b/lib/private/AppFramework/OCS/V2Response.php
index caa8302a673..47cf0f60200 100644
--- a/lib/private/AppFramework/OCS/V2Response.php
+++ b/lib/private/AppFramework/OCS/V2Response.php
@@ -11,17 +11,17 @@ use OCP\AppFramework\OCSController;
/**
* @psalm-import-type DataResponseType from DataResponse
- * @template S of int
+ * @template S of Http::STATUS_*
* @template-covariant T of DataResponseType
* @template H of array<string, mixed>
- * @template-extends BaseResponse<int, DataResponseType, array<string, mixed>>
+ * @template-extends BaseResponse<Http::STATUS_*, DataResponseType, array<string, mixed>>
*/
class V2Response extends BaseResponse {
/**
* The V2 endpoint just passes on status codes.
* Of course we have to map the OCS specific codes to proper HTTP status codes
*
- * @return int
+ * @return Http::STATUS_*
*/
public function getStatus() {
$status = parent::getStatus();
diff --git a/lib/private/Calendar/CalendarEventBuilder.php b/lib/private/Calendar/CalendarEventBuilder.php
new file mode 100644
index 00000000000..9198d383ef9
--- /dev/null
+++ b/lib/private/Calendar/CalendarEventBuilder.php
@@ -0,0 +1,132 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Calendar;
+
+use DateTimeInterface;
+use InvalidArgumentException;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Calendar\ICalendarEventBuilder;
+use OCP\Calendar\ICreateFromString;
+use Sabre\VObject\Component\VCalendar;
+use Sabre\VObject\Component\VEvent;
+
+class CalendarEventBuilder implements ICalendarEventBuilder {
+ private ?DateTimeInterface $startDate = null;
+ private ?DateTimeInterface $endDate = null;
+ private ?string $summary = null;
+ private ?string $description = null;
+ private ?string $location = null;
+ private ?array $organizer = null;
+ private array $attendees = [];
+
+ public function __construct(
+ private readonly string $uid,
+ private readonly ITimeFactory $timeFactory,
+ ) {
+ }
+
+ public function setStartDate(DateTimeInterface $start): ICalendarEventBuilder {
+ $this->startDate = $start;
+ return $this;
+ }
+
+ public function setEndDate(DateTimeInterface $end): ICalendarEventBuilder {
+ $this->endDate = $end;
+ return $this;
+ }
+
+ public function setSummary(string $summary): ICalendarEventBuilder {
+ $this->summary = $summary;
+ return $this;
+ }
+
+ public function setDescription(string $description): ICalendarEventBuilder {
+ $this->description = $description;
+ return $this;
+ }
+
+ public function setLocation(string $location): ICalendarEventBuilder {
+ $this->location = $location;
+ return $this;
+ }
+
+ public function setOrganizer(string $email, ?string $commonName = null): ICalendarEventBuilder {
+ $this->organizer = [$email, $commonName];
+ return $this;
+ }
+
+ public function addAttendee(string $email, ?string $commonName = null): ICalendarEventBuilder {
+ $this->attendees[] = [$email, $commonName];
+ return $this;
+ }
+
+ public function toIcs(): string {
+ if ($this->startDate === null) {
+ throw new InvalidArgumentException('Event is missing a start date');
+ }
+
+ if ($this->endDate === null) {
+ throw new InvalidArgumentException('Event is missing an end date');
+ }
+
+ if ($this->summary === null) {
+ throw new InvalidArgumentException('Event is missing a summary');
+ }
+
+ if ($this->organizer === null && $this->attendees !== []) {
+ throw new InvalidArgumentException('Event has attendees but is missing an organizer');
+ }
+
+ $vcalendar = new VCalendar();
+ $props = [
+ 'UID' => $this->uid,
+ 'DTSTAMP' => $this->timeFactory->now(),
+ 'SUMMARY' => $this->summary,
+ 'DTSTART' => $this->startDate,
+ 'DTEND' => $this->endDate,
+ ];
+ if ($this->description !== null) {
+ $props['DESCRIPTION'] = $this->description;
+ }
+ if ($this->location !== null) {
+ $props['LOCATION'] = $this->location;
+ }
+ /** @var VEvent $vevent */
+ $vevent = $vcalendar->add('VEVENT', $props);
+ if ($this->organizer !== null) {
+ self::addAttendeeToVEvent($vevent, 'ORGANIZER', $this->organizer);
+ }
+ foreach ($this->attendees as $attendee) {
+ self::addAttendeeToVEvent($vevent, 'ATTENDEE', $attendee);
+ }
+ return $vcalendar->serialize();
+ }
+
+ public function createInCalendar(ICreateFromString $calendar): string {
+ $fileName = $this->uid . '.ics';
+ $calendar->createFromString($fileName, $this->toIcs());
+ return $fileName;
+ }
+
+ /**
+ * @param array{0: string, 1: ?string} $tuple A tuple of [$email, $commonName] where $commonName may be null.
+ */
+ private static function addAttendeeToVEvent(VEvent $vevent, string $name, array $tuple): void {
+ [$email, $cn] = $tuple;
+ if (!str_starts_with($email, 'mailto:')) {
+ $email = "mailto:$email";
+ }
+ $params = [];
+ if ($cn !== null) {
+ $params['CN'] = $cn;
+ }
+ $vevent->add($name, $email, $params);
+ }
+}
diff --git a/lib/private/Calendar/Manager.php b/lib/private/Calendar/Manager.php
index ba2124a5c23..3469193a364 100644
--- a/lib/private/Calendar/Manager.php
+++ b/lib/private/Calendar/Manager.php
@@ -12,6 +12,7 @@ use OC\AppFramework\Bootstrap\Coordinator;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Calendar\Exceptions\CalendarException;
use OCP\Calendar\ICalendar;
+use OCP\Calendar\ICalendarEventBuilder;
use OCP\Calendar\ICalendarIsShared;
use OCP\Calendar\ICalendarIsWritable;
use OCP\Calendar\ICalendarProvider;
@@ -19,6 +20,7 @@ use OCP\Calendar\ICalendarQuery;
use OCP\Calendar\ICreateFromString;
use OCP\Calendar\IHandleImipMessage;
use OCP\Calendar\IManager;
+use OCP\Security\ISecureRandom;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Sabre\VObject\Component\VCalendar;
@@ -45,6 +47,7 @@ class Manager implements IManager {
private ContainerInterface $container,
private LoggerInterface $logger,
private ITimeFactory $timeFactory,
+ private ISecureRandom $random,
) {
}
@@ -216,21 +219,21 @@ class Manager implements IManager {
string $recipient,
string $calendarData,
): bool {
-
+
$userCalendars = $this->getCalendarsForPrincipal($principalUri);
if (empty($userCalendars)) {
$this->logger->warning('iMip message could not be processed because user has no calendars');
return false;
}
-
+
/** @var VCalendar $vObject|null */
$calendarObject = Reader::read($calendarData);
-
+
if (!isset($calendarObject->METHOD) || $calendarObject->METHOD->getValue() !== 'REQUEST') {
$this->logger->warning('iMip message contains an incorrect or invalid method');
return false;
}
-
+
if (!isset($calendarObject->VEVENT)) {
$this->logger->warning('iMip message contains no event');
return false;
@@ -242,12 +245,12 @@ class Manager implements IManager {
$this->logger->warning('iMip message event dose not contains a UID');
return false;
}
-
+
if (!isset($eventObject->ATTENDEE)) {
$this->logger->warning('iMip message event dose not contains any attendees');
return false;
}
-
+
foreach ($eventObject->ATTENDEE as $entry) {
$address = trim(str_replace('mailto:', '', $entry->getValue()));
if ($address === $recipient) {
@@ -259,17 +262,17 @@ class Manager implements IManager {
$this->logger->warning('iMip message event does not contain a attendee that matches the recipient');
return false;
}
-
+
foreach ($userCalendars as $calendar) {
-
+
if (!$calendar instanceof ICalendarIsWritable && !$calendar instanceof ICalendarIsShared) {
continue;
}
-
+
if ($calendar->isDeleted() || !$calendar->isWritable() || $calendar->isShared()) {
continue;
}
-
+
if (!empty($calendar->search($recipient, ['ATTENDEE'], ['uid' => $eventObject->UID->getValue()]))) {
try {
if ($calendar instanceof IHandleImipMessage) {
@@ -282,7 +285,7 @@ class Manager implements IManager {
}
}
}
-
+
$this->logger->warning('iMip message event could not be processed because the no corresponding event was found in any calendar');
return false;
}
@@ -464,4 +467,9 @@ class Manager implements IManager {
return false;
}
}
+
+ public function createEventBuilder(): ICalendarEventBuilder {
+ $uid = $this->random->generate(32, ISecureRandom::CHAR_ALPHANUMERIC);
+ return new CalendarEventBuilder($uid, $this->timeFactory);
+ }
}
diff --git a/lib/public/AppFramework/Http/DataDisplayResponse.php b/lib/public/AppFramework/Http/DataDisplayResponse.php
index 889c57a7901..e1ded910328 100644
--- a/lib/public/AppFramework/Http/DataDisplayResponse.php
+++ b/lib/public/AppFramework/Http/DataDisplayResponse.php
@@ -13,9 +13,9 @@ use OCP\AppFramework\Http;
* Class DataDisplayResponse
*
* @since 8.1.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
class DataDisplayResponse extends Response {
/**
diff --git a/lib/public/AppFramework/Http/DataDownloadResponse.php b/lib/public/AppFramework/Http/DataDownloadResponse.php
index 80100137c48..ee6bcf0d0c5 100644
--- a/lib/public/AppFramework/Http/DataDownloadResponse.php
+++ b/lib/public/AppFramework/Http/DataDownloadResponse.php
@@ -13,10 +13,10 @@ use OCP\AppFramework\Http;
* Class DataDownloadResponse
*
* @since 8.0.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template C of string
* @template H of array<string, mixed>
- * @template-extends DownloadResponse<int, string, array<string, mixed>>
+ * @template-extends DownloadResponse<Http::STATUS_*, string, array<string, mixed>>
*/
class DataDownloadResponse extends DownloadResponse {
/**
diff --git a/lib/public/AppFramework/Http/DataResponse.php b/lib/public/AppFramework/Http/DataResponse.php
index 2ebb66f9e73..2b54ce848ef 100644
--- a/lib/public/AppFramework/Http/DataResponse.php
+++ b/lib/public/AppFramework/Http/DataResponse.php
@@ -14,10 +14,10 @@ use OCP\AppFramework\Http;
* for responders to transform
* @since 8.0.0
* @psalm-type DataResponseType = array|int|float|string|bool|object|null|\stdClass|\JsonSerializable
- * @template S of int
+ * @template S of Http::STATUS_*
* @template-covariant T of DataResponseType
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
class DataResponse extends Response {
/**
diff --git a/lib/public/AppFramework/Http/DownloadResponse.php b/lib/public/AppFramework/Http/DownloadResponse.php
index 058b3070297..190de022d36 100644
--- a/lib/public/AppFramework/Http/DownloadResponse.php
+++ b/lib/public/AppFramework/Http/DownloadResponse.php
@@ -12,10 +12,10 @@ use OCP\AppFramework\Http;
/**
* Prompts the user to download the a file
* @since 7.0.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template C of string
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
class DownloadResponse extends Response {
/**
diff --git a/lib/public/AppFramework/Http/FileDisplayResponse.php b/lib/public/AppFramework/Http/FileDisplayResponse.php
index 0cc51f7c59f..fda160eafc5 100644
--- a/lib/public/AppFramework/Http/FileDisplayResponse.php
+++ b/lib/public/AppFramework/Http/FileDisplayResponse.php
@@ -13,9 +13,9 @@ use OCP\Files\SimpleFS\ISimpleFile;
* Class FileDisplayResponse
*
* @since 11.0.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
class FileDisplayResponse extends Response implements ICallbackResponse {
/** @var File|ISimpleFile */
diff --git a/lib/public/AppFramework/Http/JSONResponse.php b/lib/public/AppFramework/Http/JSONResponse.php
index 1d2564afab0..efcf79d5e87 100644
--- a/lib/public/AppFramework/Http/JSONResponse.php
+++ b/lib/public/AppFramework/Http/JSONResponse.php
@@ -12,10 +12,10 @@ use OCP\AppFramework\Http;
/**
* A renderer for JSON calls
* @since 6.0.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template-covariant T of null|string|int|float|bool|array|\stdClass|\JsonSerializable
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
class JSONResponse extends Response {
/**
diff --git a/lib/public/AppFramework/Http/NotFoundResponse.php b/lib/public/AppFramework/Http/NotFoundResponse.php
index 9ebefe69be1..137d1a26655 100644
--- a/lib/public/AppFramework/Http/NotFoundResponse.php
+++ b/lib/public/AppFramework/Http/NotFoundResponse.php
@@ -12,9 +12,9 @@ use OCP\AppFramework\Http;
/**
* A generic 404 response showing an 404 error page as well to the end-user
* @since 8.1.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
- * @template-extends TemplateResponse<int, array<string, mixed>>
+ * @template-extends TemplateResponse<Http::STATUS_*, array<string, mixed>>
*/
class NotFoundResponse extends TemplateResponse {
/**
diff --git a/lib/public/AppFramework/Http/RedirectResponse.php b/lib/public/AppFramework/Http/RedirectResponse.php
index 41fc4d83856..74847205976 100644
--- a/lib/public/AppFramework/Http/RedirectResponse.php
+++ b/lib/public/AppFramework/Http/RedirectResponse.php
@@ -12,9 +12,9 @@ use OCP\AppFramework\Http;
/**
* Redirects to a different URL
* @since 7.0.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
class RedirectResponse extends Response {
private $redirectURL;
diff --git a/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php b/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php
index 3e2fcf6f6c7..1681b39ce50 100644
--- a/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php
+++ b/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php
@@ -16,9 +16,9 @@ use OCP\IURLGenerator;
*
* @since 16.0.0
* @deprecated 23.0.0 Use RedirectResponse() with IURLGenerator::linkToDefaultPageUrl() instead
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
- * @template-extends RedirectResponse<int, array<string, mixed>>
+ * @template-extends RedirectResponse<Http::STATUS_*, array<string, mixed>>
*/
class RedirectToDefaultAppResponse extends RedirectResponse {
/**
diff --git a/lib/public/AppFramework/Http/Response.php b/lib/public/AppFramework/Http/Response.php
index d1860402359..6fc3d4b98ea 100644
--- a/lib/public/AppFramework/Http/Response.php
+++ b/lib/public/AppFramework/Http/Response.php
@@ -18,7 +18,7 @@ use Psr\Log\LoggerInterface;
*
* It handles headers, HTTP status code, last modified and ETag.
* @since 6.0.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
*/
class Response {
diff --git a/lib/public/AppFramework/Http/StandaloneTemplateResponse.php b/lib/public/AppFramework/Http/StandaloneTemplateResponse.php
index f729bd772fb..244a6b80f9f 100644
--- a/lib/public/AppFramework/Http/StandaloneTemplateResponse.php
+++ b/lib/public/AppFramework/Http/StandaloneTemplateResponse.php
@@ -7,6 +7,8 @@ declare(strict_types=1);
*/
namespace OCP\AppFramework\Http;
+use OCP\AppFramework\Http;
+
/**
* A template response that does not emit the loadAdditionalScripts events.
*
@@ -14,9 +16,9 @@ namespace OCP\AppFramework\Http;
* full nextcloud UI. Like the 2FA page, or the grant page in the login flow.
*
* @since 16.0.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
- * @template-extends TemplateResponse<int, array<string, mixed>>
+ * @template-extends TemplateResponse<Http::STATUS_*, array<string, mixed>>
*/
class StandaloneTemplateResponse extends TemplateResponse {
}
diff --git a/lib/public/AppFramework/Http/StreamResponse.php b/lib/public/AppFramework/Http/StreamResponse.php
index 1039e20e5c5..d0e6e3e148a 100644
--- a/lib/public/AppFramework/Http/StreamResponse.php
+++ b/lib/public/AppFramework/Http/StreamResponse.php
@@ -13,9 +13,9 @@ use OCP\AppFramework\Http;
* Class StreamResponse
*
* @since 8.1.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
class StreamResponse extends Response implements ICallbackResponse {
/** @var string */
diff --git a/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php b/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php
index 1000f4db549..ef5d2f67f7e 100644
--- a/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php
+++ b/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php
@@ -15,8 +15,8 @@ use OCP\IInitialStateService;
*
* @since 14.0.0
* @template H of array<string, mixed>
- * @template S of int
- * @template-extends TemplateResponse<int, array<string, mixed>>
+ * @template S of Http::STATUS_*
+ * @template-extends TemplateResponse<Http::STATUS_*, array<string, mixed>>
*/
class PublicTemplateResponse extends TemplateResponse {
private $headerTitle = '';
diff --git a/lib/public/AppFramework/Http/TemplateResponse.php b/lib/public/AppFramework/Http/TemplateResponse.php
index 2c7567c080b..55b9f2b06af 100644
--- a/lib/public/AppFramework/Http/TemplateResponse.php
+++ b/lib/public/AppFramework/Http/TemplateResponse.php
@@ -13,9 +13,9 @@ use OCP\AppFramework\Http;
* Response for a normal template
* @since 6.0.0
*
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
class TemplateResponse extends Response {
/**
diff --git a/lib/public/AppFramework/Http/TextPlainResponse.php b/lib/public/AppFramework/Http/TextPlainResponse.php
index e7c728c37ab..9dfa2c5544d 100644
--- a/lib/public/AppFramework/Http/TextPlainResponse.php
+++ b/lib/public/AppFramework/Http/TextPlainResponse.php
@@ -12,9 +12,9 @@ use OCP\AppFramework\Http;
/**
* A renderer for text responses
* @since 22.0.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
class TextPlainResponse extends Response {
/** @var string */
diff --git a/lib/public/AppFramework/Http/TooManyRequestsResponse.php b/lib/public/AppFramework/Http/TooManyRequestsResponse.php
index b7b0a98c9e1..6b2ef5b1b90 100644
--- a/lib/public/AppFramework/Http/TooManyRequestsResponse.php
+++ b/lib/public/AppFramework/Http/TooManyRequestsResponse.php
@@ -13,9 +13,9 @@ use OCP\Template;
/**
* A generic 429 response showing an 404 error page as well to the end-user
* @since 19.0.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
class TooManyRequestsResponse extends Response {
/**
diff --git a/lib/public/AppFramework/Http/ZipResponse.php b/lib/public/AppFramework/Http/ZipResponse.php
index 3b9e251d332..a552eb1294f 100644
--- a/lib/public/AppFramework/Http/ZipResponse.php
+++ b/lib/public/AppFramework/Http/ZipResponse.php
@@ -15,9 +15,9 @@ use OCP\IRequest;
* Public library to send several files in one zip archive.
*
* @since 15.0.0
- * @template S of int
+ * @template S of Http::STATUS_*
* @template H of array<string, mixed>
- * @template-extends Response<int, array<string, mixed>>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
*/
class ZipResponse extends Response implements ICallbackResponse {
/** @var array{internalName: string, resource: resource, size: int, time: int}[] Files to be added to the zip response */
diff --git a/lib/public/Calendar/ICalendarEventBuilder.php b/lib/public/Calendar/ICalendarEventBuilder.php
new file mode 100644
index 00000000000..8afc817a61e
--- /dev/null
+++ b/lib/public/Calendar/ICalendarEventBuilder.php
@@ -0,0 +1,110 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\Calendar;
+
+use DateTimeInterface;
+use InvalidArgumentException;
+use OCP\Calendar\Exceptions\CalendarException;
+
+/**
+ * The calendar event builder can be used to conveniently build a calendar event and then serialize
+ * it to a ICS string. The ICS string can be submitted to calendar instances implementing the
+ * {@see \OCP\Calendar\ICreateFromString} interface.
+ *
+ * Also note this class can not be injected directly with dependency injection.
+ * Instead, inject {@see \OCP\Calendar\IManager} and use
+ * {@see \OCP\Calendar\IManager::createEventBuilder()} afterwards.
+ *
+ * All setters return self to allow chaining method calls.
+ *
+ * @since 31.0.0
+ */
+interface ICalendarEventBuilder {
+ /**
+ * Set the start date, time and time zone.
+ * This property is required!
+ *
+ * @since 31.0.0
+ */
+ public function setStartDate(DateTimeInterface $start): self;
+
+ /**
+ * Set the end date, time and time zone.
+ * This property is required!
+ *
+ * @since 31.0.0
+ */
+ public function setEndDate(DateTimeInterface $end): self;
+
+ /**
+ * Set the event summary or title.
+ * This property is required!
+ *
+ * @since 31.0.0
+ */
+ public function setSummary(string $summary): self;
+
+ /**
+ * Set the event description.
+ *
+ * @since 31.0.0
+ */
+ public function setDescription(string $description): self;
+
+ /**
+ * Set the event location. It can either be a physical address or a URL.
+ *
+ * @since 31.0.0
+ */
+ public function setLocation(string $location): self;
+
+ /**
+ * Set the event organizer.
+ * This property is required if attendees are added!
+ *
+ * The "mailto:" prefix is optional and will be added automatically if it is missing.
+ *
+ * @since 31.0.0
+ */
+ public function setOrganizer(string $email, ?string $commonName = null): self;
+
+ /**
+ * Add a new attendee to the event.
+ * Adding at least one attendee requires also setting the organizer!
+ *
+ * The "mailto:" prefix is optional and will be added automatically if it is missing.
+ *
+ * @since 31.0.0
+ */
+ public function addAttendee(string $email, ?string $commonName = null): self;
+
+ /**
+ * Serialize the built event to an ICS string if all required properties set.
+ *
+ * @since 31.0.0
+ *
+ * @return string The serialized ICS string
+ *
+ * @throws InvalidArgumentException If required properties were not set
+ */
+ public function toIcs(): string;
+
+ /**
+ * Create the event in the given calendar.
+ *
+ * @since 31.0.0
+ *
+ * @return string The filename of the created event
+ *
+ * @throws InvalidArgumentException If required properties were not set
+ * @throws CalendarException If writing the event to the calendar fails
+ */
+ public function createInCalendar(ICreateFromString $calendar): string;
+}
diff --git a/lib/public/Calendar/IManager.php b/lib/public/Calendar/IManager.php
index bb3808f133c..8056d57d859 100644
--- a/lib/public/Calendar/IManager.php
+++ b/lib/public/Calendar/IManager.php
@@ -157,4 +157,12 @@ interface IManager {
* @since 25.0.0
*/
public function handleIMipCancel(string $principalUri, string $sender, ?string $replyTo, string $recipient, string $calendarData): bool;
+
+ /**
+ * Create a new event builder instance. Please have a look at its documentation and the
+ * \OCP\Calendar\ICreateFromString interface on how to use it.
+ *
+ * @since 31.0.0
+ */
+ public function createEventBuilder(): ICalendarEventBuilder;
}
diff --git a/tests/data/ics/event-builder-complete.ics b/tests/data/ics/event-builder-complete.ics
new file mode 100644
index 00000000000..d96a3137356
--- /dev/null
+++ b/tests/data/ics/event-builder-complete.ics
@@ -0,0 +1,16 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Sabre//Sabre VObject 4.5.6//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:event-uid-123
+DTSTAMP:20250105T000000Z
+SUMMARY:My event
+DTSTART:20250105T170958Z
+DTEND:20250105T171958Z
+DESCRIPTION:Foo bar baz
+ORGANIZER:mailto:organizer@domain.tld
+ATTENDEE:mailto:attendee1@domain.tld
+ATTENDEE:mailto:attendee2@domain.tld
+END:VEVENT
+END:VCALENDAR
diff --git a/tests/data/ics/event-builder-complete.ics.license b/tests/data/ics/event-builder-complete.ics.license
new file mode 100644
index 00000000000..f7f52efa96f
--- /dev/null
+++ b/tests/data/ics/event-builder-complete.ics.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/tests/data/ics/event-builder-without-attendees.ics b/tests/data/ics/event-builder-without-attendees.ics
new file mode 100644
index 00000000000..95c4c44fa1f
--- /dev/null
+++ b/tests/data/ics/event-builder-without-attendees.ics
@@ -0,0 +1,13 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Sabre//Sabre VObject 4.5.6//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:event-uid-123
+DTSTAMP:20250105T000000Z
+SUMMARY:My event
+DTSTART:20250105T170958Z
+DTEND:20250105T171958Z
+DESCRIPTION:Foo bar baz
+END:VEVENT
+END:VCALENDAR
diff --git a/tests/data/ics/event-builder-without-attendees.ics.license b/tests/data/ics/event-builder-without-attendees.ics.license
new file mode 100644
index 00000000000..f7f52efa96f
--- /dev/null
+++ b/tests/data/ics/event-builder-without-attendees.ics.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/tests/lib/Calendar/CalendarEventBuilderTest.php b/tests/lib/Calendar/CalendarEventBuilderTest.php
new file mode 100644
index 00000000000..48684d56093
--- /dev/null
+++ b/tests/lib/Calendar/CalendarEventBuilderTest.php
@@ -0,0 +1,146 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Calendar;
+
+use DateTimeImmutable;
+use InvalidArgumentException;
+use OC\Calendar\CalendarEventBuilder;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Calendar\ICreateFromString;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class CalendarEventBuilderTest extends TestCase {
+ private CalendarEventBuilder $calendarEventBuilder;
+ private ITimeFactory&MockObject $timeFactory;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->timeFactory->method('now')
+ ->willReturn(new DateTimeImmutable('20250105T000000Z'));
+
+ $this->calendarEventBuilder = new CalendarEventBuilder(
+ 'event-uid-123',
+ $this->timeFactory,
+ );
+ }
+
+ public function testToIcs(): void {
+ $this->calendarEventBuilder->setStartDate(new DateTimeImmutable('2025-01-05T17:09:58Z'));
+ $this->calendarEventBuilder->setEndDate(new DateTimeImmutable('2025-01-05T17:19:58Z'));
+ $this->calendarEventBuilder->setSummary('My event');
+ $this->calendarEventBuilder->setDescription('Foo bar baz');
+ $this->calendarEventBuilder->setOrganizer('mailto:organizer@domain.tld');
+ $this->calendarEventBuilder->addAttendee('mailto:attendee1@domain.tld');
+ $this->calendarEventBuilder->addAttendee('mailto:attendee2@domain.tld');
+
+ $expected = file_get_contents(\OC::$SERVERROOT . '/tests/data/ics/event-builder-complete.ics');
+ $actual = $this->calendarEventBuilder->toIcs();
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testToIcsWithoutOrganizerAndAttendees(): void {
+ $this->calendarEventBuilder->setStartDate(new DateTimeImmutable('2025-01-05T17:09:58Z'));
+ $this->calendarEventBuilder->setEndDate(new DateTimeImmutable('2025-01-05T17:19:58Z'));
+ $this->calendarEventBuilder->setSummary('My event');
+ $this->calendarEventBuilder->setDescription('Foo bar baz');
+
+ $expected = file_get_contents(\OC::$SERVERROOT . '/tests/data/ics/event-builder-without-attendees.ics');
+ $actual = $this->calendarEventBuilder->toIcs();
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testToIcsWithoutMailtoPrefix(): void {
+ $this->calendarEventBuilder->setStartDate(new DateTimeImmutable('2025-01-05T17:09:58Z'));
+ $this->calendarEventBuilder->setEndDate(new DateTimeImmutable('2025-01-05T17:19:58Z'));
+ $this->calendarEventBuilder->setSummary('My event');
+ $this->calendarEventBuilder->setDescription('Foo bar baz');
+ $this->calendarEventBuilder->setOrganizer('organizer@domain.tld');
+ $this->calendarEventBuilder->addAttendee('attendee1@domain.tld');
+ $this->calendarEventBuilder->addAttendee('attendee2@domain.tld');
+
+ $expected = file_get_contents(\OC::$SERVERROOT . '/tests/data/ics/event-builder-complete.ics');
+ $actual = $this->calendarEventBuilder->toIcs();
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testCreateInCalendar(): void {
+ $this->calendarEventBuilder->setStartDate(new DateTimeImmutable('2025-01-05T17:09:58Z'));
+ $this->calendarEventBuilder->setEndDate(new DateTimeImmutable('2025-01-05T17:19:58Z'));
+ $this->calendarEventBuilder->setSummary('My event');
+ $this->calendarEventBuilder->setDescription('Foo bar baz');
+ $this->calendarEventBuilder->setOrganizer('organizer@domain.tld');
+ $this->calendarEventBuilder->addAttendee('attendee1@domain.tld');
+ $this->calendarEventBuilder->addAttendee('mailto:attendee2@domain.tld');
+
+ $expectedIcs = file_get_contents(\OC::$SERVERROOT . '/tests/data/ics/event-builder-complete.ics');
+ $calendar = $this->createMock(ICreateFromString::class);
+ $calendar->expects(self::once())
+ ->method('createFromString')
+ ->with('event-uid-123.ics', $expectedIcs);
+
+ $actual = $this->calendarEventBuilder->createInCalendar($calendar);
+ $this->assertEquals('event-uid-123.ics', $actual);
+ }
+
+ public function testToIcsWithoutStartDate(): void {
+ $this->calendarEventBuilder->setEndDate(new DateTimeImmutable('2025-01-05T17:19:58Z'));
+ $this->calendarEventBuilder->setSummary('My event');
+ $this->calendarEventBuilder->setDescription('Foo bar baz');
+ $this->calendarEventBuilder->setOrganizer('organizer@domain.tld');
+ $this->calendarEventBuilder->addAttendee('attendee1@domain.tld');
+ $this->calendarEventBuilder->addAttendee('mailto:attendee2@domain.tld');
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessageMatches('/start date/i');
+ $this->calendarEventBuilder->toIcs();
+ }
+
+ public function testToIcsWithoutEndDate(): void {
+ $this->calendarEventBuilder->setStartDate(new DateTimeImmutable('2025-01-05T17:09:58Z'));
+ $this->calendarEventBuilder->setSummary('My event');
+ $this->calendarEventBuilder->setDescription('Foo bar baz');
+ $this->calendarEventBuilder->setOrganizer('organizer@domain.tld');
+ $this->calendarEventBuilder->addAttendee('attendee1@domain.tld');
+ $this->calendarEventBuilder->addAttendee('mailto:attendee2@domain.tld');
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessageMatches('/end date/i');
+ $this->calendarEventBuilder->toIcs();
+ }
+
+ public function testToIcsWithoutSummary(): void {
+ $this->calendarEventBuilder->setStartDate(new DateTimeImmutable('2025-01-05T17:09:58Z'));
+ $this->calendarEventBuilder->setEndDate(new DateTimeImmutable('2025-01-05T17:19:58Z'));
+ $this->calendarEventBuilder->setDescription('Foo bar baz');
+ $this->calendarEventBuilder->setOrganizer('organizer@domain.tld');
+ $this->calendarEventBuilder->addAttendee('attendee1@domain.tld');
+ $this->calendarEventBuilder->addAttendee('mailto:attendee2@domain.tld');
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessageMatches('/summary/i');
+ $this->calendarEventBuilder->toIcs();
+ }
+
+ public function testToIcsWithoutOrganizerWithAttendees(): void {
+ $this->calendarEventBuilder->setStartDate(new DateTimeImmutable('2025-01-05T17:09:58Z'));
+ $this->calendarEventBuilder->setEndDate(new DateTimeImmutable('2025-01-05T17:19:58Z'));
+ $this->calendarEventBuilder->setSummary('My event');
+ $this->calendarEventBuilder->setDescription('Foo bar baz');
+ $this->calendarEventBuilder->addAttendee('attendee1@domain.tld');
+ $this->calendarEventBuilder->addAttendee('mailto:attendee2@domain.tld');
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessageMatches('/organizer/i');
+ $this->calendarEventBuilder->toIcs();
+ }
+}
diff --git a/tests/lib/Calendar/ManagerTest.php b/tests/lib/Calendar/ManagerTest.php
index f0ca278f352..a7aeed98046 100644
--- a/tests/lib/Calendar/ManagerTest.php
+++ b/tests/lib/Calendar/ManagerTest.php
@@ -14,6 +14,7 @@ use OCP\Calendar\ICalendarIsShared;
use OCP\Calendar\ICalendarIsWritable;
use OCP\Calendar\ICreateFromString;
use OCP\Calendar\IHandleImipMessage;
+use OCP\Security\ISecureRandom;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
@@ -44,6 +45,9 @@ class ManagerTest extends TestCase {
/** @var ITimeFactory&MockObject */
private $time;
+ /** @var ISecureRandom&MockObject */
+ private ISecureRandom $secureRandom;
+
private VCalendar $vCalendar1a;
protected function setUp(): void {
@@ -53,12 +57,14 @@ class ManagerTest extends TestCase {
$this->container = $this->createMock(ContainerInterface::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->time = $this->createMock(ITimeFactory::class);
+ $this->secureRandom = $this->createMock(ISecureRandom::class);
$this->manager = new Manager(
$this->coordinator,
$this->container,
$this->logger,
$this->time,
+ $this->secureRandom,
);
// construct calendar with a 1 hour event and same start/end time zones
@@ -260,7 +266,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->onlyMethods(['getCalendarsForPrincipal'])
->getMock();
@@ -291,7 +298,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->onlyMethods(['getCalendarsForPrincipal'])
->getMock();
@@ -321,7 +329,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->onlyMethods(['getCalendarsForPrincipal'])
->getMock();
@@ -352,7 +361,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->onlyMethods(['getCalendarsForPrincipal'])
->getMock();
@@ -384,7 +394,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->onlyMethods(['getCalendarsForPrincipal'])
->getMock();
@@ -416,7 +427,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->onlyMethods(['getCalendarsForPrincipal'])
->getMock();
@@ -448,7 +460,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->onlyMethods(['getCalendarsForPrincipal'])
->getMock();
@@ -491,7 +504,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->onlyMethods(['getCalendarsForPrincipal'])
->getMock();
@@ -534,7 +548,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->onlyMethods(['getCalendarsForPrincipal'])
->getMock();
@@ -612,7 +627,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->setMethods([
'getCalendarsForPrincipal'
@@ -643,7 +659,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->setMethods([
'getCalendarsForPrincipal'
@@ -680,7 +697,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->setMethods([
'getCalendarsForPrincipal'
@@ -767,7 +785,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->setMethods([
'getCalendarsForPrincipal'
@@ -800,7 +819,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->setMethods([
'getCalendarsForPrincipal'
@@ -837,7 +857,8 @@ class ManagerTest extends TestCase {
$this->coordinator,
$this->container,
$this->logger,
- $this->time
+ $this->time,
+ $this->secureRandom,
])
->setMethods([
'getCalendarsForPrincipal'
@@ -866,7 +887,7 @@ class ManagerTest extends TestCase {
$result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
$this->assertTrue($result);
}
-
+
private function getVCalendarReply(): Document {
$data = <<<EOF
BEGIN:VCALENDAR