Certain form #validate or #element_validate handlers need to be able to alter the $form structure.

They currently cannot, since $form, resp. $elements, is not passed by reference.

One popular example: A form containing a #required element, and a CAPTCHA element. The user successfully champions the CAPTCHA, but fails to enter something into the #required element. In this case, the validation handler of the CAPTCHA needs to be able to alter $form or $element to hide the CAPTCHA.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

sun’s picture

Additionally, to continue that example:

In case the validation fails, a validation handler needs to be able to reset the element's #value to an empty string. In this example, if the submitted CAPTCHA value was incorrect, the user has to input a new CAPTCHA anyway, so the validation handler needs to alter the element in the form structure, not just $form_state['values'].

Clarification on the last bit: $form_state['values'] can be altered via http://api.drupal.org/api/function/form_set_value/7, but doing so has no effect on the actual form element that will be rendered.

Dries’s picture

I agree that this is much needed. It has been painful having to work around this. I think it could be useful to document this change though (if only to make sure we don't revert it) and/or to write a small test for it. Otherwise looks RTBC.

sun’s picture

Added tests. Looks ready to fly for me.

sun’s picture

We can go one step further and even test a validation handler that sets #access => FALSE on an element and the value should persist throughout subsequent validations + rebuilds.

This is currently not the case. Interestingly, this brings me back to #622922: User input keeping during multistep forms and seems to be a pretty simple test-case for it.

Dries’s picture

Status: Needs review » Reviewed & tested by the community
+++ modules/simpletest/tests/form.test	26 Nov 2009 11:39:27 -0000
@@ -153,6 +153,63 @@ class FormsTestCase extends DrupalWebTes
+      'description' => 'Tests form processing and alteration via form validation handlers.',

'alteration' is a fun word, a bit archaic even. It's correct though so no need to change this.

I think this is RTBC.

webchick’s picture

I agree this would be convenient. Only thing is I'm pretty sure this was done as a deliberate decision so that validation was just for validating, since we used to be able to do changes to forms in #validate back in 4.7/5-ish.

It'd be nice to have chx or Eaton chime in here.

sun’s picture

Really? I just checked in D5 as well as 4.7.6 and it wasn't possible in there.

If it was possible earlier, I guess that it was either mistakenly removed, or it was perhaps removed because people mistakenly thought they would be able to alter the submitted form values by altering $element['#value'], i.e. not realizing that any alterations to the form structure will of course only have an effect on the form in case of a validation error, in which case the form will be automatically rebuilt.

Are you sure you don't mean form_set_value(), which allows to alter the $form_state['values'], as mentioned in #1 already?

effulgentsia’s picture

Status: Reviewed & tested by the community » Needs review

If it was an intentional decision to not pass $form by reference, I suspect it was because if a validation function makes a change to $form and there are validation errors, then the changed $form isn't cached, so when the user re-submits it, the input would be processed against a stale cached form build. If this change goes in, we would need to change the caching logic in drupal_build_form(). I suspect the intended way to accomplish the use-case described in this issue is to use $form_state['rebuild']. However, there are currently some major problems with form rebuilding. One of them is being worked on in #622922: User input keeping during multistep forms. But another one is that drupal_build_form() only calls drupal_rebuild_form() if there were no validation errors, which seems to cancel out any possibility for a validation function to trigger a rebuild. However, image_style_form_add_validate() and book_admin_edit_validate() attempt (unsuccessfully because of previous sentence) to trigger a rebuild from a validation error. So, basically, WTFs all over the place.

Seems like we need help from chx, Eaton, or another FAPI guru to explain exactly what form rebuilding was intended for, and if we need to fix it to work from validation handlers as well as submit handlers, and if not, then how else can the CAPTCHA use-case in this issue be solved.

sun’s picture

Status: Needs review » Reviewed & tested by the community

Trying to trigger a rebuild via $form_state['rebuild'] is an entirely different issue. On that note, setting $form_state['rebuild'] AND setting a form validation error is senseless, because form errors overrule any rebuilding - the form is straight output with the same populated values again.

With regard to this - and that's what this patch is doing - form validation handlers need to be treated like #process handlers. They are the last instance in the form processing flow, and they need to be able to still alter the form elements.

However, as with everything else in form_builder() and drupal_process_form(), all of these handlers always run from scratch again, also for cached forms. Which means: A #process or #element_validate or #validate handler, which performs alterations to a form, has to execute its own conditional tasks each time.

Only the original $form, as returned by the form constructor function, plus any alterations by hook_form_alter() implementations, are cached.

effulgentsia’s picture

Works for me. Thanks for the explanation. I'll keep that in mind when working on the rebuild issues.

sun’s picture

Issue tags: +D7 Form API challenge

On a related note, comment_form() is entirely incompatible with form caching.

Why? As mentioned before: When caching is enabled, the constructed $form plus any alterations are cached and NOT rebuilt. When caching is enabled, comment_form() never has a "Submit" button.

Tagging.

chx’s picture

I am not changing the status but I am mailing Heine and want to talk to him before let this through.

chx’s picture

Status: Reviewed & tested by the community » Needs work

Suppose you change the form. if you do not change the form_state['values'] then you might get unvalidated form_state['values'] to the form submit.

However, this is not new. after_build already let's you do that. So I am blessing this if you add a warning to drupal_validate_form warning not to do that. (Optionally you can commit such a warning to the fapi docs in contrib for after_build too).

Edit: i did not need to send the mail to Heine because as I was trying to explain what caused me such unease I finally realized it and this was it.

sun’s picture

Status: Needs work » Needs review
FileSize
8.92 KB

@chx: Does the added PHPDoc meet your approval?

Status: Needs review » Needs work
Issue tags: -D7 Form API challenge

The last submitted patch failed testing.

Status: Needs work » Needs review
Issue tags: +D7 Form API challenge

sun requested that failed test be re-tested.

Dries’s picture

Found a small typo:

+++ includes/form.inc	27 Nov 2009 15:07:43 -0000
@@ -714,7 +714,13 @@ function drupal_prepare_form($form_id, &
+ *   an validation error. If a validation handler alters the form structure, it
+ *   is responsible for validating the values of changed form elements in

"an validation error" should be "a validation error". I can fix that when I commit the patch though.

sun’s picture

Fixed that typo.

chx’s picture

Status: Needs review » Reviewed & tested by the community

I believe we are good to go.

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to CVS HEAD. Thanks sun and chx.

Status: Fixed » Closed (fixed)
Issue tags: -D7 Form API challenge

Automatically closed -- issue fixed for 2 weeks with no activity.