query->has('slug')) { $this->addFlash('warning', 'Projet non spécifié !'); return $this->redirectToRoute('app_project'); } $slug = $request->query->get('slug'); $repository = $entityManager->getRepository(Project::class); $project = $repository->findOneBySlug($slug); if (!$project) { $this->addFlash('warning', 'Projet non trouvé !'); return $this->redirectToRoute('app_project'); } $task = new Task(); $task->setProject($project); $createForm = $this->createForm(TaskType::class, $task); $createForm->add('submit', SubmitType::class, [ 'label' => 'Créer', ]); $createForm->handleRequest($request); if ($createForm->isSubmitted() and $createForm->isValid()) { $task = $createForm->getData(); try { if (!$task->hasGeojson()) { $task->setGeojson('{}'); } $task->setCreatedBy($this->getUser()); $entityManager->persist($task); $entityManager->flush(); $this->addFlash('success', 'Tâche créée !'); return $this->redirectToRoute('app_project_show', ['slug' => $slug]); } catch (\Exception $exception) { $this->addFlash('danger', 'Impossible de créer la tâche !'); } } return $this->render('task/create.html.twig', [ 'project' => $project, 'create_form' => $createForm, ]); } // Page spécifique à une tâche, où l’on trouve tout ce qui la concerne #[Route('/{slug}', name: 'app_task_show')] public function show(Request $request, EntityManagerInterface $entityManager, GeoJsonManager $geoJsonManager, $slug): Response { $repository = $entityManager->getRepository(Task::class); $task = $repository->findOneBySlug($slug); if (!$task) { $this->addFlash('warning', 'Tâche non trouvée !'); if (!$request->headers->has('Referer')) { throw $this->createNotFoundException('Task not found'); } return $this->redirect($request->headers->get('Referer')); } $project = $task->getProject(); $randomTask = $repository->findRandomByProject($project, Task::STATUS_TODO); $comment = new Comment(); $commentForm = $this->createForm(CommentType::class, $comment, [ 'action' => $this->generateUrl('app_task_comment', ['slug' => $slug]), ]); $commentForm->add('submit', SubmitType::class, [ 'label' => 'Commenter', ]); // Programmation de la télécommande JOSM $josmCommands = [ // Charge l’imagerie (fond OSM par défaut) 'imagery' => [ 'id' => $project->hasImagery() ? $project->getImagery() : 'osmfr', ], // Charge le XML OSM du projet danms un calque de données // TODO C’est susceptible de planter s’il n’y a pas d’OSM dans le projet mais est-ce possible ? 'import' => [ 'new_layer' => true, 'layer_name' => $task->getName(), 'url' => $this->generateUrl( 'app_task_osm', ['slug' => $task->getSlug()], UrlGeneratorInterface::ABSOLUTE_URL ), ], ]; if ($task->hasGeojson()) { $geom = \geoPHP::load($task->getGeojson(), 'json'); $bbox = $geom->getBBox(); // Zoome sur les données et préremplit le changeset $josmCommands['zoom'] = [ 'bottom' => $bbox['miny'], 'top' => $bbox['maxy'], 'left' => $bbox['minx'], 'right' => $bbox['maxx'], 'changeset_comment' => sprintf('%s %s', $project->getName(), $task->getName()), 'changeset_source' => $project->getSource(), 'changeset_hashtags' => $project->getHashtags(), ]; } return $this->render('task/show.html.twig', [ 'task' => $task, 'project' => $project, 'commentForm' => $commentForm, 'randomTask' => $randomTask, 'josmCommands' => json_encode($josmCommands), ]); } // Ajoute un commentaire à la tâche #[Route('/{slug}/comment', name: 'app_task_comment')] public function comment(Request $request, EntityManagerInterface $entityManager, $slug): Response { $repository = $entityManager->getRepository(Task::class); $task = $repository->findOneBySlug($slug); if (!$task) { $this->addFlash('warning', 'Tâche non trouvée !'); return $this->redirect($request->headers->get('Referer')); } $comment = new Comment(); $comment->setCreatedBy($this->getUser()); $comment->setTask($task); $commentForm = $this->createForm(CommentType::class, $comment); $commentForm->add('submit', SubmitType::class, [ 'label' => 'Commenter', ]); $commentForm->handleRequest($request); if ($commentForm->isSubmitted() and $commentForm->isValid()) { $comment = $commentForm->getData(); try { $entityManager->persist($comment); $entityManager->flush(); } catch (\Exception $exception) { $this->addFlash('danger', 'Impossible de commenter ! '.$exception->getMessage()); } } return $this->redirectToRoute('app_task_show', ['slug' => $slug]); } // Modifie les informations d’une tâche #[Route('/{slug}/update', name: 'app_task_update')] public function update(Request $request, EntityManagerInterface $entityManager, $slug): Response { $repository = $entityManager->getRepository(Task::class); $task = $repository->findOneBySlug($slug); if (!$task) { $this->addFlash('warning', 'Tâche non trouvée !'); return $this->redirect($request->headers->get('Referer')); } $updateForm = $this->createForm(TaskType::class, $task); $updateForm->add('submit', SubmitType::class, [ 'label' => 'Modifier', ]); $updateForm->handleRequest($request); if ($updateForm->isSubmitted() and $updateForm->isValid()) { $task = $updateForm->getData(); try { $entityManager->persist($task); $entityManager->flush(); return $this->redirectToRoute('app_task_show', ['slug' => $slug]); } catch (\Exception $exception) { $this->addFlash('danger', 'Impossible de modifier la tâche !'); } } return $this->render('task/update.html.twig', [ 'project' => $task->getProject(), 'task' => $task, 'update_form' => $updateForm, ]); } // Supprimer une tâche #[Route('/{slug}/remove', name: 'app_task_remove')] public function remove(Request $request, EntityManagerInterface $entityManager, $slug): Response { $repository = $entityManager->getRepository(Task::class); $task = $repository->findOneBySlug($slug); if (!$task) { $this->addFlash('warning', 'Tâche non trouvée !'); return $this->redirect($request->headers->get('Referer')); } $project = $task->getProject(); try { $entityManager->remove($task); $entityManager->flush(); return $this->redirectToRoute('app_project_show', ['slug' => $project->getSlug()]); } catch (\Exception $exception) { $this->addFlash('danger', 'Impossible de supprimer la tâche !'); } return $this->redirectToRoute('app_project_show', ['slug' => $slug]); } // Passe une tâche d’un état à un autre private function transition(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, $slug, $transitionName): Response { $repository = $entityManager->getRepository(Task::class); $task = $repository->findOneBySlug($slug); if (!$task) { $this->addFlash('warning', 'Tâche non trouvée !'); return $this->redirectToRoute('app_project'); } try { $taskLifecycleStateMachine->apply($task, $transitionName); $entityManager->persist($task); $entityManager->flush(); } catch (Exception $exception) { $this->addFlash('warning', 'Impossible de modifier la tâche !'); } return $this->redirectToRoute('app_task_show', ['slug' => $slug]); } // Commence une tâche #[Route('/{slug}/start', name: 'app_task_start')] public function start(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, $slug): Response { return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_START); } // Termine une tâche #[Route('/{slug}/finish', name: 'app_task_finish')] public function finish(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, $slug): Response { return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_FINISH); } // Abandonne une tâche #[Route('/{slug}/cancel', name: 'app_task_cancel')] public function cancel(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, $slug): Response { return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_CANCEL); } // Recommence une tâche #[Route('/{slug}/reset', name: 'app_task_reset')] public function reset(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, $slug): Response { return $this->transition($taskLifecycleStateMachine, $entityManager, $slug, Task::TRANSITION_RESET); } // Renvoie le geojson associé à une tâche #[Route('/download/{slug}.geojson', name: 'app_task_geojson')] public function geojson(Request $request, EntityManagerInterface $entityManager, $slug): Response { $repository = $entityManager->getRepository(Task::class); $task = $repository->findOneBySlug($slug); if (!$task) { $this->addFlash('warning', 'Tâche non trouvée !'); if (!$request->headers->has('Referer')) { throw $this->createNotFoundException('Task not found'); } return $this->redirect($request->headers->get('Referer')); } if (!$task->hasGeojson()) { return new Response('Not found', 404); } $response = JsonResponse::fromJsonString($task->getGeojson()); $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition( HeaderUtils::DISPOSITION_ATTACHMENT, sprintf('%s.geojson', $task->getSlug()) )); return $response; } // Renvoie le gpx associé ã une tâche (concrètement il s’agit juste du geojson converti automqtiquement) #[Route('/download/{slug}.gpx', name: 'app_task_gpx')] public function gpx(Request $request, EntityManagerInterface $entityManager, $slug): Response { $repository = $entityManager->getRepository(Task::class); $task = $repository->findOneBySlug($slug); if (!$task) { $this->addFlash('warning', 'Tâche non trouvée !'); if (!$request->headers->has('Referer')) { throw $this->createNotFoundException('Task not found'); } return $this->redirect($request->headers->get('Referer')); } if (!$task->hasGeojson()) { return new Response('Not found', 404); } $geom = \geoPHP::load($task->getGeojson(), 'json'); $gpx = $geom->out('gpx'); // TODO On ne vérifie rien ici en partant du principe que tout se passe // bien. Il doit y avoir des circonstances dans lesquelles ce n’est pas // exactement le cas. $response = new Response($gpx); $response->headers->set('Content-Type', 'application/xml'); $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition( HeaderUtils::DISPOSITION_ATTACHMENT, sprintf('%s.gpx', $task->getSlug()) )); return $response; } // Renvoie le XML OSM associé à la tâche #[Route('/download/{slug}.osm', name: 'app_task_osm')] public function osm(Request $request, EntityManagerInterface $entityManager, $slug): Response { $repository = $entityManager->getRepository(Task::class); $task = $repository->findOneBySlug($slug); if (!$task) { $this->addFlash( 'warning', 'Tâche non trouvée !' ); return $this->redirect($request->headers->get('referer')); } // On est pas obligé de faire ça maid en le faisant on s’assure que // c’est bien du XML valide qu’on envoie. $xml = new \DOMDocument(); $xml->loadXml($task->getOsm()); $response = new Response($xml->saveXML()); $response->headers->set('Content-Type', 'application/xml'); $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition( HeaderUtils::DISPOSITION_ATTACHMENT, sprintf('%s.osm', $task->getSlug()) )); return $response; } // Renvoie la liste des tâches du projet sous forme de CSV (ce qui devrait // corresponddre à ce que l’on a pu importer) #[Route('/download/{slug}.csv', name: 'app_task_csv')] public function csv(Request $request, EntityManagerInterface $entityManager, $slug): Response { $repository = $entityManager->getRepository(Project::class); $project = $repository->findOneBySlug($slug); if (!$project) { $this->addFlash('warning', 'Projet non trouvé !'); return $this->redirect($request->headers->get('referer')); } $response = new StreamedResponse(); $response->setCallback(function () use ($project): void { $output = fopen('php://output', 'a'); fputcsv( $output, [ 'name', 'description', 'osm', 'geojson', 'status', ] ); foreach ($project->getTasks() as $task) { fputcsv( $output, [ $task->getName(), $task->getDescription(), $task->getOsm(), $task->getGeojson(), $task->getStatus(), ] ); } fclose($output); flush(); }); $response->headers->set('Content-Type', 'text/csv'); $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition( HeaderUtils::DISPOSITION_ATTACHMENT, sprintf('%s.csv', $project->getSlug()) )); return $response; } }