= id | priority | assignee-priority | assignee | due-date | title * organon_sort_dir_ = asc | desc * organon_sort_stick_ = on | off (default off = native one-shot behavior) */ class OrganonSortHelper extends Base { const BY_PREFIX = 'organon_sort_by_'; const DIR_PREFIX = 'organon_sort_dir_'; const STICK_PREFIX = 'organon_sort_stick_'; // The sort criteria this control offers. Native ones dispatch to core TaskReorderModel; 'title' // is our own number-aware sort. Any value not here is ignored (no reorder, nothing stored). const CRITERIA = array('id', 'priority', 'assignee-priority', 'assignee', 'due-date', 'title'); public function isSticky($project_id, $column_id) { return $this->projectMetadataModel->get((int) $project_id, self::STICK_PREFIX.(int) $column_id, 'off') === 'on'; } public function sortBy($project_id, $column_id) { return (string) $this->projectMetadataModel->get((int) $project_id, self::BY_PREFIX.(int) $column_id, ''); } public function sortDir($project_id, $column_id) { return $this->projectMetadataModel->get((int) $project_id, self::DIR_PREFIX.(int) $column_id, 'asc') === 'desc' ? 'desc' : 'asc'; } /** * Sort the column now and record the choice (for the icon + the sticky re-apply). */ public function applySort($project_id, $swimlane_id, $column_id, $sort, $direction) { if (! in_array($sort, self::CRITERIA, true)) { return; } $direction = $direction === 'desc' ? 'desc' : 'asc'; $this->reorder((int) $project_id, (int) $swimlane_id, (int) $column_id, $sort, $direction); $this->projectMetadataModel->save((int) $project_id, array( self::BY_PREFIX.(int) $column_id => $sort, self::DIR_PREFIX.(int) $column_id => $direction, )); } /** * Re-apply the stored sort (called on card drop / creation when persistent is on). No-op if the * column has no stored sort yet. */ public function reapply($project_id, $swimlane_id, $column_id) { $sort = $this->sortBy($project_id, $column_id); if ($sort !== '' && in_array($sort, self::CRITERIA, true)) { $this->reorder((int) $project_id, (int) $swimlane_id, (int) $column_id, $sort, $this->sortDir($project_id, $column_id)); } } /** * Flip the persistent flag for a column; returns the new state. * * @return string 'on' | 'off' */ public function toggle($project_id, $column_id) { $new = $this->isSticky($project_id, $column_id) ? 'off' : 'on'; $this->projectMetadataModel->save((int) $project_id, array(self::STICK_PREFIX.(int) $column_id => $new)); return $new; } /** * Dispatch the physical reorder: native criteria reuse core TaskReorderModel; 'title' is ours. */ private function reorder($project_id, $swimlane_id, $column_id, $sort, $direction) { switch ($sort) { case 'id': $this->taskReorderModel->reorderByTaskId($project_id, $swimlane_id, $column_id, $direction); break; case 'priority': $this->taskReorderModel->reorderByPriority($project_id, $swimlane_id, $column_id, $direction); break; case 'assignee-priority': $this->taskReorderModel->reorderByAssigneeAndPriority($project_id, $swimlane_id, $column_id, $direction); break; case 'assignee': $this->taskReorderModel->reorderByAssignee($project_id, $swimlane_id, $column_id, $direction); break; case 'due-date': $this->taskReorderModel->reorderByDueDate($project_id, $swimlane_id, $column_id, $direction); break; case 'title': $this->reorderByTitle($project_id, $swimlane_id, $column_id, $direction); break; } } /** * Number-aware title sort (strnatcasecmp, so "Task 2" precedes "Task 10"), then write positions. * Mirrors the native reorder scope (all tasks in the column+swimlane, no status filter). Core's * TaskReorderModel::reorderTasks is protected, so the 3-line position write is replicated here. */ private function reorderByTitle($project_id, $swimlane_id, $column_id, $direction) { $rows = $this->db->table(TaskModel::TABLE) ->columns('id', 'title') ->eq('project_id', $project_id) ->eq('swimlane_id', $swimlane_id) ->eq('column_id', $column_id) ->findAll(); usort($rows, function ($a, $b) use ($direction) { $cmp = strnatcasecmp((string) $a['title'], (string) $b['title']); return $direction === 'desc' ? -$cmp : $cmp; }); $position = 1; foreach ($rows as $row) { $this->db->table(TaskModel::TABLE)->eq('id', $row['id'])->update(array('position' => $position)); $position++; } } }