From d410452fa5369f2dbc58c388a24e79f5a075de69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Cailly?= Date: Tue, 23 Apr 2024 09:28:31 +0200 Subject: [PATCH 1/3] feat(forms): Adding file category and related sub question type --- src/Form/AnswersHandler/AnswersHandler.php | 7 +- src/Form/Question.php | 2 +- .../QuestionType/AbstractQuestionType.php | 6 + .../QuestionType/QuestionTypeCategory.php | 6 + src/Form/QuestionType/QuestionTypeFile.php | 140 ++++++++++++++++++ .../QuestionType/QuestionTypeInterface.php | 10 ++ tests/units/Glpi/Form/AnswersSet.php | 14 +- .../QuestionType/QuestionTypesManager.php | 10 +- 8 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 src/Form/QuestionType/QuestionTypeFile.php diff --git a/src/Form/AnswersHandler/AnswersHandler.php b/src/Form/AnswersHandler/AnswersHandler.php index d15244c0903..e623102b7c5 100644 --- a/src/Form/AnswersHandler/AnswersHandler.php +++ b/src/Form/AnswersHandler/AnswersHandler.php @@ -230,11 +230,12 @@ protected function createAnswserSet( // We need to keep track of some extra data like label and type because // the linked question might be deleted one day but the answer must still // be readable. + $question = $questions[$question_id]; $formatted_answers[] = [ 'question' => $question_id, - 'value' => $answer, - 'label' => $questions[$question_id]->fields['name'], - 'type' => $questions[$question_id]->fields['type'], + 'value' => $question->getQuestionType()::prepareEndUserAnswer($question, $answer), + 'label' => $question->fields['name'], + 'type' => $question->fields['type'], ]; } diff --git a/src/Form/Question.php b/src/Form/Question.php index de161803c35..0f32b091180 100644 --- a/src/Form/Question.php +++ b/src/Form/Question.php @@ -116,7 +116,7 @@ public function getExtraDatas(): ?array * * @return Form */ - protected function getForm(): Form + public function getForm(): Form { return $this->getItem()->getItem(); } diff --git a/src/Form/QuestionType/AbstractQuestionType.php b/src/Form/QuestionType/AbstractQuestionType.php index 7f9db167e60..e90122b0165 100644 --- a/src/Form/QuestionType/AbstractQuestionType.php +++ b/src/Form/QuestionType/AbstractQuestionType.php @@ -51,6 +51,12 @@ public static function formatDefaultValueForDB(mixed $value): ?string return $value; // Default value is already formatted } + #[Override] + public static function prepareEndUserAnswer(Question $question, mixed $answer): mixed + { + return $answer; + } + #[Override] public static function validateExtraDataInput(array $input): bool { diff --git a/src/Form/QuestionType/QuestionTypeCategory.php b/src/Form/QuestionType/QuestionTypeCategory.php index cf0f8d9c3c7..ebe5cf24423 100644 --- a/src/Form/QuestionType/QuestionTypeCategory.php +++ b/src/Form/QuestionType/QuestionTypeCategory.php @@ -70,6 +70,11 @@ enum QuestionTypeCategory: string */ case REQUEST_TYPE = "request_type"; + /** + * Question that expect a file upload + */ + case FILE = "file"; + /** * Get category label * @return string @@ -83,6 +88,7 @@ public function getLabel(): string self::ACTORS => __("Actors"), self::URGENCY => __("Urgency"), self::REQUEST_TYPE => __("Request type"), + self::FILE => __("File") }; } } diff --git a/src/Form/QuestionType/QuestionTypeFile.php b/src/Form/QuestionType/QuestionTypeFile.php new file mode 100644 index 00000000000..6074b5a8bb0 --- /dev/null +++ b/src/Form/QuestionType/QuestionTypeFile.php @@ -0,0 +1,140 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Form\QuestionType; + +use Document; +use Glpi\Application\View\TemplateRenderer; +use Glpi\Form\Question; +use Override; + +final class QuestionTypeFile extends AbstractQuestionType +{ + #[Override] + public static function prepareEndUserAnswer(Question $question, mixed $answer): mixed + { + $form = $question->getForm(); + $document = new Document(); + $document_ids = []; + foreach ($answer as $file) { + $document_ids[] = $document->add([ + 'name' => sprintf('%s - %s', $form->getName(), $question->getName()), + 'entities_id' => $form->getEntityID(), + 'is_recursive' => $form->isRecursive(), + '_filename' => [$file], + '_prefix_filename' => [$_POST['_prefix_' . $question->getEndUserInputName()]], + ]); + } + + return $document_ids; + } + + #[Override] + public function renderAdministrationTemplate(?Question $question): string + { + $template = <<renderFromStringTemplate($template, [ + 'question' => $question + ]); + } + + #[Override] + public function renderAdministrationOptionsTemplate(?Question $question): string + { + return ''; + } + + #[Override] + public function renderEndUserTemplate(Question $question): string + { + $template = <<renderFromStringTemplate($template, [ + 'question' => $question + ]); + } + + #[Override] + public function renderAnswerTemplate($answer): string + { + $template = << + {{ document.getLink()|raw }} + + {% endfor %} +TWIG; + + $twig = TemplateRenderer::getInstance(); + return $twig->renderFromStringTemplate($template, [ + 'documents' => array_map(fn($document_id) => (new Document())->getById($document_id), $answer) + ]); + } + + #[Override] + public function getCategory(): QuestionTypeCategory + { + return QuestionTypeCategory::FILE; + } +} diff --git a/src/Form/QuestionType/QuestionTypeInterface.php b/src/Form/QuestionType/QuestionTypeInterface.php index 309e137b6a1..1a5c4f950f4 100644 --- a/src/Form/QuestionType/QuestionTypeInterface.php +++ b/src/Form/QuestionType/QuestionTypeInterface.php @@ -63,6 +63,16 @@ public static function formatDefaultValueForDB(mixed $value): ?string; */ public static function validateExtraDataInput(array $input): bool; + /** + * Prepare the answer for the end user. + * This method is called before saving the answer. + * + * @param Question $question The question data. + * @param mixed $answer The raw answer data. + * @return mixed The prepared answer data. + */ + public static function prepareEndUserAnswer(Question $question, mixed $answer): mixed; + /** * Render the administration template for the given question. * This template is used on the form editor page. diff --git a/tests/units/Glpi/Form/AnswersSet.php b/tests/units/Glpi/Form/AnswersSet.php index 7a1fb719b18..b20eb8e89f8 100644 --- a/tests/units/Glpi/Form/AnswersSet.php +++ b/tests/units/Glpi/Form/AnswersSet.php @@ -45,12 +45,12 @@ use Glpi\Form\Destination\FormDestinationTicket; use Glpi\Form\QuestionType\QuestionTypeDateTime; use Glpi\Form\QuestionType\QuestionTypeEmail; +use Glpi\Form\QuestionType\QuestionTypeFile; use Glpi\Form\QuestionType\QuestionTypeLongText; use Glpi\Form\QuestionType\QuestionTypeNumber; use Glpi\Form\QuestionType\QuestionTypeRequestType; use Glpi\Form\QuestionType\QuestionTypeShortText; use Glpi\Form\QuestionType\QuestionTypesManager; -use Glpi\Form\QuestionType\QuestionTypeTime; use Glpi\Form\QuestionType\QuestionTypeUrgency; use Glpi\Tests\FormBuilder; use Glpi\Tests\FormTesterTrait; @@ -259,7 +259,16 @@ public function testShowForm(): void ->addQuestion("Assignee", QuestionTypeAssignee::class) ->addQuestion("Urgency", QuestionTypeUrgency::class) ->addQuestion("Request type", QuestionTypeRequestType::class) + ->addQuestion("File", QuestionTypeFile::class) ); + + // File question type requires an uploaded file + $unique_id = uniqid(); + $filename = $unique_id . '-test-show-form-question-type-file.txt'; + copy(__DIR__ . '/../../../fixtures/uploads/bar.txt', GLPI_TMP_DIR . '/' . $filename); + $question = \Glpi\Form\Question::getById($this->getQuestionId($form, "File")); + $_POST['_prefix_' . $question->getEndUserInputName()] = $unique_id; + $answers_set = $answers_handler->saveAnswers($form, [ $this->getQuestionId($form, "Name") => "Pierre Paul Jacques", $this->getQuestionId($form, "Age") => 20, @@ -282,7 +291,8 @@ public function testShowForm(): void Supplier::getForeignKeyField() . '-1' ], $this->getQuestionId($form, "Urgency") => 2, - $this->getQuestionId($form, "Request type") => 1 + $this->getQuestionId($form, "Request type") => 1, + $this->getQuestionId($form, "File") => [$filename] ], \Session::getLoginUserID()); // Ensure we used every possible questions types diff --git a/tests/units/Glpi/Form/QuestionType/QuestionTypesManager.php b/tests/units/Glpi/Form/QuestionType/QuestionTypesManager.php index 7a2a5f9c14d..51dbed6ac6d 100644 --- a/tests/units/Glpi/Form/QuestionType/QuestionTypesManager.php +++ b/tests/units/Glpi/Form/QuestionType/QuestionTypesManager.php @@ -83,7 +83,8 @@ public function testGetCategories(): void QuestionTypeCategory::DATE_AND_TIME, QuestionTypeCategory::ACTORS, QuestionTypeCategory::URGENCY, - QuestionTypeCategory::REQUEST_TYPE + QuestionTypeCategory::REQUEST_TYPE, + QuestionTypeCategory::FILE, ]; // Manual array comparison, `isEqualTo` doesn't seem to work properly @@ -146,6 +147,13 @@ protected function testGetTypesForCategoryProvider(): iterable new \Glpi\Form\QuestionType\QuestionTypeRequestType(), ] ]; + + yield [ + QuestionTypeCategory::FILE, + [ + new \Glpi\Form\QuestionType\QuestionTypeFile(), + ] + ]; } /** From 7d8d6e4b4ed851acedf77c9c63268f9c35fd4f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Cailly?= Date: Mon, 13 May 2024 13:46:25 +0200 Subject: [PATCH 2/3] refactor: make static methods to instance methods --- src/Form/AnswersHandler/AnswersHandler.php | 2 +- src/Form/Question.php | 4 ++-- src/Form/QuestionType/AbstractQuestionType.php | 6 +++--- src/Form/QuestionType/AbstractQuestionTypeActors.php | 4 ++-- src/Form/QuestionType/QuestionTypeDateTime.php | 2 +- src/Form/QuestionType/QuestionTypeFile.php | 2 +- src/Form/QuestionType/QuestionTypeInterface.php | 6 +++--- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Form/AnswersHandler/AnswersHandler.php b/src/Form/AnswersHandler/AnswersHandler.php index e623102b7c5..5146e3e96ef 100644 --- a/src/Form/AnswersHandler/AnswersHandler.php +++ b/src/Form/AnswersHandler/AnswersHandler.php @@ -233,7 +233,7 @@ protected function createAnswserSet( $question = $questions[$question_id]; $formatted_answers[] = [ 'question' => $question_id, - 'value' => $question->getQuestionType()::prepareEndUserAnswer($question, $answer), + 'value' => $question->getQuestionType()->prepareEndUserAnswer($question, $answer), 'label' => $question->fields['name'], 'type' => $question->fields['type'], ]; diff --git a/src/Form/Question.php b/src/Form/Question.php index 0f32b091180..7460c181fd9 100644 --- a/src/Form/Question.php +++ b/src/Form/Question.php @@ -154,7 +154,7 @@ private function prepareInput(&$input) if ($question_type) { if (isset($input['default_value'])) { - $input['default_value'] = $question_type::formatDefaultValueForDB($input['default_value']); + $input['default_value'] = (new $question_type())->formatDefaultValueForDB($input['default_value']); } $extra_data = $input['extra_data'] ?? []; @@ -167,7 +167,7 @@ private function prepareInput(&$input) } } - $is_extra_data_valid = $question_type::validateExtraDataInput($extra_data); + $is_extra_data_valid = (new $question_type())->validateExtraDataInput($extra_data); if (!$is_extra_data_valid) { throw new \InvalidArgumentException("Invalid extra data for question"); diff --git a/src/Form/QuestionType/AbstractQuestionType.php b/src/Form/QuestionType/AbstractQuestionType.php index e90122b0165..15d2c6be469 100644 --- a/src/Form/QuestionType/AbstractQuestionType.php +++ b/src/Form/QuestionType/AbstractQuestionType.php @@ -46,19 +46,19 @@ public function __construct() } #[Override] - public static function formatDefaultValueForDB(mixed $value): ?string + public function formatDefaultValueForDB(mixed $value): ?string { return $value; // Default value is already formatted } #[Override] - public static function prepareEndUserAnswer(Question $question, mixed $answer): mixed + public function prepareEndUserAnswer(Question $question, mixed $answer): mixed { return $answer; } #[Override] - public static function validateExtraDataInput(array $input): bool + public function validateExtraDataInput(array $input): bool { return empty($input); // No extra data by default } diff --git a/src/Form/QuestionType/AbstractQuestionTypeActors.php b/src/Form/QuestionType/AbstractQuestionTypeActors.php index 2d7f3718f26..74fcfdb1867 100644 --- a/src/Form/QuestionType/AbstractQuestionTypeActors.php +++ b/src/Form/QuestionType/AbstractQuestionTypeActors.php @@ -52,7 +52,7 @@ abstract class AbstractQuestionTypeActors extends AbstractQuestionType abstract public function getAllowedActorTypes(): array; #[Override] - public static function formatDefaultValueForDB(mixed $value): ?string + public function formatDefaultValueForDB(mixed $value): ?string { if (is_array($value)) { return implode(',', $value); @@ -62,7 +62,7 @@ public static function formatDefaultValueForDB(mixed $value): ?string } #[Override] - public static function validateExtraDataInput(array $input): bool + public function validateExtraDataInput(array $input): bool { $allowed_keys = [ 'is_multiple_actors' diff --git a/src/Form/QuestionType/QuestionTypeDateTime.php b/src/Form/QuestionType/QuestionTypeDateTime.php index ac9fbce4446..625cb87a2f0 100644 --- a/src/Form/QuestionType/QuestionTypeDateTime.php +++ b/src/Form/QuestionType/QuestionTypeDateTime.php @@ -152,7 +152,7 @@ public function isTimeEnabled(?Question $question): bool } #[Override] - public static function validateExtraDataInput(array $input): bool + public function validateExtraDataInput(array $input): bool { $allowed_keys = [ 'is_default_value_current_time', diff --git a/src/Form/QuestionType/QuestionTypeFile.php b/src/Form/QuestionType/QuestionTypeFile.php index 6074b5a8bb0..134231c9227 100644 --- a/src/Form/QuestionType/QuestionTypeFile.php +++ b/src/Form/QuestionType/QuestionTypeFile.php @@ -43,7 +43,7 @@ final class QuestionTypeFile extends AbstractQuestionType { #[Override] - public static function prepareEndUserAnswer(Question $question, mixed $answer): mixed + public function prepareEndUserAnswer(Question $question, mixed $answer): mixed { $form = $question->getForm(); $document = new Document(); diff --git a/src/Form/QuestionType/QuestionTypeInterface.php b/src/Form/QuestionType/QuestionTypeInterface.php index 1a5c4f950f4..112b8dbf5a5 100644 --- a/src/Form/QuestionType/QuestionTypeInterface.php +++ b/src/Form/QuestionType/QuestionTypeInterface.php @@ -51,7 +51,7 @@ public function __construct(); * @param mixed $value The default value to format. * @return string */ - public static function formatDefaultValueForDB(mixed $value): ?string; + public function formatDefaultValueForDB(mixed $value): ?string; /** * Validate the input for extra data of the question. @@ -61,7 +61,7 @@ public static function formatDefaultValueForDB(mixed $value): ?string; * * @return bool */ - public static function validateExtraDataInput(array $input): bool; + public function validateExtraDataInput(array $input): bool; /** * Prepare the answer for the end user. @@ -71,7 +71,7 @@ public static function validateExtraDataInput(array $input): bool; * @param mixed $answer The raw answer data. * @return mixed The prepared answer data. */ - public static function prepareEndUserAnswer(Question $question, mixed $answer): mixed; + public function prepareEndUserAnswer(Question $question, mixed $answer): mixed; /** * Render the administration template for the given question. From a43bf12be0543b5be3d2a67cf11f088dcf33694f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Cailly?= <42278610+ccailly@users.noreply.github.com> Date: Mon, 13 May 2024 13:53:27 +0200 Subject: [PATCH 3/3] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cédric Anne --- src/Form/Question.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Form/Question.php b/src/Form/Question.php index 7460c181fd9..4fffbe6b74a 100644 --- a/src/Form/Question.php +++ b/src/Form/Question.php @@ -154,7 +154,7 @@ private function prepareInput(&$input) if ($question_type) { if (isset($input['default_value'])) { - $input['default_value'] = (new $question_type())->formatDefaultValueForDB($input['default_value']); + $input['default_value'] = $question_type->formatDefaultValueForDB($input['default_value']); } $extra_data = $input['extra_data'] ?? []; @@ -167,7 +167,7 @@ private function prepareInput(&$input) } } - $is_extra_data_valid = (new $question_type())->validateExtraDataInput($extra_data); + $is_extra_data_valid = $question_type->validateExtraDataInput($extra_data); if (!$is_extra_data_valid) { throw new \InvalidArgumentException("Invalid extra data for question");