[TikiWiki-commits] [Git][tikiwiki/tiki][master] [FIX] Tracker/ItemLink: Cascade duplication of child items when duplicateCascade is enabled

"ushindi bienvenu \(@usbbush\) via TikiWiki-cvs" <[email protected]>
Newsgroups gmane.comp.cms.tiki.cvs
Message-ID <6a3eb4e9ae158_3850de08717b5@gitlab-sidekiq-low-urgency-cpu-bound-v2-dd678b676-fvcg5.mail>

ushindi bienvenu pushed to branch master at Tiki Wiki CMS Groupware / Tiki


Commits:
ee79bf95 by Landry Bitege at 2026-06-26T17:01:18+00:00
[FIX] Tracker/ItemLink: Cascade duplication of child items when duplicateCascade is enabled
---
* [FIX] Tracker/ItemLink: Cascade duplication of child items when duplicateCascade is enabled

See merge request tikiwiki/tiki!10257

- - - - -


6 changed files:

- + installer/schema/20260519_itemlink_duplicate_cascade_tiki.php
- lib/core/Services/Tracker/Controller.php
- lib/core/Services/Tracker/Utilities.php
- lib/core/Tracker/Field/ItemLink.php
- templates/tracker/edit_field.tpl
- templates/tracker/insert_item.tpl


Changes:

=====================================
installer/schema/20260519_itemlink_duplicate_cascade_tiki.php
=====================================
@@ -0,0 +1,34 @@
+<?php
+
+// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
+//
+// All Rights Reserved. See copyright.txt for details and a complete list of authors.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+
+use Tiki\Installer\Installer;
+
+function upgrade_20260519_itemlink_duplicate_cascade_tiki(Installer $installer): void
+{
+    $fields = $installer->fetchAll(
+        "SELECT fieldId, options FROM tiki_tracker_fields WHERE type = 'r'"
+    );
+
+    foreach ($fields as $field) {
+        $options = @json_decode($field['options'], true);
+
+        if (! is_array($options)) {
+            continue;
+        }
+
+        $updated = Tracker_Field_ItemLink::syncDuplicateCascadeDefaultForUpgrade($options);
+
+        if ($updated === $options) {
+            continue;
+        }
+
+        $installer->query(
+            "UPDATE tiki_tracker_fields SET options = ? WHERE fieldId = ?",
+            [json_encode($updated), $field['fieldId']]
+        );
+    }
+}


=====================================
lib/core/Services/Tracker/Controller.php
=====================================
@@ -1079,6 +1079,9 @@ class Services_Tracker_Controller
             );
 
             if ($itemId) {
+                if ($cloneFrom) {
+                    $this->utilities->cascadeChildItems($cloneFrom, $itemId);
+                }
                 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
 
@@ -1189,6 +1192,7 @@ class Services_Tracker_Controller
             'skipRefresh' => $input->skipRefresh->bool(),
             'refreshMeta' => $input->refreshMeta->raw(),
             'refreshObject' => $input->refreshObject->raw(),
+            'cloneFrom' => $cloneFrom,
         ];
     }
 


=====================================
lib/core/Services/Tracker/Utilities.php
=====================================
@@ -678,7 +678,22 @@ EXPORT;
 
         $itemObject = Tracker_Item::fromId($id);
 
-        foreach (TikiLib::lib('trk')->get_child_items($itemId) as $info) {
+        if ($this->cascadeChildItems($itemId, $id, $strict, $insertIds)) {
+            foreach ($insertIds as $insertedId) {
+                $this->removeItem($insertedId);
+            }
+            $transaction->commit(); // there is no rollback
+            return false;
+        }
+
+        $transaction->commit();
+
+        return $itemObject;
+    }
+
+    public function cascadeChildItems(int $sourceItemId, int $newParentId, bool $strict = false, array &$insertIds = []): bool
+    {
+        foreach (TikiLib::lib('trk')->get_child_items($sourceItemId) as $info) {
             $field = TikiLib::lib('trk')->get_tracker_field($info['field']);
             $options = Tracker_Options::fromSerialized($field['options'], Tracker_Field_Factory::getFieldInfo($field['type']));
             if (! $options->getParam('duplicateCascade')) {
@@ -686,39 +701,30 @@ EXPORT;
             }
 
             $childItem = Tracker_Item::fromId($info['itemId']);
+            if (! $childItem->canView()) {
+                continue;
+            }
 
-            if ($childItem->canView()) {
-                $childItem->asNew();
-                $data = $childItem->getData();
-                $data['fields'][$info['field']] = $id;
-
-                $childDefinition = $childItem->getDefinition();
-
-                // handle specific cloning actions
+            $childItem->asNew();
+            $data = $childItem->getData();
+            $data['fields'][$info['field']] = $newParentId;
 
-                foreach ($childDefinition->getFields() as $field) {
-                    $handler = $childDefinition->getFieldFactory()->getHandler($field, $data);
-                    if (method_exists($handler, 'handleClone')) {
-                        $newData = $handler->handleClone($strict);
-                        $data['fields'][$field['permName']] = $newData['value'];
-                    }
+            $childDefinition = $childItem->getDefinition();
+            foreach ($childDefinition->getFields() as $childField) {
+                $handler = $childDefinition->getFieldFactory()->getHandler($childField, $data);
+                if (method_exists($handler, 'handleClone')) {
+                    $newData = $handler->handleClone($strict);
+                    $data['fields'][$childField['permName']] = $newData['value'];
                 }
+            }
 
-                $new = $this->insertItem($childDefinition, $data);
-                if ($new === false) {
-                    foreach ($insertIds as $id) { // undo items already created
-                        $this->removeItem($id);
-                    }
-                    $transaction->commit(); // there is no rollback
-                    return false;
-                }
-                $insertIds[] = $new;
+            $new = $this->insertItem($childDefinition, $data);
+            if ($new === false) {
+                return true;
             }
+            $insertIds[] = $new;
         }
-
-        $transaction->commit();
-
-        return $itemObject;
+        return false;
     }
 
     public static function convertToDefaultCurrency($data)


=====================================
lib/core/Tracker/Field/ItemLink.php
=====================================
@@ -1169,6 +1169,18 @@ class Tracker_Field_ItemLink extends \Tracker\Field\AbstractItemField implements
         return ($this->trackerField->getOption('cascade') & $flag) > 0;
     }
 
+    public static function syncDuplicateCascadeDefaultForUpgrade(array $options): array
+    {
+        if (array_key_exists('duplicateCascade', $options)) {
+            return $options;
+        }
+
+        $cascadeAll = self::CASCADE_CATEG | self::CASCADE_STATUS | self::CASCADE_DELETE;
+        $options['duplicateCascade'] = isset($options['cascade']) && (int) $options['cascade'] === $cascadeAll ? 1 : 0;
+
+        return $options;
+    }
+
     public function watchCompare($old, $new)
     {
         if ($this->canHaveMultipleValues()) {


=====================================
templates/tracker/edit_field.tpl
=====================================
@@ -348,4 +348,11 @@
             isPasswordDropdown.val('1');
         }
     });
+
+    $(document).on("change", "select[name='option~cascade']", function() {
+        const newVal = $(this).val() === '7' ? '1' : '0';
+        const dupNative = document.querySelector("select[name='option~duplicateCascade']");
+        $(dupNative).val(newVal);
+        $(dupNative).trigger("change");
+    });
 {/jq}


=====================================
templates/tracker/insert_item.tpl
=====================================
@@ -37,6 +37,7 @@
                     <input type="button" class="btn btn-secondary previewItemBtn" title="{tr}Preview your changes.{/tr}" name="preview" value="{tr}Preview{/tr}">
                 {/if}
                 <input type="hidden" name="trackerId" value="{$trackerId|escape}">
+                {if $cloneFrom}<input type="hidden" name="clone_from" value="{$cloneFrom|escape}">{/if}
                 <input type="hidden" name="skipRefresh" value="{$skipRefresh|escape}">
                 <input type="hidden" name="refreshMeta" value="{$refreshMeta|escape}">
                 <input type="hidden" name="refreshObject" value="{$refreshObject|escape}">



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

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