From 42f8a3b515ede34a7bdda422f5d1a0585853fc8a Mon Sep 17 00:00:00 2001 From: Lainow Date: Thu, 8 Oct 2026 10:26:03 +0200 Subject: [PATCH 1/3] fix normalize Yes/No default value to prevent SQL error on item creation --- CHANGELOG.md | 6 +++++ ajax/field_specific_fields.php | 5 ++++ inc/field.class.php | 41 +++++++++++++++++++++++++++++- tests/Units/ContainerTest.php | 46 ++++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b582f6f..0558886f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] + +### Fixed + +- Fix item creation failing when a Yes/No field has a non boolean default value + ## [1.24.6] - 2026-10-06 ### Added diff --git a/ajax/field_specific_fields.php b/ajax/field_specific_fields.php index e5745ad6..a8b9272b 100644 --- a/ajax/field_specific_fields.php +++ b/ajax/field_specific_fields.php @@ -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', diff --git a/inc/field.class.php b/inc/field.class.php index c7ab2bce..fa47d598 100644 --- a/inc/field.class.php +++ b/inc/field.class.php @@ -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']], + ), + ); + } + $toolbox = new PluginFieldsToolbox(); $toolbox->fixFieldsNames($migration, ['NOT' => ['type' => 'dropdown']]); @@ -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']); } //reject adding when field name is too long for mysql @@ -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; @@ -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']); } @@ -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']; @@ -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, diff --git a/tests/Units/ContainerTest.php b/tests/Units/ContainerTest.php index 6104bd40..e81ce27a 100644 --- a/tests/Units/ContainerTest.php +++ b/tests/Units/ContainerTest.php @@ -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']; From 52741d1a6632091646b668496774e47730cd9288 Mon Sep 17 00:00:00 2001 From: Lainow <107540223+Lainow@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:06:19 +0200 Subject: [PATCH 2/3] Review: test Yes/No migration and skip default value update when 0 --- inc/field.class.php | 5 ++++ tests/Units/ContainerTest.php | 52 +++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/inc/field.class.php b/inc/field.class.php index fa47d598..c12a61a5 100644 --- a/inc/field.class.php +++ b/inc/field.class.php @@ -1581,6 +1581,11 @@ private function applyDefaultValueToExistingItems(): void return; } + // Yes/No columns already default to 0, no need to update existing items + if ($this->fields['type'] === 'yesno' && $value === 0) { + return; + } + $sql_fields = PluginFieldsMigration::getSQLFields( $this->fields['name'], $this->fields['type'], diff --git a/tests/Units/ContainerTest.php b/tests/Units/ContainerTest.php index e81ce27a..bb18d416 100644 --- a/tests/Units/ContainerTest.php +++ b/tests/Units/ContainerTest.php @@ -43,6 +43,7 @@ use PluginFieldsContainer; use PluginFieldsDropdown; use PluginFieldsField; +use PluginFieldsMigration; use PluginFieldsToolbox; use Search; use Session; @@ -544,6 +545,57 @@ public function testYesNoDefaultValueIsNormalized(): void $this->assertEquals(0, $this->getDefaultValueStored($ticket, $container, $field->fields['name'])); } + public function testMigrationNormalizesYesNoDefaultValues(): void + { + /** @var DBmysql $DB */ + global $DB; + + $this->login(); + + $container = $this->createFieldContainer([ + 'label' => 'YesNo Migration Container', + 'type' => 'dom', + 'itemtypes' => [Ticket::class], + 'is_active' => 1, + 'entities_id' => 0, + 'is_recursive' => 1, + ]); + + $cases = [ + // legacy default value => expected default value + 'Non' => '0', + 'yes' => '1', + '' => '0', + '0' => '0', + '1' => '1', + ]; + + $fields_ids = []; + foreach (array_keys($cases) as $i => $legacy_value) { + $field = $this->createField([ + 'label' => 'YesNo Migration Field ' . $i, + 'type' => 'yesno', + PluginFieldsContainer::getForeignKeyField() => $container->getID(), + 'ranking' => $i + 1, + 'is_active' => 1, + 'is_readonly' => 0, + ]); + + // Legacy data: default value typed as free text before the Yes/No selector was used + $DB->update(PluginFieldsField::getTable(), ['default_value' => (string) $legacy_value], ['id' => $field->getID()]); + $fields_ids[$legacy_value] = $field->getID(); + } + + $migration = new PluginFieldsMigration('0'); + PluginFieldsField::installBaseData($migration, '0'); + $migration->executeMigration(); + + foreach ($cases as $legacy_value => $expected_value) { + $field = PluginFieldsField::getById($fields_ids[$legacy_value]); + $this->assertSame($expected_value, $field->fields['default_value'], (string) $legacy_value); + } + } + public static function provideFieldTypesForSearchDefaultValue(): iterable { yield 'text' => ['type' => 'text', 'default_value' => 'search default text']; From af5af567b34e7a2ba90304d60db5477336d46960 Mon Sep 17 00:00:00 2001 From: Lainow <107540223+Lainow@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:07:44 +0200 Subject: [PATCH 3/3] Normalize localized Yes labels in Yes/No default values --- inc/field.class.php | 8 +++++++- tests/Units/ContainerTest.php | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/inc/field.class.php b/inc/field.class.php index c12a61a5..ce2f2e2a 100644 --- a/inc/field.class.php +++ b/inc/field.class.php @@ -1223,7 +1223,13 @@ public static function getDefaultValue(array $field) */ public static function normalizeYesNoValue(mixed $value): int { - return filter_var($value, FILTER_VALIDATE_BOOL) ? 1 : 0; + if (filter_var($value, FILTER_VALIDATE_BOOL)) { + return 1; + } + + // Free text values may also contain a localized "Yes" label + $value = mb_strtolower(trim((string) $value)); + return in_array($value, ['oui', mb_strtolower(__('Yes'))], true) ? 1 : 0; } public static function prepareHtmlFields( diff --git a/tests/Units/ContainerTest.php b/tests/Units/ContainerTest.php index bb18d416..b6e28ba1 100644 --- a/tests/Units/ContainerTest.php +++ b/tests/Units/ContainerTest.php @@ -565,6 +565,7 @@ public function testMigrationNormalizesYesNoDefaultValues(): void // legacy default value => expected default value 'Non' => '0', 'yes' => '1', + 'Oui' => '1', '' => '0', '0' => '0', '1' => '1',