|
- <?php
-
- declare(strict_types=1);
-
- namespace App\Controllers;
-
- use App\Repositories\ActivityRepository;
- use App\Repositories\ProjectRepository;
- use App\Repositories\TaskRepository;
- use Core\Controller;
- use Core\Validator;
-
- class TaskController extends Controller
- {
- private const TASK_STATUS_OPTIONS = ['backlog', 'in-progress', 'review', 'blocked', 'done'];
- private const PRIORITY_OPTIONS = ['low', 'normal', 'high', 'urgent'];
-
- public function store(string $projectId)
- {
- $this->requirePost(request());
-
- if (!verify_csrf_token((string) request()->input('_token'))) {
- return $this->renderProjectWithTaskErrors((int) $projectId, ['_token' => ['The security token expired. Refresh the page and try again.']], $this->taskFormFromRequest());
- }
-
- $projectRepository = new ProjectRepository();
- $project = $projectRepository->findBoard((int) $projectId);
-
- if ($project === null) {
- return \Core\Response::notFound('Project not found.');
- }
-
- $data = $this->taskDataFromRequest();
- $errors = $this->validateTask($data);
-
- if ($errors !== []) {
- return $this->renderProjectWithTaskErrors((int) $projectId, $errors, $data);
- }
-
- $taskId = (new TaskRepository())->create((int) $projectId, $data);
- (new ActivityRepository())->record(
- 'task_created',
- 'Added task ' . $data['title'],
- 'Task created on the project board.',
- (int) $projectId,
- $taskId
- );
-
- return $this->redirect('/projects/' . (int) $projectId . '?task=created#task-' . $taskId);
- }
-
- public function updateStatus(string $id)
- {
- $this->requirePost(request());
-
- if (!verify_csrf_token((string) request()->input('_token'))) {
- return $this->redirect('/projects');
- }
-
- $status = trim((string) request()->input('status', ''));
- if (!in_array($status, self::TASK_STATUS_OPTIONS, true)) {
- return $this->redirect('/projects');
- }
-
- $projectId = (new TaskRepository())->updateStatus((int) $id, $status);
-
- if ($projectId === null) {
- return \Core\Response::notFound('Task not found.');
- }
-
- (new ActivityRepository())->record(
- 'task_status_changed',
- 'Task updated to ' . task_status_label($status),
- 'Task status changed on the project board.',
- $projectId,
- (int) $id
- );
-
- return $this->redirect('/projects/' . $projectId . '?task-updated=1#task-' . (int) $id);
- }
-
- private function taskFormFromRequest(): array
- {
- $input = request()->all();
-
- return [
- 'title' => (string) ($input['title'] ?? ''),
- 'description' => (string) ($input['description'] ?? ''),
- 'priority' => (string) ($input['priority'] ?? 'normal'),
- 'status' => (string) ($input['status'] ?? 'backlog'),
- 'assignee' => (string) ($input['assignee'] ?? ''),
- 'estimate_hours' => (string) ($input['estimate_hours'] ?? ''),
- 'due_date' => (string) ($input['due_date'] ?? ''),
- ];
- }
-
- private function taskDataFromRequest(): array
- {
- $input = request()->all();
- $estimateHours = trim((string) ($input['estimate_hours'] ?? ''));
- $dueDate = trim((string) ($input['due_date'] ?? ''));
-
- return [
- 'title' => trim((string) ($input['title'] ?? '')),
- 'description' => trim((string) ($input['description'] ?? '')),
- 'priority' => trim((string) ($input['priority'] ?? 'normal')),
- 'status' => trim((string) ($input['status'] ?? 'backlog')),
- 'assignee' => trim((string) ($input['assignee'] ?? '')),
- 'estimate_hours' => $estimateHours !== '' ? (float) $estimateHours : null,
- 'due_date' => $dueDate !== '' ? $dueDate : null,
- ];
- }
-
- private function validateTask(array $data): array
- {
- $validator = new Validator();
-
- $validator->required('title', $data['title'], 'Task title is required.')->maxLength('title', $data['title'], 140);
- $validator->required('description', $data['description'], 'Task description is required.')->maxLength('description', $data['description'], 800);
- $validator->required('assignee', $data['assignee'], 'Assign the task to someone.')->maxLength('assignee', $data['assignee'], 100);
-
- if (!in_array($data['priority'], self::PRIORITY_OPTIONS, true)) {
- $validator->required('priority', null, 'Choose a valid task priority.');
- }
-
- if (!in_array($data['status'], self::TASK_STATUS_OPTIONS, true)) {
- $validator->required('status', null, 'Choose a valid task status.');
- }
-
- if ($data['due_date'] !== null && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['due_date'])) {
- $validator->required('due_date', null, 'Use a valid due date.');
- }
-
- if ($data['estimate_hours'] !== null && $data['estimate_hours'] < 0) {
- $validator->required('estimate_hours', null, 'Estimate hours must be 0 or more.');
- }
-
- return $validator->errors();
- }
-
- private function renderProjectWithTaskErrors(int $projectId, array $errors, array $old)
- {
- $projectRepository = new ProjectRepository();
- $project = $projectRepository->findBoard($projectId);
-
- if ($project === null) {
- return \Core\Response::notFound('Project not found.');
- }
-
- return $this->view('projects.show', [
- 'pageTitle' => $project['name'],
- 'project' => $project,
- 'taskStatusOptions' => self::TASK_STATUS_OPTIONS,
- 'priorityOptions' => self::PRIORITY_OPTIONS,
- 'activity' => (new ActivityRepository())->forProject($projectId, 12),
- 'taskErrors' => $errors,
- 'taskOld' => array_merge($this->taskFormFromRequest(), $old),
- ]);
- }
- }
|