=== modified file 'includes/form.inc' --- includes/form.inc 2009-12-17 21:59:31 +0000 +++ includes/form.inc 2009-12-28 17:18:55 +0000 @@ -2,6 +2,12 @@ // $Id: form.inc,v 1.421 2009/12/17 21:59:31 dries Exp $ /** + * Validate the section that is 1 level up from the button. + * @see _form_validate_allow() + */ +define('VALIDATE_SECTION_PARENT', -1); + +/** * @defgroup forms Form builder functions * @{ * Functions that build an abstract representation of a HTML form. @@ -884,7 +890,7 @@ function _form_validate(&$elements, &$fo // Recurse through all children. foreach (element_children($elements) as $key) { - if (isset($elements[$key]) && $elements[$key]) { + if (_form_validate_allowed($elements[$key], $form_state)) { _form_validate($elements[$key], $form_state); } } @@ -947,6 +953,27 @@ function _form_validate(&$elements, &$fo } /** + * Check whether an element can be validated. + * + * If the clicked button specifies sections to be validated then #array_parents + * is checked to be starting with one of sections. + */ +function _form_validate_allowed($elements, $form_state) { + if (empty($form_state['clicked_button']['#validate_sections'])) { + return TRUE; + } + foreach ($form_state['clicked_button']['#validate_sections'] as $section) { + if (is_int($section) && $section < 0) { + $section = array_slice($form_state['clicked_button']['#array_parents'], 0, $section); + } + if (count($element['#array_parents']) >= count($section) && array_slice($element['#array_parents'], 0, count($section)) === $section) { + return TRUE; + } + } + return FALSE; +} + +/** * A helper function used to execute custom validation and submission * handlers for a given form. Button-specific handlers are checked * first. If none exist, the function falls back to form-level handlers. @@ -967,8 +994,9 @@ function form_execute_handlers($type, &$ if (isset($form_state[$type . '_handlers'])) { $handlers = $form_state[$type . '_handlers']; } - // Otherwise, check for a form-level handler. - elseif (isset($form['#' . $type])) { + // Otherwise, check for a form-level handler. If a per-button validate is in + // effect then neither the validate nor the submit handler should fire. + elseif (isset($form['#' . $type]) && empty($form_state['clicked_button']['#validate_sections'])) { $handlers = $form['#' . $type]; } else { @@ -1006,7 +1034,7 @@ function form_execute_handlers($type, &$ * @param $message * The error message to present to the user. * @return - * Return value is for internal use only. To get a list of errors, use + * Return value is for internal use only. To get a list of errors, use * form_get_errors() or form_get_error(). */ function form_set_error($name = NULL, $message = '') { @@ -3261,7 +3289,7 @@ function batch_process($redirect = NULL, $batch =& batch_get(); drupal_theme_initialize(); - + if (isset($batch)) { // Add process information $process_info = array( @@ -3276,7 +3304,7 @@ function batch_process($redirect = NULL, ); $batch += $process_info; - // The batch is now completely built. Allow other modules to make changes to the + // The batch is now completely built. Allow other modules to make changes to the // batch so that it is easier to reuse batch processes in other enviroments. drupal_alter('batch', $batch); === modified file 'modules/field/field.form.inc' --- modules/field/field.form.inc 2009-12-21 13:47:31 +0000 +++ modules/field/field.form.inc 2009-12-28 16:48:58 +0000 @@ -214,6 +214,7 @@ function field_multiple_value_form($fiel '#name' => $field_name . '_add_more', '#value' => t('Add another item'), '#attributes' => array('class' => array('field-add-more-submit')), + '#validate_sections' => array(VALIDATE_SECTION_PARENT), // Submit callback for disabled JavaScript. '#submit' => array('field_add_more_submit'), '#ajax' => array( === modified file 'modules/poll/poll.module' --- modules/poll/poll.module 2009-12-14 20:38:15 +0000 +++ modules/poll/poll.module 2009-12-28 15:54:16 +0000 @@ -273,6 +273,7 @@ function poll_form($node, &$form_state) '#value' => t('More choices'), '#description' => t("If the amount of boxes above isn't enough, click here to add more choices."), '#weight' => 1, + '#validate_sections' => array(VALIDATE_SECTION_PARENT), '#submit' => array('poll_more_choices_submit'), // If no javascript action. '#ajax' => array( 'callback' => 'poll_choice_js', @@ -901,4 +902,3 @@ function poll_user_cancel($edit, $accoun break; } } - === modified file 'modules/simpletest/tests/form.test' --- modules/simpletest/tests/form.test 2009-12-17 17:18:02 +0000 +++ modules/simpletest/tests/form.test 2009-12-28 16:53:11 +0000 @@ -231,6 +231,21 @@ class FormValidationTestCase extends Dru $this->assertNoFieldByName('name', t('Form element was hidden.')); $this->assertText('Name value: element_validate_access', t('Value for inaccessible form element exists.')); } + + /** + * Test button press validates a section. + */ + function testValidateParents() { + $edit = array('test' => 'pass'); + foreach(array('form-test/validate-sections', 'form-test/validate-sections-parent') as $path) { + $this->drupalPost($path, $edit, t('Partial validate')); + $this->assertNoText(t('!name field is required.', array('!name' => 'Title'))); + $this->assertText('Test is validated'); + $this->drupalPost($path, $edit, t('Full validate')); + $this->assertText(t('!name field is required.', array('!name' => 'Title'))); + $this->assertText('Test is validated'); + } + } } /** === modified file 'modules/simpletest/tests/form_test.module' --- modules/simpletest/tests/form_test.module 2009-12-17 17:18:02 +0000 +++ modules/simpletest/tests/form_test.module 2009-12-28 16:57:22 +0000 @@ -17,6 +17,20 @@ function form_test_menu() { 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); + $items['form-test/validate-sections'] = array( + 'title' => 'Form validation sections test', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_validate_sections'), + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + $items['form-test/validate-sections-parent'] = array( + 'title' => 'Form validation sections test', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_validate_sections_parent'), + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); $items['form_test/tableselect/multiple-true'] = array( 'title' => 'Tableselect checkboxes test', @@ -203,6 +217,62 @@ function form_test_validate_form_validat } } +function form_test_validate_sections($form) { + $form['title'] = array( + '#type' => 'textfield', + '#title' => 'Title', + '#required' => TRUE, + ); + $form['wrapper']['test'] = array( + '#type' => 'textfield', + '#element_validate' => array('form_test_validate_test_element'), + ); + $form['buttons']['partial'] = array( + '#type' => 'submit', + '#validate_sections' => array(array('wrapper')), + '#value' => t('Partial validate'), + ); + $form['buttons']['full'] = array( + '#type' => 'submit', + '#value' => t('Full validate'), + ); + return $form; +} + +/** + * Element handler for the test element. + */ +function form_test_validate_test_element($form, $form_state) { + if ($form_state['values']['test'] == 'pass') { + drupal_set_message('Test is validated'); + } +} + +/** + * Tests the VALIDATE_SECTION_PARENT constant. + */ +function form_test_validate_sections_parent($form) { + $form['title'] = array( + '#type' => 'textfield', + '#title' => 'Title', + '#required' => TRUE, + ); + $form['wrapper']['test'] = array( + '#type' => 'textfield', + '#element_validate' => array('form_test_validate_test_element'), + ); + $form['wrapper']['partial'] = array( + '#type' => 'submit', + '#validate_sections' => array(VALIDATE_SECTION_PARENT), + '#value' => t('Partial validate'), + ); + $form['buttons']['full'] = array( + '#type' => 'submit', + '#value' => t('Full validate'), + ); + return $form; +} + /** * Create a header and options array. Helper function for callbacks. */ @@ -895,7 +965,7 @@ function form_test_state_persist($form, /** * Submit handler. - * + * * @see form_test_state_persist() */ function form_test_state_persist_submit($form, &$form_state) { @@ -905,7 +975,7 @@ function form_test_state_persist_submit( /** * Implements hook_form_FORM_ID_alter(). - * + * * @see form_test_state_persist() */ function form_test_form_form_test_state_persist_alter(&$form, &$form_state) {