Skip to content

Commit

Permalink
refactor(forms): centralize end user input name
Browse files Browse the repository at this point in the history
  • Loading branch information
ccailly committed Apr 29, 2024
1 parent 0e77c09 commit 21d6fcb
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 9 deletions.
7 changes: 4 additions & 3 deletions ajax/form/answer.php
Expand Up @@ -34,6 +34,7 @@
*/

use Glpi\Form\AnswersHandler\AnswersHandler;
use Glpi\Form\EndUserInputNameProvider;
use Glpi\Form\Form;
use Glpi\Http\Response;

Expand All @@ -57,9 +58,9 @@
Response::sendError(404, __('Form not found'));
}

// Validate answers parameter
$answers = $_POST['answers'] ?? [];
if (!is_array($answers) || empty($answers)) {
// Validate the 'answers' parameter by filtering and reindexing the $_POST array.
$answers = (new EndUserInputNameProvider())->getAnswers($_POST);
if (empty($answers)) {
Response::sendError(400, __('Invalid answers'));
}

Expand Down
109 changes: 109 additions & 0 deletions src/Form/EndUserInputNameProvider.php
@@ -0,0 +1,109 @@
<?php

/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2024 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/

namespace Glpi\Form;

/**
* Utility class to provide the end user input name
*/
final class EndUserInputNameProvider
{
public const END_USER_INPUT_NAME = 'answers_%d';
public const END_USER_INPUT_NAME_REGEX = '/^_?answers_(\d+)$/';

/**
* Get the end user input name for a given question
*
* @param Question $question
* @return string
*/
public function getEndUserInputName(Question $question): string
{
return sprintf(self::END_USER_INPUT_NAME, $question->getID());
}

/**
* Get the answers submitted by the end user
* The answers are indexed by question ID
*
* @param array $inputs The inputs submitted by the end user
* @return array
*/
public function getAnswers(array $inputs): array
{
$filteredAnswers = self::filterAnswers($inputs);
$reindexedAnswers = self::reindexAnswers($filteredAnswers);

return $reindexedAnswers;
}

/**
* Filter the answers submitted by the end user
* Only the answers that match the end user input name pattern are kept
*
* @param array $answers
* @return array
*/
private function filterAnswers(array $answers): array
{
return array_filter(
$answers,
function ($key) {
return preg_match(self::END_USER_INPUT_NAME_REGEX, $key);
},
ARRAY_FILTER_USE_KEY
);
}

/**
* Reindex the answers submitted by the end user
* The answers are indexed by question ID
*
* @param array $answers
* @return array
*/
private function reindexAnswers(array $answers): array
{
return array_reduce(
array_keys($answers),
function ($carry, $key) use ($answers) {
$question_id = (int) preg_replace(self::END_USER_INPUT_NAME_REGEX, '$1', $key);
$carry[$question_id] = $answers[$key];
return $carry;
},
[]
);
}
}
5 changes: 5 additions & 0 deletions src/Form/Question.php
Expand Up @@ -121,6 +121,11 @@ protected function getForm(): Form
return $this->getItem()->getItem();
}

public function getEndUserInputName(): string
{
return (new EndUserInputNameProvider())->getEndUserInputName($this);
}

public function prepareInputForAdd($input)
{
$this->prepareInput($input);
Expand Down
4 changes: 2 additions & 2 deletions src/Form/QuestionType/AbstractQuestionTypeActors.php
Expand Up @@ -258,7 +258,7 @@ public function renderEndUserTemplate(Question $question): string
{% import 'components/form/fields_macros.html.twig' as fields %}
{% set actors_dropdown = call('Glpi\\\\Form\\\\Dropdown\\\\FormActorsDropdown::show', [
'answers[' ~ question.fields.id ~ ']',
question.getEndUserInputName(),
value,
{
'multiple': is_multiple_actors,
Expand All @@ -267,7 +267,7 @@ public function renderEndUserTemplate(Question $question): string
]) %}
{{ fields.htmlField(
'answers[' ~ question.fields.id ~ ']',
question.getEndUserInputName(),
actors_dropdown,
'',
{
Expand Down
2 changes: 1 addition & 1 deletion src/Form/QuestionType/AbstractQuestionTypeShortAnswer.php
Expand Up @@ -82,7 +82,7 @@ public function renderEndUserTemplate(
<input
type="{{ input_type|e('html_attr') }}"
class="form-control"
name="answers[{{ question.fields.id|e('html_attr') }}]"
name="{{ question.getEndUserInputName() }}"
value="{{ question.fields.default_value|e('html_attr') }}"
{{ question.fields.is_mandatory ? 'required' : '' }}
>
Expand Down
2 changes: 1 addition & 1 deletion src/Form/QuestionType/QuestionTypeDateTime.php
Expand Up @@ -324,7 +324,7 @@ public function renderEndUserTemplate(
<input
type="{{ input_type|e('html_attr') }}"
class="form-control"
name="answers[{{ question.fields.id|e('html_attr') }}]"
name="{{ question.getEndUserInputName() }}"
value="{{ default_value|e('html_attr') }}"
{{ question.fields.is_mandatory ? 'required' : '' }}
>
Expand Down
2 changes: 1 addition & 1 deletion src/Form/QuestionType/QuestionTypeLongText.php
Expand Up @@ -82,7 +82,7 @@ public function renderEndUserTemplate(Question $question): string
{% import 'components/form/fields_macros.html.twig' as fields %}
{{ fields.textareaField(
"answers[" ~ question.fields.id ~ "]",
question.getEndUserInputName(),
question.fields.default_value,
"",
{
Expand Down
2 changes: 1 addition & 1 deletion src/Form/QuestionType/QuestionTypeUrgency.php
Expand Up @@ -101,7 +101,7 @@ public function renderEndUserTemplate(Question $question): string
{% import 'components/form/fields_macros.html.twig' as fields %}
{{ fields.dropdownArrayField(
'answers[' ~ question.fields.id ~ ']',
question.getEndUserInputName(),
value,
urgency_levels,
'',
Expand Down
88 changes: 88 additions & 0 deletions tests/functional/Glpi/Form/EndUserInputNameProvider.php
@@ -0,0 +1,88 @@
<?php

/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2024 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/

namespace tests\units\Glpi\Form;

use DbTestCase;
use Glpi\Form\QuestionType\QuestionTypeShortText;
use Glpi\Tests\FormBuilder;
use Glpi\Tests\FormTesterTrait;

class EndUserInputNameProvider extends DbTestCase
{
use FormTesterTrait;

public function testGetEndUserInputName()
{
// Create a new form
$form = $this->createForm(
(new FormBuilder())
->addQuestion('Name', QuestionTypeShortText::class)
);

// Check that the end user input name contains the question ID
// and if the regex match with the generated end user input name
foreach ($form->getQuestions() as $question) {
$this->string($question->getEndUserInputName())
->contains($question->getID())
->match(\Glpi\Form\EndUserInputNameProvider::END_USER_INPUT_NAME_REGEX);
}
}

public function testGetAnswers()
{
// Create a new form
$form = $this->createForm(
(new FormBuilder())
->addQuestion('Name', QuestionTypeShortText::class)
->addQuestion('Email', QuestionTypeShortText::class)
);

// Generate the answers
$inputs = [
$form->getQuestions()[array_keys($form->getQuestions())[0]]->getEndUserInputName() => 'John Doe',
$form->getQuestions()[array_keys($form->getQuestions())[1]]->getEndUserInputName() => 'john.doe@mail.mail',
'invalid_input' => 'invalid_value',
];

// Check that the answers are correctly indexed by question ID
$this->array((new \Glpi\Form\EndUserInputNameProvider())->getAnswers($inputs))
->hasSize(2)
->isEqualTo([
$form->getQuestions()[array_keys($form->getQuestions())[0]]->getID() => 'John Doe',
$form->getQuestions()[array_keys($form->getQuestions())[1]]->getID() => 'john.doe@mail.mail',
]);
}
}

0 comments on commit 21d6fcb

Please sign in to comment.