diff --git a/core/includes/form.inc b/core/includes/form.inc index 35acd8439bf6279e98172712fec20ffa50f6b0c1..65c462a866dfb8f8797479d328b5494760207eeb 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -3104,6 +3104,19 @@ function form_pre_render_conditional_form_element($element) { return $element; } +/** + * Processes a form button element. + */ +function form_process_button($element, $form_state) { + // If this is a button intentionally allowing incomplete form submission + // (e.g., a "Previous" or "Add another item" button), then also skip + // client-side validation. + if (isset($element['#limit_validation_errors']) && $element['#limit_validation_errors'] !== FALSE) { + $element['#attributes']['formnovalidate'] = 'formnovalidate'; + } + return $element; +} + /** * Sets the #checked property of a checkbox element. */ @@ -4312,6 +4325,10 @@ function _form_set_class(&$element, $class = array()) { // form_builder(). if (!empty($element['#required'])) { $element['#attributes']['class'][] = 'required'; + // @todo Rename the _form_set_class() function to reflect that we're setting + // non-class attributes too. + $element['#attributes']['required'] = 'required'; + $element['#attributes']['aria-required'] = 'true'; } if (isset($element['#parents']) && form_get_error($element)) { $element['#attributes']['class'][] = 'error'; diff --git a/core/modules/simpletest/tests/form.test b/core/modules/simpletest/tests/form.test index c51d3e659d7c7efe3b7a263810fe4d0109440e2a..e79983c25f8a720dd03c8b2991c6ac7fe9386ed9 100644 --- a/core/modules/simpletest/tests/form.test +++ b/core/modules/simpletest/tests/form.test @@ -451,6 +451,29 @@ class FormsTestCase extends DrupalWebTestCase { $this->drupalPost(NULL, array('checkboxes[one]' => TRUE, 'checkboxes[two]' => TRUE), t('Submit')); $this->assertText('An illegal choice has been detected.', t('Input forgery was detected.')); } + + /** + * Tests required attribute. + */ + function testRequiredAttribute() { + $this->drupalGet('form-test/required-attribute'); + $expected = 'required'; + // Test to make sure the elements have the proper required attribute. + foreach (array('textfield', 'password') as $type) { + $element = $this->xpath('//input[@id=:id and @required=:expected]', array( + ':id' => 'edit-' . $type, + ':expected' => $expected, + )); + $this->assertTrue(!empty($element), t('The @type has the proper required attribute.', array('@type' => $type))); + } + + // Test to make sure textarea has the proper required attribute. + $element = $this->xpath('//textarea[@id=:id and @required=:expected]', array( + ':id' => 'edit-textarea', + ':expected' => $expected, + )); + $this->assertTrue(!empty($element), t('The textarea has the proper required attribute.')); + } } /** @@ -633,6 +656,25 @@ class FormValidationTestCase extends DrupalWebTestCase { ); $path = 'form-test/limit-validation-errors'; + // Render the form, and verify that the buttons with limited server-side + // validation have the proper 'formnovalidate' attribute (to prevent + // client-side validation by the browser). + $this->drupalGet($path); + $expected = 'formnovalidate'; + foreach (array('partial', 'partial-numeric-index', 'substring') as $type) { + $element = $this->xpath('//input[@id=:id and @formnovalidate=:expected]', array( + ':id' => 'edit-' . $type, + ':expected' => $expected, + )); + $this->assertTrue(!empty($element), t('The @type button has the proper formnovalidate attribute.', array('@type' => $type))); + } + // The button with full server-side validation should not have the + // 'formnovalidate' attribute. + $element = $this->xpath('//input[@id=:id and not(@formnovalidate)]', array( + ':id' => 'edit-full', + )); + $this->assertTrue(!empty($element), t('The button with full server-side validation does not have the formnovalidate attribute.')); + // Submit the form by pressing the 'Partial validate' button (uses // #limit_validation_errors) and ensure that the title field is not // validated, but the #element_validate handler for the 'test' field diff --git a/core/modules/simpletest/tests/form_test.module b/core/modules/simpletest/tests/form_test.module index cddd9a6434efee737a2697398811c8afec46d0a1..3dcd24742dd5c8b64ab2ce9a1a695787567c300d 100644 --- a/core/modules/simpletest/tests/form_test.module +++ b/core/modules/simpletest/tests/form_test.module @@ -229,6 +229,13 @@ function form_test_menu() { 'type' => MENU_CALLBACK, ); + $items['form-test/required-attribute'] = array( + 'title' => 'Required', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_required_attribute'), + 'access callback' => TRUE, + ); + return $items; } @@ -1830,3 +1837,18 @@ function form_test_checkboxes_zero($form, &$form_state, $json = TRUE) { function _form_test_checkboxes_zero_no_redirect($form, &$form_state) { $form_state['redirect'] = FALSE; } + +/** + * Builds a form to test the required attribute. + */ +function form_test_required_attribute($form, &$form_state) { + foreach (array('textfield', 'textarea', 'password') as $type) { + $form[$type] = array( + '#type' => $type, + '#required' => TRUE, + '#title' => $type, + ); + } + + return $form; +} diff --git a/core/modules/system/system.module b/core/modules/system/system.module index e66202ed636d65fa6e6c2d650120ae791ceac776..e6d0972d93ed63c88f4b0e59a307001562c5d6c7 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -333,7 +333,7 @@ function system_element_info() { '#button_type' => 'submit', '#executes_submit_callback' => TRUE, '#limit_validation_errors' => FALSE, - '#process' => array('ajax_process_form'), + '#process' => array('form_process_button', 'ajax_process_form'), '#theme_wrappers' => array('button'), ); $types['button'] = array( @@ -342,7 +342,7 @@ function system_element_info() { '#button_type' => 'submit', '#executes_submit_callback' => FALSE, '#limit_validation_errors' => FALSE, - '#process' => array('ajax_process_form'), + '#process' => array('form_process_button', 'ajax_process_form'), '#theme_wrappers' => array('button'), ); $types['image_button'] = array( @@ -350,7 +350,7 @@ function system_element_info() { '#button_type' => 'submit', '#executes_submit_callback' => TRUE, '#limit_validation_errors' => FALSE, - '#process' => array('ajax_process_form'), + '#process' => array('form_process_button', 'ajax_process_form'), '#return_value' => TRUE, '#has_garbage_value' => TRUE, '#src' => NULL,