diff options
author | Angie Byron <webchick@24967.no-reply.drupal.org> | 2010-01-02 23:30:53 +0000 |
---|---|---|
committer | Angie Byron <webchick@24967.no-reply.drupal.org> | 2010-01-02 23:30:53 +0000 |
commit | 70e53b33c1074655f4ee917c0c4f4b1219bb109d (patch) | |
tree | 7033541b9bc09dfc85aa69be2b57a1f8c8fa9b85 /includes/form.inc | |
parent | d4f4d3c32e2b7028527b13fc3d63d84576562590 (diff) | |
download | brdo-70e53b33c1074655f4ee917c0c4f4b1219bb109d.tar.gz brdo-70e53b33c1074655f4ee917c0c4f4b1219bb109d.tar.bz2 |
#370537 by chx, sun, effulgentsia, quicksketch, eaton, Heine, and yched: Allow buttons to only validate sections of forms, e.g. More buttons. (with tests)
Diffstat (limited to 'includes/form.inc')
-rw-r--r-- | includes/form.inc | 142 |
1 files changed, 125 insertions, 17 deletions
diff --git a/includes/form.inc b/includes/form.inc index 0e5212444..13571a111 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -888,19 +888,11 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) { _form_validate($elements[$key], $form_state); } } + // Validate the current input. if (!isset($elements['#validated']) || !$elements['#validated']) { + // The following errors are always shown. if (isset($elements['#needs_validation'])) { - // Make sure a value is passed when the field is required. - // A simple call to empty() will not cut it here as some fields, like - // checkboxes, can return a valid value of '0'. Instead, check the - // length if it's a string, and the item count if it's an array. - // An unchecked checkbox has a #value of numeric 0, different than string - // '0', which could be a valid value. - if ($elements['#required'] && (!count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0) || $elements['#value'] === 0)) { - form_error($elements, $t('!name field is required.', array('!name' => $elements['#title']))); - } - // Verify that the value is not longer than #maxlength. if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) { form_error($elements, $t('!name cannot be longer than %max characters but is currently %length characters long.', array('!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'], '%max' => $elements['#maxlength'], '%length' => drupal_strlen($elements['#value'])))); @@ -929,6 +921,36 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) { } } + // While this element is being validated, it may be desired that some calls + // to form_set_error() be suppressed and not result in a form error, so + // that a button that implements low-risk functionality (such as "Previous" + // or "Add more") that doesn't require all user input to be valid can still + // have its submit handlers triggered. The clicked button's + // #limit_validation_errors property contains the information for which + // errors are needed, and all other errors are to be suppressed. The + // #limit_validation_errors property is ignored if the button doesn't also + // define its own submit handlers, because it's too large a security risk to + // have any invalid user input when executing form-level submit handlers. + if (isset($form_state['clicked_button']['#limit_validation_errors']) && isset($form_state['clicked_button']['#submit'])) { + form_set_error(NULL, '', $form_state['clicked_button']['#limit_validation_errors']); + } + else { + // As an extra security measure, explicitly turn off error suppression. + // Since this is also done at the end of this function, doing it here is + // only to handle the rare edge case where a validate handler invokes form + // processing of another form. + drupal_static_reset('form_set_error:limit_validation_errors'); + } + // Make sure a value is passed when the field is required. + // A simple call to empty() will not cut it here as some fields, like + // checkboxes, can return a valid value of '0'. Instead, check the + // length if it's a string, and the item count if it's an array. + // An unchecked checkbox has a #value of numeric 0, different than string + // '0', which could be a valid value. + if (isset($elements['#needs_validation']) && $elements['#required'] && (!count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0) || $elements['#value'] === 0)) { + form_error($elements, $t('!name field is required.', array('!name' => $elements['#title']))); + } + // Call user-defined form level validators. if (isset($form_id)) { form_execute_handlers('validate', $elements, $form_state); @@ -944,6 +966,11 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) { } $elements['#validated'] = TRUE; } + + // Done validating this element, so turn off error suppression. + // _form_validate() turns it on again when starting on the next element, if + // it's still appropriate to do so. + drupal_static_reset('form_set_error:limit_validation_errors'); } /** @@ -1005,18 +1032,99 @@ function form_execute_handlers($type, &$form, &$form_state) { * element where the #parents array starts with 'foo'. * @param $message * The error message to present to the user. + * @param $limit_validation_errors + * Internal use only. The #limit_validation_errors property of the clicked + * button if it exists. Multistep forms not wanting to validate the whole form + * can set the #limit_validation_errors property on buttons to avoid + * validation errors of some elements preventing the button's submit handlers + * from running. For example, pressing the "Previous" button should not fire + * validation errors just because the current step has invalid values. AJAX is + * another typical example. + * If this property is set on the clicked button, the button must also define + * its #submit property and those handlers will be executed even if there is + * invalid input, so extreme care should be taken with respect to what is + * performed by them. This is typically not a problem with buttons like + * "Previous" or "Add more" that do not invoke persistent storage of the + * submitted form values. + * Do not use the #limit_validation_errors property on buttons that trigger + * saving of form values to the database. + * The #limit_validation_errors property is a list of "sections" within + * $form_state['values'] that must contain valid values. Each "section" is an + * array with the ordered set of keys needed to reach that part of + * $form_state['values'] (i.e., the #parents property of the element). + * For example: + * @code + * $form['actions']['previous']['#limit_validation_errors'] = array( + * array('step1'), + * array('foo', 'bar'), + * ); + * @endcode + * This will require $form_state['values']['step1'] and everything within it + * (for example, $form_state['values']['step1']['choice']) to be valid, so + * calls to form_set_error('step1', $message) or + * form_set_error('step1][choice', $message) will prevent the submit handlers + * from running, and result in the error message being displayed to the user. + * However, calls to form_set_error('step2', $message) and + * form_set_error('step2][groupX][choiceY', $message) will be suppressed, + * resulting in the message not being displayed to the user, and the submit + * handlers will run despite $form_state['values']['step2'] and + * $form_state['values']['step2']['groupX']['choiceY'] containing invalid + * values. Errors for an invalid $form_state['values']['foo'] will be + * suppressed, but errors for invalid values for + * $form_state['values']['foo']['bar'] and everything within it will be + * recorded. If the button doesn't need any user input to be valid, then the + * #limit_validation_errors can be set to an empty array, in which case, all + * calls to form_set_error() will be suppressed. + * Partial form validation is implemented by suppressing errors rather than by + * skipping the input processing and validation steps entirely, because some + * forms have button-level submit handlers that call Drupal API functions that + * assume that certain data exists within $form_state['values'], and while not + * doing anything with that data that requires it to be valid, PHP errors + * would be triggered if the input processing and validation steps were fully + * skipped. @see http://drupal.org/node/370537. + * * @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 = '') { +function form_set_error($name = NULL, $message = '', $limit_validation_errors = NULL) { $form = &drupal_static(__FUNCTION__, array()); + $sections = &drupal_static(__FUNCTION__ . ':limit_validation_errors'); + if (isset($limit_validation_errors)) { + $sections = $limit_validation_errors; + } + if (isset($name) && !isset($form[$name])) { - $form[$name] = $message; - if ($message) { - drupal_set_message($message, 'error'); + $record = TRUE; + if (isset($sections)) { + // #limit_validation_errors is an array of "sections" within which user + // input must be valid. If the element is within one of these sections, + // the error must be recorded. Otherwise, it can be suppressed. + // #limit_validation_errors can be an empty array, in which case all + // errors are suppressed. For example, a "Previous" button might want its + // submit action to be triggered even if none of the submitted values are + // valid. + $record = FALSE; + foreach ($sections as $section) { + // Exploding by '][' reconstructs the element's #parents. If the + // reconstructed #parents begin with the same keys as the specified + // section, then the element's values are within the part of + // $form_state['values'] that the clicked button requires to be valid, + // so errors for this element must be recorded. + if (array_slice(explode('][', $name), 0, count($section)) === $section) { + $record = TRUE; + break; + } + } + } + if ($record) { + $form[$name] = $message; + if ($message) { + drupal_set_message($message, 'error'); + } } } + return $form; } @@ -3261,7 +3369,7 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd $batch =& batch_get(); drupal_theme_initialize(); - + if (isset($batch)) { // Add process information $process_info = array( @@ -3276,7 +3384,7 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd ); $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); |