closesTask()) { return isset($task['is_active']) && (int) $task['is_active'] === 0; } return $this->taskMetadataModel->get((int) $task['id'], self::DONE_KEY, '') === 'on'; } /** * Should toggling Done also close (and reopen) the task? Global setting, default off. * * @return bool */ public function closesTask() { return (int) $this->configModel->get(self::CLOSES_KEY, 0) === 1; } /** * Mark a task Done in the current mode. Idempotent -- a no-op if it is already Done. Close-mode * closes the task; marker-mode sets the DONE_KEY metadata. * * @param int $task_id */ public function markDone($task_id) { $task = $this->taskFinderModel->getById((int) $task_id); if (empty($task) || $this->isDone($task)) { return; } if ($this->closesTask()) { $this->taskStatusModel->close((int) $task_id); } else { $this->taskMetadataModel->save((int) $task_id, array(self::DONE_KEY => 'on')); } } /** * Recompute whether the task's subtasks are ALL done and, only on the up-transition * (was-not-all-done -> now-all-done), auto-mark the task Done. The last-seen all-done state is kept * in ALLDONE_KEY ('on'/'off') to detect that edge -- so a manual unmark is honored (nothing * re-fires) and title edits / refreshes never re-mark. Never auto-unmarks. * * @param int $task_id * @param int $exclude_id a subtask id to exclude from the counts (the row being deleted -- * EVENT_DELETE fires BEFORE the row is removed, so it still counts) */ public function syncSubtasksDone($task_id, $exclude_id = 0) { $task_id = (int) $task_id; $exclude_id = (int) $exclude_id; $totalQuery = $this->db->table(SubtaskModel::TABLE)->eq('task_id', $task_id); $doneQuery = $this->db->table(SubtaskModel::TABLE)->eq('task_id', $task_id)->eq('status', SubtaskModel::STATUS_DONE); if ($exclude_id > 0) { $totalQuery->neq('id', $exclude_id); $doneQuery->neq('id', $exclude_id); } $total = $totalQuery->count(); $done = $doneQuery->count(); $allDone = $total > 0 && $done === $total; $prev = $this->taskMetadataModel->get($task_id, self::ALLDONE_KEY, 'off') === 'on'; // Save the new state BEFORE marking: in close-mode markDone() -> close() -> closeAll() re-fires // subtask events into this method; with the marker already 'on', that re-entry sees no // transition and is a no-op (no loop). $this->taskMetadataModel->save($task_id, array(self::ALLDONE_KEY => $allDone ? 'on' : 'off')); if ($allDone && ! $prev) { $this->markDone($task_id); } } }