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

442 lines 14.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\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'], 6, $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 $task->is_watching = $task->isWatching();
336
337 return [
338 'task' => $task,
339 ];
340 }
341
342 public function detachYourselfFromTask($board_id, $task_id)
343 {
344 $task = $this->taskService->detachYourselfFromTask($board_id, $task_id);
345 $task->assignees = Helper::sanitizeUserCollections($task->assignees);
346 $task->is_watching = $task->isWatching();
347
348 return [
349 'task' => $task,
350 ];
351 }
352
353 private function taskMetaSanitizeAndValidate($data, array $rules = [])
354 {
355 $data = Helper::sanitizeTaskMeta($data);
356
357 return $this->validate($data, $rules);
358 }
359
360 public function moveTaskToNextStage($board_id, $task_id)
361 {
362 $task = $this->taskService->moveTaskToNextStage($task_id);
363
364 return [
365 'task' => $task
366 ];
367 }
368
369 /**
370 * @throws \Exception
371 */
372 public function moveTask(Request $request, $board_id, $task_id)
373 {
374 $task = Task::findOrFail($task_id);
375 $oldStageId = $task->stage_id;
376 $newStageId = $request->getSafe('newStageId', 'intval');
377 $newIndex = $request->getSafe('newIndex', 'intval');
378 $newBoardId = $request->getSafe('newBoardId', 'intval');
379
380 if ((!is_numeric($newStageId) || $newStageId == 0)) {
381 throw new \Exception(__('Invalid Stage', 'fluent-boards'));
382 }
383 if ((!is_numeric($newIndex) || $newIndex == 0)) {
384 throw new \Exception(__('Invalid Value', 'fluent-boards'));
385 }
386 if ($newBoardId) {
387 if ((!is_numeric($newBoardId) || $newBoardId == 0)) {
388 throw new \Exception(__('Invalid Board', 'fluent-boards'));
389 }
390 $task = $this->taskService->changeBoardByTask($task, $newBoardId);
391 }
392
393 $task->stage_id = $newStageId;
394 $task = $task->moveToNewPosition($newIndex);
395
396 if ($oldStageId != $newStageId) {
397
398 $defaultPosition = $task->stage->defaultTaskStatus();
399
400 if ($defaultPosition == 'closed' && $task->status != 'closed') {
401 $task = $task->close();
402 }
403
404 do_action('fluent_boards/task_moved_to_new_stage', $task, $oldStageId);
405
406 do_action('fluent_boards/task_stage_updated', $task, $oldStageId);
407
408 $usersToSendEmail = $this->notificationService->filterAssigneeToSendEmail($task->id, Constant::BOARD_EMAIL_STAGE_CHANGE);
409 $this->taskService->sendMailAfterTaskModify('stage_change', $usersToSendEmail, $task->id);
410 }
411
412 do_action('fluent_boards/task_updated', $task, 'position');
413
414 $updatedTasks = $this->taskService->getLastOneMinuteUpdatedTasks($task->board_id, $request->get('last_boards_updated'));
415
416 return [
417 'new_position' => $task,
418 'message' => __('Task has been updated', 'fluent-boards'),
419 'task' => $task,
420 'updatedTasks' => $updatedTasks,
421 'last_updated' => current_time('mysql')
422 ];
423 }
424
425 public function sendMailAfterStageChange($usersToSendEmail, $taskId)
426 {
427 $current_user_id = get_current_user_id();
428
429 /* this will run in background as soon as possible */
430 /* sending Model or Model Instance won't work here */
431 as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_stage_change', [$taskId, $usersToSendEmail, $current_user_id], 'fluent-boards');
432 }
433
434 public function getAssociatedTasks($associated_id)
435 {
436 return [
437 'tasks' => $this->taskService->getAssociatedTasks($associated_id)
438 ];
439 }
440
441 }
442