[TikiWiki-commits] [Git][tikiwiki/tiki][master] [FIX][ENH][UX] Calendar: 'Copy to new event' workflow

"Victor Emanouilov \(@kroky\) via TikiWiki-cvs" <[email protected]>
Newsgroups gmane.comp.cms.tiki.cvs
Message-ID <69aeeee218b38_3b1892f04585c@gitlab-sidekiq-low-urgency-cpu-bound-v2-74685b5685-vxj9q.mail>

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


Commits:
08bd0c0a by MAGENE Sem Joel at 2026-03-09T15:21:48+00:00
[FIX][ENH][UX] Calendar: 'Copy to new event' workflow
---
* [FIX][ENH][UX] Calendar: 'Copy to new event' workflow

See merge request tikiwiki/tiki!9667

- - - - -


6 changed files:

- doc/devtools/codesniffer/standards/TikiIgnore/ignore_list.json
- lib/calendar/calendarlib.php
- lib/core/Services/Calendar/Controller.php
- templates/calendar/edit_item.tpl
- templates/calendar/view_item.tpl
- themes/base_files/scss/_tiki-bootstrap_overrides.scss


Changes:

=====================================
doc/devtools/codesniffer/standards/TikiIgnore/ignore_list.json
=====================================
@@ -5580,7 +5580,6 @@
             "Services_Calendar_Controller::action_del_me": true,
             "Services_Calendar_Controller::action_list_items": true,
             "Services_Calendar_Controller::action_edit_item": true,
-            "Services_Calendar_Controller::action_copy_item": true,
             "Services_Calendar_Controller::action_view_item": true,
             "Services_Calendar_Controller::action_delete_item": true,
             "Services_Calendar_Controller::action_delete_recurrent_items": true


=====================================
lib/calendar/calendarlib.php
=====================================
@@ -2385,4 +2385,59 @@ class CalendarLib extends TikiLib
         }
         return count(array_unique($dates));
     }
+
+    /**
+     * Prepares an event array for cloning/copying.
+     * Removes unique identifiers, resets status, and sets the new owner.
+     *
+     * @param int $sourceCalItemId The ID of the event to copy
+     * @param string $user The current user (new owner)
+     * @return array|null Returns the cleaned event array or null if not found
+     */
+    public function getCopyData($sourceCalItemId, $user)
+    {
+        $item = $this->get_item($sourceCalItemId);
+
+        if (! $item) {
+            return null;
+        }
+
+        $item['calitemId'] = 0;
+        $item['recurrenceId'] = 0;
+        $item['uid'] = ''; // Will be regenerated on save
+        $item['uri'] = '';
+        $item['created'] = 0;
+        $item['lastModif'] = 0;
+
+        $item['user'] = $user;
+        $item['organizers'] = [$user];
+        $item['name'] = tr('Copy of %0', $item['name']);
+        $userInParticipants = false;
+
+        if (! empty($item['participants'])) {
+            foreach ($item['participants'] as &$participant) {
+                $participant['partstat'] = '';
+                $participant['comment'] = '';
+                if ($participant['username'] === $user) {
+                    $userInParticipants = true;
+                }
+            }
+        } else {
+            $item['participants'] = [];
+        }
+        if (! $userInParticipants) {
+            $item['participants'][] = [
+                'username' => $user,
+                'role' => '',
+                'partstat' => '',
+                'comment' => ''
+            ];
+            if (isset($item['selected_participants'])) {
+                $item['selected_participants'][] = $user;
+            } else {
+                $item['selected_participants'] = [$user];
+            }
+        }
+        return $item;
+    }
 }


=====================================
lib/core/Services/Calendar/Controller.php
=====================================
@@ -304,8 +304,8 @@ class Services_Calendar_Controller extends Services_Calendar_BaseController
                                 if ($return_url && ! $access->is_xml_http_request()) {
                                     $access->redirect($return_url, tr('The event was saved successfully'));
                                 }
-                                // reload the page?
-                                return [];
+                                // Ensure AJAX modals redirects
+                                return ['url' => $return_url ?: 'tiki-calendar.php'];
                             }
                         } else {
                             Feedback::error(tr('Calendar edit error')); // TODO more
@@ -368,98 +368,127 @@ class Services_Calendar_Controller extends Services_Calendar_BaseController
 
             $trackerItems = $this->calendarLib->getAttachedTrackerItems($calitemId);
         } else {
-            // new event
-            $title = tr('Calendar event : %0', tr('New'));
+            // new event or copy action
             $calitemId = 0;
-            $calendar = $calendars[0];
-            $calendarId = $input->defaultCalendarId->int() > 0 ? $input->defaultCalendarId->int() : $calendar['calendarId'];
-
-            $participants = [];
-            if ($user) {
-                $participants[] = [
-                    'username' => $user,
-                    'role'     => '',
-                    'partstat' => '',
-                ];
+            $calitem = null; // Initialize to ensure clean state
+            $trackerItems = [];
+            if ($input->copy_from->int()) {
+                $copyFromId = $input->copy_from->int();
+                $input->offsetSet('calitemId', $copyFromId);
+                $copyFromId = $this->getItemId($input, 'view_events'); // Throws exception if unauthorized
+                $input->offsetSet('calitemId', 0); // Reset back to 0 for new event creation
+                $calitem = $this->calendarLib->getCopyData($copyFromId, $user);
+                if ($calitem) {
+                    $title = tr('Calendar event : %0', $calitem['name']);
+                    $calendarId = $calitem['calendarId'];
+                    $calendar = $this->calendarLib->get_calendar($calendarId);
+                    $trackerItems = $this->calendarLib->getAttachedTrackerItems($copyFromId);
+                    // Apply timezone offset for the UI form
+                    $start = new TikiDate();
+                    $start->setDate($calitem['start']);
+                    $start->setTZbyID($displayTimezone);
+                    $end = new TikiDate();
+                    $end->setDate($calitem['end']);
+                    $end->setTZbyID($displayTimezone);
+
+                    $calitem['start'] = $start->getTime();
+                    $calitem['end']   = $end->getTime();
+                    $calitem['duration'] = 0;
+                }
             }
 
-            // 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;
-                    }
-                    $duration = $end - $start;
-                } else {
-                    $duration = 60 * 60;
-                    $end = $start + $duration;
-                }
-                if ($input->target_user->text()) {
-                    if ($user) {
-                        $participants[0]['role'] = '1';
-                        $participants[0]['partstat'] = 'ACCEPTED';
-                    }
+            if (empty($calitem)) {
+                $title = tr('Calendar event : %0', tr('New'));
+                $calendar = $calendars[0];
+                $calendarId = $input->defaultCalendarId->int() > 0 ? $input->defaultCalendarId->int() : $calendar['calendarId'];
+
+                $participants = [];
+                if ($user) {
                     $participants[] = [
-                        'username' => $input->target_user->text(),
-                        'role'     => '1',
+                        'username' => $user,
+                        'role'     => '',
                         'partstat' => '',
                     ];
                 }
-            } else {
-                $hour = $dateNow->date->format('H');
-                if ($input->offsetExists('todate')) {
-                    $dateNow->setTZbyID($displayTimezone);
-                    $dateNow->setDate($input->todate->text(), $displayTimezone);
+                // 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;
+                        }
+                        $duration = $end - $start;
+                    } else {
+                        $duration = 60 * 60;
+                        $end = $start + $duration;
+                    }
+                    if ($input->target_user->text()) {
+                        if ($user) {
+                            $participants[0]['role'] = '1';
+                            $participants[0]['partstat'] = 'ACCEPTED';
+                        }
+                        $participants[] = [
+                            'username' => $input->target_user->text(),
+                            'role'     => '1',
+                            'partstat' => '',
+                        ];
+                    }
+                } 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;
                 }
-                $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;
-            }
 
-            $calitem = [
-                'calitemId'             => $calitemId,
-                'calendarId'            => $calendarId,
-                'user'                  => $user,
-                'name'                  => $input->prefill_title->text(),
-                'url'                   => '',
-                'description'           => '',
-                'status'                => $calendar['defaulteventstatus'],
-                'priority'              => 0,
-                'locationId'            => 0,
-                'categoryId'            => 0,
-                'nlId'                  => 0,
-                'start'                 => $start,
-                'end'                   => $end,
-                'duration'              => $duration,
-                'recurrenceId'          => 0,
-                'allday'                => $calendar['allday'] == 'y' ? 1 : 0,
-                'organizers'            => [$user],
-                'participants'          => $participants,
-                'returnURL'             => $return_url,
-            ];
+                $calitem = [
+                    'calitemId'             => $calitemId,
+                    'calendarId'            => $calendarId,
+                    'user'                  => $user,
+                    'name'                  => $input->prefill_title->text(),
+                    'url'                   => '',
+                    'description'           => '',
+                    'status'                => $calendar['defaulteventstatus'],
+                    'priority'              => 0,
+                    'locationId'            => 0,
+                    'categoryId'            => 0,
+                    'nlId'                  => 0,
+                    'start'                 => $start,
+                    'end'                   => $end,
+                    'duration'              => $duration,
+                    'recurrenceId'          => 0,
+                    'allday'                => $calendar['allday'] == 'y' ? 1 : 0,
+                    'organizers'            => [$user],
+                    'participants'          => $participants,
+                    'returnURL'             => $return_url,
+                ];
+            }
         }
 
         if (isset($calitem['recurrenceId']) && $calitem['recurrenceId'] > 0) {
@@ -598,13 +627,6 @@ class Services_Calendar_Controller extends Services_Calendar_BaseController
         ];
     }
 
-    public function action_copy_item(JitFilter $input): array
-    {
-        $input->offsetSet('calitemId', 0);
-
-        return $this->action_edit_item($input);
-    }
-
     /**
      * Shows a calendar item (event)
      *


=====================================
templates/calendar/edit_item.tpl
=====================================
@@ -443,10 +443,6 @@
             <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">
-            {if $tiki_p_add_events eq 'y' and empty($saveas) and not empty($calitemId)}
-                <input type="submit" class="btn btn-secondary cleanable-false" name="saveas" data-alt_controller="calendar" data-alt_action="copy_item"
-                       onclick="needToConfirm=false" value="{tr}Copy to a new event{/tr}">
-            {/if}
             {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}">


=====================================
templates/calendar/view_item.tpl
=====================================
@@ -356,5 +356,13 @@
                 {tr}Edit{/tr}
             </a>
         {/permission}
+        {if $tiki_p_add_events eq 'y'}
+            <a 
+                href="{service controller='calendar' action='edit_item' calitemId=0 copy_from=$calitem.calitemId|escape modal=1 return_url='tiki-calendar.php'}"
+                class="btn btn-tinted-primary edit-calendar-item-btn cleanable-false"
+                title="{tr}Create a new event based on this one{/tr}">
+                {icon name='copy'} {tr}Copy{/tr}
+            </a>
+        {/if}
     {/if}
 {/block}


=====================================
themes/base_files/scss/_tiki-bootstrap_overrides.scss
=====================================
@@ -458,6 +458,29 @@ body.fullscreen.tiki.navbar-padding {
     margin-bottom: 0;
 }
 
+.btn-tinted-primary {
+    --bs-btn-color: var(--bs-primary);
+    --bs-btn-bg: rgba(var(--bs-primary-rgb), 0.08);
+    --bs-btn-border-color: transparent;
+
+    --bs-btn-hover-color: var(--bs-primary);
+    --bs-btn-hover-bg: rgba(var(--bs-primary-rgb), 0.15);
+    --bs-btn-hover-border-color: transparent;
+
+    --bs-btn-active-color: var(--bs-primary);
+    --bs-btn-active-bg: rgba(var(--bs-primary-rgb), 0.20);
+    --bs-btn-active-border-color: transparent;
+
+    --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
+
+    color: var(--bs-btn-color) !important;
+
+    &:focus,
+    &:active {
+        box-shadow: none !important;
+    }
+}
+
 @media screen and (max-width: 480px) {
     .tiki #conversejs.converse-embedded {
         margin: 0;



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

-- 
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/08bd0c0ad7937441573853df24b910d81f517889
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.