From 1fa3acae748355e625923ebe825ef842df84163d Mon Sep 17 00:00:00 2001 From: Ruben Carlo Benante Date: Tue, 7 Jul 2026 23:11:08 -0300 Subject: [PATCH] reconcile duplicate links retroactively with the setting (v0.3.2) --- Controller/RecurrenceController.php | 6 +++++ Model/RecoRecoModel.php | 42 ++++++++++++++++++++--------- Plugin.php | 2 +- VERSION | 2 +- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/Controller/RecurrenceController.php b/Controller/RecurrenceController.php index b36d1b3..dedc7d8 100644 --- a/Controller/RecurrenceController.php +++ b/Controller/RecurrenceController.php @@ -62,6 +62,12 @@ class RecurrenceController extends BaseController } $this->taskMetadataModel->save($task['id'], $values); + + // Reconcile the template's duplicate links with the setting right away (removes existing + // links to its RecoReco clones when the option is off). + $model = new \Kanboard\Plugin\RecoReco\Model\RecoRecoModel($this->container); + $model->syncCloneLinks($task['id'], $values['recoreco_link_copies'] == 1); + $this->flash->success(t('Recurring schedule saved.')); return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); diff --git a/Model/RecoRecoModel.php b/Model/RecoRecoModel.php index 1c924af..e6bf89c 100644 --- a/Model/RecoRecoModel.php +++ b/Model/RecoRecoModel.php @@ -91,7 +91,7 @@ class RecoRecoModel extends Base $fireTime = $cursor - $daysBefore * 86400; while ($fireTime <= $now && $spawned < self::CATCHUP_CAP) { - $this->spawn($task, $targetColumn, $linkCopies); + $this->spawn($task, $targetColumn); $next = $this->calculator()->occurrenceFrom($anchor, $frequency, $lastDay, $cursor, false); @@ -105,9 +105,35 @@ class RecoRecoModel extends Base $spawned++; } + // Reconcile the duplicate links with the setting (removes both new and old ones when off). + $this->syncCloneLinks($task_id, $linkCopies); + return $spawned; } + /** + * Reconcile a template's "is a duplicate of" links with the link-copies setting. When off, remove + * the links to THIS template's own RecoReco clones (identified by recoreco_source, so manual + * links are left untouched). When on, do nothing -- new spawns already carry the link. + * + * @param int $template_id + * @param bool $linkCopies + */ + public function syncCloneLinks($template_id, $linkCopies) + { + if ($linkCopies) { + return; + } + + foreach ($this->taskLinkModel->getAll($template_id) as $link) { + $opposite = (int) $link['task_id']; + + if ((int) $this->taskMetadataModel->get($opposite, 'recoreco_source', 0) === (int) $template_id) { + $this->taskLinkModel->remove($link['id']); + } + } + } + /** * The first occurrence: respect an explicitly future anchor (the due date the user set) as the * first one; otherwise jump to the next occurrence on/after now. @@ -128,7 +154,7 @@ class RecoRecoModel extends Base $this->taskMetadataModel->save($task_id, array('recoreco_synced_due' => $cursor)); } - private function spawn(array $template, $targetColumn, $linkCopies) + private function spawn(array $template, $targetColumn) { // The template's date_due is already the occurrence (setDue), so duplicate() copies it onto // the clone. duplicate() copies fields + tags + links + subtasks, but NO metadata, so the @@ -149,16 +175,8 @@ class RecoRecoModel extends Base $this->taskModificationModel->update(array('id' => $newId, 'title' => $template['title']), false); // Kanboard's duplicate() adds an "is a duplicate of" task link between the template and the - // clone. Kept when recoreco_link_copies is on (a running count + one-click navigation to - // each copy); stripped when off (the default), to avoid piling up links on frequent - // schedules. getAll() returns the opposite task as $link['task_id'] and the row id as 'id'. - if (! $linkCopies) { - foreach ($this->taskLinkModel->getAll($newId) as $link) { - if ((int) $link['task_id'] === (int) $template['id']) { - $this->taskLinkModel->remove($link['id']); - } - } - } + // clone. It is reconciled with the recoreco_link_copies setting by syncCloneLinks() after + // the spawn loop (kept when on, removed when off). // Mark the clone: white icon + cannot itself be made recurring; record its source template. $this->taskMetadataModel->save($newId, array( diff --git a/Plugin.php b/Plugin.php index 8619016..fbb6fbb 100644 --- a/Plugin.php +++ b/Plugin.php @@ -36,7 +36,7 @@ class Plugin extends Base public function getPluginVersion() { - return '0.3.1'; + return '0.3.2'; } public function getPluginHomepage() diff --git a/VERSION b/VERSION index 5daebf3..5627a88 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -RecoReco v0.3.1 +RecoReco v0.3.2