From b6084826787b316595307d1dbbd6876508a60d28 Mon Sep 17 00:00:00 2001
From: Dries Buytaert <dries@buytaert.net>
Date: Sat, 28 Nov 2009 14:39:31 +0000
Subject: [PATCH] - Patch #642702 by sun: form validation handlers cannot alter
  structure.

---
 includes/form.inc                         | 12 ++-
 modules/simpletest/tests/form.test        | 55 ++++++++++++++
 modules/simpletest/tests/form_test.module | 90 +++++++++++++++++++++++
 3 files changed, 154 insertions(+), 3 deletions(-)

diff --git a/includes/form.inc b/includes/form.inc
index 04be2cba9692..113c0b00bc07 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -726,7 +726,13 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
  *   A unique string identifying the form for validation, submission,
  *   theming, and hook_form_alter functions.
  * @param $form
- *   An associative array containing the structure of the form.
+ *   An associative array containing the structure of the form, which is passed
+ *   by reference. Form validation handlers are able to alter the form structure
+ *   (like #process and #after_build callbacks during form building) in case of
+ *   a validation error. If a validation handler alters the form structure, it
+ *   is responsible for validating the values of changed form elements in
+ *   $form_state['values'] to prevent form submit handlers from receiving
+ *   unvalidated values.
  * @param $form_state
  *   A keyed array containing the current state of the form. The current
  *   user-submitted data is stored in $form_state['values'], though
@@ -738,7 +744,7 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
  *   web service requests, or other expensive requests that should
  *   not be repeated in the submission step.
  */
-function drupal_validate_form($form_id, $form, &$form_state) {
+function drupal_validate_form($form_id, &$form, &$form_state) {
   $validated_forms = &drupal_static(__FUNCTION__, array());
 
   if (isset($validated_forms[$form_id]) && empty($form_state['must_validate'])) {
@@ -846,7 +852,7 @@ function drupal_redirect_form($form_state) {
  *   A unique string identifying the form for validation, submission,
  *   theming, and hook_form_alter functions.
  */
-function _form_validate($elements, &$form_state, $form_id = NULL) {
+function _form_validate(&$elements, &$form_state, $form_id = NULL) {
   // Also used in the installer, pre-database setup.
   $t = get_t();
 
diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test
index ed556c54054d..857b32128200 100644
--- a/modules/simpletest/tests/form.test
+++ b/modules/simpletest/tests/form.test
@@ -152,6 +152,61 @@ class FormsTestCase extends DrupalWebTestCase {
   }
 }
 
+/**
+ * Test form validation handlers.
+ */
+class FormValidationTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Form validation handlers',
+      'description' => 'Tests form processing and alteration via form validation handlers.',
+      'group' => 'Form API',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('form_test');
+  }
+
+  /**
+   * Tests form alterations by #element_validate, #validate, and form_set_value().
+   */
+  function testValidate() {
+    $this->drupalGet('form-test/validate');
+    // Verify that #element_validate handlers can alter the form and submitted
+    // form values.
+    $edit = array(
+      'name' => 'element_validate',
+    );
+    $this->drupalPost(NULL, $edit, 'Save');
+    $this->assertFieldByName('name', '#value changed by #element_validate', t('Form element #value was altered.'));
+    $this->assertText('Name value: value changed by form_set_value() in #element_validate', t('Form element value in $form_state was altered.'));
+
+    // Verify that #validate handlers can alter the form and submitted
+    // form values.
+    $edit = array(
+      'name' => 'validate',
+    );
+    $this->drupalPost(NULL, $edit, 'Save');
+    $this->assertFieldByName('name', '#value changed by #validate', t('Form element #value was altered.'));
+    $this->assertText('Name value: value changed by form_set_value() in #validate', t('Form element value in $form_state was altered.'));
+
+    // Verify that #element_validate handlers can make form elements
+    // inaccessible, but values persist.
+    $edit = array(
+      'name' => 'element_validate_access',
+    );
+    $this->drupalPost(NULL, $edit, 'Save');
+    $this->assertNoFieldByName('name', t('Form element was hidden.'));
+    $this->assertText('Name value: element_validate_access', t('Value for inaccessible form element exists.'));
+
+    // Verify that value for inaccessible form element persists.
+    $this->drupalPost(NULL, array(), 'Save');
+    $this->assertNoFieldByName('name', t('Form element was hidden.'));
+    $this->assertText('Name value: element_validate_access', t('Value for inaccessible form element exists.'));
+  }
+}
+
 /**
  * Test the tableselect form element for expected behavior.
  */
diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module
index 5c809580fdf9..15a141f153b1 100644
--- a/modules/simpletest/tests/form_test.module
+++ b/modules/simpletest/tests/form_test.module
@@ -10,6 +10,14 @@
  * Implement hook_menu().
  */
 function form_test_menu() {
+  $items['form-test/validate'] = array(
+    'title' => 'Form validation handlers test',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('form_test_validate_form'),
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+  );
+
   $items['form_test/tableselect/multiple-true'] = array(
     'title' => 'Tableselect checkboxes test',
     'page callback' => 'drupal_get_form',
@@ -81,6 +89,88 @@ function form_test_menu() {
   return $items;
 }
 
+/**
+ * Form builder for testing drupal_validate_form().
+ *
+ * Serves for testing form processing and alterations by form validation
+ * handlers, especially for the case of a validation error:
+ * - form_set_value() should be able to alter submitted values in
+ *   $form_state['values'] without affecting the form element.
+ * - #element_validate handlers should be able to alter the $element in the form
+ *   structure and the alterations should be contained in the rebuilt form.
+ * - #validate handlers should be able to alter the $form and the alterations
+ *   should be contained in the rebuilt form.
+ */
+function form_test_validate_form($form, &$form_state) {
+  $form['name'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Name',
+    '#default_value' => '',
+    '#element_validate' => array('form_test_element_validate_name'),
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => 'Save',
+  );
+  return $form;
+}
+
+/**
+ * Form element validation handler for 'name' in form_test_validate_form().
+ */
+function form_test_element_validate_name(&$element, &$form_state) {
+  $triggered = FALSE;
+  if ($form_state['values']['name'] == 'element_validate') {
+    // Alter the form element.
+    $element['#value'] = '#value changed by #element_validate';
+    // Alter the submitted value in $form_state.
+    form_set_value($element, 'value changed by form_set_value() in #element_validate', $form_state);
+
+    $triggered = TRUE;
+  }
+  if ($form_state['values']['name'] == 'element_validate_access') {
+    // To simplify this test, enable form caching and use form storage to
+    // remember our alteration.
+    $form_state['cache'] = TRUE;
+    $form_state['storage']['form_test_name'] = $form_state['values']['name'];
+    // Alter the form element.
+    $element['#access'] = FALSE;
+
+    $triggered = TRUE;
+  }
+  elseif (!empty($form_state['storage']['form_test_name'])) {
+    // To simplify this test, just take over the element's value into $form_state.
+    form_set_value($element, $form_state['storage']['form_test_name'], $form_state);
+
+    $triggered = TRUE;
+  }
+
+  if ($triggered) {
+    // Output the element's value from $form_state.
+    drupal_set_message(t('@label value: @value', array('@label' => $element['#title'], '@value' => $form_state['values']['name'])));
+
+    // Trigger a form validation error to see our changes.
+    form_set_error('');
+  }
+}
+
+/**
+ * Form validation handler for form_test_validate_form().
+ */
+function form_test_validate_form_validate(&$form, &$form_state) {
+  if ($form_state['values']['name'] == 'validate') {
+    // Alter the form element.
+    $form['name']['#value'] = '#value changed by #validate';
+    // Alter the submitted value in $form_state.
+    form_set_value($form['name'], 'value changed by form_set_value() in #validate', $form_state);
+    // Output the element's value from $form_state.
+    drupal_set_message(t('@label value: @value', array('@label' => $form['name']['#title'], '@value' => $form_state['values']['name'])));
+
+    // Trigger a form validation error to see our changes.
+    form_set_error('');
+  }
+}
+
 /**
  * Create a header and options array. Helper function for callbacks.
  */
-- 
GitLab