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

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