PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.91.6
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.91.6
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
fluent-boards / app / Services / StageService.php

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

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