I'm using hook_field_attach_form() in http://drupal.org/project/metatags to reliably attach form elements into entity forms. I have to add a submit handler to 'cleanup' some $form_state['values'] compared to meta tag defaults to see which values actually need to be saved. Unfortunately this means that unless the form has specified a specific submit handler in its form definition, the default FORM_ID_validate() and FORM_ID_submit() handlers are not added to $form['#validate'] and $form['#submit'] respectively since those arrays exist.

The problem lies in drupal_prepare_form() which just fails if $form['#validate'] or $form['#submit'] are defined at all. It should *STILL* attempt to add its default validate and submit handlers by checking in_array().

This issue is a major core blocker for the Meta tags issue, which we had to workaround for now using the following:

/**
 * Implements hook_field_attach_form().
 */
function metatag_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
  $instance = "{$entity_type}:{$bundle}";

  $metatags = isset($entity->metatags) ? $entity->metatags : array();
  $options['token types'] = array(token_get_entity_mapping('entity', $entity_type));
  $options['context'] = $entity_type;

  // This function adds $form['#submit'][] = 'metatag_metatags_form_submit' but
  // because of this core bug cannot be added here anymore.
  //metatag_metatags_form($form, $instance, $metatags, $options);

  $form['#metatags'] = array(
    'instance' => $instance,
    'metatags' => $metatags,
    'options' => $options,
  );
}

/**
 * Implements hook_form_alter().
 */
function metatag_form_alter(&$form, $form_state, $form_id) {
  if (!empty($form['#metatags']) && !isset($form['metatags'])) {
    extract($form['#metatags']);
    metatag_metatags_form($form, $instance, $metatags, $options);
  }
}

Comments

chx’s picture

Issue tags: -Metatags

Aye and then you would override the defaults how?

chx’s picture

jherencia’s picture

damienmckenna’s picture

@chx: If the default function exists it should always be added if it isn't already, e.g. changing this:

  if (!isset($form['#validate'])) {
    // Ensure that modules can rely on #validate being set.
    $form['#validate'] = array();
    // Check for a handler specific to $form_id.
    if (function_exists($form_id . '_validate')) {
      $form['#validate'][] = $form_id . '_validate';
    }
    // Otherwise check whether this is a shared form and whether there is a
    // handler for the shared $form_id.
    elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_validate')) {
      $form['#validate'][] = $form_state['build_info']['base_form_id'] . '_validate';
    }
  }

to this:

if (!isset($form['#validate'])) {
  // Ensure that modules can rely on #validate being set.
  $form['#validate'] = array();
}
// Check for a handler specific to $form_id.
if (function_exists($form_id . '_validate') && !in_array($form_id . '_validate', $form['#validate'])) {
  $form['#validate'][] = $form_id . '_validate';
}
// Otherwise check whether this is a shared form and whether there is a
// handler for the shared $form_id.
elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_validate')) {
  $form['#validate'][] = $form_state['build_info']['base_form_id'] . '_validate';
}
damienmckenna’s picture

Status: Active » Needs review
StatusFileSize
new2.61 KB

Here's a patch following my idea from #7 above applied to both #validate and #submit.

dave reid’s picture

Correct #5 is exactly what I had in mind. Thanks for beating me to the patch DamienMcKenna. :)

@chx Could you clarify what you're asking?

xen’s picture

I think what chx is asking is how to replace a default submit handler, rather than add another one. The answer would be hook_form_alter , but then you're asking those that want to replace a submit handler to do the same workaround as you're currently doing to add one.

Really, it would be much handier if the #validate/#submit attributes is already set when hook_field_attach_form is invoked, but that moves the responsibility to the original form builder (which might come from who knows where) or field_attach_form (which is an odd place).

damienmckenna’s picture

@Xen/@chx: the patch above doesn't do anything strange and completely allows you to override the default validation/submit handlers, it only adds the default ones to the process list if the functions actually exist.

Lets turn this around: please explain why the patch does not provide what most people would expect to happen.

xen’s picture

@DamienMcKenna: It changes how you can override the default handler, in the slightly odd case of hook_field_attach_form() (and possibly others) where you have third party code that get the form handed over *before* drupal_prepare_form has processed it. Before the patch you simply set the attribute in the relevant function, after the patch you will have to add an form_alter to do it (which seems odd, as your module was already handed the complete form once).

Not saying it is the right behavior now, but it is a change of behavior, and I wasn't as surprised over this as Dave obviously was, as I thought it was a known thing. We have the same kind of issue with #element_validate, which is not available in form_alters, as it's added later in form_builder(). That's another case where you currently have to add in the default explicitly, if you want it, because it will only be added if not set explicitly.

What we have now is "defaults is applied if nothing else has set it", with the patch it's "the defaults are always set". Not saying it shouldn't be changed, but then #element_validate should be fixed to (which is not as trivial), or it will be confusing as hell that two handlers that's so alike, behave very differently.

dave reid’s picture

Tagging.

dave reid’s picture

Issue tags: +Metatags

.

catch’s picture

Issue tags: +Needs backport to D7
dave reid’s picture

@Xen: If the root-level form was treated exactly the same as its form elements, I would agree. But it's not.

xen’s picture

@Dave Reid: I don't think that's a good reason to diverge them further.

catch’s picture

Status: Needs review » Needs work

Needs a re-roll, and a non-suffixed patch.

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new2.63 KB

Here's a straight reroll.

Status: Needs review » Needs work

The last submitted patch, drupal-1284642-16.patch, failed testing.

grndlvl’s picture

Status: Needs work » Needs review
StatusFileSize
new2.55 KB

[ignore]

grndlvl’s picture

Status: Needs work » Needs review

Ignore me don't know what I was thinking.

grndlvl’s picture

Status: Needs review » Needs work

Back to needs work as #16 still failed.

berdir’s picture

Status: Needs review » Needs work

Had a quick look at the test failures. Looked at the block availability test and the problem is quite obvious.

Previously, you were able to override the default behavior by adding an explicit submit callback. node_form() for example does some crazy things by adding the generic submit callback on the submit button and then maybe add a node type specific one and always initialize #submit to an empty array to prevent the default behavior.

Because what happens now is that the same submit callback is re-added globally on the form, and because that one does also execute global submit callbacks, for some weird backwards compatible thingy, this means we're running into an endless loop.

This would be quite a change, not sure if that qualifies as bug fix and certainly not something that's backportable. The only backportable bugfix for this problem that I can think of is adding the same check at the beginning of field_attach_form() to make sure that submit/validate callbacks are initialized at that point. I've been running into the same problem with privatemsg and organic groups a while ago.

tim.plunkett’s picture

Status: Needs work » Postponed (maintainer needs more info)

Is this still relevant? Our entity forms have changed a lot.

damienmckenna’s picture

@tim.plunkett: Maybe it should be changed to be just a D7 issue then?

tim.plunkett’s picture

Yes, if its not relevant that's what would happen.

plach’s picture

Now form action handlers are consistently attached at button level, which is the recommended way also for attaching custom handlers. I don't think this is still an issue in D8 and would be ok to moving it to D7. OTOH if we change the logic in drupal_prepare_form(), we should do that consistently in both branches.

plach’s picture

Issue summary: View changes

Fixing PHP tags

jhedstrom’s picture

Version: 8.0.x-dev » 7.x-dev

Back to D7 as per #22, #23, and #25.

harry slaughter’s picture

Well, I just wasted half a day wondering why my form_alter submit/validate handlers weren't being called.

I can't imagine how much pain this has caused developers. Shouldn't this be a priority rather than a 4 year old bug?

harry slaughter’s picture

StatusFileSize
new840 bytes

This patch makes a small change to form_execute_handlers() to include both sets of submit/validate callbacks.

mrded’s picture

Status: Postponed (maintainer needs more info) » Needs review

Status: Needs review » Needs work

The last submitted patch, 28: drupal-1284642-28.patch, failed testing.

Status: Needs work » Closed (outdated)

Automatically closed because Drupal 7 security and bugfix support has ended as of 5 January 2025. If the issue verifiably applies to later versions, please reopen with details and update the version.