Skip to content

Commit

Permalink
fix(radiosfield,checkboxesfield,glpiselectfield,requesttypefield,urge…
Browse files Browse the repository at this point in the history
…ncyfield): validate default value before saving
  • Loading branch information
btry committed Aug 24, 2023
1 parent fc0ea0a commit 4815abe
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 69 deletions.
16 changes: 9 additions & 7 deletions inc/abstractfield.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -199,6 +200,7 @@ function ($value) {
$value
);

return $value;
return $DB->escape(json_encode($value, JSON_UNESCAPED_UNICODE));
}

Expand Down
23 changes: 17 additions & 6 deletions inc/field/checkboxesfield.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
7 changes: 5 additions & 2 deletions inc/field/glpiselectfield.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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'],
Expand Down Expand Up @@ -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;
}
Expand All @@ -131,7 +134,7 @@ public static function canRequire(): bool {
return true;
}

public function getAvailableValues(): array {
public function getAvailableValues(array $values = null): array {
return [];
}

Expand Down
33 changes: 28 additions & 5 deletions inc/field/radiosfield.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
Expand Down Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion inc/field/requesttypefield.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public static function canRequire(): bool {
return true;
}

public function getAvailableValues() {
public function getAvailableValues(array $values = null): array {
return Ticket::getTypes();
}

Expand Down
2 changes: 1 addition & 1 deletion inc/field/urgencyfield.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
8 changes: 8 additions & 0 deletions inc/formanswer.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
*
Expand Down
2 changes: 1 addition & 1 deletion templates/field/glpiselectfield.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
147 changes: 138 additions & 9 deletions tests/3-unit/GlpiPlugin/Formcreator/Field/CheckboxesField.php
Original file line number Diff line number Diff line change
Expand Up @@ -234,41 +234,170 @@ 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\"]');
$this->string($out['default_values'])->isEqualTo('[\"éè\"]');

// 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\"]');
Expand Down

0 comments on commit 4815abe

Please sign in to comment.