From a2925ac6d1bd41f61941887dfc1ff81dc304e148 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Tollk=C3=BChn?=
Date: Wed, 7 Oct 2026 22:20:07 +0200
Subject: [PATCH] - mergeUrlIntoDescription - friendly error if no Personal
calendar - use users timezone if possible
---
lib/AppInfo/Application.php | 3 +-
lib/Controller/CalendarController.php | 95 +++++++++++++++++++--------
templates/confirm.php | 6 ++
templates/style.css | 8 +++
4 files changed, 85 insertions(+), 27 deletions(-)
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 764af8c..6d892d7 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -26,7 +26,8 @@ class Application extends App
$c->get('Request'),
$c->get(\OCP\Calendar\IManager::class),
$c->get(\OCP\IUserSession::class),
- $c->get(\OCP\IURLGenerator::class)
+ $c->get(\OCP\IURLGenerator::class),
+ $c->get(\OCP\IDateTimeZone::class)
);
}
);
diff --git a/lib/Controller/CalendarController.php b/lib/Controller/CalendarController.php
index 168e9e1..7bfd6af 100644
--- a/lib/Controller/CalendarController.php
+++ b/lib/Controller/CalendarController.php
@@ -13,6 +13,7 @@ use OCP\Calendar\IManager;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
+use OCP\IDateTimeZone;
class CalendarController extends Controller
{
@@ -30,19 +31,22 @@ class CalendarController extends Controller
private IManager $calendarManager;
private IUserSession $userSession;
private IURLGenerator $urlGenerator;
+ private IDateTimeZone $dateTimeZone;
public function __construct(
string $appName,
IRequest $request,
IManager $calendarManager,
IUserSession $userSession,
- IURLGenerator $urlGenerator
+ IURLGenerator $urlGenerator,
+ IDateTimeZone $dateTimeZone
) {
parent::__construct($appName, $request);
$this->calendarManager = $calendarManager;
$this->userSession = $userSession;
$this->urlGenerator = $urlGenerator;
+ $this->dateTimeZone = $dateTimeZone;
}
/**
@@ -71,22 +75,7 @@ class CalendarController extends Controller
$url
);
- return new TemplateResponse(
- Application::APP_ID,
- 'confirm',
- [
- 'summary' => $data['summary'],
- 'description' => $data['description'],
- 'start' => $data['start']->format(self::DATETIME_LOCAL),
- 'end' => $data['end']->format(self::DATETIME_LOCAL),
- 'organizer' => $data['organizer'],
- 'location' => $data['location'],
- 'url' => $data['url'],
- 'createUrl' => $this->urlGenerator->linkToRoute(
- Application::APP_ID . '.calendar.create'
- ),
- ]
- );
+ return $this->renderConfirm($data);
}
/**
@@ -100,7 +89,7 @@ class CalendarController extends Controller
string $organizer = '',
string $location = '',
string $url = ''
- ): RedirectResponse {
+ ): RedirectResponse|TemplateResponse {
$data = $this->validateEventData(
$summary,
$description,
@@ -119,11 +108,16 @@ class CalendarController extends Controller
$calendar = $this->findWritableCalendar($user->getUID());
+ /*
+ * Friendly error instead of a hard server error page.
+ */
if ($calendar === null) {
- throw new \RuntimeException(
+ return $this->renderConfirm(
+ $data,
'No writable calendar named "'
. self::CALENDAR_NAME
- . '" is available for this user.'
+ . '" was found in your account. Create it in the '
+ . 'Nextcloud Calendar app, then try again.'
);
}
@@ -162,6 +156,30 @@ class CalendarController extends Controller
return null;
}
+ /**
+ * Render the confirmation page, optionally with an error message.
+ */
+ private function renderConfirm(array $data, string $error = ''): TemplateResponse
+ {
+ return new TemplateResponse(
+ Application::APP_ID,
+ 'confirm',
+ [
+ 'summary' => $data['summary'],
+ 'description' => $data['description'],
+ 'start' => $data['start']->format(self::DATETIME_LOCAL),
+ 'end' => $data['end']->format(self::DATETIME_LOCAL),
+ 'organizer' => $data['organizer'],
+ 'location' => $data['location'],
+ 'url' => $data['url'],
+ 'error' => $error,
+ 'createUrl' => $this->urlGenerator->linkToRoute(
+ Application::APP_ID . '.calendar.create'
+ ),
+ ]
+ );
+ }
+
/**
* Create the event through the platform event builder,
* which handles escaping and ICS serialization.
@@ -174,8 +192,10 @@ class CalendarController extends Controller
$builder->setEndDate($data['end']);
$builder->setSummary($data['summary']);
- if ($data['description'] !== '') {
- $builder->setDescription($data['description']);
+ $description = $this->mergeUrlIntoDescription($data);
+
+ if ($description !== '') {
+ $builder->setDescription($description);
}
if ($data['location'] !== '') {
@@ -189,6 +209,24 @@ class CalendarController extends Controller
$builder->createInCalendar($calendar);
}
+ /**
+ * The event builder has no URL property, so a URL that
+ * differs from the location is kept in the description.
+ */
+ private function mergeUrlIntoDescription(array $data): string
+ {
+ $urlOnly = $data['url'] !== ''
+ && $data['url'] !== $data['location'];
+
+ if (!$urlOnly) {
+ return $data['description'];
+ }
+
+ $prefix = $data['description'] === '' ? '' : $data['description'] . "\n\n";
+
+ return $prefix . $data['url'];
+ }
+
/**
* Validate and normalize all event input.
*/
@@ -280,20 +318,25 @@ class CalendarController extends Controller
/**
* Parse datetime-local values.
*
- * datetime-local has no timezone information, so we intentionally
- * interpret the value in the Nextcloud server's configured timezone.
+ * datetime-local carries no timezone, so it is interpreted
+ * in the user's Nextcloud timezone. It falls back to the
+ * server default timezone if the user has none set.
*/
private function parseLocalDateTime(string $value): \DateTimeImmutable
{
+ $timeZone = $this->dateTimeZone->getTimeZone();
+
$date = \DateTimeImmutable::createFromFormat(
self::DATETIME_LOCAL,
- $value
+ $value,
+ $timeZone
);
if ($date === false) {
$date = \DateTimeImmutable::createFromFormat(
'Y-m-d\TH:i:s',
- $value
+ $value,
+ $timeZone
);
}
diff --git a/templates/confirm.php b/templates/confirm.php
index 9af7445..ffaa1a4 100644
--- a/templates/confirm.php
+++ b/templates/confirm.php
@@ -25,6 +25,12 @@ $h = static function (string $value): string {
Nextcloud Calendar.
+
+
+ = $h($_['error']) ?>
+
+
+
diff --git a/templates/style.css b/templates/style.css
index afa02e4..4139872 100644
--- a/templates/style.css
+++ b/templates/style.css
@@ -22,6 +22,14 @@
margin-bottom: 28px;
}
+.jitsi-calendar-error {
+ background: var(--color-error);
+ color: var(--color-error-text);
+ border-radius: 6px;
+ padding: 12px 16px;
+ margin-bottom: 28px;
+}
+
.jitsi-calendar-details {
border-top: 1px solid var(--color-border);
border-bottom: 1px solid var(--color-border);