eventDispatcher = $eventDispatcher; $this->timelineBuilder = $timelineBuilder; $this->logger = $logger; $this->translator = $translator; $this->centerResolverDispatcher = $centerResolverDispatcher; $this->paginatorFactory = $paginatorFactory; $this->singleTaskAclAwareRepository = $singleTaskAclAwareRepository; $this->filterOrderHelperFactory = $filterOrderHelperFactory; } /** * @Route( * "/{_locale}/task/single-task/{id}/delete", * name="chill_task_single_task_delete" * ) * * @param mixed $id */ public function deleteAction(Request $request, $id) { $course = null; $em = $this->getDoctrine()->getManager(); $task = $em->getRepository(SingleTask::class)->find($id); if (null === $task) { throw $this->createNotFoundException('Unable to find Task entity.'); } if ($task->getPerson() !== null) { $personId = $task->getPerson()->getId(); if (null === $personId) { return new Response('You must provide a person_id', Response::HTTP_BAD_REQUEST); } $person = $this->getDoctrine()->getManager() ->getRepository(Person::class) ->find($personId); if (null === $person) { throw $this->createNotFoundException('Invalid person id'); } } else { $courseId = $task->getCourse()->getId(); if (null === $courseId) { return new Response('You must provide a course_id', Response::HTTP_BAD_REQUEST); } $course = $this->getDoctrine()->getManager() ->getRepository(AccompanyingPeriod::class) ->find($courseId); if (null === $course) { throw $this->createNotFoundException('Invalid accompanying period id'); } } // TODO: reactivate right to delete // $this->denyAccessUnlessGranted(TaskVoter::DELETE, $task, 'You are not ' // . 'allowed to delete this task'); $form = $this->createDeleteForm($id); if ($request->getMethod() === Request::METHOD_DELETE) { $form->handleRequest($request); if ($form->isValid()) { $this->logger->notice('A task has been removed', [ 'by_user' => $this->getUser()->getUsername(), 'task_id' => $task->getId(), 'description' => $task->getDescription(), 'assignee' => $task->getAssignee(), // TODO reimplement scope // 'scope_id' => $task->getScope()->getId(), ]); $em = $this->getDoctrine()->getManager(); $em->remove($task); $em->flush(); $this->addFlash('success', $this->translator ->trans('The task has been successfully removed.')); if ($task->getContext() instanceof Person) { return $this->redirect($this->generateUrl( 'chill_task_singletask_by-person_list', ['id' => $task->getPerson()->getId()] )); } return $this->redirect($this->generateUrl( 'chill_task_singletask_by-course_list', ['id' => $task->getCourse()->getId()] )); } } if ($task->getContext() instanceof Person) { return $this->render( '@ChillTask/SingleTask/Person/confirm_delete.html.twig', [ 'task' => $task, 'delete_form' => $form->createView(), ] ); } return $this->render( '@ChillTask/SingleTask/AccompanyingCourse/confirm_delete.html.twig', [ 'task' => $task, 'delete_form' => $form->createView(), 'accompanyingCourse' => $course, ] ); } /** * @Route( * "/{_locale}/task/single-task/{id}/edit", * name="chill_task_single_task_edit" * ) */ public function editAction( SingleTask $task, Request $request ) { $this->denyAccessUnlessGranted(TaskVoter::UPDATE, $task, 'You are not ' . 'allowed to edit this task'); $event = (new UIEvent('single-task', $task)) ->setForm($this->setCreateForm($task, TaskVoter::UPDATE)); $this->eventDispatcher->dispatch(UIEvent::EDIT_FORM, $event); $form = $event->getForm(); $form->handleRequest($request); if ($form->isSubmitted()) { if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($task); $em->flush(); $this->addFlash('success', $this->translator ->trans('The task has been updated')); if ($task->getContext() instanceof Person) { $event = new PrivacyEvent($task->getPerson(), [ 'element_class' => SingleTask::class, 'element_id' => $task->getId(), 'action' => 'update', ]); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); if ($request->query->has('returnPath')) { return $this->redirect($request->query->get('returnPath')); } return $this->redirectToRoute( 'chill_task_singletask_list', ); } if ($request->query->has('returnPath')) { return $this->redirect($request->query->get('returnPath')); } return $this->redirectToRoute( 'chill_task_singletask_by-course_list', ['id' => $task->getCourse()->getId()] ); } $this->addFlash('error', $this->translator->trans('This form contains errors')); } $this->eventDispatcher->dispatch(UIEvent::EDIT_PAGE, $event); if ($event->hasResponse()) { return $event->getResponse(); } if ($task->getContext() instanceof Person) { $event = new PrivacyEvent($task->getPerson(), [ 'element_class' => SingleTask::class, 'element_id' => $task->getId(), 'action' => 'edit', ]); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); return $this->render('@ChillTask/SingleTask/Person/edit.html.twig', [ 'task' => $task, 'form' => $form->createView(), ]); } return $this->render('@ChillTask/SingleTask/AccompanyingCourse/edit.html.twig', [ 'task' => $task, 'form' => $form->createView(), 'accompanyingCourse' => $task->getCourse(), ]); } /** * Arguments: * - user_id * - scope_id * - s * - person_id * - hide_form (hide the form to filter the tasks) * - status: date state, amongst SingleTaskRepository::DATE_STATUSES, or 'closed'. * * @Route( * "/{_locale}/task/single-task/list", * name="chill_task_singletask_list" * ) */ public function listAction( Request $request ) { $this->denyAccessUnlessGranted(TaskVoter::SHOW, null); $filterOrder = $this->buildFilterOrder(); $flags = array_merge( $filterOrder->getCheckboxData('status'), array_map(static fn ($i) => 'state_' . $i, $filterOrder->getCheckboxData('states')) ); $nb = $this->singleTaskAclAwareRepository->countByAllViewable( $filterOrder->getQueryString(), $flags ); $paginator = $this->paginatorFactory->create($nb); if (0 < $nb) { $tasks = $this->singleTaskAclAwareRepository->findByAllViewable( $filterOrder->getQueryString(), $flags, $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage(), [ 'startDate' => 'DESC', 'endDate' => 'DESC', ] ); } else { $tasks = []; } return $this->render('@ChillTask/SingleTask/List/index.html.twig', [ 'tasks' => $tasks, 'paginator' => $paginator, 'filter_order' => $filterOrder, ]); } /** * @Route( * "/{_locale}/task/single-task/by-course/{id}", * name="chill_task_singletask_by-course_list") */ public function listCourseTasks( AccompanyingPeriod $course, FormFactoryInterface $formFactory, Request $request ): Response { $this->denyAccessUnlessGranted(TaskVoter::SHOW, $course); $filterOrder = $this->buildFilterOrder(); $flags = array_merge( $filterOrder->getCheckboxData('status'), array_map(static fn ($i) => 'state_' . $i, $filterOrder->getCheckboxData('states')) ); $nb = $this->singleTaskAclAwareRepository->countByCourse( $course, $filterOrder->getQueryString(), $flags ); $paginator = $this->paginatorFactory->create($nb); if (0 < $nb) { $tasks = $this->singleTaskAclAwareRepository->findByCourse( $course, $filterOrder->getQueryString(), $flags, $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage(), [ 'startDate' => 'DESC', 'endDate' => 'DESC', ] ); } else { $tasks = []; } return $this->render( '@ChillTask/SingleTask/AccompanyingCourse/list.html.twig', [ 'tasks' => $tasks, 'accompanyingCourse' => $course, 'paginator' => $paginator, 'filter_order' => $filterOrder, ] ); } /** * @Route( * "/{_locale}/task/single-task/by-person/{id}", * name="chill_task_singletask_by-person_list") */ public function listPersonTasks( Person $person ): Response { $this->denyAccessUnlessGranted(TaskVoter::SHOW, $person); $filterOrder = $this->buildFilterOrder(); $flags = array_merge( $filterOrder->getCheckboxData('status'), array_map(static fn ($i) => 'state_' . $i, $filterOrder->getCheckboxData('states')) ); $nb = $this->singleTaskAclAwareRepository->countByPerson( $person, $filterOrder->getQueryString(), $flags ); $paginator = $this->paginatorFactory->create($nb); if (0 < $nb) { $tasks = $this->singleTaskAclAwareRepository->findByPerson( $person, $filterOrder->getQueryString(), $flags, $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage(), [ 'startDate' => 'DESC', 'endDate' => 'DESC', ] ); } else { $tasks = []; } return $this->render( '@ChillTask/SingleTask/Person/list.html.twig', [ 'tasks' => $tasks, 'person' => $person, 'paginator' => $paginator, 'filter_order' => $filterOrder, ] ); } /** * @return Response * @Route( * "/{_locale}/task/single-task/list/my", * name="chill_task_singletask_my_tasks", * defaults={"_format": "html"} * ) * @Route( * "/api/1.0/task/single-task/list/my", * defaults={"_format": "json"} * ) */ public function myTasksAction(string $_format, Request $request) { $this->denyAccessUnlessGranted('ROLE_USER'); $filterOrder = $this->buildFilterOrder(); $flags = array_merge( $filterOrder->getCheckboxData('status'), array_map(static fn ($i) => 'state_' . $i, $filterOrder->getCheckboxData('states')) ); $nb = $this->singleTaskAclAwareRepository->countByCurrentUsersTasks( $filterOrder->getQueryString(), $flags ); if ('json' === $_format && $request->query->getBoolean('countOnly')) { return $this->json( new Counter($nb), ); } $paginator = $this->paginatorFactory->create($nb); $tasks = $this->singleTaskAclAwareRepository->findByCurrentUsersTasks( $filterOrder->getQueryString(), $flags, $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage(), [ 'startDate' => 'DESC', 'endDate' => 'DESC', ] ); switch ($_format) { case 'html': return $this->render('@ChillTask/SingleTask/List/index_my_tasks.html.twig', [ 'tasks' => $tasks, 'paginator' => $paginator, 'filter_order' => $filterOrder, ]); case 'json': $collection = new Collection($tasks, $paginator); return $this->json( $collection, JsonResponse::HTTP_OK, [], ['groups' => ['read']] ); default: throw new BadRequestHttpException("format not supported: {$_format}"); } } /** * @Route( * "/{_locale}/task/single-task/new", * name="chill_task_single_task_new" * ) */ public function newAction(Request $request) { $task = (new SingleTask()) ->setAssignee($this->getUser()) ->setType('task_default'); $entityType = $this->getEntityContext($request); if (null === $entityType) { throw new BadRequestHttpException('You must provide a entity_type'); } $entityId = $request->query->getInt("{$entityType}_id", 0); if (null === $entityId) { return new BadRequestHttpException("You must provide a {$entityType}_id"); } switch ($entityType) { case 'person': $person = $this->getDoctrine()->getManager() ->getRepository(Person::class) ->find($entityId); if (null === $person) { $this->createNotFoundException('Invalid person id'); } $task->setPerson($person); $role = TaskVoter::CREATE_PERSON; break; case 'course': $course = $this->getDoctrine()->getManager() ->getRepository(AccompanyingPeriod::class) ->find($entityId); if (null === $course) { $this->createNotFoundException('Invalid accompanying course id'); } $task->setCourse($course); $role = TaskVoter::CREATE_COURSE; break; default: return new BadRequestHttpException("context with {$entityType} is not supported"); } $this->denyAccessUnlessGranted($role, $task, 'You are not ' . 'allowed to create this task'); $form = $this->setCreateForm($task, $role); $form->handleRequest($request); if ($form->isSubmitted()) { if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($task); $this->eventDispatcher->dispatch(TaskEvent::PERSIST, new TaskEvent($task)); $em->flush(); $this->addFlash('success', $this->translator->trans('The task is created')); if ($request->query->has('returnPath')) { return $this->redirect($request->query->get('returnPath')); } if ('person' === $entityType) { return $this->redirectToRoute('chill_task_singletask_by-person_list', [ 'id' => $task->getPerson()->getId(), ]); } if ('course' === $entityType) { return $this->redirectToRoute('chill_task_singletask_by-course_list', [ 'id' => $task->getCourse()->getId(), ]); } } else { $this->addFlash('error', $this->translator->trans('This form contains errors')); } } switch ($entityType) { case 'person': return $this->render('@ChillTask/SingleTask/Person/new.html.twig', [ 'form' => $form->createView(), 'task' => $task, 'person' => $task->getPerson(), ]); case 'course': return $this->render('@ChillTask/SingleTask/AccompanyingCourse/new.html.twig', [ 'form' => $form->createView(), 'task' => $task, 'accompanyingCourse' => $task->getCourse(), ]); default: throw new LogicException('entity context not supported'); } } /** * @Route( * "/{_locale}/task/single-task/{id}/show", * name="chill_task_single_task_show" * ) */ public function showAction(SingleTask $task, Request $request) { $this->denyAccessUnlessGranted(TaskVoter::SHOW, $task); if ($task->getContext() instanceof Person) { $event = new PrivacyEvent($task->getContext(), [ 'element_class' => SingleTask::class, 'element_id' => $task->getId(), 'action' => 'show', ]); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); } $timeline = $this->timelineBuilder ->getTimelineHTML('task', ['task' => $task]); if ($task->getContext() instanceof Person) { return $this->render('@ChillTask/SingleTask/Person/show.html.twig', [ 'task' => $task, 'timeline' => $timeline, ]); } return $this->render('@ChillTask/SingleTask/AccompanyingCourse/show.html.twig', [ 'task' => $task, 'timeline' => $timeline, ]); } /** * @return \Symfony\Component\Form\FormInterface */ protected function setCreateForm(SingleTask $task, string $role) { $form = $this->createForm(SingleTaskType::class, $task, [ 'role' => $role, ]); $form->add('submit', SubmitType::class); return $form; } private function buildFilterOrder(): FilterOrderHelper { $statuses = ['no-alert', 'warning', 'alert']; $statusTrans = [ 'Tasks without alert', 'Tasks near deadline', 'Tasks over deadline', ]; $states = [ // todo: get a list of possible states dynamically 'new', 'in_progress', 'closed', 'canceled', ]; return $this->filterOrderHelperFactory ->create(self::class) ->addSearchBox() ->addCheckbox('status', $statuses, $statuses, $statusTrans) ->addCheckbox('states', $states, ['new', 'in_progress']) ->addUserPickers('userPicker', 'userPicker', ['multiple' => True]) ->build(); } /** * Creates a form to delete a Task entity by id. * @param mixed $id */ private function createDeleteForm($id): FormInterface { return $this->createFormBuilder() ->setAction($this->generateUrl( 'chill_task_single_task_delete', ['id' => $id] )) ->setMethod('DELETE') ->add('submit', SubmitType::class, ['label' => 'Delete']) ->getForm(); } private function getEntityContext(Request $request) { if ($request->query->has('person_id')) { return 'person'; } if ($request->query->has('course_id')) { return 'course'; } return null; } }