diff --git a/inc/abstractfield.class.php b/inc/abstractfield.class.php index 0dfb61e99..a36b6b723 100644 --- a/inc/abstractfield.class.php +++ b/inc/abstractfield.class.php @@ -160,10 +160,13 @@ public function getLabel() { /** * Gets the available values for the field + * @param array $values values to parse. If null, the values are ised from the instance of the question * @return array available values */ - public function getAvailableValues() { - $values = json_decode($this->question->fields['values']); + public function getAvailableValues(array $values = null): array { + if ($values === null) { + $values = json_decode($this->question->fields['values']); + } $tab_values = []; foreach ($values as $value) { $trimmedValue = trim($value); @@ -179,13 +182,11 @@ public function isRequired(): bool { } /** - * trim values separated by \r\n + * trim and explode values separated by \r\n * @param string $value a value or default value - * @return string + * @return array */ - protected function trimValue($value) { - global $DB; - + protected function trimValue($value): array { $value = explode('\r\n', $value); // input has escpaed single quotes $value = Toolbox::stripslashes_deep($value); @@ -199,6 +200,7 @@ function ($value) { $value ); + return $value; return $DB->escape(json_encode($value, JSON_UNESCAPED_UNICODE)); } diff --git a/inc/field/checkboxesfield.class.php b/inc/field/checkboxesfield.class.php index eeb7fdde0..fb44ba339 100644 --- a/inc/field/checkboxesfield.class.php +++ b/inc/field/checkboxesfield.class.php @@ -249,22 +249,33 @@ public function isValidValue($value): bool { } public function prepareQuestionInputForSave($input) { + global $DB; + if (!isset($input['values']) || empty($input['values'])) { Session::addMessageAfterRedirect( - __('The field value is required:', 'formcreator') . ' ' . $input['name'], + __('The field value is required.', 'formcreator'), false, ERROR ); return []; } - // trim values - $input['values'] = $this->trimValue($input['values']); - - if (isset($input['default_values'])) { + $values = $this->trimValue($input['values']); + $defaultValues = $this->trimValue($input['default_values'] ?? ''); + if (count($defaultValues) > 0) { // trim values - $input['default_values'] = $this->trimValue($input['default_values']); + $validDefaultValues = array_intersect($this->getAvailableValues($values), $defaultValues); + if (count($validDefaultValues) != (count($defaultValues))) { + Session::addMessageAfterRedirect( + __('The default values are not in the list of available values.', 'formcreator'), + false, + ERROR + ); + return []; + } + $input['default_values'] = $DB->escape(json_encode($defaultValues, JSON_UNESCAPED_UNICODE)); } + $input['values'] = $DB->escape(json_encode($values, JSON_UNESCAPED_UNICODE)); return $input; } diff --git a/inc/field/glpiselectfield.class.php b/inc/field/glpiselectfield.class.php index f10168244..0e851523e 100644 --- a/inc/field/glpiselectfield.class.php +++ b/inc/field/glpiselectfield.class.php @@ -72,6 +72,7 @@ public function showForm(array $options): void { } $this->question->fields['default_values'] = Html::entities_deep($this->question->fields['default_values']); $this->deserializeValue($this->question->fields['default_values']); + $this->question->fields['_default_values'] = $this->value; TemplateRenderer::getInstance()->display($template, [ 'item' => $this->question, @@ -93,6 +94,8 @@ public function isValidValue($value): bool { } public function prepareQuestionInputForSave($input) { + global $DB; + if (!isset($input['itemtype']) || empty($input['itemtype'])) { Session::addMessageAfterRedirect( __('The field value is required:', 'formcreator') . ' ' . $input['name'], @@ -122,7 +125,7 @@ public function prepareQuestionInputForSave($input) { unset($input['show_tree_depth']); unset($input['selectable_tree_root']); - $input['values'] = json_encode($input['values']); + $input['values'] = $DB->escape(json_encode($input['values'], JSON_UNESCAPED_UNICODE)); return $input; } @@ -131,7 +134,7 @@ public static function canRequire(): bool { return true; } - public function getAvailableValues(): array { + public function getAvailableValues(array $values = null): array { return []; } diff --git a/inc/field/radiosfield.class.php b/inc/field/radiosfield.class.php index 5d8049ed2..f158189cc 100644 --- a/inc/field/radiosfield.class.php +++ b/inc/field/radiosfield.class.php @@ -49,7 +49,7 @@ public function isPrerequisites(): bool { public function showForm(array $options): void { $template = '@formcreator/field/' . $this->question->fields['fieldtype'] . 'field.html.twig'; - $this->question->fields['values'] = json_decode($this->question->fields['values']); + $this->question->fields['values'] = json_decode($this->question->fields['values']); $this->question->fields['values'] = is_array($this->question->fields['values']) ? $this->question->fields['values'] : []; $this->question->fields['values'] = implode("\r\n", $this->question->fields['values']); $this->deserializeValue($this->question->fields['default_values']); @@ -109,18 +109,41 @@ public static function getName(): string { } public function prepareQuestionInputForSave($input) { + global $DB; + if (!isset($input['values']) || empty($input['values'])) { Session::addMessageAfterRedirect( - __('The field value is required:', 'formcreator') . ' ' . $input['name'], + __('The field value is required.', 'formcreator'), false, ERROR ); return []; } - // trim values - $input['values'] = $this->trimValue($input['values']); - $input['default_values'] = trim($input['default_values']); + // trim values (actually there is only one value then no \r\n expected) + $defaultValues = $this->trimValue($input['default_values'] ?? ''); + if (count($defaultValues) > 1) { + Session::addMessageAfterRedirect( + __('Only one default value is allowed.', 'formcreator'), + false, + ERROR + ); + return []; + } + $values = $this->trimValue($input['values']); + if (count($defaultValues) > 0) { + $validDefaultValues = array_intersect($this->getAvailableValues($values), $defaultValues); + if (count($validDefaultValues) != count($defaultValues)) { + Session::addMessageAfterRedirect( + __('The default value is not in the list of available values.', 'formcreator'), + false, + ERROR + ); + return []; + } + } + $input['values'] = $DB->escape(json_encode($values, JSON_UNESCAPED_UNICODE)); + $input['default_values'] = array_pop($defaultValues); return $input; } diff --git a/inc/field/requesttypefield.class.php b/inc/field/requesttypefield.class.php index aa9f25f9e..7dcf66deb 100644 --- a/inc/field/requesttypefield.class.php +++ b/inc/field/requesttypefield.class.php @@ -111,7 +111,7 @@ public static function canRequire(): bool { return true; } - public function getAvailableValues() { + public function getAvailableValues(array $values = null): array { return Ticket::getTypes(); } diff --git a/inc/field/urgencyfield.class.php b/inc/field/urgencyfield.class.php index e2377475f..95ca463d8 100644 --- a/inc/field/urgencyfield.class.php +++ b/inc/field/urgencyfield.class.php @@ -114,7 +114,7 @@ public static function canRequire(): bool { return true; } - public function getAvailableValues() { + public function getAvailableValues(array $values = null): array { return [ 5 => _x('urgency', 'Very high'), 4 => _x('urgency', 'High'), diff --git a/inc/formanswer.class.php b/inc/formanswer.class.php index 92f5aab61..81952fc61 100644 --- a/inc/formanswer.class.php +++ b/inc/formanswer.class.php @@ -1368,6 +1368,8 @@ public function parseTags(string $content, PluginFormcreatorTargetInterface $tar $content = Sanitizer::sanitize($content); } + $content = $this->parseExtraTags($content, $target, $richText); + $hook_data = Plugin::doHookFunction('formcreator_parse_extra_tags', [ 'formanswer' => $this, 'content' => $content, @@ -1378,6 +1380,12 @@ public function parseTags(string $content, PluginFormcreatorTargetInterface $tar return $hook_data['content']; } + protected function parseExtraTags(string $content, PluginFormcreatorTargetInterface $target = null, $richText = false): string { + $content = str_replace('##answer_id##', $this->getField('id'), $content); + + return $content; + } + /** * Validates answers of a form * diff --git a/templates/field/glpiselectfield.html.twig b/templates/field/glpiselectfield.html.twig index 462396c72..9c8157866 100644 --- a/templates/field/glpiselectfield.html.twig +++ b/templates/field/glpiselectfield.html.twig @@ -72,7 +72,7 @@ {{ fields.dropdownField( item.fields['itemtype'], 'default_values', - default_values, + item.fields['_default_values'], __('Default values'), { label_class: 'col-xxl-4', input_class: 'col-xxl-8', diff --git a/tests/3-unit/GlpiPlugin/Formcreator/Field/CheckboxesField.php b/tests/3-unit/GlpiPlugin/Formcreator/Field/CheckboxesField.php index 247ea52eb..cd1080797 100644 --- a/tests/3-unit/GlpiPlugin/Formcreator/Field/CheckboxesField.php +++ b/tests/3-unit/GlpiPlugin/Formcreator/Field/CheckboxesField.php @@ -234,32 +234,161 @@ public function testDeserializeValue($value, $expected) { $this->string($output)->isEqualTo(implode(', ', $expected)); } - public function testPrepareQuestionInputForSave() { + public function providerPrepareQuestionInputForSave() { + global $DB; + $question = $this->getQuestion([ 'fieldtype' => 'checkboxes', 'name' => 'question', 'required' => '0', - 'default_values' => json_encode(['1', '2', '3', '5', '6']), - 'values' => json_encode(['1', '2', '3', '4', '5', '6']), + 'values' => '1\r\n2\r\n3\r\n4\r\n5\r\n6', + 'default_values' => '1\r\n2\r\n3\r\n5\r\n6', 'order' => '1', 'show_rule' => \PluginFormcreatorCondition::SHOW_RULE_ALWAYS, 'range_min' => 3, 'range_max' => 4, ]); + + yield [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => "", + 'name' => 'foo', + ], + 'expected' => [], + 'message' => 'The field value is required.', + ]; + + yield [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => 'éè\r\nsomething else', + 'default_values' => 'éè', + ], + 'expected' => [ + 'values' => '[\"éè\",\"something else\"]', + 'default_values' => '[\"éè\"]', + ], + 'message' => '', + ]; + + yield [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => ' something \r\n something else ', + 'default_values' => ' something ', + ], + 'expected' => [ + 'values' => '[\"something\",\"something else\"]', + 'default_values' => '[\"something\"]', + ], + 'message' => '', + ]; + + yield 'no default value' => [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => 'a\r\nb\r\nc', + 'name' => 'foo', + 'default_values' => '' + ], + 'expected' => [ + 'values' => $DB->escape('["a","b","c"]'), + 'name' => 'foo', + 'default_values' => '', + ], + 'message' => '', + ]; + + yield 'several default values' => [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => 'a\r\nb\r\nc', + 'name' => 'foo', + 'default_values' => 'a\r\n\b' + ], + 'expected' => [ + 'values' => $DB->escape('["a","b","c"]'), + 'name' => 'foo', + 'default_values' => $DB->escape('["a","b"]'), + ], + 'message' => '', + ]; + + yield 'one default value' => [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => 'a\r\nb\r\nc', + 'name' => 'foo', + 'default_values' => 'b' + ], + 'expected' => [ + 'values' => $DB->escape('["a","b","c"]'), + 'name' => 'foo', + 'default_values' => $DB->escape('["b"]'), + ], + 'message' => '', + ]; + + yield 'invalid default value' => [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => 'a\r\nb\r\nc', + 'name' => 'foo', + 'default_values' => 'z' + ], + 'expected' => [], + 'message' => 'The default values are not in the list of available values.', + ]; + } + + /** + * @dataProvider providerPrepareQuestionInputForSave + * + * @return void + */ + public function testPrepareQuestionInputForSave($field, $input, $expected, $message) { + + // Clean error messages + $_SESSION['MESSAGE_AFTER_REDIRECT'] = []; + + $output = $field->prepareQuestionInputForSave($input); + if ($expected === false || is_array($expected) && count($expected) == 0) { + $this->array($output)->hasSize(0); + $this->sessionHasMessage($message, ERROR); + //End of test on expected failure + return; + } + + $this->array($output)->isEqualTo($expected); + + return; + + $question = $this->getQuestion([ + 'fieldtype' => 'checkboxes', + 'name' => 'question', + 'required' => '0', + 'default_values' => json_encode(['1', '2', '3', '5', '6']), + 'values' => json_encode(['1', '2', '3', '4', '5', '6']), + 'order' => '1', + 'show_rule' => \PluginFormcreatorCondition::SHOW_RULE_ALWAYS, + 'range_min' => 3, + 'range_max' => 4, + ]); $fieldInstance = $this->newTestedInstance($question); // Test a value is mandatory $input = [ - 'values' => "", - 'name' => 'foo', + 'values' => "", + 'name' => 'foo', ]; $out = $fieldInstance->prepareQuestionInputForSave($input); $this->integer(count($out))->isEqualTo(0); // Test accented chars are kept $input = [ - 'values' => 'éè\r\nsomething else', - 'default_values' => 'éè', + 'values' => 'éè\r\nsomething else', + 'default_values' => 'éè', ]; $out = $fieldInstance->prepareQuestionInputForSave($input); $this->string($out['values'])->isEqualTo('[\"éè\",\"something else\"]'); @@ -267,8 +396,8 @@ public function testPrepareQuestionInputForSave() { // Test values are trimmed $input = [ - 'values' => ' something \r\n something else ', - 'default_values' => ' something ', + 'values' => ' something \r\n something else ', + 'default_values' => ' something ', ]; $out = $fieldInstance->prepareQuestionInputForSave($input); $this->string($out['values'])->isEqualTo('[\"something\",\"something else\"]'); diff --git a/tests/3-unit/GlpiPlugin/Formcreator/Field/RadiosField.php b/tests/3-unit/GlpiPlugin/Formcreator/Field/RadiosField.php index 172ac3343..2977a99ea 100644 --- a/tests/3-unit/GlpiPlugin/Formcreator/Field/RadiosField.php +++ b/tests/3-unit/GlpiPlugin/Formcreator/Field/RadiosField.php @@ -32,47 +32,132 @@ namespace GlpiPlugin\Formcreator\Field\tests\units; use GlpiPlugin\Formcreator\Tests\CommonAbstractFieldTestCase; use PluginFormcreatorFormAnswer; +use PluginFormcreatorCondition; class RadiosField extends CommonAbstractFieldTestCase { - public function testPrepareQuestionInputForSave() { + + public function providerPrepareQuestionInputForSave() { + global $DB; + $question = $this->getQuestion([ 'fieldtype' => 'radios', 'name' => 'question', 'required' => '0', - 'default_values' => '1\r\n2\r\n3\r\n4\r\n5\r\n6', - 'values' => '1\r\n2\r\n3\r\n4\r\n5\r\n6', + 'default_values' => '1', + 'values' => '1', 'order' => '1', - 'show_rule' => \PluginFormcreatorCondition::SHOW_RULE_ALWAYS, + 'show_rule' => PluginFormcreatorCondition::SHOW_RULE_ALWAYS, 'range_min' => 3, 'range_max' => 4, ]); - $fieldInstance = $this->newTestedInstance($question); - // Test a value is mandatory - $input = [ - 'values' => "", - 'name' => 'foo', + yield [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => "", + 'name' => 'foo', + ], + 'expected' => [], + 'message' => 'The field value is required.', ]; - $out = $fieldInstance->prepareQuestionInputForSave($input); - $this->integer(count($out))->isEqualTo(0); - // Test accented chars are kept - $input = [ - 'values' => 'éè\r\nsomething else', - 'default_values' => 'éè', + yield [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => 'éè\r\nsomething else', + 'default_values' => 'éè', + ], + 'expected' => [ + 'values' => '[\"éè\",\"something else\"]', + 'default_values' => 'éè', + ], + 'message' => '', ]; - $out = $fieldInstance->prepareQuestionInputForSave($input); - $this->string($out['values'])->isEqualTo('[\"éè\",\"something else\"]'); - $this->string($out['default_values'])->isEqualTo("éè"); - - // Test values are trimmed - $input = [ - 'values' => ' something \r\n something else ', - 'default_values' => ' something ', + + yield [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => ' something \r\n something else ', + 'default_values' => ' something ', + ], + 'expected' => [ + 'values' => '[\"something\",\"something else\"]', + 'default_values' => 'something', + ], + 'message' => '', + ]; + + yield 'no default value' => [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => 'a\r\nb\r\nc', + 'name' => 'foo', + 'default_values' => '' + ], + 'expected' => [ + 'values' => $DB->escape('["a","b","c"]'), + 'name' => 'foo', + 'default_values' => null, + ], + 'message' => '', ]; - $out = $fieldInstance->prepareQuestionInputForSave($input); - $this->string($out['values'])->isEqualTo('[\"something\",\"something else\"]'); - $this->string($out['default_values'])->isEqualTo("something"); + + yield 'several default values not allowed' => [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => 'a\r\nb\r\nc', + 'name' => 'foo', + 'default_values' => 'a\r\n\b' + ], + 'expected' => [], + 'message' => 'Only one default value is allowed.', + ]; + + yield 'one default value' => [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => 'a\r\nb\r\nc', + 'name' => 'foo', + 'default_values' => 'b' + ], + 'expected' => [ + 'values' => $DB->escape('["a","b","c"]'), + 'name' => 'foo', + 'default_values' => 'b' + ], + 'message' => '', + ]; + + yield 'invalid default value' => [ + 'field' => $this->newTestedInstance($question), + 'input' => [ + 'values' => 'a\r\nb\r\nc', + 'name' => 'foo', + 'default_values' => 'z' + ], + 'expected' => [], + 'message' => 'The default value is not in the list of available values.', + ]; + } + + /** + * @dataProvider providerPrepareQuestionInputForSave + * + * @return void + */ + public function testPrepareQuestionInputForSave($field, $input, $expected, $message) { + // Clean error messages + $_SESSION['MESSAGE_AFTER_REDIRECT'] = []; + + $output = $field->prepareQuestionInputForSave($input); + if ($expected === false || is_array($expected) && count($expected) == 0) { + $this->array($output)->hasSize(0); + $this->sessionHasMessage($message, ERROR); + //End of test on expected failure + return; + } + + $this->array($output)->isEqualTo($expected); } public function testGetName() {