Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Fixed

- Fix item creation failing when a Yes/No field has a non boolean default value
- Fix mandatory fields of a block hidden by its display conditions blocking the save of an item
- Fix fields values updates not applying the expected checks in some cases

Expand Down
5 changes: 5 additions & 0 deletions ajax/field_specific_fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@
'rand' => $rand,
],
);
} elseif ($type === 'yesno') {
Dropdown::showYesNo(
'default_value',
PluginFieldsField::normalizeYesNoValue($field->fields['default_value']),
);
} else {
echo Html::input(
'default_value',
Expand Down
41 changes: 40 additions & 1 deletion inc/field.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,25 @@ public static function installBaseData(Migration $migration, $version)
// change default_value from varchar to longtext
$migration->changeField($table, 'default_value', 'default_value', 'longtext');

// Yes/No default values were typed as free text, normalize them to 0/1
$yesno_fields = $DB->request([
'SELECT' => ['id', 'default_value'],
'FROM' => $table,
'WHERE' => [
'type' => 'yesno',
'NOT' => ['default_value' => ['0', '1']],
],
]);
foreach ($yesno_fields as $yesno_field) {
$migration->addPostQuery(
$DB->buildUpdate(
$table,
['default_value' => (string) self::normalizeYesNoValue($yesno_field['default_value'])],
['id' => $yesno_field['id']],
),
);
}
Comment on lines +130 to +147

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test for this migration (e.g. Non and yes rewritten to 0/1)?


$toolbox = new PluginFieldsToolbox();
$toolbox->fixFieldsNames($migration, ['NOT' => ['type' => 'dropdown']]);

Expand Down Expand Up @@ -288,6 +307,8 @@ public function prepareInputForAdd($input)

if ($input['multiple'] ?? false) {
$input['default_value'] = json_encode($input['default_value'] ?: []);
} elseif (($input['type'] ?? '') === 'yesno' && array_key_exists('default_value', $input)) {
$input['default_value'] = (string) self::normalizeYesNoValue($input['default_value']);
Comment on lines +310 to +311

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new Yes/No field now always stores '0', so applyDefaultValueToExistingItems() runs a full-table UPDATE to 0 on creation. Can you skip it when the value is 0?

}

//reject adding when field name is too long for mysql
Expand Down Expand Up @@ -368,6 +389,11 @@ public function prepareInputForUpdate($input)
&& $this->fields['multiple']
) {
$input['default_value'] = json_encode($input['default_value'] ?: []);
} elseif (
array_key_exists('default_value', $input)
&& $this->fields['type'] === 'yesno'
) {
$input['default_value'] = (string) self::normalizeYesNoValue($input['default_value']);
}

return $input;
Expand Down Expand Up @@ -744,6 +770,8 @@ public function showSummary($container)
} else {
echo Dropdown::getDropdownName($table, $this->fields['default_value']);
}
} elseif ($this->fields['type'] === 'yesno') {
echo Dropdown::getYesNo(self::getDefaultValue($this->fields));
} else {
echo htmlspecialchars((string) $this->fields['default_value']);
}
Expand Down Expand Up @@ -1170,7 +1198,10 @@ public static function getDefaultValue(array $field)
{
$value = null;

if (in_array($field['type'], ['dropdown', 'yesno']) && $field['default_value'] === '') {
if ($field['type'] === 'yesno') {
// Yes/No values are stored in an integer column, ignore any non boolean default value
$value = self::normalizeYesNoValue($field['default_value']);
} elseif ($field['type'] === 'dropdown' && $field['default_value'] === '') {
$value = 0;
} elseif ($field['default_value'] !== '') {
$value = $field['default_value'];
Expand All @@ -1187,6 +1218,14 @@ public static function getDefaultValue(array $field)
return $value;
}

/**
* Normalize a Yes/No value to 0 or 1.
*/
public static function normalizeYesNoValue(mixed $value): int
{
return filter_var($value, FILTER_VALIDATE_BOOL) ? 1 : 0;
}

public static function prepareHtmlFields(
$fields,
$item,
Expand Down
46 changes: 46 additions & 0 deletions tests/Units/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,52 @@ public function testDefaultValueIsAppliedToExistingItemsOnlyAtFieldCreation(
$this->assertEquals($updated_default, $readValue($ticket3));
}

public function testYesNoDefaultValueIsNormalized(): void
{
/** @var DBmysql $DB */
global $DB;

$this->login();

$container = $this->createFieldContainer([
'label' => 'YesNo Default Container',
'type' => 'dom',
'itemtypes' => [Ticket::class],
'is_active' => 1,
'entities_id' => 0,
'is_recursive' => 1,
]);

$field = $this->createField(
[
'label' => 'YesNo Field',
'type' => 'yesno',
PluginFieldsContainer::getForeignKeyField() => $container->getID(),
'ranking' => 1,
'is_active' => 1,
'is_readonly' => 0,
'default_value' => 'yes',
],
['default_value'],
);
$this->assertSame('1', $field->fields['default_value']);

$this->updateItem(PluginFieldsField::class, $field->getID(), ['default_value' => 'Non'], ['default_value']);
$this->assertTrue($field->getFromDB($field->getID()));
$this->assertSame('0', $field->fields['default_value']);

// Legacy data: default value typed as free text before the Yes/No selector was used
$DB->update(PluginFieldsField::getTable(), ['default_value' => 'Non'], ['id' => $field->getID()]);

$ticket = $this->createItem(Ticket::class, [
'name' => 'YesNo ticket ' . $this->getUniqueString(),
'content' => 'Test',
'entities_id' => 0,
]);

$this->assertEquals(0, $this->getDefaultValueStored($ticket, $container, $field->fields['name']));
}

public static function provideFieldTypesForSearchDefaultValue(): iterable
{
yield 'text' => ['type' => 'text', 'default_value' => 'search default text'];
Expand Down
Loading