[TikiWiki-commits] [Git][tikiwiki/tiki][master] [FIX][UX] Calendar: fix prefilled new-event date, save and calendar selector regressions

"Victor Emanouilov \(@kroky\) via TikiWiki-cvs" <[email protected]>
Newsgroups gmane.comp.cms.tiki.cvs
Message-ID <69ca1daddca34_3b18fb2897644@gitlab-sidekiq-low-urgency-cpu-bound-v2-5c9f55b86f-7zg9k.mail>

Victor Emanouilov pushed to branch master at Tiki Wiki CMS Groupware / Tiki


Commits:
ffb4d26a by MAGENE Sem Joel at 2026-03-30T06:45:18+00:00
[FIX][UX] Calendar: fix prefilled new-event date, save and calendar selector regressions
---
* [FIX][UX] Calendar: fix prefilled new-event date, save and calendar selector regressions

See merge request tikiwiki/tiki!9826

- - - - -


3 changed files:

- lib/core/Services/Calendar/Controller.php
- src/js/jquery-tiki/tiki-calendar.js
- templates/calendar/edit_item.tpl


Changes:

=====================================
lib/core/Services/Calendar/Controller.php
=====================================
@@ -413,25 +413,34 @@ class Services_Calendar_Controller extends Services_Calendar_BaseController
                 // set up default start and end
                 $dateNow->setTZbyID($displayTimezone);
                 if ($input->prefill_start->text()) {
-                    $prefillStart = $input->prefill_start->text();
-                    $prefillEnd = $input->prefill_end->text();
-
-                    $tikidate = new TikiDate();
-                    $tikidate->setTZbyID($displayTimezone);
-
-                    $tikidate->setDate($prefillStart, $displayTimezone);
-                    $start = $tikidate->getTime();
-                    if ($prefillEnd && strtotime($prefillEnd) !== false) {
-                        $tikidate->setDate($prefillEnd, $displayTimezone);
-                        $end = $tikidate->getTime();
-                        // subtract 1 sec to make it inclusive
-                        if (strlen($prefillEnd) <= 10 || strpos($prefillEnd, '00:00:00') !== false) {
-                            $end -= 1;
+                    $prefillStart = trim($input->prefill_start->text());
+                    $prefillEnd = trim((string) $input->prefill_end->text());
+                    $prefillTimezoneRequested = trim((string) $input->prefill_tz->text());
+                    $prefillTimezone = $this->resolvePrefillTimezone($prefillTimezoneRequested, $displayTimezone);
+
+                    $start = $this->parsePrefillDateTime($prefillStart, $prefillTimezone);
+                    if (! is_null($start) && $prefillEnd !== '') {
+                        $end = $this->parsePrefillDateTime($prefillEnd, $prefillTimezone);
+                        if (! is_null($end)) {
+                            // convert exclusive all-day selection end to inclusive end
+                            if ($this->isPrefillStartOfDay($prefillEnd, $prefillTimezone)) {
+                                $end -= 1;
+                            }
+                            if ($end <= $start) {
+                                $duration = 60 * 60;
+                                $end = $start + $duration;
+                            } else {
+                                $duration = $end - $start;
+                            }
+                        } else {
+                            $duration = 60 * 60;
+                            $end = $start + $duration;
                         }
-                        $duration = $end - $start;
-                    } else {
+                    } elseif (! is_null($start)) {
                         $duration = 60 * 60;
                         $end = $start + $duration;
+                    } else {
+                        [$start, $end, $duration] = $this->getDefaultStartEndDurationFromDateNow($dateNow, $displayTimezone);
                     }
                     if ($input->target_user->text()) {
                         if ($user) {
@@ -445,26 +454,12 @@ class Services_Calendar_Controller extends Services_Calendar_BaseController
                         ];
                     }
                 } else {
-                    $hour = $dateNow->date->format('H');
                     if ($input->offsetExists('todate')) {
                         // set the correct day clicked on
                         $dateNow->setTZbyID($displayTimezone);
                         $dateNow->setDate($input->todate->text(), $displayTimezone);
-                        $hour = $dateNow->date->format('H');
                     }
-                    $tz = date_default_timezone_get();
-                    date_default_timezone_set($displayTimezone);
-                    $start = mktime(
-                        $hour,
-                        $dateNow->date->format('i'),
-                        $dateNow->date->format('s'),
-                        $dateNow->date->format('m'),
-                        $dateNow->date->format('d'),
-                        $dateNow->date->format('Y')
-                    );
-                    date_default_timezone_set($tz);
-                    $duration = 60 * 60;
-                    $end = $start + $duration;
+                    [$start, $end, $duration] = $this->getDefaultStartEndDurationFromDateNow($dateNow, $displayTimezone);
                 }
 
                 $calitem = [
@@ -622,6 +617,8 @@ class Services_Calendar_Controller extends Services_Calendar_BaseController
             'displayTimezone'            => $displayTimezone,
             'timezones'                  => $timezones,
             'prefilled'                  => $input->prefill_start->text() ? true : false,
+            'requireParticipant'         => $input->target_user->text() !== '',
+            'hideCalendarSelector'       => $input->prefill_start->text() !== '' && $input->target_user->text() !== '',
             // related tracker items
             'trackerItems'              => ! empty($trackerItems) ? $trackerItems : [],
         ];
@@ -943,6 +940,89 @@ class Services_Calendar_Controller extends Services_Calendar_BaseController
         return $recurrence;
     }
 
+    /**
+     * Build default start/end in the display timezone from current date/time context.
+     *
+     * @return array{0:int,1:int,2:int}
+     */
+    private function getDefaultStartEndDurationFromDateNow(TikiDate $dateNow, string $displayTimezone): array
+    {
+        $hour = $dateNow->date->format('H');
+        $tz = date_default_timezone_get();
+        date_default_timezone_set($displayTimezone);
+        $start = mktime(
+            $hour,
+            $dateNow->date->format('i'),
+            $dateNow->date->format('s'),
+            $dateNow->date->format('m'),
+            $dateNow->date->format('d'),
+            $dateNow->date->format('Y')
+        );
+        date_default_timezone_set($tz);
+
+        $duration = 60 * 60;
+        $end = $start + $duration;
+
+        return [$start, $end, $duration];
+    }
+
+    /**
+     * Resolve prefill timezone from request, falling back to display timezone when missing or invalid.
+     */
+    private function resolvePrefillTimezone(string $requestedTimezone, string $displayTimezone): string
+    {
+        if ($requestedTimezone === '') {
+            return $displayTimezone;
+        }
+
+        try {
+            new DateTimeZone($requestedTimezone);
+            return $requestedTimezone;
+        } catch (\Exception $e) {
+            return $displayTimezone;
+        }
+    }
+
+    /**
+     * Parse prefilled date string from calendar click/drag.
+     * Accepts timezone-aware ISO strings and falls back to display timezone when no offset is present.
+     */
+    private function parsePrefillDateTime(string $value, string $displayTimezone): ?int
+    {
+        if ($value === '') {
+            return null;
+        }
+
+        try {
+            if (preg_match('/^-?\\d+$/', $value) === 1) {
+                return (int) $value;
+            }
+
+            $date = new DateTimeImmutable($value, new DateTimeZone($displayTimezone));
+            return $date->getTimestamp();
+        } catch (\Exception $e) {
+            return null;
+        }
+    }
+
+    /**
+     * Detects whether a prefill value points to local start-of-day.
+     */
+    private function isPrefillStartOfDay(string $value, string $displayTimezone): bool
+    {
+        try {
+            if (preg_match('/^-?\\d+$/', $value) === 1) {
+                $date = new DateTimeImmutable('@' . $value);
+                $date = $date->setTimezone(new DateTimeZone($displayTimezone));
+            } else {
+                $date = new DateTimeImmutable($value, new DateTimeZone($displayTimezone));
+            }
+            return $date->format('H:i:s') === '00:00:00';
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
     /**
      * Convert submitted start/end times to UTC
      */


=====================================
src/js/jquery-tiki/tiki-calendar.js
=====================================
@@ -16,20 +16,56 @@ $.fn.setupEventCalendar = function (
     this.each(function () {
         const calendarEl = document.getElementById(targetId);
         $(calendarEl).tikiModal(tr("Loading..."));
-        const openNewEventModal = (startStr, endStr = null) => {
+        const toTimezoneStableIso = (dateValue) => moment(dateValue).format("YYYY-MM-DD[T]HH:mm:ssZ");
+        const browserTimezone = (() => {
+            try {
+                return Intl.DateTimeFormat().resolvedOptions().timeZone || null;
+            } catch (e) {
+                return null;
+            }
+        })();
+        const toPrefillParamValue = (value) => {
+            if (value === null || typeof value === "undefined") {
+                return null;
+            }
+            if (typeof value === "string") {
+                return value;
+            }
+            if (value instanceof Date && !isNaN(value.getTime())) {
+                return toTimezoneStableIso(value);
+            }
+            return String(value);
+        };
+
+        const openNewEventModal = (startValue, endValue = null) => {
             if (isOpeningModal) return;
             const countCals = $(".filtercal ul li").length;
             if (countCals >= 1 || targetId != "calendar") {
                 isOpeningModal = true;
                 $(calendarEl).tikiModal(" "); // Use the container for the loading overlay
+                const prefillStart = toPrefillParamValue(startValue);
+                if (!prefillStart) {
+                    isOpeningModal = false;
+                    return;
+                }
+
                 const params = {
-                    prefill_start: startStr,
+                    prefill_start: prefillStart,
                     modal: 1,
                     return_url: returnUrl,
                 };
-                if (endStr) {
-                    params.prefill_end = endStr;
+
+                if (browserTimezone) {
+                    params.prefill_tz = browserTimezone;
+                }
+
+                if (endValue !== null && typeof endValue !== "undefined") {
+                    const prefillEnd = toPrefillParamValue(endValue);
+                    if (prefillEnd) {
+                        params.prefill_end = prefillEnd;
+                    }
                 }
+
                 $.openModal({
                     title: tr("New event"),
                     size: "modal-lg",
@@ -67,7 +103,7 @@ $.fn.setupEventCalendar = function (
             eventSources: [{ url: urlEventSource }],
             select: function (info) {
                 // Handle Drag Selection
-                openNewEventModal(info.startStr, info.endStr);
+                openNewEventModal(info.startStr ?? info.start, info.endStr ?? info.end);
             },
             slotMinTime: eventCalendarParams.minHourOfDay,
             slotMaxTime: eventCalendarParams.maxHourOfDay,
@@ -266,7 +302,7 @@ $.fn.setupEventCalendar = function (
                     calendarContainer[0].unselect();
                 } else {
                     // Handle Single Click
-                    openNewEventModal(info.dateStr);
+                    openNewEventModal(info.dateStr ?? info.date);
                 }
             },
             eventResize: function (info) {


=====================================
templates/calendar/edit_item.tpl
=====================================
@@ -32,7 +32,7 @@
             {if $prefs.calendar_addtogooglecal == 'y'}
                 {wikiplugin _name="addtogooglecal" calitemid=$calitemId}{/wikiplugin}
             {/if}
-            {if $prefilled}
+            {if $hideCalendarSelector}
             <input type="hidden" name="calitem[calendarId]" value="{$calitem.calendarId}">
             {else}
             <div class="mb-3 row">
@@ -442,7 +442,7 @@
         <div class="submit">
             <input type="hidden" id="act" name="act" value="">
             <input type="submit" class="btn btn-secondary cleanable-false" name="preview" value="{tr}Preview{/tr}" onclick="needToConfirm=false">
-            <input type="submit" class="btn btn-primary cleanable-false {if $prefilled}need-participant{/if}" name="saveitem" value="{tr}Save{/tr}" onclick="needToConfirm=false">
+            <input type="submit" class="btn btn-primary cleanable-false {if $requireParticipant}need-participant{/if}" name="saveitem" value="{tr}Save{/tr}" onclick="needToConfirm=false">
             {if $calitemId && ! $recurrence.id}
                 <input type="submit" name="delete" data-alt_controller="calendar" data-alt_action="delete_item"
                        class="btn btn-danger cleanable-false" onclick="needToConfirm=false;" data-bs-dismiss="modal" value="{tr}Delete event{/tr}">



View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/ffb4d26a4d1ba50a1e60c35650768ac1ac701292

-- 
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/ffb4d26a4d1ba50a1e60c35650768ac1ac701292
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help

_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.