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

"luci \(@luciash\) via TikiWiki-cvs" <[email protected]> Fri, 17 Jul 2026 15:20:54 +0000
Newsgroups gmane.comp.cms.tiki.cvs
Message-ID <6a5a4856dfdba_3819cfa8738f8@gitlab-sidekiq-low-urgency-cpu-bound-v2-7456c65c79-dtttt.mail>

luci pushed to branch 30.x at Tiki Wiki CMS Groupware / Tiki


Commits:
ae48e56b by Landry Bitege at 2026-07-17T15:12:54+00:00
[BP][FIX] Tracker/ItemLink: Cascade duplication of child items when duplicateCascade is enabled
---
* [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

(cherry picked from commit ee79bf9599c5952fb176fc510d53623c76b563ee)

See merge request tikiwiki/tiki!10738

- - - - -


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
=====================================
@@ -1078,6 +1078,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
 
@@ -1188,6 +1191,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
=====================================
@@ -672,7 +672,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')) {
@@ -680,39 +695,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
=====================================
@@ -1197,6 +1197,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
=====================================
@@ -338,4 +338,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/ae48e56b0cb39713ef8047ecc85f67cb210dcb8e

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