[TikiWiki-commits] [Git][tikiwiki/tiki][master] [ENH] Tracker: Implement duplicate item workflow with field-level control

Benoit Grégoire (@benoitg) via TikiWiki-cvs <[email protected]>
Newsgroups gmane.comp.cms.tiki.cvs
Message-ID <698394ea6b758_3b184570766a2@gitlab-sidekiq-low-urgency-cpu-bound-v2-7d899c995b-g6hwp.mail>

Benoit Grégoire pushed to branch master at Tiki Wiki CMS Groupware / Tiki


Commits:
5a093356 by Moïse Nturubika at 2026-02-04T18:41:14+00:00
[ENH] Tracker: Implement duplicate item workflow with field-level control
---
* [ENH] Tracker: Implement duplicate item workflow with field-level control

See merge request tikiwiki/tiki!9462

- - - - -


5 changed files:

- lib/core/Services/Tracker/Controller.php
- lib/core/Services/Tracker/Utilities.php
- templates/tiki-view_tracker.tpl
- − templates/tracker/clone_item.tpl
- templates/tracker/replace.tpl


Changes:

=====================================
lib/core/Services/Tracker/Controller.php
=====================================
@@ -847,96 +847,6 @@ class Services_Tracker_Controller
         return $displayedFields;
     }
 
-    public function action_clone_item($input)
-    {
-        global $prefs;
-
-        Services_Exception_Disabled::check('tracker_clone_item');
-
-        $trackerId = $input->trackerId->int();
-        $definition = Tracker_Definition::get($trackerId);
-
-        if (! $definition) {
-            throw new Services_Exception_NotFound();
-        }
-
-        $itemId = $input->itemId->int();
-        if (! $itemId) {
-            throw new Services_Exception_Denied(tr('No item to clone'));
-        }
-
-        $itemObject = Tracker_Item::fromId($itemId);
-
-        if (! $itemObject->canView()) {
-            throw new Services_Exception_Denied(tr("The item to clone isn't visible"));
-        }
-
-        $newItem = Tracker_Item::newItem($trackerId);
-
-        if (! $newItem->canModify()) {
-            throw new Services_Exception_Denied(tr("You don't have permission to create new items"));
-        }
-
-        global $prefs;
-        if ($prefs['feature_jquery_validation'] === 'y') {
-            $_REQUEST['itemId'] = 0;    // let the validation code know this will be a new item
-            $validationjs = TikiLib::lib('validators')->generateTrackerValidateJS(
-                $definition,
-                '',
-                '',
-                // not custom submit handler that is only needed when called by this service
-                'submitHandler: function(form, event){return process_submit(form, event);}'
-            );
-            TikiLib::lib('header')->add_jq_onready('$("#cloneItemForm' . $trackerId . '").validate({' . $validationjs . $this->get_validation_options());
-        }
-
-        $itemObject->asNew();
-        $itemData = $itemObject->getData($input);
-        $processedFields = [];
-
-        $id = 0;
-        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-            $itemObject = $this->utilities->cloneItem($definition, $itemData, $itemId);
-            $id = $itemObject->getId();
-            if ($id === false) {
-                throw new Services_Exception_Denied(tr("There were errors cloning the item, please check error messages"));
-            }
-
-            TikiLib::lib('unifiedsearch')->processUpdateQueue();
-            TikiLib::events()->trigger('tiki.process.redirect'); // wait for indexing to complete before loading of next request to ensure updated info shown
-
-            $processedItem = $this->utilities->processValues($definition, $itemData);
-            $processedFields = $processedItem['fields'];
-        }
-
-        // sets all fields for the tracker item with their value
-        $processedFields = $itemObject->prepareInput($input);
-        // fields where the value is forced.
-        $forcedFields = $input->forced->none();
-
-        // if forced fields are set, remove them from the processedFields since they will not show up visually
-        // in the form; they will be set up separately and hidden.
-        if (! empty($forcedFields)) {
-            foreach ($processedFields as $k => $f) {
-                $permName = $f['permName'];
-                if (isset($forcedFields[$permName])) {
-                    unset($processedFields[$k]);
-                }
-            }
-        }
-
-        $displayedFields = $this->fieldsToDisplay($input, $processedFields);
-
-        return [
-            'title' => tr('Duplicate Item'),
-            'trackerId' => $trackerId,
-            'itemId' => $itemId,
-            'created' => $id,
-            'data' => $itemData['fields'],
-            'fields' => $displayedFields,
-            'forced' => $forcedFields,
-        ];
-    }
 
     public function action_insert_item($input)
     {
@@ -968,6 +878,44 @@ class Services_Tracker_Controller
         $fields = $input->fields->none() ?: [];
         $forced = $input->forced->none() ?: [];
         $editable = $input->editable->none() ?: [];
+
+        $cloneFrom = $input->clone_from->int();
+        if ($cloneFrom && $_SERVER['REQUEST_METHOD'] !== 'POST') {
+            try {
+                $sourceItem = Tracker_Item::fromId($cloneFrom);
+            } catch (Exception $e) {
+                $sourceItem = null;
+            }
+
+            if ($sourceItem && $sourceItem->canView()) {
+                $sourceData = $sourceItem->getData();
+                $duplicateRules = json_decode($definition->getConfiguration('duplicateRules', '[]'), true);
+
+                foreach ($definition->getFields() as $field) {
+                    $fid = $field['fieldId'];
+                    $permName = $field['permName'];
+                    $rule = $duplicateRules[$fid] ?? 'copy';
+
+                    if ($rule === 'copy') {
+                        if (isset($sourceData['fields'][$permName])) {
+                            $value = $sourceData['fields'][$permName];
+                            if ($permName) {
+                                $input->offsetSet($permName, $value);
+                            }
+                            $input->offsetSet('ins_' . $fid, $value);
+                        }
+                    } elseif ($rule === 'clear') {
+                        if ($permName) {
+                            $input->offsetSet($permName, '');
+                        }
+                        $input->offsetSet('ins_' . $fid, '');
+                    }
+                    // default: do nothing, let prepareInput handle defaults
+                }
+
+                Feedback::note(tr('Initializing duplication of item #%0', $cloneFrom));
+            }
+        }
         $processedFields = $itemObject->prepareInput($input);
         $suppressFeedback = $input->suppressFeedback->bool();
         $toRemove = [];
@@ -1165,6 +1113,10 @@ class Services_Tracker_Controller
                 $util->setTicket();
                 $item['nextTicket'] = $util->getTicket();
 
+                if (! $redirect && ! $access->is_xml_http_request()) {
+                    $redirect = smarty_modifier_sefurl($trackerId, 'tracker');
+                }
+
                 if ($redirect) {
                     //return to page
                     if ($access->is_xml_http_request()) {
@@ -1210,6 +1162,7 @@ class Services_Tracker_Controller
         return [
             'title' => $title,
             'trackerId' => $trackerId,
+            'tracker_info' => $definition->getInformation(),
             'trackerName' => $trackerName,
             'itemId' => $itemId,
             'fields' => $displayedFields,
@@ -2217,6 +2170,8 @@ class Services_Tracker_Controller
                 'name' => $name,
                 'description' => $input->description->text(),
                 'descriptionIsParsed' => $input->descriptionIsParsed->int() ? 'y' : 'n',
+                'duplicateRules' => json_encode($input->asArray('duplicateRules')),
+                'enableDuplicate' => $input->enableDuplicate->int() ? 'y' : 'n',
             ], TikiLib::lib('trk')->trackerOptionsFromInput($input));
 
             $trackerId = $this->utilities->updateTracker($trackerId, $data);
@@ -2251,6 +2206,9 @@ class Services_Tracker_Controller
         if (! empty($info['descriptionIsParsed']) && $info['descriptionIsParsed'] == 'y') {
             $info['description'] = TikiLib::lib('edit')->removeSyntaxPlugin($info['description']);
         }
+        if (! empty($info['duplicateRules'])) {
+            $info['val_duplicateRules'] = json_decode($info['duplicateRules'], true);
+        }
 
         return [
             'accordion_pos' => $accordion_pos,
@@ -2272,6 +2230,7 @@ class Services_Tracker_Controller
             'remoteTabulars' => TikiLib::lib('tabular')->getList(['odbc_config' => new TikiDb_Expr('((odbc_config != ? AND odbc_config IS NOT NULL) OR (api_config != ? AND api_config IS NOT NULL))', ['[]', '[]'])]),
             'relationshipBehaviourList' => array_keys(Tiki\Relation\Semantics::BEHAVIOUR_LIST),
             'displayTimezone' => TikiLib::lib('tiki')->get_display_timezone(),
+            'fields' => $trackerId ? $definition->getFields() : [],
         ];
     }
 


=====================================
lib/core/Services/Tracker/Utilities.php
=====================================
@@ -110,7 +110,7 @@ class Services_Tracker_Utilities
         }
 
         if (! $options['validate'] || count($errors) == 0) {
-            $newItem = $trklib->replace_item($trackerId, $itemId, ['data' => $fields], $status, 0, $options['bulk_import'], $options['skip_sync'], $options['deleted_files'] ?? [], $options['notify_watchers']);
+            $newItem = $trklib->replace_item($trackerId, $itemId, ['data' => $fields], $status, 0, $options['bulk_import'], $options['skip_sync'], $options['deleted_files'] ?? [], $options['notify_watchers'] ?? null);
             return $newItem;
         }
 


=====================================
templates/tiki-view_tracker.tpl
=====================================
@@ -307,9 +307,9 @@
                                                             </a>
                                                         </action>
                                                     {/if}
-                                                    {if $tiki_p_create_tracker_items eq 'y' and $prefs.tracker_clone_item eq 'y'}
+                                                    {if $tiki_p_create_tracker_items eq 'y' and ($prefs.tracker_clone_item eq 'y' or $tracker_info.enableDuplicate eq 'y')}
                                                         <action>
-                                                            <a href="{bootstrap_modal controller=tracker action=clone_item trackerId=$trackerId itemId=$items[user].itemId size='modal-lg'}"
+                                                            <a href="{service controller=tracker action=insert_item trackerId=$trackerId clone_from=$items[user].itemId}"
                                                                 onclick="$('[data-bs-toggle=popover]').popover('hide');"
                                                             >
                                                                 {icon name="copy" _menu_text='y' _menu_icon='y' alt="{tr}Duplicate{/tr}"}


=====================================
templates/tracker/clone_item.tpl deleted
=====================================
@@ -1,16 +0,0 @@
-{extends $global_extend_layout|default:'layout_view.tpl'}
-
-{block name="title"}
-    {title}{$title|escape}{/title}
-{/block}
-
-{block name="content"}
-<form class="simple" method="post" action="{service controller=tracker action=clone_item}" id="cloneItemForm{$trackerId|escape}">
-    {trackerfields trackerId=$trackerId fields=$fields itemId=$itemId}
-    <div class="submit">
-        <input type="hidden" name="itemId" value="{$itemId|escape}">
-        <input type="hidden" name="trackerId" value="{$trackerId|escape}">
-        <input type="submit" class="btn btn-primary" value="{tr}Duplicate{/tr}">
-    </div>
-</form>
-{/block}


=====================================
templates/tracker/replace.tpl
=====================================
@@ -138,6 +138,45 @@
                 </div>
             {/if}
         {/accordion_group}
+        {accordion_group title="{tr}Duplication{/tr}" accordion_pos=$accordion_pos}
+            <div class="form-check">
+                <input type="checkbox" class="form-check-input" name="enableDuplicate" id="enableDuplicate" value="1"
+                    {if $info.enableDuplicate eq 'y'} checked="checked"{/if}>
+                <label class="form-check-label" for="enableDuplicate">
+                    {tr}Enable Duplicate Action{/tr}
+                </label>
+            </div>
+            <div class="mb-3 mx-0 depends" data-on="enableDuplicate">
+                <label class="form-label">{tr}Field Duplication Rules{/tr}</label>
+                <div class="table-responsive">
+                    <table class="table table-bordered table-hover">
+                        <thead>
+                            <tr>
+                                <th>{tr}Field{/tr}</th>
+                                <th>{tr}Action{/tr}</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {foreach from=$fields item=field}
+                                <tr>
+                                    <td>{$field.name|escape}</td>
+                                    <td>
+                                        <select name="duplicateRules[{$field.fieldId}]" class="form-select">
+                                            <option value="copy" {if empty($info.val_duplicateRules[$field.fieldId]) or $info.val_duplicateRules[$field.fieldId] eq 'copy'}selected{/if}>{tr}Copy{/tr}</option>
+                                            <option value="clear" {if $info.val_duplicateRules[$field.fieldId] eq 'clear'}selected{/if}>{tr}Clear/Empty{/tr}</option>
+                                            <option value="default" {if $info.val_duplicateRules[$field.fieldId] eq 'default'}selected{/if}>{tr}Set to Default{/tr}</option>
+                                        </select>
+                                    </td>
+                                </tr>
+                            {/foreach}
+                        </tbody>
+                    </table>
+                </div>
+                <div class="form-text">
+                    {tr}Define how each field should be handled when duplicating an item.{/tr}
+                </div>
+            </div>
+        {/accordion_group}
         {accordion_group title="{tr}Display{/tr}" accordion_pos=$accordion_pos}
             <div class="mb-3 mx-0">
                 <label class="col-form-label" for="logo">{tr}Logo{/tr}</label>



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

-- 
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/5a09335683c64aa941f1b9838ab62c1d345d12b1
You're receiving this email because of your account on gitlab.com.

_______________________________________________
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.