PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.0
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
← All changes | app/Services/StageService.php +415 -102 1.132.1.0 View file →
@@ -4,10 +4,17 @@
4 4
5 5 use FluentBoards\App\Models\Task;
6 6 use FluentBoards\App\Models\Board;
7 7 use FluentBoards\App\Models\Stage;
8 +use FluentBoards\App\Models\TaskMeta;
9 +use FluentBoards\App\Models\Relation;
10 +use FluentBoards\App\Models\User;
11 +use FluentBoards\App\App;
8 12 use FluentBoards\Framework\Http\Request\Request;
9 13 use FluentBoards\Framework\Support\Arr;
14 +use FluentBoards\App\Services\Constant;
15 +use FluentBoards\App\Services\AttachmentFileService;
16 +use FluentBoards\App\Services\PermissionManager;
10 17
11 18 class StageService
12 19 {
13 20 public function createDefaultStages($board)
@@ -54,37 +61,141 @@
54 61 ]
55 62 ]
56 63 ];
57 64 }
65 +
66 + /**
67 + * Update one stage property and emit side effects for real title transitions.
68 + *
69 + * @param string $col
70 + * @param mixed $value
71 + * @param int $stageId
72 + * @return Stage
73 + */
58 74 public function updateStageProperty($col, $value, $stageId)
59 75 {
60 76 $stage = Stage::findOrFail($stageId);
77 + $stageBeforeUpdate = clone $stage;
61 78
62 79 if ('title' == $col) {
63 - $this->updateTitle($value, $stage);
80 + $stage = $this->updateTitle($value, $stage);
64 81 } elseif ('status' == $col) {
65 - $this->updateStatus($value, $stage);
82 + $stage = $this->updateStatus($value, $stage);
66 83 } elseif ('color' == $col) {
67 - $this->updateColor($value, $stage);
84 + $stage = $this->updateColor($value, $stage);
68 85 } elseif ('bg_color' == $col) {
69 - $this->updateBackgroundColor($value, $stage);
86 + $stage = $this->updateBackgroundColor($value, $stage);
70 87 } elseif ('archived_at' == $col) {
71 - $this->updateArchivedAt($value, $stage);
88 + $stage = $this->updateArchivedAt($value, $stage);
72 89 }
90 +
91 + if ('title' == $col && $stageBeforeUpdate->title !== $stage->title) {
92 + do_action('fluent_boards/stage_updated', $stage->board_id, [
93 + 'title' => $stage->title,
94 + 'cover_bg' => $stage->bg_color
95 + ], $stageBeforeUpdate);
96 + }
97 +
73 98 return $stage;
74 99 }
75 100
76 - public function getLastOneMinuteUpdatedStages($boardId)
101 + public function getLastOneMinuteUpdatedStages($boardId, $lastUpdated = null, $includeArchived = true)
77 102 {
78 - $oneMinuteAgoTimestamp = current_time('timestamp') - 60;
79 - return Stage::where('board_id', $boardId)
80 - ->where('updated_at', '>=', date_i18n('Y-m-d H:i:s', $oneMinuteAgoTimestamp))
81 - ->get();
103 + if (!$lastUpdated) {
104 + $oneMinuteAgoTimestamp = current_time('timestamp') - 60;
105 + $lastUpdated = date_i18n('Y-m-d H:i:s', $oneMinuteAgoTimestamp);
106 + }
107 +
108 + $stagesQuery = Stage::where('board_id', $boardId)
109 + ->where('updated_at', '>=', $lastUpdated);
110 +
111 + if (!$includeArchived) {
112 + $stagesQuery->whereNull('archived_at');
113 + }
114 +
115 + return $stagesQuery->get();
82 116 }
83 117
118 + /**
119 + * Get archived stages newest-first with archive actor metadata.
120 + *
121 + * @param array $data
122 + * @param int $boardId
123 + * @return mixed
124 + * @throws \Exception
125 + */
126 + public function getArchivedStages($data, $boardId)
127 + {
128 + if (!$boardId) {
129 + throw new \Exception(esc_html__('Board id is required', 'fluent-boards'));
130 + }
131 +
132 + $noPagination = !empty($data['noPagination']);
133 + $perPage = max(1, min(50, absint($data['per_page'] ?? 30)));
134 + $page = max(1, absint($data['page'] ?? 1));
135 +
136 + $stagesQuery = Stage::where('board_id', $boardId)
137 + ->whereNotNull('archived_at')
138 + ->orderBy('archived_at', 'DESC')
139 + ->orderBy('id', 'DESC');
140 +
141 + if ($noPagination) {
142 + $stages = $stagesQuery->get();
143 + } else {
144 + $stages = $stagesQuery->paginate($perPage, ['*'], 'page', $page);
145 + }
146 +
147 + $this->attachArchivedStageActors($stages);
148 +
149 + return $stages;
150 + }
151 +
152 + /**
153 + * Attach nullable archived-by fields from stage settings without per-stage user queries.
154 + *
155 + * @param mixed $stages
156 + * @return void
157 + */
158 + private function attachArchivedStageActors($stages)
159 + {
160 + $archivedByIds = [];
161 +
162 + foreach ($stages as $stage) {
163 + $stage->archived_by_id = null;
164 + $stage->archived_by = null;
165 +
166 + $settings = is_array($stage->settings) ? $stage->settings : [];
167 + $archivedById = !empty($settings['archived_by_id']) ? absint($settings['archived_by_id']) : 0;
168 +
169 + if ($archivedById) {
170 + $stage->archived_by_id = $archivedById;
171 + $archivedByIds[] = $archivedById;
172 + }
173 + }
174 +
175 + $archivedByIds = array_values(array_unique(array_filter($archivedByIds)));
176 +
177 + if (empty($archivedByIds)) {
178 + return;
179 + }
180 +
181 + $users = User::whereIn('ID', $archivedByIds)->get()->keyBy('ID');
182 +
183 + foreach ($stages as $stage) {
184 + if (!$stage->archived_by_id) {
185 + continue;
186 + }
187 +
188 + $user = $users->get($stage->archived_by_id);
189 + $stage->archived_by = $user ? Helper::sanitizeUserCollections($user) : null;
190 + }
191 + }
192 +
84 193 private function updateTitle($value, $stage)
85 194 {
86 -
195 + $stage->title = $value;
196 + $stage->save();
197 + return $stage;
87 198 }
88 199
89 200 private function updateStatus($value, $stage)
90 201 {
@@ -91,23 +202,30 @@
91 202 $oldSettings = $stage->settings;
92 203 $oldSettings['default_task_status'] = $value;
93 204 $stage->settings = $oldSettings;
94 205 $stage->save();
206 + return $stage;
95 207 }
96 208
97 209 private function updateColor($value, $stage)
98 210 {
99 -
211 + $stage->color = $value;
212 + $stage->save();
213 + return $stage;
100 214 }
101 215
102 216 private function updateBackgroundColor($value, $stage)
103 217 {
104 -
218 + $stage->bg_color = $value;
219 + $stage->save();
220 + return $stage;
105 221 }
106 222
107 223 private function updateArchivedAt($value, $stage)
108 224 {
109 -
225 + $stage->archived_at = $value;
226 + $stage->save();
227 + return $stage;
110 228 }
111 229
112 230 public function createStage($stageData, $boardId)
113 231 {
@@ -114,20 +232,18 @@
114 232 $stage = new Stage();
115 233 $stage->board_id = $boardId;
116 234 $stage->title = $stageData['title'];
117 235 $stage->settings = [
118 - 'default_task_status' => Arr::get($stageData, 'status') ?? 'open'
236 + 'default_task_status' => Arr::get($stageData, 'status') ?? 'open',
237 + 'default_task_assignees' => []
119 238 ];
120 - if (!Arr::get($stageData, 'position')) {
121 - $lastStagePosition = $this->getLastPositionOfStagesOfBoard($boardId);
122 - $stage->position = $lastStagePosition ? $lastStagePosition->position + 1 : 1;
123 - $stage->save();
124 - } else {
125 - $stage->position = (int)$stageData['position'];
126 - $stage->save();
127 - $this->moveOtherStages($stage);
239 + $providerPosition = Arr::get($stageData, 'position');
240 + $lastStagePosition = $this->getLastPositionOfStagesOfBoard($boardId);
241 + $stage->position = $lastStagePosition ? $lastStagePosition->position + 1 : 1;
242 + $stage->save();
243 + if($providerPosition){
244 + $stage->moveToNewPosition($providerPosition);
128 245 }
129 -
130 246 return $stage;
131 247 }
132 248
133 249 public function getLastPositionOfStagesOfBoard($boardId)
@@ -152,36 +268,110 @@
152 268 $stage->save();
153 269 }
154 270 }
155 271
156 - public function copyStagesOfBoard($board, $fromBoardId)
272 + /**
273 + * Copy all active stages and their settings to another board in display order.
274 + *
275 + * @param Board $board
276 + * @param int $fromBoardId
277 + * @param string $isWithTemplates
278 + * @param array|null $stageIds
279 + * @return array
280 + */
281 + public function copyStagesOfBoard($board, $fromBoardId, $isWithTemplates = 'no', $stageIds = null)
157 282 {
158 - $stages = Stage::where('board_id', $fromBoardId)->where('type', 'stage')->whereNull('archived_at')->get();
159 - $stageMapForCopyingTask = array();
160 - foreach($stages as $key => $stage)
161 - {
162 - $stageToSave = array();
163 - $stageToSave['title'] = $stage['title'];
164 - $stageToSave['board_id'] = $board->id;
165 - $stageToSave['slug'] = str_replace(' ', '-', strtolower($stage['title']));
166 - $stageToSave['type'] = 'stage';
167 - $stageToSave['position'] = $key + 1;
168 - $stageToSave['settings'] = [
169 - 'default_task_status' => $stage->settings['default_task_status']
283 + $stageQuery = Stage::where('board_id', $fromBoardId)
284 + ->where('type', 'stage')
285 + ->whereNull('archived_at');
286 +
287 + if (is_array($stageIds)) {
288 + $stageIds = array_values(array_filter(array_map('absint', $stageIds)));
289 +
290 + if (!$stageIds) {
291 + return [];
292 + }
293 +
294 + $stageQuery->whereIn('id', $stageIds);
295 + }
296 +
297 + $stages = $stageQuery->orderBy('position', 'asc')->get();
298 + $stageMap = [];
299 +
300 + foreach ($stages as $key => $stage) {
301 + $settings = $stage->settings ?: [];
302 +
303 + // Board copies must not turn their stages into reusable stage templates.
304 + if ($isWithTemplates !== 'yes') {
305 + unset($settings['is_template']);
306 + }
307 +
308 + $stageData = [
309 + 'title' => $stage->title,
310 + 'board_id' => $board->id,
311 + 'slug' => str_replace(' ', '-', strtolower($stage->title)),
312 + 'type' => 'stage',
313 + 'position' => $key + 1,
314 + 'bg_color' => $stage->bg_color,
315 + 'settings' => $settings,
170 316 ];
171 - $newStage = Stage::create($stageToSave);
172 - $stageMapForCopyingTask[$stage['id']] = $newStage->id;
317 +
318 + $newStage = Stage::create($stageData);
319 + $stageMap[$stage->id] = $newStage->id;
173 320 }
174 - return $stageMapForCopyingTask;
321 +
322 + return $stageMap;
175 323 }
176 324
177 - public function importStagesFromBoard($board_id, $selectedStages)
325 + public function importStagesFromBoard($board_id, $selectedStages, $position = null)
178 326 {
179 - $targetStages = Stage::whereIn('id', $selectedStages)->get();
327 + $board_id = absint($board_id);
328 + $selectedStages = array_values(array_unique(array_filter(array_map('absint', (array) $selectedStages))));
329 +
330 + if (!$selectedStages) {
331 + return;
332 + }
333 +
334 + $targetStages = Stage::whereIn('id', $selectedStages)
335 + ->whereNull('archived_at')
336 + ->get();
337 +
338 + if (count($targetStages) !== count($selectedStages)) {
339 + throw new \Exception(esc_html__('Stage not found', 'fluent-boards'));
340 + }
341 +
342 + $sourceBoardIds = array_values(array_unique(array_map('absint', $targetStages->pluck('board_id')->toArray())));
343 +
344 + if (in_array(0, $sourceBoardIds, true)) {
345 + throw new \Exception(esc_html__('Stage not found', 'fluent-boards'));
346 + }
347 +
348 + $userId = get_current_user_id();
349 +
350 + if (!$userId) {
351 + throw new \Exception(esc_html__('Stage not found', 'fluent-boards'));
352 + }
353 +
354 + if (!PermissionManager::isAdmin($userId)) {
355 + $allowedBoardIds = Relation::where('foreign_id', $userId)
356 + ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
357 + ->whereIn('object_id', $sourceBoardIds)
358 + ->pluck('object_id')
359 + ->toArray();
360 +
361 + $allowedBoardIds = array_map('intval', $allowedBoardIds);
362 + $unauthorizedBoardIds = array_filter($sourceBoardIds, function ($sourceBoardId) use ($allowedBoardIds) {
363 + return !in_array($sourceBoardId, $allowedBoardIds, true);
364 + });
365 +
366 + if ($unauthorizedBoardIds) {
367 + throw new \Exception(esc_html__('Stage not found', 'fluent-boards'));
368 + }
369 + }
370 +
180 371 $stageMapForCopyingTask = array();
181 372 $stageIdsToCopy = array();
182 373
183 - //need to define position of stage
184 374 $numberOfStages = Stage::where('board_id', $board_id)->whereNull('archived_at')->count();
185 375
186 376 foreach($targetStages as $key => $stage)
187 377 {
@@ -194,8 +384,11 @@
194 384 $stageToSave['settings'] = [
195 385 'default_task_status' => $stage->settings['default_task_status']
196 386 ];
197 387 $newStage = Stage::create($stageToSave);
388 + if($position) {
389 + $newStage->moveToNewPosition( (int) $position + $key );
390 + }
198 391 $stageMapForCopyingTask[$stage['id']] = $newStage->id;
199 392 $stageIdsToCopy[] = $stage['id'];
200 393 }
201 394
@@ -203,36 +396,84 @@
203 396 }
204 397
205 398 public function importTasks($boardId, $stageMapper, $stageIds)
206 399 {
207 - $tasksToImport = Task::whereIn('stage_id', $stageIds)->whereNull('archived_at')->get();
400 + $tasksToImport = $this->getAllParentAndSubTasksOfStages($stageIds);
401 + $subtaskGroupChildRelations = [];
402 + $taskIdsToImport = $tasksToImport->pluck('id')->toArray();
208 403
209 - foreach($tasksToImport as $task)
210 - {
211 - $newTask = array();
212 - $newTask['title'] = $task->title;
213 - $newTask['description'] = $task->description;
214 - $newTask['board_id'] = $boardId;
215 - $newTask['stage_id'] = $stageMapper[$task->stage_id];
216 - $newTask['status'] = $task->status;
217 - $newTask['priority'] = $task->priority;
218 - $newTask['position'] = $task->position;
219 - $newTask['due_at'] = $task->due_at;
220 - Task::create($newTask);
404 + if ($taskIdsToImport) {
405 + foreach (TaskMeta::where('key', Constant::SUBTASK_GROUP_CHILD)
406 + ->whereIn('task_id', $taskIdsToImport)
407 + ->get() as $relation) {
408 + $subtaskGroupChildRelations[$relation->task_id] = $relation;
409 + }
221 410 }
411 + $taskMap = [];
412 + $subtaskGroupMap = [];
413 + $attachmentFileService = new AttachmentFileService();
414 + $dbInstance = App::getInstance('db');
222 415
223 - //update task count of board
224 - $totalTasks = sizeof($tasksToImport);
225 - $board = Board::findOrFail($boardId);
226 - $settings = $board->settings ?? [];
416 + $dbInstance->beginTransaction();
227 417
228 - if (isset($settings['tasks_count'])) {
229 - $settings['tasks_count'] += $totalTasks;
230 - } else {
231 - $settings['tasks_count'] = $totalTasks;
418 + try {
419 + foreach($tasksToImport as $task)
420 + {
421 + $newTask = array();
422 + $newTask['title'] = $task->title;
423 + $newTask['parent_id'] = $task->parent_id ? $taskMap[$task->parent_id] : null;
424 + $newTask['description'] = $task->description;
425 + $newTask['board_id'] = $boardId;
426 + $newTask['stage_id'] = $stageMapper[$task->stage_id];
427 + $newTask['status'] = $task->status;
428 + $newTask['priority'] = $task->priority;
429 + $newTask['position'] = $task->position;
430 + $newTask['due_at'] = $task->due_at;
431 + $backgroundColor = $task->settings['cover']['backgroundColor'] ?? '';
432 + $newTask['settings'] = [
433 + 'cover' => [
434 + 'backgroundColor' => $backgroundColor,
435 + ]
436 + ];
437 + $newTask = Task::create($newTask);
438 + $attachmentFileService->cloneTaskFilesToBoard($task, $newTask, $boardId);
439 +
440 + if (!$task->parent_id) {
441 + $taskMap[$task->id] = $newTask->id;
442 + //group mapping
443 + $subtaskGroupMap = (new TaskService())->copySubtaskGroup($task, $newTask, $subtaskGroupMap);
444 + } else {
445 + $groupRelationOfTask = $subtaskGroupChildRelations[$task->id] ?? null;
446 +
447 + if ($groupRelationOfTask && $subtaskGroupMap[$groupRelationOfTask->value]) {
448 + TaskMeta::create([
449 + 'task_id' => $newTask->id,
450 + 'key' => Constant::SUBTASK_GROUP_CHILD,
451 + 'value' => $subtaskGroupMap[$groupRelationOfTask->value]
452 + ]);
453 + }
454 + }
455 + }
456 +
457 + //update task count of board
458 + $totalTasks = sizeof($tasksToImport);
459 + $board = Board::findOrFail($boardId);
460 + $settings = $board->settings ?? [];
461 +
462 + if (isset($settings['tasks_count'])) {
463 + $settings['tasks_count'] += $totalTasks;
464 + } else {
465 + $settings['tasks_count'] = $totalTasks;
466 + }
467 + $board->settings = $settings;
468 + $board->save();
469 +
470 + $dbInstance->commit();
471 + } catch (\Exception $e) {
472 + $dbInstance->rollBack();
473 + $attachmentFileService->rollbackCreatedFiles();
474 + throw $e;
232 475 }
233 - $board->settings = $settings;
234 - $board->save();
235 476 }
236 477
237 478 public function updateStageTemplate($stage_id)
238 479 {
@@ -267,28 +508,67 @@
267 508
268 509 public function moveAllTasks($oldStageId, $newStageId)
269 510 {
270 511 $tasks = Task::where('stage_id', $oldStageId)->whereNull('parent_id')->whereNull('archived_at')->get();
512 + $sourceStage = Stage::findOrFail($oldStageId);
513 + $targetStage = Stage::findOrFail($newStageId);
271 514
515 + // Load shared watcher data once; stage data is injected per task below.
516 + $tasks->load('watchers');
517 +
272 518 // get the last position available of that stage
273 519 $position = (new TaskService())->getLastPositionOfTasks($newStageId);
274 520
275 521 // update tasks stage and position
276 522 foreach ($tasks as $key => $task) {
523 + $taskOldStageId = $task->stage_id;
524 +
525 + // Clean up archived_by_stage meta when moving to different stage
526 + TaskMeta::where('task_id', $task->id)
527 + ->where('key', Constant::META_KEY_ARCHIVED_BY_STAGE)
528 + ->delete();
529 +
277 530 $task->stage_id = $newStageId;
278 531 $task->position = $position + $key;
532 + $task->setRelation('stage', $targetStage);
279 533 $task->save();
534 +
535 + if ((int) $taskOldStageId !== (int) $newStageId) {
536 + do_action('fluent_boards/task_stage_updated', $task, $taskOldStageId, [
537 + 'source' => $sourceStage,
538 + 'target' => $targetStage,
539 + ]);
540 + }
280 541 }
542 +
543 + if ($tasks->count() && (int) $sourceStage->id !== (int) $targetStage->id) {
544 + do_action(
545 + 'fluent_boards/tasks_moved_between_stages',
546 + $sourceStage->board_id,
547 + $sourceStage,
548 + $targetStage,
549 + $tasks->count()
550 + );
551 + }
552 +
281 553 return $tasks;
282 554 }
283 - public function archiveAllTasksInStage($stage_id)
555 + public function archiveAllTasksInStage($stage_id, $boardId = null)
284 556 {
285 - $tasks = Task::where('stage_id', $stage_id)->whereNull('parent_id')->whereNull('archived_at')->get();
557 + $tasksQuery = Task::where('stage_id', absint($stage_id))
558 + ->whereNull('parent_id')
559 + ->whereNull('archived_at');
560 +
561 + if ($boardId) {
562 + $tasksQuery->where('board_id', absint($boardId));
563 + }
564 +
565 + $tasks = $tasksQuery->get();
286 566 foreach ($tasks as $task) {
287 567 $task->position = 0;
288 568 $task->archived_at = current_time('mysql');
289 569 $task->save();
290 - do_action('fluent_boards/board_task_archived', $task);
570 + do_action('fluent_boards/task_archived', $task);
291 571 }
292 572 return $tasks;
293 573 }
294 574
@@ -293,15 +573,15 @@
293 573 }
294 574
295 575 public function createRoadmapStages($board, $stagesData)
296 576 {
297 - foreach ($stagesData as $formStageData) {
577 + foreach ($stagesData as $index => $formStageData) {
298 578 $stage = new Stage();
299 579 $stage->board_id = $board->id;
300 580 $stage->title = $formStageData['title'];
301 581 $stage->slug = $formStageData['slug'];
302 582 $stage->position = $formStageData['position'] ? $formStageData['position'] : 1;
303 - $stage->settings = $this->roadmapStageSetting();
583 + $stage->settings = $this->roadmapStageSetting($index);
304 584 $stage->save();
305 585 }
306 586 return $stagesData;
307 587 }
@@ -309,12 +589,12 @@
309 589 /*
310 590 * I have no idea what this function does, but I am doing it
311 591 * to make the code work
312 592 */
313 - public function roadmapStageSetting()
593 + public function roadmapStageSetting($index)
314 594 {
315 595 return [
316 - 'is_public' => true,
596 + 'is_public' => $index > 0 ? true : false,
317 597 'default_task_status' => 'open',
318 598 'is_template' => false,
319 599 ];
320 600 }
@@ -328,16 +608,14 @@
328 608 $stageToPush['title'] = $stage['title'];
329 609 $stageToPush['board_id'] = $board->id;
330 610 $stageToPush['position'] = $index + 1;
331 611 $stageToPush['slug'] = $this->createSlug($stage['title']);
612 + $closedStageTitles = ['completed', 'done'];
613 + $stageToPush['settings'] = [
614 + 'default_task_status' => in_array(strtolower(Arr::get($stage, 'title')), $closedStageTitles) ? 'closed' : 'open',
615 + 'is_template' => false
616 + ];
332 617
333 - if (Arr::get($stage, 'title') == 'Completed') {
334 - $stageToPush['settings'] = [
335 - 'default_task_status' => 'closed',
336 - 'is_template' => false
337 - ];
338 - }
339 -
340 618 $stage = Stage::create($stageToPush);
341 619 if($index == 0){
342 620 $firstStage = $stage;
343 621 }
@@ -351,9 +629,9 @@
351 629 }
352 630
353 631 public function stagesByBoardId($boardId)
354 632 {
355 - return Stage::where('board_id', $boardId)->whereNull('archived_at')->get();
633 + return Stage::where('board_id', $boardId)->whereNull('archived_at')->orderBy('position', 'asc')->get();
356 634 }
357 635
358 636
359 637
@@ -363,9 +641,9 @@
363 641 $orderOptions = ['ASC', 'DESC'];
364 642
365 643 // Validate order and orderBy parameters
366 644 if (!in_array($order, $sortOptions) || !in_array($orderBy, $orderOptions)) {
367 - throw new \Exception(__('Invalid sort or orderBy parameter', 'fluent-boards'));
645 + throw new \Exception(esc_html__('Invalid sort or orderBy parameter', 'fluent-boards'));
368 646 }
369 647
370 648 $tasksQuery = Task::where('stage_id', $stage_id)
371 649 ->whereNull('parent_id')
@@ -374,9 +652,9 @@
374 652
375 653 // Apply ordering based on the specified order and orderBy
376 654 switch ($order) {
377 655 case 'priority':
378 - $tasksQuery->orderByRaw("FIELD(priority, 'High', 'Medium', 'Low') {$orderBy}");
656 + $tasksQuery->orderByRaw("FIELD(priority, 'urgent', 'high', 'medium', 'low') {$orderBy}");
379 657 break;
380 658
381 659 case 'due_at':
382 660 if ($orderBy === 'ASC') {
@@ -410,31 +688,66 @@
410 688 });
411 689 return $tasks;
412 690 }
413 691
414 - public function checkStageEditable($stages, $toBeUpdatedStage)
692 +
693 + public function updateStage($updatedStage, $board_id, $oldStage)
415 694 {
416 - $stageEditable = true;
417 - if (empty(sanitize_text_field($toBeUpdatedStage['title']))) {
418 - $stageEditable = false;
419 - } else {
420 - foreach ($stages as $stage) { // check if stage label is similar to any other stage label
421 - if (strtolower($stage->title) == strtolower(trim($toBeUpdatedStage['title']))) {
422 - $stageEditable = false;
423 - break;
424 - }
425 - }
695 + $stageBeforeUpdate = clone $oldStage;
696 + $oldStage->title = sanitize_text_field(Arr::get($updatedStage, 'title'));
697 + $oldStage->bg_color = sanitize_text_field(Arr::get($updatedStage, 'cover_bg'));
698 + $oldStage->save();
699 + do_action('fluent_boards/stage_updated', $board_id, $updatedStage, $stageBeforeUpdate);
700 + return $oldStage;
701 + }
702 +
703 + public function setDefaultAssignees($stage_id, $assignees)
704 + {
705 + $stage = Stage::findOrFail($stage_id);
706 + if ($stage) {
707 + $settings = $stage->settings;
708 + $settings['default_task_assignees'] = $assignees;
709 + $stage->settings = $settings;
710 + $stage->save();
711 +
712 + do_action('fluent_boards/default_assignees_updated', $stage, $assignees);
713 +
714 + return $stage;
426 715 }
716 + }
427 717
428 - return $stageEditable;
718 + public function setDefaultWatchers($stage_id, $watchers)
719 + {
720 + $stage = Stage::findOrFail($stage_id);
721 + if ($stage) {
722 + $settings = $stage->settings;
723 + $settings['default_task_watchers'] = $watchers;
724 + $stage->settings = $settings;
725 + $stage->save();
726 +
727 + do_action('fluent_boards/default_watchers_updated', $stage, $watchers);
728 +
729 + return $stage;
730 + }
429 731 }
430 732
431 - public function updateStage($updatedStage, $board_id, $oldStage)
733 + public function getAllParentAndSubTasksOfStages($stageIds)
432 734 {
433 - $oldTitle = $oldStage->title;
434 - $oldStage->title = Arr::get($updatedStage, 'title');
435 - $oldStage->bg_color = Arr::get($updatedStage, 'cover_bg');
436 - $oldStage->save();
437 - do_action('fluent_boards/board_stage_updated', $board_id, $updatedStage['title'], $oldTitle);
438 - return $oldStage;
735 + // Fetch parent task IDs for the given stage_ids
736 + $parentTaskIds = Task::whereIn('stage_id', $stageIds)
737 + ->whereNull('archived_at')
738 + ->whereNull('parent_id')
739 + ->pluck('id');
740 +
741 + // Fetch parent tasks and subtasks in a single query
742 + $tasks = Task::whereIn('stage_id', $stageIds)
743 + ->whereNull('parent_id')
744 + ->whereNull('archived_at')
745 + ->get();
746 +
747 + $subtasks = Task::whereIn('parent_id', $parentTaskIds)
748 + ->get();
749 +
750 + // Combine parent tasks and subtasks into a single collection
751 + return $tasks->merge($subtasks);
439 752 }
440 753 }