diff --git a/core/lib/Drupal/Core/Render/Element/Radio.php b/core/lib/Drupal/Core/Render/Element/Radio.php index 8687a9dcfc..e178463eea 100644 --- a/core/lib/Drupal/Core/Render/Element/Radio.php +++ b/core/lib/Drupal/Core/Render/Element/Radio.php @@ -54,7 +54,18 @@ public static function preRenderRadio($element) { $element['#attributes']['type'] = 'radio'; Element::setAttributes($element, ['id', 'name', '#return_value' => 'value']); - if (isset($element['#return_value']) && $element['#value'] !== FALSE && $element['#value'] == $element['#return_value']) { + // To avoid auto-casting during '==' we convert $element['#value'] and + // $element['#return_value'] to strings. It will prevent wrong true-checking + // for both cases: 0 == 'string' and 'string' == 0, this will occur because + // all numeric array values will be integers and all submitted values will + // be strings. Array values are never valid for radios and are skipped. To + // account for FALSE and empty string values in the #return_value, we will + // consider any #value that evaluates to empty to be the same as any + // #return_value that evaluates to empty. + if (isset($element['#return_value']) && + $element['#value'] !== FALSE && + !is_array($element['#value']) && + ((empty($element['#value']) && empty($element['#return_value'])) || (string) $element['#value'] === (string) $element['#return_value'])) { $element['#attributes']['checked'] = 'checked'; } static::setAttributes($element, ['form-radio']); diff --git a/core/modules/system/src/Tests/Form/ElementTest.php b/core/modules/system/src/Tests/Form/ElementTest.php index 0e1c3ce0cb..8388ffb8e2 100644 --- a/core/modules/system/src/Tests/Form/ElementTest.php +++ b/core/modules/system/src/Tests/Form/ElementTest.php @@ -89,6 +89,46 @@ public function testOptions() { } /** + * Tests correct checked attribute for radios element. + */ + public function testRadiosChecked() { + // Verify that there is only one radio option checked. + $this->drupalGet('form-test/radios-checked'); + $elements = $this->xpath('//input[@name="radios" and @checked]'); + $this->assertEqual(count($elements), 1); + $this->assertIdentical((string) $elements[0]['value'], '0'); + $elements = $this->xpath('//input[@name="radios-string" and @checked]'); + $this->assertEqual(count($elements), 1); + $this->assertIdentical((string) $elements[0]['value'], 'bar'); + $elements = $this->xpath('//input[@name="radios-boolean-true" and @checked]'); + $this->assertEqual(count($elements), 1); + $this->assertIdentical((string) $elements[0]['value'], '1'); + // A default value of FALSE indicates that nothing is set. + $elements = $this->xpath('//input[@name="radios-boolean-false" and @checked]'); + $this->assertEqual(count($elements), 0); + $elements = $this->xpath('//input[@name="radios-boolean-any" and @checked]'); + $this->assertEqual(count($elements), 1); + $this->assertIdentical((string) $elements[0]['value'], 'All'); + $elements = $this->xpath('//input[@name="radios-string-zero" and @checked]'); + $this->assertEqual(count($elements), 1); + $this->assertIdentical((string) $elements[0]['value'], '0'); + $elements = $this->xpath('//input[@name="radios-int-non-zero" and @checked]'); + $this->assertEqual(count($elements), 1); + $this->assertIdentical((string) $elements[0]['value'], '10'); + $elements = $this->xpath('//input[@name="radios-int-non-zero-as-string" and @checked]'); + $this->assertEqual(count($elements), 1); + $this->assertIdentical((string) $elements[0]['value'], '100'); + $elements = $this->xpath('//input[@name="radios-empty-string" and @checked]'); + $this->assertEqual(count($elements), 1); + $this->assertIdentical((string) $elements[0]['value'], '0'); + $elements = $this->xpath('//input[@name="radios-empty-array" and @checked]'); + $this->assertEqual(count($elements), 0); + $elements = $this->xpath('//input[@name="radios-key-FALSE" and @checked]'); + $this->assertEqual(count($elements), 1); + $this->assertIdentical((string) $elements[0]['value'], '0'); + } + + /** * Tests wrapper ids for checkboxes and radios. */ public function testWrapperIds() { diff --git a/core/modules/system/tests/modules/form_test/form_test.routing.yml b/core/modules/system/tests/modules/form_test/form_test.routing.yml index 2250a0bb2f..0ee125c95a 100644 --- a/core/modules/system/tests/modules/form_test/form_test.routing.yml +++ b/core/modules/system/tests/modules/form_test/form_test.routing.yml @@ -294,6 +294,14 @@ form_test.checkboxes_radios: requirements: _access: 'TRUE' +form_test.radios_checked: + path: '/form-test/radios-checked' + defaults: + _form: '\Drupal\form_test\Form\FormTestRadiosCheckedForm' + _title: 'Radios checked defalt value' + requirements: + _access: 'TRUE' + form_test.email: path: '/form-test/email' defaults: diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestRadiosCheckedForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestRadiosCheckedForm.php new file mode 100644 index 0000000000..efb1d4d421 --- /dev/null +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestRadiosCheckedForm.php @@ -0,0 +1,151 @@ + 'radios', + '#title' => 'Radios', + '#options' => [ + 0 => 'Zero', + 'foo' => 'Foo', + 1 => 'One', + 'bar' => 'Bar - radios', + '>' => "Special Char", + ], + '#default_value' => 0, + ]; + $form['radios-string'] = [ + '#type' => 'radios', + '#title' => 'Radios', + '#options' => [ + 0 => 'Zero', + 'foo' => 'Foo', + 1 => 'One', + 'bar' => 'Bar - radios', + '>' => "Special Char", + ], + '#default_value' => 'bar', + ]; + $form['radios-boolean-true'] = [ + '#type' => 'radios', + '#title' => 'Radios', + '#options' => [ + 'All' => '- Any -', + 1 => 'True', + 0 => 'False', + ], + '#default_value' => TRUE, + ]; + $form['radios-boolean-false'] = [ + '#type' => 'radios', + '#title' => 'Radios', + '#options' => [ + 'All' => '- Any -', + 1 => 'True', + 0 => 'False', + ], + '#default_value' => FALSE, + ]; + $form['radios-boolean-any'] = [ + '#type' => 'radios', + '#title' => 'Radios', + '#options' => [ + 'All' => '- Any -', + 1 => 'True', + 0 => 'False', + ], + '#default_value' => 'All', + ]; + $form['radios-string-zero'] = [ + '#type' => 'radios', + '#title' => 'Radios', + '#options' => [ + 'All' => '- Any -', + '0' => 'Zero', + 100 => 'One hundred', + ], + '#default_value' => 0, + ]; + $form['radios-int-non-zero'] = [ + '#type' => 'radios', + '#title' => 'Radios', + '#options' => [ + 'All' => '- Any -', + 0 => 'Zero', + 10 => 'Ten', + 100 => 'One hundred', + ], + '#default_value' => 10, + ]; + $form['radios-int-non-zero-as-string'] = [ + '#type' => 'radios', + '#title' => 'Radios', + '#options' => [ + 'All' => '- Any -', + '0' => 'Zero', + '10' => 'Ten', + '100' => 'One hundred', + ], + '#default_value' => '100', + ]; + $form['radios-empty-string'] = [ + '#type' => 'radios', + '#title' => 'Radios', + '#options' => [ + 'All' => '- Any -', + 0 => 'None', + ], + '#default_value' => '', + ]; + $form['radios-empty-array'] = [ + '#type' => 'radios', + '#title' => 'Radios', + '#options' => [ + 'All' => '- Any -', + 0 => 'None', + ], + '#default_value' => [], + ]; + $form['radios-key-FALSE'] = [ + '#type' => 'radios', + '#title' => 'Radios', + '#options' => [ + 'All' => '- Any -', + FALSE => 'False', + ], + '#default_value' => '', + ]; + + $form['submit'] = ['#type' => 'submit', '#value' => 'Submit']; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $form_state->setResponse(new JsonResponse($form_state->getValues())); + } + +}