From 533656d13a1d49038260bcbc1291c3c50fc216a9 Mon Sep 17 00:00:00 2001 From: AdrienClairembault Date: Tue, 6 Aug 2024 11:56:17 +0200 Subject: [PATCH] Add proper "Form\SubmitAnswerController" --- ajax/form/answer.php | 93 ------------- .../functional/Glpi/Form/AnswersSetTest.php | 37 +++++- .../Form/SubmitAnswerController.php | 123 ++++++++++++++++++ src/Glpi/Form/AnswersSet.php | 24 ++++ templates/pages/form_renderer.html.twig | 4 +- 5 files changed, 186 insertions(+), 95 deletions(-) delete mode 100644 ajax/form/answer.php create mode 100644 src/Glpi/Controller/Form/SubmitAnswerController.php diff --git a/ajax/form/answer.php b/ajax/form/answer.php deleted file mode 100644 index fefaa1f87e3..00000000000 --- a/ajax/form/answer.php +++ /dev/null @@ -1,93 +0,0 @@ -. - * - * --------------------------------------------------------------------- - */ - -use Glpi\Form\AnswersHandler\AnswersHandler; -use Glpi\Form\EndUserInputNameProvider; -use Glpi\Form\Form; -use Glpi\Http\Response; - -/** - * AJAX endpoint used to submit answers for a given form. - */ - -// TODO: check that the current user is allowed to respond to forms - -// Validate forms_forms_id parameter -$forms_id = $_POST['forms_id'] ?? 0; -if (!$forms_id) { - Response::sendError(400, __('Missing form id')); -} - -// Load form -$form = Form::getById($forms_id); -if (!$form) { - Response::sendError(404, __('Form not found')); -} - -// Validate the 'answers' parameter by filtering and reindexing the $_POST array. -$answers = (new EndUserInputNameProvider())->getAnswers($_POST); -if (empty($answers)) { - Response::sendError(400, __('Invalid answers')); -} - -// Try to save answers -$handler = AnswersHandler::getInstance(); -try { - $answers_set = $handler->saveAnswers($form, $answers, Session::getLoginUserID()); -} catch (\Throwable $e) { - Response::sendError(500, __('Failed to save answers')); -} - -$links = []; -foreach ($answers_set->getCreatedItems() as $item) { - if ($item->can($item->getID(), READ)) { - $links[] = $item->getLink(); - } -} - -// If no items were created, display a link to the answers themselves instead -if (empty($links)) { - $links[] = $answers_set->getLink(); -} - -// Success response -$response = new Response( - 200, - ['Content-Type' => 'application/json'], - json_encode([ - 'links_to_created_items' => $links, - ]), -); -$response->send(); diff --git a/phpunit/functional/Glpi/Form/AnswersSetTest.php b/phpunit/functional/Glpi/Form/AnswersSetTest.php index 5dd86838eef..1d95dfcbbf4 100644 --- a/phpunit/functional/Glpi/Form/AnswersSetTest.php +++ b/phpunit/functional/Glpi/Form/AnswersSetTest.php @@ -36,11 +36,11 @@ namespace tests\units\Glpi\Form; use CommonGLPI; -use Computer; use DbTestCase; use Glpi\Form\Answer; use Glpi\Form\AnswersHandler\AnswersHandler; use Glpi\Form\AnswersSet; +use Glpi\Form\Destination\FormDestinationProblem; use Glpi\Form\Form; use Glpi\Form\Question; use Glpi\Form\QuestionType\QuestionTypeAssignee; @@ -322,6 +322,29 @@ public function testGetCreatedItems(): void // handled in the "FormDestinationTicket" tests class. } + public function testGetLinksToCreatedItemssForAdmin() + { + $this->login(); + $form = $this->createAndGetFormWithTwoProblemDestination(); + + $answers_handler = AnswersHandler::getInstance(); + $answers = $answers_handler->saveAnswers($form, [], \Session::getLoginUserID()); + + $this->assertCount(2, $answers->getLinksToCreatedItems()); + } + + public function testGetLinksToCreatedItemssForEndUser() + { + $this->login("post-only", "postonly"); + $form = $this->createAndGetFormWithTwoProblemDestination(); + + $answers_handler = AnswersHandler::getInstance(); + $answers = $answers_handler->saveAnswers($form, [], \Session::getLoginUserID()); + + // User can't see problems, still there is one fallback link to the answers + $this->assertCount(1, $answers->getLinksToCreatedItems()); + } + private function createAndGetFormWithTwoAnswers(): Form { $form = $this->createForm( @@ -339,4 +362,16 @@ private function createAndGetFormWithTwoAnswers(): Form return $form; } + + private function createAndGetFormWithTwoProblemDestination(): Form + { + $form = $this->createForm( + (new FormBuilder()) + ->addQuestion("Name", QuestionTypeShortText::class) + ->addDestination(FormDestinationProblem::class, "My first problem") + ->addDestination(FormDestinationProblem::class, "My second problem") + ); + + return $form; + } } diff --git a/src/Glpi/Controller/Form/SubmitAnswerController.php b/src/Glpi/Controller/Form/SubmitAnswerController.php new file mode 100644 index 00000000000..460ecdb2119 --- /dev/null +++ b/src/Glpi/Controller/Form/SubmitAnswerController.php @@ -0,0 +1,123 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Controller\Form; + +use Glpi\Controller\AbstractController; +use Glpi\Form\AccessControl\FormAccessControlManager; +use Glpi\Form\AccessControl\FormAccessParameters; +use Glpi\Form\AnswersHandler\AnswersHandler; +use Glpi\Form\AnswersSet; +use Glpi\Form\EndUserInputNameProvider; +use Glpi\Form\Form; +use Glpi\Security\Attribute\SecurityStrategy; +use Session; +use Symfony\Component\CssSelector\Exception\InternalErrorException; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Routing\Attribute\Route; + +final class SubmitAnswerController extends AbstractController +{ + #[SecurityStrategy('no_check')] // Some forms can be accessed anonymously + #[Route( + "/form/submit-answers", + name: "glpi_form_submit_answers", + methods: "POST" + )] + public function __invoke(Request $request): Response + { + $form = $this->loadSubmittedForm($request); + $this->checkFormAccessPolicies($form, $request); + + $answers = $this->saveSubmittedAnswers($form, $request); + $links = $answers->getLinksToCreatedItems(); + + return new JsonResponse([ + 'links_to_created_items' => $links, + ]); + } + + private function loadSubmittedForm(Request $request): Form + { + $forms_id = $request->request->getInt("forms_id"); + if (!$forms_id) { + throw new BadRequestHttpException(); + } + + $form = Form::getById($forms_id); + if (!$form) { + throw new NotFoundHttpException(); + } + + return $form; + } + + private function checkFormAccessPolicies(Form $form, Request $request) + { + $form_access_manager = FormAccessControlManager::getInstance(); + + $parameters = new FormAccessParameters( + session_info: Session::getCurrentSessionInfo(), + url_parameters: $request->request->all(), + ); + + if (!$form_access_manager->canAnswerForm($form, $parameters)) { + throw new AccessDeniedHttpException(); + } + } + + private function saveSubmittedAnswers( + Form $form, + Request $request + ): AnswersSet { + $post = $request->request->all(); + $answers = (new EndUserInputNameProvider())->getAnswers($post); + if (empty($answers)) { + throw new BadRequestHttpException(); + } + + $handler = AnswersHandler::getInstance(); + return $handler->saveAnswers( + $form, + $answers, + Session::getLoginUserID() + ); + } +} diff --git a/src/Glpi/Form/AnswersSet.php b/src/Glpi/Form/AnswersSet.php index f22c84bd831..74d255fd6e6 100644 --- a/src/Glpi/Form/AnswersSet.php +++ b/src/Glpi/Form/AnswersSet.php @@ -277,6 +277,30 @@ public function getCreatedItems(): array return $items; } + /** + * Get links to created items that are visible for the current user. + * + * @return string[] + */ + public function getLinksToCreatedItems(): array + { + $links = []; + foreach ($this->getCreatedItems() as $item) { + if ($item->canViewItem()) { + $links[] = $item->getLink(); + } + } + + // If no items were created, display one link to the answers themselves + // TODO: delete this later as we will force at least one ticket to + // be always created. + if (empty($links)) { + $links[] = $this->getLink(); + } + + return $links; + } + /** * Count answers for a given form * diff --git a/templates/pages/form_renderer.html.twig b/templates/pages/form_renderer.html.twig index 67ec7a7b8fc..84133ca3233 100644 --- a/templates/pages/form_renderer.html.twig +++ b/templates/pages/form_renderer.html.twig @@ -34,7 +34,7 @@ {# Is this a single or multi sections forms ? #} {% set is_single_section_form = form.getSections()|length == 1 %} -
+
@@ -189,6 +189,8 @@

+ +