Steps te reproduce:
Enable webform and uuid module.
Create a webform
Enable antibot and configure it run on all webforms (webform*)

Antibot is now enabled on the webform and uses the form id webform-client-form-(node_id), but the uuid module has changed this to webform-uuid-(node_uuid) making all submits fail.

I have tried to debug this but really can't figure out why the form_id in form_alter hooks differs from the actual rendered form id.

Comments

weseze created an issue. See original summary.

weseze’s picture

Issue summary: View changes
mstef’s picture

What versions of each module are you using?

weseze’s picture

latest official releases

mstef’s picture

Status: Active » Closed (works as designed)

Sounds more like a webform issue. If not, please reopen.

weseze’s picture

Status: Closed (works as designed) » Active

It is actually an antibot issue.

Problem is the form id's where antibot should be active are listed in an array in php code and passed on to javascript.

function antibot_form_pre_render($form) {
  // Attach the needed JavaScript to re-enable this form.
  $form['antibot'] = array(
    '#attached' => array(
      'js' => array(
        array(
          'type' => 'setting',
          'data' => array(
            'antibot' => array(
              'forms' => array(
                $form['#id'] => array(
                  'action' => $form['#action'],
                  'key' => !empty($form['#antibot_key']) ? $form['#antibot_key'] : NULL,
                ),
              ),
            ),
          ),
        ),
        drupal_get_path('module', 'antibot') . '/js/antibot.js',
      ),
    ),
  );

...

}

Other modules are allowed to make changes to form id's using all sorts of hooks en templates. So the id's from php code might not match those in JS settings.

 Drupal.antibot.unlockForms = function() {
    // Act only if we haven't yet verified this user as being human.
    if (!Drupal.settings.antibot.human) {
      // Iterate all antibot forms that we need to unlock.
      for (var id in Drupal.settings.antibot.forms) {
        // Switch the action to the original value.
        $('form#' + id).attr('action', Drupal.settings.antibot.forms[id].action);

        // Check if a key is required.
        if (Drupal.settings.antibot.forms[id].key) {
          // Inject the key value.
          $('form#' + id).find('input[name="antibot_key"]').val(Drupal.settings.antibot.forms[id].key);
        }
      }
      // Mark this user as being human.
      Drupal.settings.antibot.human = true;
    }
  };

It would be fixed by getting all forms with an antibot class on instead of relying on id's calculated in PHP.

mstef’s picture

Category: Bug report » Feature request

If the ID changes that far down the line, the hidden key will not work either. I've never heard of a form ID changing and cannot think of a reason why that would be.

weseze’s picture

Wat kind of patch would you accept?
1/ Change the logic in JS so it does not work directly on id's anymore
2/ Increase this modules weight so it runs after uuid/webform modules

mstef’s picture

Changing the weight to affect the order or hook invocation died out in D6. I would gladly accept the addition of https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Extension... to move antibot to the end of hook_form_alter(). I'm not entirely sure that will fix your issue though, since most of what antibot does is in a pre_render callback. But it's worth a try. The hook is available in D7 and D8.

mstef’s picture

weseze’s picture

Moving it to the end does not fix the problem... As you said most of the magic happens in the pre_render and the damage with changing form_id's happens later on in the uuid module.

I'm currently using these 2 alterations in a custom module to guarantee compatibility between antibot-webform-uuid:

/**
 * Implements hook_form_alter().
 */
function MY_MODULE_base_form_alter(&$form, &$form_state, $form_id) {
  // Hardcode webform id's to make antibot work.
  if (module_exists('uuid') && stristr($form_id, 'webform_client_form')) {
    $node = $form['#node'];
    if ($node->nid) {
      $form['#attributes']['id'] = 'webform-client-form-' . $node->nid;
    }
  }
}

/**
 * Implements hook_module_implements_alter().
 */
function MY_MODULE_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'form_alter') {
    $group = $implementations['MY_MODULE'];
    unset($implementations['MY_MODULE']);
    $implementations['MY_MODULE'] = $group;
  }
}

Could something like this be included in the antibot module?

I understand uuid module is ultimately the cause of the error, but I feel that any module that might change a form_id and happens to run after antibot (which is likely ;)) can cause the same error. So with that in mind the fix really should be in antibot itself?

mstef’s picture

I understand how you think it should be on antibot to catch these types of changes, but I don't know that form IDs are supposed to change at all - and if we were going to change antibot to handle it, we certainly cannot be checking for a specific contrib doing a specific thing; it would have to be a more generic approach, such as moving the logic to as close to the end as possible.

Perhaps adding the module implements alter hook to push it to the very end, then moving all of the form alter code in to the pre render? Not sure if that works at all, but try it out.

jurgenr’s picture

I agree that it's bad practice to change a form ID but as Drupal 7 allows changing it situations like this can happen with any module.
Would it be an option to switch from form ID's to an antibot specific data attribute?

mstef’s picture

I would recommend first trying what I suggested.

weseze’s picture

@mstef: moving all logic from the prerender to the form_alter does not work. Implementing a hook_module_implements_alter on top of it also does not work.

Using the suggestion from @JurgenR with a data attribute does work.

I've added this code to antibot.module:

    // Store the form id as data attribute to prevent antibot from not working
    // when this id is overriden somehow. The combo webform-uuid does this.
    $form['#attributes']['data-form-id'] = $form['#form_id'];

so the entire function now looks like this: (anitbot.module)

/**
 * Helper function to enable Antibot protection for a given form.
 *
 * @param array &$form
 *   The form to enable Antibot protection on.
 */
function antibot_protect_form(array &$form) {
  // Ensure the form has an ID set.
  if (!empty($form['#form_id'])) {
    // Store the form id as data attribute to prevent antibot from not working
    // when this id is overriden somehow. The combo webform-uuid does this.
    $form['#attributes']['data-form-id'] = $form['#form_id'];

    // Generate a key for this form.
    $key = md5($form['#form_id']);

    // Store the key in the form.
    $form['#antibot_key'] = $key;

    // Add a hidden value which will receive the key via JS.
    // The point of this is to add an additonal layer of protection for remotely
    // POSTed forms. Since the key will not be present in that scenario, the
    // remote post will fail.
    $form['antibot_key'] = array(
      '#type' => 'hidden',
      '#value' => '',
    );

    // Add validation for the key.
    $form['#validate'][] = 'antibot_form_validation';
  }

  // Add a pre-render to activate antibot.
  $form['#pre_render'][] = 'antibot_form_pre_render';
}

And in antibot.js I have change the below function to work with this data attribute instead of id

  /**
   * Revert the action on the protected forms to what it was originally
   * set to.
   */
  Drupal.antibot.unlockForms = function() {
    // Act only if we haven't yet verified this user as being human.
    if (!Drupal.settings.antibot.human) {
      // Iterate all antibot forms that we need to unlock.
      for (var id in Drupal.settings.antibot.forms) {
        // Switch the action to the original value. Get the form id from the
        // data attribute to ensure it is the correct one.
        // Drupal replaces - with _ so we need to do that here.
        var data_id = id.replace(/-/g, '_');
        $('form[data-form-id="' + data_id + '"]').attr('action', Drupal.settings.antibot.forms[id].action);

        // Check if a key is required.
        if (Drupal.settings.antibot.forms[id].key) {
          // Inject the key value.
          $('form[data-form-id="' + data_id + '"]').find('input[name="antibot_key"]').val(Drupal.settings.antibot.forms[id].key);
        }
      }
      // Mark this user as being human.
      Drupal.settings.antibot.human = true;
    }
  };
mstef’s picture

Can you point out where in webform/uuid the form ID is actually changed?

mstef’s picture

Status: Active » Postponed (maintainer needs more info)
avpaderno’s picture

Priority: Major » Normal
danrod’s picture

Won't fix, 7.x is not supported anymore.

danrod’s picture

Status: Postponed (maintainer needs more info) » Closed (won't fix)
danrod’s picture

Status: Closed (won't fix) » Postponed (maintainer needs more info)
danrod’s picture

Status: Postponed (maintainer needs more info) » Closed (outdated)

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.