Index: modules/poll/poll.module =================================================================== RCS file: /cvs/drupal/drupal/modules/poll/poll.module,v retrieving revision 1.288 diff -u -r1.288 poll.module --- modules/poll/poll.module 3 Feb 2009 18:55:31 -0000 1.288 +++ modules/poll/poll.module 7 Feb 2009 05:27:26 -0000 @@ -258,6 +258,8 @@ '#description' => t("If the amount of boxes above isn't enough, click here to add more choices."), '#weight' => 1, '#submit' => array('poll_more_choices_submit'), // If no javascript action. + '#validate' => array(), // No validation needed for the more button. + '#validate_portion' => array('poll_more_validate_portion'), '#ahah' => array( 'callback' => 'poll_choice_js', 'wrapper' => 'poll-choices', @@ -304,7 +306,9 @@ */ function poll_more_choices_submit($form, &$form_state) { // Set the form to rebuild and run submit handlers. - node_form_submit_build_node($form, $form_state); + if (drupal_function_exists('node_form_submit_build_node')) { + node_form_submit_build_node($form, $form_state); + } // Make the changes we want to the form state. if ($form_state['values']['poll_more']) { @@ -313,6 +317,13 @@ } } +/** + * Validate portion callback. Only validate the poll portion of the form. + */ +function poll_more_validate_portion($form, &$form_state) { + return $form['choice_wrapper']['choice']; +} + function _poll_choice_form($key, $chid = NULL, $value = '', $votes = 0, $weight = 0, $size = 10) { $admin = user_access('administer nodes'); Index: modules/poll/poll.test =================================================================== RCS file: /cvs/drupal/drupal/modules/poll/poll.test,v retrieving revision 1.15 diff -u -r1.15 poll.test --- modules/poll/poll.test 1 Feb 2009 06:48:15 -0000 1.15 +++ modules/poll/poll.test 7 Feb 2009 05:27:26 -0000 @@ -178,11 +178,7 @@ $web_user = $this->drupalCreateUser(array('create poll content', 'access content')); $this->drupalLogin($web_user); $this->drupalGet('node/add/poll'); - $edit = array( - 'title' => $this->randomName(), - 'choice[new:0][chtext]' => $this->randomName(), - 'choice[new:1][chtext]' => $this->randomName(), - ); + $edit = array(); // @TODO: the framework should make it possible to submit a form to a // different URL than its action or the current. For now, we can just force Index: modules/simpletest/tests/form.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v retrieving revision 1.3 diff -u -r1.3 form.test --- modules/simpletest/tests/form.test 28 Jan 2009 07:43:26 -0000 1.3 +++ modules/simpletest/tests/form.test 7 Feb 2009 05:27:27 -0000 @@ -23,7 +23,7 @@ */ function testRequiredFields() { // Originates from http://drupal.org/node/117748 - // Sets of empty strings and arrays + // Sets of empty strings and arrays. $empty_strings = array('""' => "", '"\n"' => "\n", '" "' => " ", '"\t"' => "\t", '" \n\t "' => " \n\t ", '"\n\n\n\n\n"' => "\n\n\n\n\n"); $empty_arrays = array('array()' => array()); @@ -51,7 +51,7 @@ $elements['file']['element'] = array('#title' => $this->randomName(), '#type' => 'file', '#required' => TRUE); $elements['file']['empty_values'] = $empty_strings; - // Go through all the elements and all the empty values for them + // Go through all the elements and all the empty values for them. foreach ($elements as $type => $data) { foreach ($data['empty_values'] as $key => $empty) { $form_id = $this->randomName(); @@ -71,6 +71,94 @@ // Clear the expected form error messages so they don't appear as exceptions. drupal_get_messages(); } + + /** + * Validate a portion of a form but not others. + * + * If the validated form field is found in form_get_errors() and the + * non-validated field is not found then the test pass. + */ + function testValidatePortion() { + /** + * Sample #validate_portion function used in this test. + */ + function test_validate_portion($form, $form_state) { + $portion = array(); + foreach ($form['#validate_keys'] as $key) { + $portion[$key] = $form[$key]; + } + return $portion; + } + + // A list of elements that will intentionally fail validation. + $validated = array( + $this->randomName(), + $this->randomName(), + $this->randomName(), + ); + + // A list of elements that will intentionally be skipped in validation. + $non_validated = array( + $this->randomName(), + ); + + // A fieldset to test test validation within a nested field. + $fieldset = $this->randomName(); + + $form = array( + '#validate_keys' => array($fieldset, $validated[0]), + ); + $form[$validated[0]] = array( + '#type' => 'textfield', + '#required' => TRUE, + ); + $form[$non_validated[0]] = array( + '#type' => 'textfield', + '#required' => TRUE, + ); + $form[$fieldset] = array( + '#type' => 'fieldset', + '#tree' => TRUE, + ); + $form[$fieldset][$validated[1]] = array( + '#type' => 'textfield', + '#required' => TRUE, + ); + $form[$fieldset][$this->randomName()][$validated[2]] = array( + '#type' => 'textfield', + '#required' => TRUE, + ); + $form['op'] = array( + '#type' => 'submit', + '#value' => t('Submit'), + '#validate_portion' => array('test_validate_portion'), + ); + + $form_id = $this->randomName(); + $form_state = array(); + $form_state['values'] = array( + 'op' => t('Submit'), + ); + $form['#post'] = $form_state['values']; + $form['#post']['form_id'] = $form_id; + drupal_prepare_form($form_id, $form, $form_state); + drupal_process_form($form_id, $form, $form_state); + $errors = form_get_errors(); + + foreach ($validated as $element) { + foreach ($errors as $element_tree => $error) { + if (strpos($element_tree, $element) !== FALSE) { + $this->assertTrue(isset($errors[$element_tree]), "Partially validated form element $element_tree validated."); + } + } + } + foreach ($non_validated as $element) { + $this->assertFalse(isset($errors[$element]), "Partially validated form element $element intentionally not validated."); + } + + // Clear the expected form error messages so they don't appear as exceptions. + drupal_get_messages(); + } } /** Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.320 diff -u -r1.320 form.inc --- includes/form.inc 3 Feb 2009 18:55:29 -0000 1.320 +++ includes/form.inc 7 Feb 2009 05:27:26 -0000 @@ -581,7 +581,21 @@ } } - _form_validate($form, $form_state, $form_id); + // Check if a section function is specified to validate only a portion of + // the form using the built-in validation. + $portions = form_execute_handlers('validate_portion', $form, $form_state); + + // If no portions are specified, validate the entire form. + if (empty($portions)) { + $portions = $form; + } + + // Validate individual #element_validate, #required, and #options properties. + _form_validate($form, $form_state, $portions); + + // Call user-defined form level validators. + form_execute_handlers('validate', $form, $form_state); + $validated_forms[$form_id] = TRUE; } @@ -645,8 +659,8 @@ * completed, #maxlength is not exceeded, and selected options were in the * list of options given to the user. Then calls user-defined validators. * - * @param $elements - * An associative array containing the structure of the form. + * @param $form + * An associative array containing the complete structure of the form. * @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 @@ -657,20 +671,17 @@ * This technique is useful when validation requires file parsing, * web service requests, or other expensive requests that should * not be repeated in the submission step. - * @param $form_id - * A unique string identifying the form for validation, submission, - * theming, and hook_form_alter functions. + * @param $elements + * An associative array containing the portion of the form to be validated. */ -function _form_validate($elements, &$form_state, $form_id = NULL) { - static $complete_form; - +function _form_validate(&$form, &$form_state, &$elements) { // Also used in the installer, pre-database setup. $t = get_t(); // Recurse through all children. foreach (element_children($elements) as $key) { if (isset($elements[$key]) && $elements[$key]) { - _form_validate($elements[$key], $form_state); + _form_validate($form, $form_state, $elements[$key]); } } // Validate the current input. @@ -712,19 +723,12 @@ } } - // Call user-defined form level validators and store a copy of the full - // form so that element-specific validators can examine the entire structure - // if necessary. - if (isset($form_id)) { - form_execute_handlers('validate', $elements, $form_state); - $complete_form = $elements; - } // Call any element-specific validators. These must act on the element // #value data. - elseif (isset($elements['#element_validate'])) { + if (isset($elements['#element_validate'])) { foreach ($elements['#element_validate'] as $function) { if (drupal_function_exists($function)) { - $function($elements, $form_state, $complete_form); + $function($elements, $form_state, $form); } } } @@ -746,9 +750,12 @@ * A keyed array containing the current state of the form. If the user * submitted the form by clicking a button with custom handler functions * defined, those handlers will be stored here. + * @return + * An array of the results of each submit handler, or simply TRUE for each + * submit handler executed if the handler does not return a value. */ function form_execute_handlers($type, &$form, &$form_state) { - $return = FALSE; + $return = array(); if (isset($form_state[$type . '_handlers'])) { $handlers = $form_state[$type . '_handlers']; } @@ -768,9 +775,9 @@ $batch['sets'][] = array('form_submit' => $function); } else { - $function($form, $form_state); + $return[$function] = $function($form, $form_state); } - $return = TRUE; + $return[$function] = isset($return[$function]) ? $return[$function] : TRUE; } } return $return; @@ -1050,6 +1057,9 @@ if (isset($form['#validate'])) { $form_state['validate_handlers'] = $form['#validate']; } + if (isset($form['#validate_portion'])) { + $form_state['validate_portion_handlers'] = $form['#validate_portion']; + } if (isset($form['#submit'])) { $form_state['submit_handlers'] = $form['#submit']; } @@ -1120,6 +1130,7 @@ $form_state['submitted'] = TRUE; $form_state['submit_handlers'] = empty($button['#submit']) ? NULL : $button['#submit']; $form_state['validate_handlers'] = empty($button['#validate']) ? NULL : $button['#validate']; + $form_state['validate_portion_handlers'] = empty($button['#validate_portion']) ? NULL : $button['#validate_portion']; $form_state['values'][$button['#name']] = $button['#value']; $form_state['clicked_button'] = $button; }