I have a simple form that includes an "#action" element. It won't validate. It doesn't even hit the hook_validate function I've written for it. When I comment out the "#action" element in the form, the form validates when "submit" is clicked. Here's the code:

function lesspaper_batch_og() {

  drupal_set_message('In lesspaper_batch_og ');

  $form_id = 'lesspaper_batch_og';
  $form = array();

  $form['#multistep'] = TRUE;
  $form['#action'] = url('admin/settings/lesspaper/batch_og/confirm');
  $form['go'] = array(
    '#type' => 'checkbox',
    '#title' => t('Go')
  );

  $form['#validate'] = array('lesspaper_batch_og_validate' => array($form_id, &$form));          

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Continue'),
  );

  return $form;

}

function lesspaper_batch_og_validate($form_id, &$form) {
  drupal_set_message('Form validation');
  // Doesn't get here unless I remove the "#action" element from form
}

function lesspaper_batch_og_submit($form_id, &$form) {
  drupal_set_message(t('Submitted.'));
}

The "#multistep" form element doesn't seem to make any difference at all.

Is this a bug, or is it by design?

Comments

Reg’s picture

I just noticed the same thing with the submit hook. Comment out #action and it works fine. Oops, this for a client with the older 4.7, don't know if it's still an issue in 5.x.

SomebodySysop’s picture

Yes, it appears to still be an issue in 5.7 unless I'm missing something. I need the "#action" element in order to process my form correctly.

Reg’s picture

Category: bug » support
Priority: Normal » Minor
Status: Active » Closed (works as designed)

I don't think this is a bug. I think it was a lack of understanding of the form module. If you redirect then you the page that you have the function on does not get loaded. Since it is not loaded the function doesn't exists and hence doesn't get called. The solution is to put the redirect in the my_module_submit as such:

function my_module_submit () {
   my things to do...
   drupal_goto('page to redirect to');
}

This worked for me and I can't believe it's a bug since as far back 4.7 because it's too fundamental of a module for such a glaring problem to be ignored. So, I can only assume it was our lack of understanding. Because of this I am changing the settings for this post.

gregrenner’s picture

Greetings,
I too have used the drupal_goto() to submit the form values to a remote address. While its not ideal, in my humble opinion, it does work. However, I have problems with this method because it is only good when the receiving end of the submitted form is using the GET method. Unless I'm misunderstanding the intricacies of drupal_goto(), you can't POST $form_values using drupal_goto().

A real life example:
I have a credit card payment form on www.example.com. This form's #action is "https://www.payment_gateway.com" and the #method is POST. Obviously, I want to make sure the person's name was entered, run a regex on the CC number, make sure they chose an expiration date, etc. before sending it off to the payment gateway. But because of this blind spot in Form API (or my misunderstanding it's capabilities), the end user can just hit submit with invalid or non-existent information and then are presented with less that friendly error messages from the payment gateway. I need to be able to POST $form_values to a remote address specified in #action, but only AFTER hook_validate has done it's job and given the blessing to proceed.

I realize that I could use JavaScript to run validation, but this isn't as reliable or secure as server side validation, of course. I suppose I could remove #action in order to use hook_validate, then submit $form_values to the payment gateway in a different function altogether, but like Red said... 'it's too fundamental of a module for such a glaring problem to be ignored.'

Its quite possible that I'm missing something, but it seems like this is a pretty huge hole in the Form API. Rather than a workaround, shouldn't validation be called every time a submit button is clicked, regardless of #action? I haven't found much on drupal.org, api.drupal.org, groups, etc. about this topic. Is there a patch out there for this? Is there a really good reason validation is ignored when #action is present?

Thanks,
~Greg Renner

SomebodySysop’s picture

Yes, and that was my next question. If I use drupal_goto in my submit:

function my_module_submit ($form_id, $form_values) {
   my things to do...
   drupal_goto('page to redirect to');
}

How do I get the submitted $form_values over to 'page to redirect to'?

gregrenner’s picture

@SomebodySysop
Something like this:

function my_module_submit ($form_id, $form_values) {
   my things to do...
   drupal_goto("node/123?item1=$form_values['item1']&item2=$form_values['item2']");
}

But, again, this will only work if 'node/123' is looking for GET values, POST values can't be sent through drupal_goto().

~Greg Renner
www.gregrenner.com

gregrenner’s picture

Version: 5.7 » 5.9

So, now I'm confused. I'm seeing a few places in core modules where '#action' is used and validation still works. How is this accomplished? I need a Drupal Jedi to explain this to me, I'm not understanding what I'm doing thats causing validation to fail.

In 5.9:

  • comment.module - line 1570
  • block.module - line 229
  • poll.module - line 385
  • poll.module - line 450
  • etc...
bigjim’s picture

I'm having similar issues, I want to POST values to GetActive to add users to an account. Consequently drupal_goto is not an option.

purely by accident, I just noticed the validate functions works if one of the required fields is empty in my form.

sorry posted this in haste, I must have had something else going on, I couldn't reproduce this affect.

gregrenner’s picture

Version: 5.9 » 5.10

You can use drupal_execute() to POST your form values after validation runs. The process goes something like this:

  • Form is generated and displayed to user w/o #action.
  • User fills out form and hits submit.
  • Your form_validate function runs and if the user input validates okay, then continue.
  • Your form_submit function inserts the #action information into $form_values.
  • Then you use drupal_execute('your_form_id_here', $form_values) to submit a POST to the address in #action.

Feedback?

drupal_execute() http://api.drupal.org/api/function/drupal_execute/5

-Greg

SomebodySysop’s picture

Thanks for digging through to a resolution.

Could you provide an example of this? If you don't have one, then please take a look at the following example, which is my original example modified to follow your logic as I understand it:

function lesspaper_batch_og() {

  drupal_set_message('In lesspaper_batch_og ');

  $form_id = 'lesspaper_batch_og';
  $form = array();

  $form['go'] = array(
    '#type' => 'checkbox',
    '#title' => t('Go')
  );

  $form['#validate'] = array('lesspaper_batch_og_validate' => array($form_id, &$form));         

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Continue'),
  );

  return $form;

}

function lesspaper_batch_og_validate($form_id, &$form) {
  drupal_set_message('Form validation');
}

function lesspaper_batch_og_submit($form_id, &$form) {

  $form['#action'] = url('admin/settings/lesspaper/batch_og/confirm');

  drupal_execute('your_form_id_here', $form);
  
}

I guess the part I'm not understanding is 'your_form_id_here'. If I include the form id, why do I need the #action element which, theoretically, would go to the form whose url is submitted in this element?

In other words, if my hook_menu is:

    $items[] = array(
      'path' => 'admin/settings/lesspaper/batch_og/confirm',
      'title' => t('Batch Group Roles Processing'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('lesspaper_batch_og_confirm'),
      'access' => user_access('administer lesspaper'),
      'type' => MENU_CALLBACK
     );

Then all I really need is drupal_execute('lesspaper_batch_og_confirm', $form) and the #action element isn't needed since that's the form it goes to. Correct?

bigjim’s picture

does druapl_execute() work for remote addresses, I can't seem to get it to work

here's what I have for validate & submit

function getactive_enter_validate($form_id, $form_values){
  if (!valid_email_address($form_values['email'])) {
    form_set_error('email', t("The e-mail address you supplied is not valid."));
  }
  if (variable_get('getactive_domain','') == '' || variable_get('getactive_host','') == '') {
    form_set_error('return_url', t("The GetActive domain or host is not set.  Those can be sett on the GetActive Setting page,".l(t('here'),'admin/settings/getactive')));
  }

}

function getactive_enter_submit($form_id, $form_values){
$form_values['#action'] = variable_get('getactive_host','http://ga1.org') .'/offsite-join.tcl';
drupal_execute($form_id, $form_values);
greg.harvey’s picture

Version: 5.10 » 6.9

Be careful not to create a loop with the drupal_execute stuff. If you need to submit the same form again, you'll need a copy of it, or it will submit over itself forever (or until PHP times out, which could be a looooong time if you're using something like FlashVideo and have it set to 10 minutes...!)

greg.harvey’s picture

Actually, #9 will never work because drupal_execute() ignores '#action' as by design it only executes locally.

You have to use drupal_http_request(). I blogged it here:

http://drupaler.co.uk/blog/validating-submitting-forms-other-websites-dr...

wastrilith2k’s picture

Can you give an example of how to do this? I'm encountering this issue.

What I am trying to do is have an ubercart product be defined and then when the form is submitted, have it go to another product with the same form values so that any attributes from the first product that are also in the second are automatically populated. But, here is my code using drupal_execute():

// Function to handle a character being added
function pjc_character_form_submit($form_id, $form_values) {
  $form_values['#action'] = $_POST['pjc_product_types'];
  drupal_execute($form_id, $form_values);
//  return url($_POST['pjc_product_types']);
}

If I use the last commented out line, it doesn't loop, but I don't have the form values passed.

Thank you,

James