summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--includes/ajax.inc80
-rw-r--r--includes/form.inc290
-rw-r--r--misc/ajax.js8
-rw-r--r--modules/file/file.module12
-rw-r--r--modules/overlay/overlay.module13
-rw-r--r--modules/poll/poll.test2
-rw-r--r--modules/simpletest/drupal_web_test_case.php32
-rw-r--r--modules/simpletest/tests/ajax.test58
-rw-r--r--modules/simpletest/tests/ajax_forms_test.module62
-rw-r--r--modules/simpletest/tests/form.test71
-rw-r--r--modules/simpletest/tests/form_test.module86
-rw-r--r--modules/system/system.module3
12 files changed, 512 insertions, 205 deletions
diff --git a/includes/ajax.inc b/includes/ajax.inc
index 1526411d1..fbf1da89e 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -257,28 +257,12 @@ function ajax_get_form() {
* The Form API #ajax property can be set both for buttons and other input
* elements.
*
- * ajax_process_form() defines an additional 'formPath' JavaScript setting
- * that is used by Drupal.ajax.prototype.beforeSubmit() to automatically inject
- * an additional field 'ajax_triggering_element' to the submitted form values,
- * which contains the array #parents of the element in the form structure.
- * This additional field allows ajax_form_callback() to determine which
- * element triggered the action, as non-submit form elements do not
- * provide this information in $form_state['clicked_button'], which can
- * also be used to determine triggering element, but only submit-type
- * form elements.
- *
* This function is also the canonical example of how to implement
* #ajax['path']. If processing is required that cannot be accomplished with
* a callback, re-implement this function and set #ajax['path'] to the
* enhanced function.
*/
function ajax_form_callback() {
- // Find the triggering element, which was set up for us on the client side.
- if (!empty($_REQUEST['ajax_triggering_element'])) {
- $triggering_element_path = $_REQUEST['ajax_triggering_element'];
- // Remove the value for form validation.
- unset($_REQUEST['ajax_triggering_element']);
- }
list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
// Build, validate and if possible, submit the form.
@@ -288,32 +272,11 @@ function ajax_form_callback() {
// drupal_process_form() set up.
$form = drupal_rebuild_form($form_id, $form_state, $form_build_id);
- // $triggering_element_path in a simple form might just be 'myselect', which
- // would mean we should use the element $form['myselect']. For nested form
- // elements we need to recurse into the form structure to find the triggering
- // element, so we can retrieve the #ajax['callback'] from it.
- if (!empty($triggering_element_path)) {
- if (!isset($form['#access']) || $form['#access']) {
- $triggering_element = $form;
- foreach (explode('/', $triggering_element_path) as $key) {
- if (!empty($triggering_element[$key]) && (!isset($triggering_element[$key]['#access']) || $triggering_element[$key]['#access'])) {
- $triggering_element = $triggering_element[$key];
- }
- else {
- // We did not find the $triggering_element or do not have #access,
- // so break out and do not provide it.
- $triggering_element = NULL;
- break;
- }
- }
- }
- }
- if (empty($triggering_element)) {
- $triggering_element = $form_state['clicked_button'];
- }
- // Now that we have the element, get a callback if there is one.
- if (!empty($triggering_element)) {
- $callback = $triggering_element['#ajax']['callback'];
+ // As part of drupal_process_form(), the element that triggered the form
+ // submission is determined, and in the case of AJAX, it might not be a
+ // button. This lets us route to the appropriate callback.
+ if (!empty($form_state['triggering_element'])) {
+ $callback = $form_state['triggering_element']['#ajax']['callback'];
}
if (!empty($callback) && function_exists($callback)) {
return $callback($form, $form_state);
@@ -499,13 +462,40 @@ function ajax_process_form($element, &$form_state) {
'speed' => 'none',
'method' => 'replace',
'progress' => array('type' => 'throbber'),
- 'formPath' => implode('/', $element['#array_parents']),
);
- // Process special settings.
+ // Change path to url.
$settings['url'] = isset($settings['path']) ? url($settings['path']) : url('system/ajax');
unset($settings['path']);
- $settings['button'] = isset($element['#executes_submit_callback']) ? array($element['#name'] => $element['#value']) : FALSE;
+
+ // Add special data to $settings['submit'] so that when this element
+ // triggers an AJAX submission, Drupal's form processing can determine which
+ // element triggered it.
+ // @see _form_element_triggered_scripted_submission()
+ if (isset($settings['trigger_as'])) {
+ // An element can add a 'trigger_as' key within #ajax to make the element
+ // submit as though another one (for example, a non-button can use this
+ // to submit the form as though a button were clicked). When using this,
+ // the 'name' key is always required to identify the element to trigger
+ // as. The 'value' key is optional, and only needed when multiple elements
+ // share the same name, which is commonly the case for buttons.
+ $settings['submit']['_triggering_element_name'] = $settings['trigger_as']['name'];
+ if (isset($settings['trigger_as']['value'])) {
+ $settings['submit']['_triggering_element_value'] = $settings['trigger_as']['value'];
+ }
+ unset($settings['trigger_as']);
+ }
+ else {
+ // Most of the time, elements can submit as themselves, in which case the
+ // 'trigger_as' key isn't needed, and the element's name is used.
+ $settings['submit']['_triggering_element_name'] = $element['#name'];
+ // If the element is a (non-image) button, its name may not identify it
+ // uniquely, in which case a match on value is also needed.
+ // @see _form_button_was_clicked()
+ if (isset($element['#button_type']) && empty($element['#has_garbage_value'])) {
+ $settings['submit']['_triggering_element_value'] = $element['#value'];
+ }
+ }
// Convert a simple #ajax['progress'] string into an array.
if (is_string($settings['progress'])) {
diff --git a/includes/form.inc b/includes/form.inc
index fe14c083b..bc6dc4865 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -281,19 +281,21 @@ function form_state_defaults() {
'cache'=> FALSE,
'method' => 'post',
'groups' => array(),
+ 'buttons' => array(),
);
}
/**
* Retrieves a form, caches it and processes it again.
*
- * If your AHAH callback simulates the pressing of a button, then your AHAH
- * callback will need to do the same as what drupal_get_form would do when the
+ * If your AJAX callback simulates the pressing of a button, then your AJAX
+ * callback will need to do the same as what drupal_get_form() would do when the
* button is pressed: get the form from the cache, run drupal_process_form over
- * it and then if it needs rebuild, run drupal_rebuild_form over it. Then send
+ * it and then if it needs rebuild, run drupal_rebuild_form() over it. Then send
* back a part of the returned form.
- * $form_state['clicked_button']['#array_parents'] will help you to find which
- * part.
+ * $form_state['triggering_element']['#array_parents'] will help you to find
+ * which part.
+ * @see ajax_form_callback() for an example.
*
* @param $form_id
* The unique string identifying the desired form. If a function
@@ -403,6 +405,7 @@ function form_state_keys_no_cache() {
'temporary',
// Internal properties defined by form processing.
'buttons',
+ 'triggering_element',
'clicked_button',
'complete form',
'groups',
@@ -935,22 +938,35 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) {
// 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
+ // have its submit handlers triggered. The triggering element'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']);
- }
+ // #limit_validation_errors property is ignored if submit handlers will run,
+ // but the element doesn't have a #submit property, because it's too large a
+ // security risk to have any invalid user input when executing form-level
+ // submit handlers.
+ if (isset($form_state['triggering_element']['#limit_validation_errors']) && ($form_state['triggering_element']['#limit_validation_errors'] !== FALSE) && !($form_state['submitted'] && !isset($form_state['triggering_element']['#submit']))) {
+ form_set_error(NULL, '', $form_state['triggering_element']['#limit_validation_errors']);
+ }
+ // If submit handlers won't run (due to the submission having been triggered
+ // by an element whose #executes_submit_callback property isn't TRUE), then
+ // it's safe to suppress all validation errors, and we do so by default,
+ // which is particularly useful during an AJAX submission triggered by a
+ // non-button. An element can override this default by setting the
+ // #limit_validation_errors property. For button element types,
+ // #limit_validation_errors defaults to FALSE (via system_element_info()),
+ // so that full validation is their default behavior.
+ elseif (isset($form_state['triggering_element']) && !isset($form_state['triggering_element']['#limit_validation_errors']) && !$form_state['submitted']) {
+ form_set_error(NULL, '', array());
+ }
+ // As an extra security measure, explicitly turn off error suppression if
+ // one of the above conditions wasn't met. 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.
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
@@ -1296,22 +1312,61 @@ function form_builder($form_id, $element, &$form_state) {
$element['#after_build_done'] = TRUE;
}
- // Now that we've processed everything, we can go back to handle the funky
- // Internet Explorer button-click scenario.
- _form_builder_ie_cleanup($element, $form_state);
-
// If there is a file element, we need to flip a flag so later the
// form encoding can be set.
if (isset($element['#type']) && $element['#type'] == 'file') {
$form_state['has_file_element'] = TRUE;
}
+ // Final tasks for the form element after form_builder() has run for all other
+ // elements.
if (isset($element['#type']) && $element['#type'] == 'form') {
- // We are on the top form.
// If there is a file element, we set the form encoding.
if (isset($form_state['has_file_element'])) {
$element['#attributes']['enctype'] = 'multipart/form-data';
}
+
+ // If a form contains a single textfield, and the ENTER key is pressed
+ // within it, Internet Explorer submits the form with no POST data
+ // identifying any submit button. Other browsers submit POST data as though
+ // the user clicked the first button. Therefore, to be as consistent as we
+ // can be across browsers, if no 'triggering_element' has been identified
+ // yet, default it to the first button.
+ if (!$form_state['programmed'] && !isset($form_state['triggering_element']) && !empty($form_state['buttons'])) {
+ $form_state['triggering_element'] = $form_state['buttons'][0];
+ }
+
+ // If the triggering element specifies "button-level" validation and submit
+ // handlers to run instead of the default form-level ones, then add those to
+ // the form state.
+ foreach (array('validate', 'submit') as $type) {
+ if (isset($form_state['triggering_element']['#' . $type])) {
+ $form_state[$type . '_handlers'] = $form_state['triggering_element']['#' . $type];
+ }
+ }
+
+ // If the triggering element executes submit handlers, then set the form
+ // state key that's needed for those handlers to run.
+ if (!empty($form_state['triggering_element']['#executes_submit_callback'])) {
+ $form_state['submitted'] = TRUE;
+ }
+
+ // Special processing if the triggering element is a button.
+ if (isset($form_state['triggering_element']['#button_type'])) {
+ // Because there are several ways in which the triggering element could
+ // have been determined (including from input variables set by JavaScript
+ // or fallback behavior implemented for IE), and because buttons often
+ // have their #name property not derived from their #parents property, we
+ // can't assume that input processing that's happened up until here has
+ // resulted in $form_state['values'][BUTTON_NAME] being set. But it's
+ // common for forms to have several buttons named 'op' and switch on
+ // $form_state['values']['op'] during submit handler execution.
+ $form_state['values'][$form_state['triggering_element']['#name']] = $form_state['triggering_element']['#value'];
+
+ // @todo Legacy support. Remove in Drupal 8.
+ $form_state['clicked_button'] = $form_state['triggering_element'];
+ }
+
// Update the copy of the complete form for usage in validation handlers.
$form_state['complete form'] = $element;
}
@@ -1408,33 +1463,42 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
}
}
- // Determine which button (if any) was clicked to submit the form.
- // We compare the incoming values with the buttons defined in the form,
- // and flag the one that matches. We have to do some funky tricks to
- // deal with Internet Explorer's handling of single-button forms, though.
- if (!empty($form_state['input']) && isset($element['#executes_submit_callback'])) {
- // First, accumulate a collection of buttons, divided into two bins:
- // those that execute full submit callbacks and those that only validate.
- $button_type = $element['#executes_submit_callback'] ? 'submit' : 'button';
- $form_state['buttons'][$button_type][] = $element;
-
- if (_form_button_was_clicked($element, $form_state)) {
- $form_state['submitted'] = $form_state['submitted'] || $element['#executes_submit_callback'];
-
- // In most cases, we want to use form_set_value() to manipulate
- // the global variables. In this special case, we want to make sure that
- // the value of this element is listed in $form_variables under 'op'.
- $form_state['values'][$element['#name']] = $element['#value'];
- $form_state['clicked_button'] = $element;
-
- if (isset($element['#validate'])) {
- $form_state['validate_handlers'] = $element['#validate'];
+ // Determine which element (if any) triggered the submission of the form and
+ // keep track of all the buttons in the form for form_state_values_clean().
+ // @todo We need to add a #access check here, so that someone can't fake the
+ // click of a button they shouldn't have access to, but first we need to
+ // fix file.module's managed_file element pipeline to handle the click of
+ // the remove button in a submit handler instead of in a #process function.
+ // During the first run of form_builder() after the form is submitted,
+ // #process functions need to return the expanded element with child
+ // elements' #access properties matching what they were when the form was
+ // displayed to the user, since that is what we are processing input for.
+ // Changes to the form (like toggling the upload/remove button) need to wait
+ // until form rebuild: http://drupal.org/node/736298.
+ if (!empty($form_state['input'])) {
+ // Detect if the element triggered the submission via AJAX.
+ if (_form_element_triggered_scripted_submission($element, $form_state)) {
+ $form_state['triggering_element'] = $element;
+ }
+
+ // If the form was submitted by the browser rather than via AJAX, then it
+ // can only have been triggered by a button, and we need to determine which
+ // button within the constraints of how browsers provide this information.
+ if (isset($element['#button_type'])) {
+ // All buttons in the form need to be tracked for
+ // form_state_values_clean() and for the form_builder() code that handles
+ // a form submission containing no button information in $_POST.
+ // @todo When #access is checked in an outer if statement (see above), it
+ // won't need to be checked here.
+ if ($form_state['programmed'] || !isset($element['#access']) || $element['#access']) {
+ $form_state['buttons'][] = $element;
}
- if (isset($element['#submit'])) {
- $form_state['submit_handlers'] = $element['#submit'];
+ if (_form_button_was_clicked($element, $form_state)) {
+ $form_state['triggering_element'] = $element;
}
}
}
+
// Set the element's value in $form_state['values'], but only, if its key
// does not exist yet (a #value_callback may have already populated it).
$values = $form_state['values'];
@@ -1447,21 +1511,52 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
}
/**
- * Helper function to handle the sometimes-convoluted logic of button
- * click detection.
+ * Helper function to handle the convoluted logic of button click detection.
*
- * In Internet Explorer, if ONLY one submit button is present, AND the
- * enter key is used to submit the form, no form value is sent for it
- * and we'll never detect a match. That special case is handled by
- * _form_builder_ie_cleanup().
+ * This detects button or non-button controls that trigger a form submission via
+ * AJAX or some other scriptable environment. These environments can set the
+ * special input key '_triggering_element_name' to identify the triggering
+ * element. If the name alone doesn't identify the element uniquely, the input
+ * key '_triggering_element_value' may also be set to require a match on element
+ * value. An example where this is needed is if there are several buttons all
+ * named 'op', and only differing in their value.
*/
-function _form_button_was_clicked($form, &$form_state) {
+function _form_element_triggered_scripted_submission($element, &$form_state) {
+ if (!empty($form_state['input']['_triggering_element_name']) && $element['#name'] == $form_state['input']['_triggering_element_name']) {
+ if (empty($form_state['input']['_triggering_element_value']) || $form_state['input']['_triggering_element_value'] == $element['#value']) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Helper function to handle the convoluted logic of button click detection.
+ *
+ * This detects button controls that trigger a form submission by being clicked
+ * and having the click processed by the browser rather than being captured by
+ * JavaScript. Essentially, it detects if the button's name and value are part
+ * of the POST data, but with extra code to deal with the convoluted way in
+ * which browsers submit data for image button clicks.
+ *
+ * This does not detect button clicks processed by AJAX (that is done in
+ * _form_element_triggered_scripted_submission()) and it does not detect form
+ * submissions from Internet Explorer in response to an ENTER key pressed in a
+ * textfield (form_builder() has extra code for that).
+ *
+ * Because this function contains only part of the logic needed to determine
+ * $form_state['triggering_element'], it should not be called from anywhere
+ * other than within the Form API. Form validation and submit handlers needing
+ * to know which button was clicked should get that information from
+ * $form_state['triggering_element'].
+ */
+function _form_button_was_clicked($element, &$form_state) {
// First detect normal 'vanilla' button clicks. Traditionally, all
// standard buttons on a form share the same name (usually 'op'),
// and the specific return value is used to determine which was
// clicked. This ONLY works as long as $form['#name'] puts the
// value at the top level of the tree of $_POST data.
- if (isset($form_state['input'][$form['#name']]) && $form_state['input'][$form['#name']] == $form['#value']) {
+ if (isset($form_state['input'][$element['#name']]) && $form_state['input'][$element['#name']] == $element['#value']) {
return TRUE;
}
// When image buttons are clicked, browsers do NOT pass the form element
@@ -1469,42 +1564,13 @@ function _form_button_was_clicked($form, &$form_state) {
// coordinates of the click on the button image. This means that image
// buttons MUST have unique $form['#name'] values, but the details of
// their $_POST data should be ignored.
- elseif (!empty($form['#has_garbage_value']) && isset($form['#value']) && $form['#value'] !== '') {
+ elseif (!empty($element['#has_garbage_value']) && isset($element['#value']) && $element['#value'] !== '') {
return TRUE;
}
return FALSE;
}
/**
- * In IE, if only one submit button is present, AND the enter key is
- * used to submit the form, no form value is sent for it and our normal
- * button detection code will never detect a match. We call this
- * function after all other button-detection is complete to check
- * for the proper conditions, and treat the single button on the form
- * as 'clicked' if they are met.
- */
-function _form_builder_ie_cleanup($form, &$form_state) {
- // Quick check to make sure we're always looking at the full form
- // and not a sub-element.
- if (!empty($form['#type']) && $form['#type'] == 'form') {
- // If we haven't recognized a submission yet, and there's a single
- // submit button, we know that we've hit the right conditions. Grab
- // the first one and treat it as the clicked button.
- if (empty($form_state['submitted']) && !empty($form_state['buttons']['submit']) && empty($form_state['buttons']['button'])) {
- $button = $form_state['buttons']['submit'][0];
-
- // Set up all the $form_state information that would have been
- // populated had the button been recognized earlier.
- $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['values'][$button['#name']] = $button['#value'];
- $form_state['clicked_button'] = $button;
- }
- }
-}
-
-/**
* Removes internal Form API elements and buttons from submitted form values.
*
* This function can be used when a module wants to store all submitted form
@@ -1527,37 +1593,35 @@ function form_state_values_clean(&$form_state) {
unset($form_state['values']['form_id'], $form_state['values']['form_token'], $form_state['values']['form_build_id'], $form_state['values']['op']);
// Remove button values.
- // form_builder() collects all button elements in a form, keyed by button
- // type. We remove the button value separately for each button element.
- foreach ($form_state['buttons'] as $button_type => $buttons) {
- foreach ($buttons as $button) {
- // Remove this button's value from the submitted form values by finding
- // the value corresponding to this button.
- // We iterate over the #parents of this button and move a reference to
- // each parent in $form_state['values']. For example, if #parents is:
- // array('foo', 'bar', 'baz')
- // then the corresponding $form_state['values'] part will look like this:
- // array(
- // 'foo' => array(
- // 'bar' => array(
- // 'baz' => 'button_value',
- // ),
- // ),
- // )
- // We start by (re)moving 'baz' to $last_parent, so we are able unset it
- // at the end of the iteration. Initially, $values will contain a
- // reference to $form_state['values'], but in the iteration we move the
- // reference to $form_state['values']['foo'], and finally to
- // $form_state['values']['foo']['bar'], which is the level where we can
- // unset 'baz' (that is stored in $last_parent).
- $parents = $button['#parents'];
- $values = &$form_state['values'];
- $last_parent = array_pop($parents);
- foreach ($parents as $parent) {
- $values = &$values[$parent];
- }
- unset($values[$last_parent]);
- }
+ // form_builder() collects all button elements in a form. We remove the button
+ // value separately for each button element.
+ foreach ($form_state['buttons'] as $button) {
+ // Remove this button's value from the submitted form values by finding
+ // the value corresponding to this button.
+ // We iterate over the #parents of this button and move a reference to
+ // each parent in $form_state['values']. For example, if #parents is:
+ // array('foo', 'bar', 'baz')
+ // then the corresponding $form_state['values'] part will look like this:
+ // array(
+ // 'foo' => array(
+ // 'bar' => array(
+ // 'baz' => 'button_value',
+ // ),
+ // ),
+ // )
+ // We start by (re)moving 'baz' to $last_parent, so we are able unset it
+ // at the end of the iteration. Initially, $values will contain a
+ // reference to $form_state['values'], but in the iteration we move the
+ // reference to $form_state['values']['foo'], and finally to
+ // $form_state['values']['foo']['bar'], which is the level where we can
+ // unset 'baz' (that is stored in $last_parent).
+ $parents = $button['#parents'];
+ $values = &$form_state['values'];
+ $last_parent = array_pop($parents);
+ foreach ($parents as $parent) {
+ $values = &$values[$parent];
+ }
+ unset($values[$last_parent]);
}
}
diff --git a/misc/ajax.js b/misc/ajax.js
index 3d0357cbc..23179fd07 100644
--- a/misc/ajax.js
+++ b/misc/ajax.js
@@ -98,7 +98,7 @@ Drupal.ajax = function (base, element, element_settings) {
type: 'bar',
message: 'Please wait...'
},
- button: {}
+ submit: {}
};
$.extend(this, defaults, element_settings);
@@ -121,7 +121,7 @@ Drupal.ajax = function (base, element, element_settings) {
var ajax = this;
var options = {
url: ajax.url,
- data: ajax.button,
+ data: ajax.submit,
beforeSerialize: function (element_settings, options) {
return ajax.beforeSerialize(element_settings, options);
},
@@ -200,10 +200,6 @@ Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
// Disable the element that received the change.
$(this.element).addClass('progress-disabled').attr('disabled', true);
- // Server-side code needs to know what element triggered the call, so it can
- // find the #ajax binding.
- form_values.push({ name: 'ajax_triggering_element', value: this.formPath });
-
// Insert progressbar or throbber.
if (this.progress.type == 'bar') {
var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback));
diff --git a/modules/file/file.module b/modules/file/file.module
index d0f95cfb8..0eba84715 100644
--- a/modules/file/file.module
+++ b/modules/file/file.module
@@ -385,12 +385,12 @@ function file_managed_file_process($element, &$form_state, $form) {
'#weight' => -5,
);
- // Because the output of this field changes depending on the button clicked,
- // we need to ask FAPI immediately if the remove button was clicked.
- // It's not good that we call this private function, but
- // $form_state['clicked_button'] is only available after this #process
- // callback is finished.
- if (_form_button_was_clicked($element['remove_button'], $form_state)) {
+ // @todo It is not good to call these private functions. This should be
+ // refactored so that the file deletion happens during a submit handler,
+ // and form changes affected by that (such as toggling the upload and remove
+ // buttons) happens during the 2nd run of this function that is triggered by
+ // a form rebuild: http://drupal.org/node/736298.
+ if (_form_button_was_clicked($element['remove_button'], $form_state) || _form_element_triggered_scripted_submission($element['remove_button'], $form_state)) {
// If it's a temporary file we can safely remove it immediately, otherwise
// it's up to the implementing module to clean up files that are in use.
if ($element['#file'] && $element['#file']->status == 0) {
diff --git a/modules/overlay/overlay.module b/modules/overlay/overlay.module
index eeb9096bf..b5ec993b8 100644
--- a/modules/overlay/overlay.module
+++ b/modules/overlay/overlay.module
@@ -317,8 +317,6 @@ function overlay_preprocess_toolbar(&$variables) {
* processing, so that it's possible to close the overlay after submitting
* a form.
*
- * @see _form_builder_handle_input_element()
- * @see _form_builder_ie_cleanup()
* @see form_execute_handlers()
* @see form_builder()
* @see overlay_form_submit()
@@ -327,17 +325,6 @@ function overlay_preprocess_toolbar(&$variables) {
*/
function overlay_form_after_build($form, &$form_state) {
if (overlay_get_mode() == 'child') {
- // Form API may have already captured submit handlers from the submitted
- // button before after_build callback is invoked. This may have been done
- // by _form_builder_handle_input_element(). If so, the list of submit
- // handlers is stored in the $form_state array, which is something we can
- // also alter from here, luckily. Rememeber: our goal here is to set
- // $form_state['redirect'] to FALSE if the API function
- // overlay_request_dialog_close() has been invoked. That's because we want
- // to tell the parent window to close the overlay.
- if (!empty($form_state['submit_handlers']) && !in_array('overlay_form_submit', $form_state['submit_handlers'])) {
- $form_state['submit_handlers'][] = 'overlay_form_submit';
- }
// If this element has submit handlers, then append our own.
if (isset($form['#submit'])) {
$form['#submit'][] = 'overlay_form_submit';
diff --git a/modules/poll/poll.test b/modules/poll/poll.test
index 2b5b43efb..e4a7db848 100644
--- a/modules/poll/poll.test
+++ b/modules/poll/poll.test
@@ -341,7 +341,7 @@ class PollJSAddChoice extends DrupalWebTestCase {
// Press 'add choice' button through AJAX, and place the expected HTML result
// as the tested content.
- $commands = $this->drupalPostAJAX(NULL, $edit, 'poll_more');
+ $commands = $this->drupalPostAJAX(NULL, $edit, array('op' => t('More choices')));
$this->content = $commands[1]['data'];
$this->assertFieldByName('choice[chid:0][chtext]', $edit['choice[new:0][chtext]'], t('Field !i found', array('!i' => 0)));
diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php
index f2622444d..42ac83058 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -1576,11 +1576,18 @@ class DrupalWebTestCase extends DrupalTestCase {
* which is likely different than the $path parameter used for retrieving
* the initial form. Defaults to 'system/ajax'.
* - triggering_element: If the value for the 'path' key is 'system/ajax' or
- * another generic AJAX processing path, this needs to be set to the '/'
- * separated path to the element within the server's cached $form array.
- * The callback for the generic AJAX processing path uses this to find
- * the #ajax information for the element, including which specific
- * callback to use for processing the request.
+ * another generic AJAX processing path, this needs to be set to the name
+ * of the element. If the name doesn't identify the element uniquely, then
+ * this should instead be an array with a single key/value pair,
+ * corresponding to the element name and value. The callback for the
+ * generic AJAX processing path uses this to find the #ajax information
+ * for the element, including which specific callback to use for
+ * processing the request.
+ *
+ * This can also be set to NULL in order to emulate an Internet Explorer
+ * submission of a form with a single text field, and pressing ENTER in that
+ * textfield: under these conditions, no button information is added to the
+ * POST data.
* @param $options
* Options to be forwarded to url().
* @param $headers
@@ -1622,7 +1629,7 @@ class DrupalWebTestCase extends DrupalTestCase {
// We post only if we managed to handle every field in edit and the
// submit button matches.
- if (!$edit && $submit_matches) {
+ if (!$edit && ($submit_matches || !isset($submit))) {
$post_array = $post;
if ($upload) {
// TODO: cURL handles file uploads for us, but the implementation
@@ -1643,7 +1650,14 @@ class DrupalWebTestCase extends DrupalTestCase {
$post[$key] = urlencode($key) . '=' . urlencode($value);
}
if ($ajax && isset($submit['triggering_element'])) {
- $post['ajax_triggering_element'] = 'ajax_triggering_element=' . urlencode($submit['triggering_element']);
+ if (is_array($submit['triggering_element'])) {
+ // Get the first key/value pair in the array.
+ $post['_triggering_element_value'] = '_triggering_element_value=' . urlencode(reset($submit['triggering_element']));
+ $post['_triggering_element_name'] = '_triggering_element_name=' . urlencode(key($submit['triggering_element']));
+ }
+ else {
+ $post['_triggering_element_name'] = '_triggering_element_name=' . urlencode($submit['triggering_element']);
+ }
}
$post = implode('&', $post);
}
@@ -1666,7 +1680,7 @@ class DrupalWebTestCase extends DrupalTestCase {
foreach ($edit as $name => $value) {
$this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value)));
}
- if (!$ajax) {
+ if (!$ajax && isset($submit)) {
$this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit)));
}
$this->fail(t('Found the requested form fields at @path', array('@path' => $path)));
@@ -1856,7 +1870,7 @@ class DrupalWebTestCase extends DrupalTestCase {
break;
case 'submit':
case 'image':
- if ($submit == $value) {
+ if (isset($submit) && $submit == $value) {
$post[$name] = $value;
$submit_matches = TRUE;
}
diff --git a/modules/simpletest/tests/ajax.test b/modules/simpletest/tests/ajax.test
index d959a4def..ca6f718dd 100644
--- a/modules/simpletest/tests/ajax.test
+++ b/modules/simpletest/tests/ajax.test
@@ -98,62 +98,62 @@ class AJAXCommandsTestCase extends AJAXTestCase {
$edit = array();
// Tests the 'after' command.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'after_command_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'After': Click to put something after the div"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'insert' && $command['method'] == 'after' && $command['data'] == 'This will be placed after', "'after' AJAX command issued with correct data");
// Tests the 'alert' command.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'alert_command_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Alert': Click to alert"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'alert' && $command['text'] == 'Alert', "'alert' AJAX Command issued with correct text");
// Tests the 'append' command.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'append_command_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Append': Click to append something"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'insert' && $command['method'] == 'append' && $command['data'] == 'Appended text', "'append' AJAX command issued with correct data");
// Tests the 'before' command.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'before_command_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'before': Click to put something before the div"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'insert' && $command['method'] == 'before' && $command['data'] == 'Before text', "'before' AJAX command issued with correct data");
// Tests the 'changed' command.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'changed_command_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed."))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'changed' && $command['selector'] == '#changed_div', "'changed' AJAX command issued with correct selector");
// Tests the 'changed' command using the second argument.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'changed_command_asterisk_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed with asterisk."))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'changed' && $command['selector'] == '#changed_div' && $command['asterisk'] == '#changed_div_mark_this', "'changed' AJAX command (with asterisk) issued with correct selector");
// Tests the 'css' command.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'css_command_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the the '#box' div to be blue."))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'css' && $command['selector'] == '#css_div' && $command['argument']['background-color'] == 'blue', "'css' AJAX command issued with correct selector");
// Tests the 'data' command.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'data_command_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX data command: Issue command."))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'data' && $command['name'] == 'testkey' && $command['value'] == 'testvalue', "'data' AJAX command issued with correct key and value");
// Tests the 'html' command.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'html_command_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX html: Replace the HTML in a selector."))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'insert' && $command['method'] == 'html' && $command['data'] == 'replacement text', "'html' AJAX command issued with correct data");
// Tests the 'prepend' command.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'prepend_command_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'prepend': Click to prepend something"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'insert' && $command['method'] == 'prepend' && $command['data'] == 'prepended text', "'prepend' AJAX command issued with correct data");
// Tests the 'remove' command.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'remove_command_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'remove': Click to remove text"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'remove' && $command['selector'] == '#remove_text', "'remove' AJAX command issued with correct command and selector");
// Tests the 'restripe' command.
- $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'restripe_command_example'));
+ $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'restripe' command"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'restripe' && $command['selector'] == '#restripe_table', "'restripe' AJAX command issued with correct selector");
}
@@ -203,3 +203,37 @@ class AJAXFormValuesTestCase extends AJAXTestCase {
}
}
}
+
+
+/**
+ * Miscellaneous AJAX tests using ajax_test module.
+ */
+class AJAXElementValidation extends AJAXTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Miscellaneous AJAX tests',
+ 'description' => 'Various tests of AJAX behavior',
+ 'group' => 'AJAX',
+ );
+ }
+
+ /**
+ * Try to post an AJAX change to a form that has a validated element.
+ *
+ * The drivertext field is AJAX-enabled. An additional field is not, but
+ * is set to be a required field. In this test the required field is not
+ * filled in, and we want to see if the activation of the "drivertext"
+ * AJAX-enabled field fails due to the required field being empty.
+ */
+ function testAJAXElementValidation() {
+ $web_user = $this->drupalCreateUser();
+ $edit = array('drivertext' => t('some dumb text'));
+
+ // Post with 'drivertext' as the triggering element.
+ $post_result = $this->drupalPostAJAX('ajax_validation_test', $edit, 'drivertext');
+ // Look for a validation failure in the resultant JSON.
+ $this->assertNoText(t('Error message'), t("No error message in resultant JSON"));
+ $this->assertText('ajax_forms_test_validation_form_callback invoked', t('The correct callback was invoked'));
+ }
+}
+
diff --git a/modules/simpletest/tests/ajax_forms_test.module b/modules/simpletest/tests/ajax_forms_test.module
index 040a1d9b8..5de2b842c 100644
--- a/modules/simpletest/tests/ajax_forms_test.module
+++ b/modules/simpletest/tests/ajax_forms_test.module
@@ -24,6 +24,12 @@ function ajax_forms_test_menu() {
'page arguments' => array('ajax_forms_test_ajax_commands_form'),
'access callback' => TRUE,
);
+ $items['ajax_validation_test'] = array(
+ 'title' => 'AJAX Validation Test',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ajax_forms_test_validation_form'),
+ 'access callback' => TRUE,
+ );
return $items;
}
@@ -340,3 +346,59 @@ function ajax_forms_test_advanced_commands_restripe_callback($form, $form_state)
$commands[] = ajax_command_restripe('#restripe_table');
return array('#type' => 'ajax', '#commands' => $commands);
}
+
+
+
+/**
+ * This form and its related submit and callback functions demonstrate
+ * not validating another form element when a single AJAX element is triggered.
+ *
+ * The "drivertext" element is an AJAX-enabled textfield, free-form.
+ * The "required_field" element is a textfield marked required.
+ *
+ * The correct behavior is that the AJAX-enabled drivertext element should
+ * be able to trigger without causing validation of the "required_field".
+ */
+function ajax_forms_test_validation_form($form, &$form_state) {
+
+ $form['drivertext'] = array(
+ '#title' => t('AJAX-enabled textfield.'),
+ '#description' => t("When this one AJAX-triggers and the spare required field is empty, you should not get an error."),
+ '#type' => 'textfield',
+ '#default_value' => !empty($form_state['values']['drivertext']) ? $form_state['values']['drivertext'] : "",
+ '#ajax' => array(
+ 'callback' => 'ajax_forms_test_validation_form_callback',
+ 'wrapper' => 'message_area',
+ 'method' => 'replace',
+ ),
+ '#suffix' => '<div id="message_area"></div>',
+ );
+
+ $form['spare_required_field'] = array(
+ '#title' => t("Spare Required Field"),
+ '#type' => 'textfield',
+ '#required' => TRUE,
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Submit'),
+ );
+
+ return $form;
+}
+/**
+ * Submit handler for the validation form.
+ */
+function ajax_forms_test_validation_form_submit($form, $form_state) {
+ drupal_set_message(t("Validation form submitted"));
+}
+
+/**
+ * AJAX callback for the 'drivertext' element of the validation form.
+ */
+function ajax_forms_test_validation_form_callback($form, $form_state) {
+ drupal_set_message("ajax_forms_test_validation_form_callback invoked");
+ drupal_set_message(t("Callback: drivertext=%drivertext, spare_required_field=%spare_required_field", array('%drivertext' => $form_state['values']['drivertext'], '%spare_required_field' => $form_state['values']['spare_required_field'])));
+ return '<div id="message_area">ajax_forms_test_validation_form_callback at ' . date('c') . '</div>';
+}
diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test
index 3c447d14d..4683fb8d4 100644
--- a/modules/simpletest/tests/form.test
+++ b/modules/simpletest/tests/form.test
@@ -850,6 +850,77 @@ class FormsProgrammaticTestCase extends DrupalWebTestCase {
}
}
+/**
+ * Test that FAPI correctly determines $form_state['clicked_button'].
+ */
+class FormsClickedButtonTestCase extends DrupalWebTestCase {
+
+ function getInfo() {
+ return array(
+ 'name' => 'Form clicked button determination',
+ 'description' => 'Test the determination of $form_state[\'clicked_button\'].',
+ 'group' => 'Form API',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('form_test');
+ }
+
+ /**
+ * Test the determination of $form_state['clicked_button'] when no button
+ * information is included in the POST data, as is sometimes the case when
+ * the ENTER key is pressed in a textfield in Internet Explorer.
+ */
+ function testNoButtonInfoInPost() {
+ $path = 'form-test/clicked-button';
+ $edit = array();
+ $form_id = 'form-test-clicked-button';
+
+ // Ensure submitting a form with no buttons results in no
+ // $form_state['clicked_button'] and the form submit handler not running.
+ drupal_static_reset('drupal_html_id');
+ $this->drupalPost($path, $edit, NULL, array(), array(), $form_id);
+ $this->assertText('There is no clicked button.', t('$form_state[\'clicked_button\'] set to NULL.'));
+ $this->assertNoText('Submit handler for form_test_clicked_button executed.', t('Form submit handler did not execute.'));
+
+ // Ensure submitting a form with one or more submit buttons results in
+ // $form_state['clicked_button'] being set to the first one the user has
+ // access to. An argument with 'r' in it indicates a restricted
+ // (#access=FALSE) button.
+ drupal_static_reset('drupal_html_id');
+ $this->drupalPost($path . '/s', $edit, NULL, array(), array(), $form_id);
+ $this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to only button.'));
+ $this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.'));
+ drupal_static_reset('drupal_html_id');
+ $this->drupalPost($path . '/s/s', $edit, NULL, array(), array(), $form_id);
+ $this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.'));
+ $this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.'));
+ drupal_static_reset('drupal_html_id');
+ $this->drupalPost($path . '/rs/s', $edit, NULL, array(), array(), $form_id);
+ $this->assertText('The clicked button is button2.', t('$form_state[\'clicked_button\'] set to first available button.'));
+ $this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.'));
+
+ // Ensure submitting a form with buttons of different types results in
+ // $form_state['clicked_button'] being set to the first button, regardless
+ // of type. For the FAPI 'button' type, this should result in the submit
+ // handler not executing. The types are 's'(ubmit), 'b'(utton), and
+ // 'i'(mage_button).
+ drupal_static_reset('drupal_html_id');
+ $this->drupalPost($path . '/s/b/i', $edit, NULL, array(), array(), $form_id);
+ $this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.'));
+ $this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.'));
+ drupal_static_reset('drupal_html_id');
+ $this->drupalPost($path . '/b/s/i', $edit, NULL, array(), array(), $form_id);
+ $this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.'));
+ $this->assertNoText('Submit handler for form_test_clicked_button executed.', t('Form submit handler did not execute.'));
+ drupal_static_reset('drupal_html_id');
+ $this->drupalPost($path . '/i/s/b', $edit, NULL, array(), array(), $form_id);
+ $this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.'));
+ $this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.'));
+ }
+}
+
/**
* Tests rebuilding of arbitrary forms by altering them.
diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module
index ad9be7032..b35e43251 100644
--- a/modules/simpletest/tests/form_test.module
+++ b/modules/simpletest/tests/form_test.module
@@ -118,6 +118,14 @@ function form_test_menu() {
'type' => MENU_CALLBACK,
);
+ $items['form-test/clicked-button'] = array(
+ 'title' => 'Clicked button test',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('form_test_clicked_button'),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+
return $items;
}
@@ -923,6 +931,84 @@ function form_test_programmatic_form_submit($form, &$form_state) {
$form_state['storage']['programmatic_form_submit'] = $form_state['values']['submitted_field'];
}
+/**
+ * Form builder to test button click detection.
+ */
+function form_test_clicked_button($form, &$form_state) {
+ // A single text field. In IE, when a form has only one non-button input field
+ // and the ENTER key is pressed while that field has focus, the form is
+ // submitted without any information identifying the button responsible for
+ // the submission. In other browsers, the form is submitted as though the
+ // first button were clicked.
+ $form['text'] = array(
+ '#title' => 'Text',
+ '#type' => 'textfield',
+ );
+
+ // Loop through each path argument, addding buttons based on the information
+ // in the argument. For example, if the path is
+ // form-test/clicked-button/s/i/rb, then 3 buttons are added: a 'submit', an
+ // 'image_button', and a 'button' with #access=FALSE. This enables form.test
+ // to test a variety of combinations.
+ $i=0;
+ $args = array_slice(arg(), 2);
+ foreach ($args as $arg) {
+ $name = 'button' . ++$i;
+ // 's', 'b', or 'i' in the argument define the button type wanted.
+ if (strpos($arg, 's') !== FALSE) {
+ $type = 'submit';
+ }
+ elseif (strpos($arg, 'b') !== FALSE) {
+ $type = 'button';
+ }
+ elseif (strpos($arg, 'i') !== FALSE) {
+ $type = 'image_button';
+ }
+ else {
+ $type = NULL;
+ }
+ if (isset($type)) {
+ $form[$name] = array(
+ '#type' => $type,
+ '#name' => $name,
+ );
+ // Image buttons need a #src; the others need a #value.
+ if ($type == 'image_button') {
+ $form[$name]['#src'] = 'misc/druplicon.png';
+ }
+ else {
+ $form[$name]['#value'] = $name;
+ }
+ // 'r' for restricted, so we can test that button click detection code
+ // correctly takes #access security into account.
+ if (strpos($arg, 'r') !== FALSE) {
+ $form[$name]['#access'] = FALSE;
+ }
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Form validation handler for the form_test_clicked_button() form.
+ */
+function form_test_clicked_button_validate($form, &$form_state) {
+ if (isset($form_state['clicked_button'])) {
+ drupal_set_message(t('The clicked button is %name.', array('%name' => $form_state['clicked_button']['#name'])));
+ }
+ else {
+ drupal_set_message('There is no clicked button.');
+ }
+}
+
+/**
+ * Form submit handler for the form_test_clicked_button() form.
+ */
+function form_test_clicked_button_submit($form, &$form_state) {
+ drupal_set_message('Submit handler for form_test_clicked_button executed.');
+}
+
/**
* Implements hook_form_FORM_ID_alter() for the registration form.
diff --git a/modules/system/system.module b/modules/system/system.module
index 039528738..a8e57d924 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -318,6 +318,7 @@ function system_element_info() {
'#name' => 'op',
'#button_type' => 'submit',
'#executes_submit_callback' => TRUE,
+ '#limit_validation_errors' => FALSE,
'#process' => array('ajax_process_form'),
'#theme_wrappers' => array('button'),
);
@@ -326,6 +327,7 @@ function system_element_info() {
'#name' => 'op',
'#button_type' => 'submit',
'#executes_submit_callback' => FALSE,
+ '#limit_validation_errors' => FALSE,
'#process' => array('ajax_process_form'),
'#theme_wrappers' => array('button'),
);
@@ -333,6 +335,7 @@ function system_element_info() {
'#input' => TRUE,
'#button_type' => 'submit',
'#executes_submit_callback' => TRUE,
+ '#limit_validation_errors' => FALSE,
'#process' => array('ajax_process_form'),
'#return_value' => TRUE,
'#has_garbage_value' => TRUE,