PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.31
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.31
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 / Http / Controllers / TaskController.php

TaskController.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.31, at app/Http/Controllers/TaskController.php

503 lines 17.2 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\Http\Controllers;
4
5 use DateTimeImmutable;
6 use FluentBoards\App\Models\Stage;
7 use FluentBoards\App\Models\Task;
8 use FluentBoards\App\Models\Board;
9 use FluentBoards\App\Services\Constant;
10 use FluentBoards\App\Services\Helper;
11 use FluentBoards\App\Services\StageService;
12 use FluentBoards\App\Services\TaskService;
13 use FluentBoards\App\Services\NotificationService;
14 use FluentBoards\App\Services\UploadService;
15 use FluentBoards\Framework\Http\Request\Request;
16 use FluentBoards\App\Services\PermissionManager;
17 use FluentBoards\Framework\Support\Arr;
18 class TaskController extends Controller
19 {
20 private TaskService $taskService;
21
22 private NotificationService $notificationService;
23
24 public function __construct(TaskService $taskService, NotificationService $notificationService)
25 {
26
27 parent::__construct();
28 $this->taskService = $taskService;
29 $this->notificationService = $notificationService;
30 }
31
32 public function getTopTasksForBoards()
33 {
34 $userId = get_current_user_id();
35 $task_ids = PermissionManager::getTaskIdsWatchByUser($userId);
36 $tasksArray = $this->taskService->getTasksForBoards(['overdue', 'upcoming'], 6, $task_ids);
37
38 return [
39 'data' => $tasksArray,
40 ];
41 }
42
43 public function getTasksByBoard($board_id)
44 {
45 $board = Board::findOrFail($board_id);
46
47 $tasks = Task::with(['assignees', 'labels', 'watchers', 'taskCustomFields'])
48 ->where('board_id', $board_id)
49 ->whereNull('archived_at')
50 ->whereNull('parent_id')
51 ->orderBy('due_at', 'ASC')
52 ->get();
53
54 foreach ($tasks as $task) {
55 $task->isOverdue = $task->isOverdue();
56 $task->isUpcoming = $task->upcoming();
57 $task->contact = Helper::crm_contact($task->crm_contact_id);
58 $task->is_watching = $task->isWatching();
59 $task->assignees = Helper::sanitizeUserCollections($task->assignees);
60 $task->watchers = Helper::sanitizeUserCollections($task->watchers);
61 $task->notifications = $this->notificationService->getUnreadNotificationsOfTasks($task);
62 if($board->type == 'roadmap') {
63 $task->popular = $task->getPopularCount();
64 }
65 }
66
67 return [
68 'tasks' => $tasks,
69 ];
70 }
71
72 public function create(Request $request, $board_id)
73 {
74 $taskData = $this->taskSanitizeAndValidate($request->get('task'), [
75 'title' => 'required|string',
76 'board_id' => 'required|numeric',
77 'stage_id' => 'required|numeric',
78 'priority' => 'nullable|string',
79 'crm_contact_id' => 'nullable|numeric',
80 'is_template' => 'string',
81 ]);
82
83 try {
84 if ($taskData['board_id'] != $board_id) {
85 throw new \Exception(__('Board id is not valid', 'fluent-boards'));
86 }
87
88 $task = $this->taskService->createTask($taskData, $board_id);
89
90 return $this->sendSuccess([
91 'task' => $task,
92 'message' => __('Task has been successfully created', 'fluent-boards'),
93 'updatedTasks' => $this->taskService->getLastOneMinuteUpdatedTasks($task->board_id)
94 ], 201);
95 } catch (\Exception $e) {
96 return $this->sendError($e->getMessage(), 400);
97 }
98 }
99
100 public function find($board_id, $task_id)
101 {
102 try {
103
104 $stageService = new StageService();
105
106 $task = Task::findOrFail($task_id);
107
108 if(!$task) {
109 throw new \Exception(__('Task not found', 'fluent-boards'));
110 }
111 if (defined('FLUENT_BOARDS_PRO')) {
112 $task->load(['attachments']);
113 }
114
115 $task->load(['board', 'stage', 'labels', 'assignees']);
116
117 $task->assignees = Helper::sanitizeUserCollections($task->assignees);
118
119 $task->isOverdue = $task->isOverdue();
120 $task->contact = Task::lead_contact($task->crm_contact_id);
121 $task->board->stages = $stageService->stagesByBoardId($board_id);
122 $task->is_watching = $this->notificationService->isCurrentUserObservingTask($task);
123
124 $task = $this->taskService->loadNextStage($task);
125
126 if ($task->type == 'roadmap') {
127 $task->vote_statistics = $this->taskService->getIdeaVoteStatistics($task_id);
128 }
129
130 return [
131 'task' => $task
132 ];
133
134 } catch (\Exception $e ) {
135 return $this->sendError($e->getMessage(), 400);
136 }
137
138
139 }
140
141 public function getStageType(Request $request)
142 {
143 $stage = Stage::findOrFail($request->stage_id);
144
145 return [
146 'stage' => $stage,
147 ];
148 }
149
150 public function getActivities(Request $request, $board_id, $task_id)
151 {
152 $filter = $request->getSafe('filter');
153 $per_page = 15; // Apparently, let's use a fixed number of items per page.
154
155 return [
156 'activities' => $this->taskService->getActivities($task_id, $per_page, $filter)
157 ];
158
159 }
160
161 public function getArchivedTasks(Request $request, $board_id)
162 {
163 $tasks = $this->taskService->getArchivedTasks($request->all(), $board_id);
164
165 foreach ($tasks as $task) {
166 $task->assignees = Helper::sanitizeUserCollections($task->assignees);
167 }
168
169 return [
170 'tasks' => $tasks
171 ];
172 }
173
174 public function updateTaskProperties(Request $request, $board_id, $task_id)
175 {
176 $col = $request->getSafe('property', 'sanitize_text_field');
177 $value = $request->get('value');
178
179 $validatedData = $this->updateTaskPropValidationAndSanitation($col, $value);
180 $task = Task::with(['board', 'labels', 'assignees'])->findOrFail($task_id);
181
182 if ($task->parent_id && !$task->board_id) {
183 $task->board_id = $board_id;
184 $task->save();
185 }
186
187 $task = $this->taskService->updateTaskProperty($col, $validatedData[$col], $task);
188 $task->isOverdue = $task->isOverdue();
189 $task->isUpcoming = $task->upcoming();
190 $task->contact = Helper::crm_contact($task->crm_contact_id);
191 $task->is_watching = $task->isWatching();
192 $task->assignees = Helper::sanitizeUserCollections($task->assignees);
193
194 // A recent update to a task might impact other tasks on the board.
195 $updatedTasks = $this->taskService->getLastOneMinuteUpdatedTasks($board_id);
196
197 return [
198 'message' => __('Task has been updated', 'fluent-boards'),
199 'task' => $task,
200 'updatedTasks' => $updatedTasks
201 ];
202 }
203
204 public function updateTaskDates(Request $request, $board_id, $task_id)
205 {
206 $task = Task::findOrFail($task_id);
207
208 $startAt = $request->getSafe('started_at', 'sanitize_text_field', NULL);
209 $dueAt = $request->getSafe('due_at', 'sanitize_text_field', NULL);
210
211 if ($startAt && $dueAt) {
212 if (strtotime($startAt) > strtotime($dueAt)) {
213 $startAt = gmdate('Y-m-d 00:00:00', strtotime($dueAt));
214 }
215 }
216
217 $task = $this->taskService->updateTaskProperty('started_at', $startAt, $task);
218 $task = $this->taskService->updateTaskProperty('due_at', $dueAt, $task);
219
220 return [
221 'task' => $task,
222 'message' => __('Dates has been updated', 'fluent-boards'),
223 'updatedTasks' => $this->taskService->getLastOneMinuteUpdatedTasks($board_id),
224 ];
225 }
226
227 public function updateTaskCoverPhoto(Request $request, $board_id, $task_id)
228 {
229 $imagePath = $request->thumbnail;
230 $task = $this->taskService->taskCoverPhotoUpdate($task_id, $imagePath);
231
232 return [
233 'message' => __('Task cover photo has been updated', 'fluent-boards'),
234 'task' => $task,
235 ];
236
237 }
238
239 public function taskStatusUpdate(Request $request, $board_id, $task_id)
240 {
241 return [
242 'message' => __('Task status has been updated', 'fluent-boards'),
243 'task' => $this->taskService->taskStatusUpdate($task_id, $request->integrationType),
244 ];
245 }
246
247 public function deleteTask($board_id, $task_id)
248 {
249 $task = Task::findOrFail($task_id);
250 $options = null;
251 //if we need to do something before a task is deleted
252 do_action('fluent_boards/before_task_deleted', $task, $options);
253
254 $this->taskService->deleteTask($task);
255
256 return [
257 'updatedTasks' => $this->taskService->getLastOneMinuteUpdatedTasks($board_id),
258 'message' => __('Task has been deleted', 'fluent-boards'),
259 ];
260 }
261
262 private function taskSanitizeAndValidate($data, array $rules = [])
263 {
264 $data = Helper::sanitizeTask($data);
265
266 return $this->validate($data, $rules);
267 }
268
269 private function updateTaskPropValidationAndSanitation($col, $value)
270 {
271 $rules = [
272 'title' => 'required|string',
273 'board_id' => 'required',
274 'parent_id' => 'required',
275 'crm_contact_id' => 'nullable',
276 'task_type' => 'nullable|string',
277 'status' => 'nullable|string',
278 'stage_id' => 'required',
279 'reminder_type' => 'nullable|string',
280 'priority' => 'nullable|string',
281 'lead_value' => 'nullable|numeric|between:0,9999999.99',
282 'remind_at' => 'nullable|string',
283 'scope' => 'nullable|string',
284 'source' => 'nullable|string',
285 'description' => 'nullable|string',
286 'due_at' => 'nullable|string',
287 'started_at' => 'nullable|string',
288 'start_at' => 'nullable|string',
289 'log_minutes' => 'nullable|integer|unsigned',
290 'last_completed' => 'nullable|date',
291 'assignees' => 'nullable|integer',
292 'archived_at' => 'nullable|string',
293 'is_watching' => 'nullable|string',
294 'is_template' => 'string',
295 'last_completed_at' => 'nullable',
296 'settings' => 'nullable|array',
297 ];
298 if (array_key_exists($col, $rules)) {
299 $rule = $rules[$col];
300 if ('assignees' == $col && is_array($value)) {
301 $sanitizedAndValidatedValue = [];
302 foreach ($value as $val) {
303 $sanitizeData = Helper::sanitizeTask([$col => $val]);
304 $validatedData = $this->validate($sanitizeData, [
305 $col => $rule,
306 ]);
307 array_push($sanitizedAndValidatedValue, $validatedData[$col]);
308 }
309
310 return [$col => $sanitizedAndValidatedValue];
311 }
312 $data = Helper::sanitizeTask([$col => $value]);
313
314 return $this->validate($data, [
315 $col => $rule,
316 ]);
317 }
318 }
319
320 public function getLabelsByTask($task_id)
321 {
322 $labels = $this->taskService->getLabelsByTask($task_id);
323
324 return $this->sendSuccess([
325 'labels' => $labels,
326 ], 200);
327 }
328
329 public function getStageByTask($task_id)
330 {
331 $stage = $this->taskService->getStageByTask($task_id);
332
333 return [
334 'stage' => $stage,
335 ];
336 }
337
338 public function assignYourselfInTask($board_id, $task_id)
339 {
340 $task = $this->taskService->assignYourselfInTask($board_id, $task_id);
341 $task->is_watching = $task->isWatching();
342
343 return [
344 'task' => $task,
345 ];
346 }
347
348 public function detachYourselfFromTask($board_id, $task_id)
349 {
350 $task = $this->taskService->detachYourselfFromTask($board_id, $task_id);
351 $task->assignees = Helper::sanitizeUserCollections($task->assignees);
352 $task->is_watching = $task->isWatching();
353
354 return [
355 'task' => $task,
356 ];
357 }
358
359 private function taskMetaSanitizeAndValidate($data, array $rules = [])
360 {
361 $data = Helper::sanitizeTaskMeta($data);
362
363 return $this->validate($data, $rules);
364 }
365
366 public function moveTaskToNextStage($board_id, $task_id)
367 {
368 $task = $this->taskService->moveTaskToNextStage($task_id);
369
370 return [
371 'task' => $task
372 ];
373 }
374
375 /**
376 * @throws \Exception
377 */
378 public function moveTask(Request $request, $board_id, $task_id)
379 {
380 $task = Task::findOrFail($task_id);
381 $oldStageId = $task->stage_id;
382 $newStageId = $request->getSafe('newStageId', 'intval');
383 $newIndex = $request->getSafe('newIndex', 'intval');
384 $newBoardId = $request->getSafe('newBoardId', 'intval');
385
386 if ((!is_numeric($newStageId) || $newStageId == 0)) {
387 throw new \Exception(__('Invalid Stage', 'fluent-boards'));
388 }
389 if ((!is_numeric($newIndex) || $newIndex == 0)) {
390 throw new \Exception(__('Invalid Value', 'fluent-boards'));
391 }
392 if ($newBoardId) {
393 if ((!is_numeric($newBoardId) || $newBoardId == 0)) {
394 throw new \Exception(__('Invalid Board', 'fluent-boards'));
395 }
396 $task = $this->taskService->changeBoardByTask($task, $newBoardId);
397 }
398
399 $task->stage_id = $newStageId;
400 $task = $task->moveToNewPosition($newIndex);
401
402 if ($oldStageId != $newStageId) {
403
404 $defaultPosition = $task->stage->defaultTaskStatus();
405
406 if ($defaultPosition == 'closed' && $task->status != 'closed') {
407 $task = $task->close();
408 }
409
410 // do_action('fluent_boards/task_moved_to_new_stage', $task, $oldStageId);
411
412 do_action('fluent_boards/task_stage_updated', $task, $oldStageId);
413
414 $usersToSendEmail = $this->notificationService->filterAssigneeToSendEmail($task->id, Constant::BOARD_EMAIL_STAGE_CHANGE);
415 $this->taskService->sendMailAfterTaskModify('stage_change', $usersToSendEmail, $task->id);
416 }
417
418 do_action('fluent_boards/task_updated', $task, 'position');
419
420 $updatedTasks = $this->taskService->getLastOneMinuteUpdatedTasks($task->board_id, $request->get('last_boards_updated'));
421
422 return [
423 'new_position' => $task,
424 'message' => __('Task has been updated', 'fluent-boards'),
425 'task' => $task,
426 'updatedTasks' => $updatedTasks,
427 'last_updated' => current_time('mysql')
428 ];
429 }
430
431 /**
432 * Get comments and activities for a task, merged into a single array, sorted by creation date, and paginated.
433 *
434 * @param Request $request The HTTP request instance.
435 * @param int $board_id The ID of the board.
436 * @param int $task_id The ID of the task.
437 * @return \WP_REST_Response The response containing paginated comments and activities, total count, current page, and items per page.
438 */
439 public function getCommentsAndActivities( Request $request, $board_id, $task_id)
440 {
441 try {
442 // Pagination parameters
443 $page = $request->get('page', 1);
444 $perPage = $request->get('per_page', 10);
445 $filter = $request->get('filter', 'newest'); // Filter for comments and activities
446
447 $commentsAndActivities = $this->taskService->getCommentsAndActivities($task_id, $perPage, $page, $filter);
448 // Return the response with the task, paginated comments and activities, total count, current page, and items per page
449 return $this->sendSuccess([
450 'comments_and_activities' => $commentsAndActivities,
451 ]);
452 } catch (\Exception $e) {
453 return $this->sendError($e->getMessage(), 500);
454 }
455 }
456
457 public function sendMailAfterStageChange($usersToSendEmail, $taskId)
458 {
459 $current_user_id = get_current_user_id();
460
461 /* this will run in background as soon as possible */
462 /* sending Model or Model Instance won't work here */
463 as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_stage_change', [$taskId, $usersToSendEmail, $current_user_id], 'fluent-boards');
464 }
465 public function getAssociatedTasks($associated_id)
466 {
467 return [
468 'tasks' => $this->taskService->getAssociatedTasks($associated_id)
469 ];
470 }
471
472 /**
473 * @param Request $request
474 * @param $board_id
475 * @param $task_id
476 * @return \WP_REST_Response
477 */
478 public function uploadMediaFileFromWpEditor(Request $request, $board_id, $task_id)
479 {
480 try {
481
482
483 $file = Arr::get($request->files(), 'file')->toArray();
484 (new \FluentBoards\App\Services\UploadService)->validateFile($file);
485
486 $uploadInfo = UploadService::handleFileUpload( $request->files(), $board_id);
487
488 $fileData = $uploadInfo[0];
489 $fileUploadedData = $this->taskService->uploadMediaFileFromWpEditor($task_id, $fileData, Constant::TASK_DESCRIPTION);
490
491 return $this->sendSuccess([
492 'message' => __('Image has been uploaded', 'fluent-boards-pro'),
493 'file' => $fileUploadedData
494 ], 200);
495
496
497 } catch (\Exception $e) {
498 return $this->sendError($e->getMessage(), 400);
499 }
500 }
501
502 }
503