PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.95
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.95
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 1.45 All 41 releases
fluent-boards / app / Services / StageService.php

StageService.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.95, at app/Services/StageService.php

555 lines 18.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Services;
4
5 use FluentBoards\App\Models\Task;
6 use FluentBoards\App\Models\Board;
7 use FluentBoards\App\Models\Stage;
8 use FluentBoards\App\Models\TaskMeta;
9 use FluentBoards\App\App;
10 use FluentBoards\Framework\Http\Request\Request;
11 use FluentBoards\Framework\Support\Arr;
12 use FluentBoards\App\Services\Constant;
13 use FluentBoards\App\Services\AttachmentFileService;
14
15 class StageService
16 {
17 public function createDefaultStages($board)
18 {
19 $stages = $this->defaultStages($board);
20 foreach ($stages as $stage) {
21 Stage::create($stage);
22 }
23 }
24
25 public function defaultStages($board)
26 {
27 return $this->defaultStagesForTodos($board->id);
28 }
29
30 public function defaultStagesForTodos($boardId)
31 {
32 return [
33 [
34 'board_id' => $boardId,
35 'title' => 'Open',
36 'position' => 1,
37 'slug' => 'open',
38 'settings' => [
39 'default_task_status' => 'open'
40 ]
41 ],
42 [
43 'board_id' => $boardId,
44 'title' => 'In Progress',
45 'position' => 2,
46 'slug' => 'in-progress',
47 'settings' => [
48 'default_task_status' => 'open'
49 ]
50 ],
51 [
52 'board_id' => $boardId,
53 'title' => 'Completed',
54 'position' => 3,
55 'slug' => 'completed',
56 'settings' => [
57 'default_task_status' => 'closed'
58 ]
59 ]
60 ];
61 }
62 public function updateStageProperty($col, $value, $stageId)
63 {
64 $stage = Stage::findOrFail($stageId);
65
66 if ('title' == $col) {
67 $stage = $this->updateTitle($value, $stage);
68 } elseif ('status' == $col) {
69 $stage = $this->updateStatus($value, $stage);
70 } elseif ('color' == $col) {
71 $stage = $this->updateColor($value, $stage);
72 } elseif ('bg_color' == $col) {
73 $stage = $this->updateBackgroundColor($value, $stage);
74 } elseif ('archived_at' == $col) {
75 $stage = $this->updateArchivedAt($value, $stage);
76 }
77 return $stage;
78 }
79
80 public function getLastOneMinuteUpdatedStages($boardId, $lastUpdated = null, $includeArchived = true)
81 {
82 if (!$lastUpdated) {
83 $oneMinuteAgoTimestamp = current_time('timestamp') - 60;
84 $lastUpdated = date_i18n('Y-m-d H:i:s', $oneMinuteAgoTimestamp);
85 }
86
87 $stagesQuery = Stage::where('board_id', $boardId)
88 ->where('updated_at', '>=', $lastUpdated);
89
90 if (!$includeArchived) {
91 $stagesQuery->whereNull('archived_at');
92 }
93
94 return $stagesQuery->get();
95 }
96
97 private function updateTitle($value, $stage)
98 {
99 $stage->title = $value;
100 $stage->save();
101 return $stage;
102 }
103
104 private function updateStatus($value, $stage)
105 {
106 $oldSettings = $stage->settings;
107 $oldSettings['default_task_status'] = $value;
108 $stage->settings = $oldSettings;
109 $stage->save();
110 return $stage;
111 }
112
113 private function updateColor($value, $stage)
114 {
115 $stage->color = $value;
116 $stage->save();
117 return $stage;
118 }
119
120 private function updateBackgroundColor($value, $stage)
121 {
122 $stage->bg_color = $value;
123 $stage->save();
124 return $stage;
125 }
126
127 private function updateArchivedAt($value, $stage)
128 {
129 $stage->archived_at = $value;
130 $stage->save();
131 return $stage;
132 }
133
134 public function createStage($stageData, $boardId)
135 {
136 $stage = new Stage();
137 $stage->board_id = $boardId;
138 $stage->title = $stageData['title'];
139 $stage->settings = [
140 'default_task_status' => Arr::get($stageData, 'status') ?? 'open',
141 'default_task_assignees' => []
142 ];
143 $providerPosition = Arr::get($stageData, 'position');
144 $lastStagePosition = $this->getLastPositionOfStagesOfBoard($boardId);
145 $stage->position = $lastStagePosition ? $lastStagePosition->position + 1 : 1;
146 $stage->save();
147 if($providerPosition){
148 $stage->moveToNewPosition($providerPosition);
149 }
150 return $stage;
151 }
152
153 public function getLastPositionOfStagesOfBoard($boardId)
154 {
155 // return last position of stages of board
156 return Stage::where('board_id', $boardId)
157 ->whereNull('archived_at')
158 ->orderBy('position', 'desc')
159 ->first();
160 }
161
162 protected function moveOtherStages($stage)
163 {
164
165 $stages = Stage::where('board_id', $stage->board_id)
166 ->where('position', '>=', $stage->position)
167 ->whereNotIn('id', [$stage->id])
168 ->whereNull('archived_at')->get();
169
170 foreach ($stages as $stage) {
171 $stage->position = $stage->position + 1;
172 $stage->save();
173 }
174 }
175
176 public function copyStagesOfBoard($board, $fromBoardId, $isWithTemplates='no')
177 {
178 $stages = Stage::where('board_id', $fromBoardId)->where('type', 'stage')->whereNull('archived_at')->orderBy('position', 'asc')->get();
179 $stageMapForCopyingTask = array();
180 foreach($stages as $key => $stage)
181 {
182 $stageToSave = array();
183 $stageToSave['title'] = $stage['title'];
184 $stageToSave['board_id'] = $board->id;
185 $stageToSave['slug'] = str_replace(' ', '-', strtolower($stage['title']));
186 $stageToSave['type'] = 'stage';
187 $stageToSave['position'] = $key + 1;
188 $stageToSave['bg_color'] = $stage['bg_color'];
189 $stageToSave['settings'] = [
190 'default_task_status' => $stage->settings['default_task_status']
191 ];
192 if (!empty($stage->settings['is_template']) && $isWithTemplates == 'yes') {
193 $stageToSave['settings']['is_template'] = $stage->settings['is_template'];
194 }
195 $newStage = Stage::create($stageToSave);
196 $stageMapForCopyingTask[$stage['id']] = $newStage->id;
197 }
198 return $stageMapForCopyingTask;
199 }
200
201 public function importStagesFromBoard($board_id, $selectedStages, $position = null)
202 {
203 $targetStages = Stage::whereIn('id', $selectedStages)->get();
204 $stageMapForCopyingTask = array();
205 $stageIdsToCopy = array();
206
207 $numberOfStages = Stage::where('board_id', $board_id)->whereNull('archived_at')->count();
208
209 foreach($targetStages as $key => $stage)
210 {
211 $stageToSave = array();
212 $stageToSave['title'] = $stage['title'] . ' - imported';
213 $stageToSave['board_id'] = $board_id;
214 $stageToSave['slug'] = str_replace(' ', '-', strtolower($stage['title']));
215 $stageToSave['type'] = 'stage';
216 $stageToSave['position'] = $numberOfStages + $key + 1;
217 $stageToSave['settings'] = [
218 'default_task_status' => $stage->settings['default_task_status']
219 ];
220 $newStage = Stage::create($stageToSave);
221 if($position) {
222 $newStage->moveToNewPosition( (int) $position + $key );
223 }
224 $stageMapForCopyingTask[$stage['id']] = $newStage->id;
225 $stageIdsToCopy[] = $stage['id'];
226 }
227
228 $this->importTasks($board_id, $stageMapForCopyingTask, $stageIdsToCopy);
229 }
230
231 public function importTasks($boardId, $stageMapper, $stageIds)
232 {
233 $tasksToImport = $this->getAllParentAndSubTasksOfStages($stageIds);
234 $subtaskGroupChildRelations = [];
235 $taskIdsToImport = $tasksToImport->pluck('id')->toArray();
236
237 if ($taskIdsToImport) {
238 foreach (TaskMeta::where('key', Constant::SUBTASK_GROUP_CHILD)
239 ->whereIn('task_id', $taskIdsToImport)
240 ->get() as $relation) {
241 $subtaskGroupChildRelations[$relation->task_id] = $relation;
242 }
243 }
244 $taskMap = [];
245 $subtaskGroupMap = [];
246 $attachmentFileService = new AttachmentFileService();
247 $dbInstance = App::getInstance('db');
248
249 $dbInstance->beginTransaction();
250
251 try {
252 foreach($tasksToImport as $task)
253 {
254 $newTask = array();
255 $newTask['title'] = $task->title;
256 $newTask['parent_id'] = $task->parent_id ? $taskMap[$task->parent_id] : null;
257 $newTask['description'] = $task->description;
258 $newTask['board_id'] = $boardId;
259 $newTask['stage_id'] = $stageMapper[$task->stage_id];
260 $newTask['status'] = $task->status;
261 $newTask['priority'] = $task->priority;
262 $newTask['position'] = $task->position;
263 $newTask['due_at'] = $task->due_at;
264 $backgroundColor = $task->settings['cover']['backgroundColor'] ?? '';
265 $newTask['settings'] = [
266 'cover' => [
267 'backgroundColor' => $backgroundColor,
268 ]
269 ];
270 $newTask = Task::create($newTask);
271 $attachmentFileService->cloneTaskFilesToBoard($task, $newTask, $boardId);
272
273 if (!$task->parent_id) {
274 $taskMap[$task->id] = $newTask->id;
275 //group mapping
276 $subtaskGroupMap = (new TaskService())->copySubtaskGroup($task, $newTask, $subtaskGroupMap);
277 } else {
278 $groupRelationOfTask = $subtaskGroupChildRelations[$task->id] ?? null;
279
280 if ($groupRelationOfTask && $subtaskGroupMap[$groupRelationOfTask->value]) {
281 TaskMeta::create([
282 'task_id' => $newTask->id,
283 'key' => Constant::SUBTASK_GROUP_CHILD,
284 'value' => $subtaskGroupMap[$groupRelationOfTask->value]
285 ]);
286 }
287 }
288 }
289
290 //update task count of board
291 $totalTasks = sizeof($tasksToImport);
292 $board = Board::findOrFail($boardId);
293 $settings = $board->settings ?? [];
294
295 if (isset($settings['tasks_count'])) {
296 $settings['tasks_count'] += $totalTasks;
297 } else {
298 $settings['tasks_count'] = $totalTasks;
299 }
300 $board->settings = $settings;
301 $board->save();
302
303 $dbInstance->commit();
304 } catch (\Exception $e) {
305 $dbInstance->rollBack();
306 $attachmentFileService->rollbackCreatedFiles();
307 throw $e;
308 }
309 }
310
311 public function updateStageTemplate($stage_id)
312 {
313 $stage = Stage::findOrFail($stage_id);
314 $stageSettings = $stage->settings;
315 if($stageSettings && array_key_exists('is_template', $stageSettings))
316 {
317 $currentlyIsTemplate = $stageSettings['is_template'];
318 if($currentlyIsTemplate){
319 $stageSettings['is_template'] = false;
320 $stage->settings = $stageSettings;
321 }else{
322 $stageSettings['is_template'] = true;
323 $stage->settings = $stageSettings;
324 }
325 }else {
326 if(!$stageSettings) {
327 $stage->settings = [
328 'is_template' => true
329 ];
330 } else {
331 $stage->settings = array_merge($stage->settings, [
332 'is_template' => true
333 ]);
334 }
335
336 }
337 $stage->save();
338
339 return $stage;
340 }
341
342 public function moveAllTasks($oldStageId, $newStageId)
343 {
344 $tasks = Task::where('stage_id', $oldStageId)->whereNull('parent_id')->whereNull('archived_at')->get();
345
346 // get the last position available of that stage
347 $position = (new TaskService())->getLastPositionOfTasks($newStageId);
348
349 // update tasks stage and position
350 foreach ($tasks as $key => $task) {
351 // Clean up archived_by_stage meta when moving to different stage
352 TaskMeta::where('task_id', $task->id)
353 ->where('key', Constant::META_KEY_ARCHIVED_BY_STAGE)
354 ->delete();
355
356 $task->stage_id = $newStageId;
357 $task->position = $position + $key;
358 $task->save();
359 }
360 return $tasks;
361 }
362 public function archiveAllTasksInStage($stage_id)
363 {
364 $tasks = Task::where('stage_id', $stage_id)->whereNull('parent_id')->whereNull('archived_at')->get();
365 foreach ($tasks as $task) {
366 $task->position = 0;
367 $task->archived_at = current_time('mysql');
368 $task->save();
369 do_action('fluent_boards/task_archived', $task);
370 }
371 return $tasks;
372 }
373
374 public function createRoadmapStages($board, $stagesData)
375 {
376 foreach ($stagesData as $index => $formStageData) {
377 $stage = new Stage();
378 $stage->board_id = $board->id;
379 $stage->title = $formStageData['title'];
380 $stage->slug = $formStageData['slug'];
381 $stage->position = $formStageData['position'] ? $formStageData['position'] : 1;
382 $stage->settings = $this->roadmapStageSetting($index);
383 $stage->save();
384 }
385 return $stagesData;
386 }
387
388 /*
389 * I have no idea what this function does, but I am doing it
390 * to make the code work
391 */
392 public function roadmapStageSetting($index)
393 {
394 return [
395 'is_public' => $index > 0 ? true : false,
396 'default_task_status' => 'open',
397 'is_template' => false,
398 ];
399 }
400
401
402 public function createStages($board, $stageData)
403 {
404 $firstStage = null;
405 foreach ($stageData as $index => $stage) {
406 $stageToPush = array();
407 $stageToPush['title'] = $stage['title'];
408 $stageToPush['board_id'] = $board->id;
409 $stageToPush['position'] = $index + 1;
410 $stageToPush['slug'] = $this->createSlug($stage['title']);
411
412 if (Arr::get($stage, 'title') == 'Completed') {
413 $stageToPush['settings'] = [
414 'default_task_status' => 'closed',
415 'is_template' => false
416 ];
417 }
418
419 $stage = Stage::create($stageToPush);
420 if($index == 0){
421 $firstStage = $stage;
422 }
423 }
424 return $firstStage;
425 }
426
427 private function createSlug($title)
428 {
429 return str_replace(' ', '-', strtolower($title));
430 }
431
432 public function stagesByBoardId($boardId)
433 {
434 return Stage::where('board_id', $boardId)->whereNull('archived_at')->orderBy('position', 'asc')->get();
435 }
436
437
438
439 public function sortStageTasks($order, $orderBy, $stage_id)
440 {
441 $sortOptions = ['priority', 'due_at', 'position', 'created_at', 'title'];
442 $orderOptions = ['ASC', 'DESC'];
443
444 // Validate order and orderBy parameters
445 if (!in_array($order, $sortOptions) || !in_array($orderBy, $orderOptions)) {
446 throw new \Exception(esc_html__('Invalid sort or orderBy parameter', 'fluent-boards'));
447 }
448
449 $tasksQuery = Task::where('stage_id', $stage_id)
450 ->whereNull('parent_id')
451 ->whereNull('archived_at')
452 ->with(['assignees', 'labels', 'watchers']);
453
454 // Apply ordering based on the specified order and orderBy
455 switch ($order) {
456 case 'priority':
457 $tasksQuery->orderByRaw("FIELD(priority, 'High', 'Medium', 'Low') {$orderBy}");
458 break;
459
460 case 'due_at':
461 if ($orderBy === 'ASC') {
462 // Separate ordering for tasks with and without due dates
463 $tasksWithDueDate = (clone $tasksQuery)->whereNotNull('due_at')->orderBy('due_at')->get();
464 $tasksWithoutDueDate = (clone $tasksQuery)->whereNull('due_at')->get();
465 $tasks = $tasksWithDueDate->merge($tasksWithoutDueDate);
466 } else {
467 $tasksQuery->orderBy('due_at', $orderBy);
468 }
469 break;
470
471 default:
472 $tasksQuery->orderBy($order, $orderBy);
473 break;
474 }
475
476 // Fetch tasks if not already fetched
477 if (!isset($tasks)) {
478 $tasks = $tasksQuery->get();
479 }
480
481 // Update tasks with additional attributes
482 $tasks->each(function ($task, $key) {
483 $task->position = $key + 1;
484 $task->save();
485 $task->isOverdue = $task->isOverdue();
486 $task->isUpcoming = $task->upcoming();
487 $task->is_watching = $task->isWatching();
488 $task->contact = Task::lead_contact($task->crm_contact_id);
489 });
490 return $tasks;
491 }
492
493
494 public function updateStage($updatedStage, $board_id, $oldStage)
495 {
496 $stageBeforeUpdate = clone $oldStage;
497 $oldStage->title = sanitize_text_field(Arr::get($updatedStage, 'title'));
498 $oldStage->bg_color = sanitize_text_field(Arr::get($updatedStage, 'cover_bg'));
499 $oldStage->save();
500 do_action('fluent_boards/stage_updated', $board_id, $updatedStage, $stageBeforeUpdate);
501 return $oldStage;
502 }
503
504 public function setDefaultAssignees($stage_id, $assignees)
505 {
506 $stage = Stage::findOrFail($stage_id);
507 if ($stage) {
508 $settings = $stage->settings;
509 $settings['default_task_assignees'] = $assignees;
510 $stage->settings = $settings;
511 $stage->save();
512
513 do_action('fluent_boards/default_assignees_updated', $stage, $assignees);
514
515 return $stage;
516 }
517 }
518
519 public function setDefaultWatchers($stage_id, $watchers)
520 {
521 $stage = Stage::findOrFail($stage_id);
522 if ($stage) {
523 $settings = $stage->settings;
524 $settings['default_task_watchers'] = $watchers;
525 $stage->settings = $settings;
526 $stage->save();
527
528 do_action('fluent_boards/default_watchers_updated', $stage, $watchers);
529
530 return $stage;
531 }
532 }
533
534 public function getAllParentAndSubTasksOfStages($stageIds)
535 {
536 // Fetch parent task IDs for the given stage_ids
537 $parentTaskIds = Task::whereIn('stage_id', $stageIds)
538 ->whereNull('archived_at')
539 ->whereNull('parent_id')
540 ->pluck('id');
541
542 // Fetch parent tasks and subtasks in a single query
543 $tasks = Task::whereIn('stage_id', $stageIds)
544 ->whereNull('parent_id')
545 ->whereNull('archived_at')
546 ->get();
547
548 $subtasks = Task::whereIn('parent_id', $parentTaskIds)
549 ->get();
550
551 // Combine parent tasks and subtasks into a single collection
552 return $tasks->merge($subtasks);
553 }
554 }
555