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 2 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
8 changes: 5 additions & 3 deletions ajax/form/answer.php
Expand Up @@ -34,7 +34,9 @@
*/

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

include('../../inc/includes.php');
Expand All @@ -57,9 +59,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 = EndUserInputNameProvider::getAnswers();
if (empty($answers)) {
Response::sendError(400, __('Invalid answers'));
}

Expand Down
108 changes: 108 additions & 0 deletions src/Form/EndUserInputNameProvider.php
@@ -0,0 +1,108 @@
<?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;

/**
* Helpdesk form
*/
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 static function getEndUserInputName(Question $question): string
{
return sprintf(static::END_USER_INPUT_NAME, $question->getID());
ccailly marked this conversation as resolved.
Show resolved Hide resolved
}

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

return $reindexedAnswers;
}
ccailly marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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 static 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 static 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 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
$_POST = [
$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(\Glpi\Form\EndUserInputNameProvider::getAnswers())
->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',
]);
}
}