From aa0961ea915c8956202352d2945071df50eee758 Mon Sep 17 00:00:00 2001 From: Bob Vincent Date: Fri, 12 Aug 2011 18:40:23 -0400 Subject: [PATCH] Issue #742344: Allow forms to set custom validation error messages on required fields. --- includes/form.inc | 36 +++++++++++++++------ modules/simpletest/tests/form.test | 46 +++++++++++++++++++++++++++ modules/simpletest/tests/form_test.module | 48 +++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 10 deletions(-) diff --git a/includes/form.inc b/includes/form.inc index 40363d6a738e9b0f373f4f8179898a45a91de928..fcaaa3fedc976772a67be6b149c540033ebd1bd1 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1337,16 +1337,11 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) { $is_empty_string = (is_string($elements['#value']) && drupal_strlen(trim($elements['#value'])) == 0); $is_empty_value = ($elements['#value'] === 0); if ($is_empty_multiple || $is_empty_string || $is_empty_value) { - // Although discouraged, a #title is not mandatory for form elements. In - // case there is no #title, we cannot set a form error message. - // Instead of setting no #title, form constructors are encouraged to set - // #title_display to 'invisible' to improve accessibility. - if (isset($elements['#title'])) { - form_error($elements, $t('!name field is required.', array('!name' => $elements['#title']))); - } - else { - form_error($elements); - } + // Flag this element as #required_is_empty to allow #element_validate + // handlers to set a custom required error message, but without having + // to re-implement the complex logic to figure out whether the field + // value is empty. + $elements['#required_is_empty'] = TRUE; } } @@ -1361,6 +1356,27 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) { $function($elements, $form_state, $form_state['complete_form']); } } + + // Ensure that a #required form error is thrown, regardless of whether + // #element_validate handlers changed any properties. If $is_empty_value + // is defined, then above #required validation code ran, so the other + // variables are also known to be defined and we can test them again. + if (isset($is_empty_value) && ($is_empty_multiple || $is_empty_string || $is_empty_value)) { + if (isset($elements['#required_error'])) { + form_error($elements, $elements['#required_error']); + } + // Although discouraged, a #title is not mandatory for form elements. In + // case there is no #title, we cannot set a form error message. + // Instead of setting no #title, form constructors are encouraged to set + // #title_display to 'invisible' to improve accessibility. + elseif (isset($elements['#title'])) { + form_error($elements, $t('!name field is required.', array('!name' => $elements['#title']))); + } + else { + form_error($elements); + } + } + $elements['#validated'] = TRUE; } diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test index 71187b5763e75127273132ac23b4913b8071d877..0f9d5e71d19feeda71c80308bb391a4c67b56856 100644 --- a/modules/simpletest/tests/form.test +++ b/modules/simpletest/tests/form.test @@ -122,6 +122,52 @@ class FormsTestCase extends DrupalWebTestCase { } /** + * Tests #required with custom validation errors. + * + * @see form_test_validate_required_form() + */ + function testCustomRequiredError() { + $form = $form_state = array(); + $form = form_test_validate_required_form($form, $form_state); + + // Verify that a custom #required error can be set. + $edit = array(); + $this->drupalPost('form-test/validate-required', $edit, 'Submit'); + + foreach (element_children($form) as $key) { + if (isset($form[$key]['#required_error'])) { + $this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title']))); + $this->assertText($form[$key]['#required_error']); + } + elseif (isset($form[$key]['#form_test_required_error'])) { + $this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title']))); + $this->assertText($form[$key]['#form_test_required_error']); + } + } + $this->assertNoText(t('An illegal choice has been detected. Please contact the site administrator.')); + + // Verify that no custom validation error appears with valid values. + $edit = array( + 'textfield' => $this->randomString(), + 'checkboxes[foo]' => TRUE, + 'select' => 'foo', + ); + $this->drupalPost('form-test/validate-required', $edit, 'Submit'); + + foreach (element_children($form) as $key) { + if (isset($form[$key]['#required_error'])) { + $this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title']))); + $this->assertNoText($form[$key]['#required_error']); + } + elseif (isset($form[$key]['#form_test_required_error'])) { + $this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title']))); + $this->assertNoText($form[$key]['#form_test_required_error']); + } + } + $this->assertNoText(t('An illegal choice has been detected. Please contact the site administrator.')); + } + + /** * Test default value handling for checkboxes. * * @see _form_test_checkbox() diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module index a9348161a22a0e362571a117821aff6f46d97241..cc040647409544688ed2ecd06ac12a8f0bc5dc63 100644 --- a/modules/simpletest/tests/form_test.module +++ b/modules/simpletest/tests/form_test.module @@ -23,6 +23,12 @@ function form_test_menu() { 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); + $items['form-test/validate-required'] = array( + 'title' => 'Form #required validation', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_validate_required_form'), + 'access callback' => TRUE, + ); $items['form-test/limit-validation-errors'] = array( 'title' => 'Form validation with some error suppression', 'page callback' => 'drupal_get_form', @@ -338,6 +344,48 @@ function form_test_validate_form_validate(&$form, &$form_state) { } /** + * Form constructor for simple #required tests. + */ +function form_test_validate_required_form($form, &$form_state) { + $form['textfield'] = array( + '#type' => 'textfield', + '#title' => 'Name', + '#required' => TRUE, + '#required_error' => t('Please enter a name.'), + '#element_validate' => array('form_test_validate_required_form_element_validate'), + ); + $form['checkboxes'] = array( + '#type' => 'checkboxes', + '#title' => 'Checkboxes', + '#options' => drupal_map_assoc(array('foo', 'bar')), + '#required' => TRUE, + '#form_test_required_error' => t('Please choose at least one option.'), + '#element_validate' => array('form_test_validate_required_form_element_validate'), + ); + $form['select'] = array( + '#type' => 'select', + '#title' => 'Select', + '#options' => drupal_map_assoc(array('foo', 'bar')), + '#required' => TRUE, + '#form_test_required_error' => t('Please select something.'), + '#element_validate' => array('form_test_validate_required_form_element_validate'), + ); + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array('#type' => 'submit', '#value' => 'Submit'); + return $form; +} + +/** + * Form element validation handler for 'Name' field in form_test_validate_required_form(). + */ +function form_test_validate_required_form_element_validate($element, &$form_state) { + // Set a custom validation error on the #required element. + if (!empty($element['#required_is_empty']) && isset($element['#form_test_required_error'])) { + form_error($element, $element['#form_test_required_error']); + } +} + +/** * Builds a simple form with a button triggering partial validation. */ function form_test_limit_validation_errors_form($form, &$form_state) { -- 1.7.4.1