From d672bcc4019552f987d1fa6887b42aaa64e43967 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Tue, 27 Mar 2012 15:23:35 +0900
Subject: [PATCH] Issue #1174938 by ericduran, aspilicious, voxpelli,
 David_Rothstein, effulgentsia: Added Natively support the HTML5 #required
 FAPI property.

---
 core/includes/form.inc                        | 17 ++++++++
 core/modules/simpletest/tests/form.test       | 42 +++++++++++++++++++
 .../modules/simpletest/tests/form_test.module | 22 ++++++++++
 core/modules/system/system.module             |  6 +--
 4 files changed, 84 insertions(+), 3 deletions(-)

diff --git a/core/includes/form.inc b/core/includes/form.inc
index 35acd8439bf6..65c462a866df 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 c51d3e659d7c..e79983c25f8a 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 cddd9a6434ef..3dcd24742dd5 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 e66202ed636d..e6d0972d93ed 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,
-- 
GitLab