Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(forms): centralize end user input name #17015

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
ccailly marked this conversation as resolved.
Show resolved Hide resolved
{
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 @@ -263,7 +263,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 @@ -272,7 +272,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 @@ -105,7 +105,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 @@ -347,7 +347,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 @@ -105,7 +105,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 @@ -124,7 +124,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',
]);
}
}