Problem/Motivation

tl;dr: Customer profile data is not available in custom order form validation callbacks.

To reproduce:

1. Minimal drupal install.
2. drush en commerce_order_ui commerce_product_ui commerce_cart
3. Add a product at /admin/commerce/products/add/product.
4. Create a custom module with the following code:

function MYMODULE_form_commerce_order_ui_order_form_alter(&$form, $form_state) {
  $form['actions']['submit']['#validate'][] = 'mytest_validate';
}

function mytest_validate($form, &$form_state) {
  $order_w  = entity_metadata_wrapper('commerce_order', $form_state['commerce_order']);
  $a = $order_w->commerce_customer_billing->commerce_customer_address->value();
}

4. Go to /admin/commerce/orders/add and add a line item, fill out the billing address.
5. Submit the order form. We get:

EntityMetadataWrapperException: Unknown data property commerce_customer_address. in EntityStructureWrapper->getPropertyInfo() (line 335 of /sites/all/modules/entity/includes/entity.wrapper.inc).

The problem is that in mytest_validate() the commerce_customer_billing reference on the order is empty. This is caused by the form_set_value($element, array(), $form_state); call in commerce_customer_profile_manager_validate(). The early return; after this line also prevents the profile_id to be properly set in $form_state['values']['commerce_customer_billing'].

Note that this only happens when before submission an AJAX request is triggered on the order form.

This issue does not appear if validate functions do not try to access customer profiles.

My use case is trying to validate the order after drupal validation has passed using a remote service.

Proposed resolution

Try and set the profile_id on $form_state['values']['commerce_customer_billing'] even if we don't want to validate.

Remaining tasks

User interface changes

API changes

Data model changes

Comments

czigor created an issue. See original summary.

czigor’s picture

Issue summary: View changes
czigor’s picture

I wonder what makes the commerce_customer_profile_manager widget so special that it needs to check #limit_validation_errors of the triggering element in its #element_validate callback. There's no example for this in any of the contrib modules I've used: they only set #limit_validation_errors but never check its value. Should not be this the job of _form_validate()?

czigor’s picture

Issue summary: View changes
mglaman’s picture

I hit something close to this hell last week. Can you debug commerce_order_commerce_customer_profile_presave() to see if the act of preserving profile data is causing issue? For me it kept kicking back profile IDs.

mglaman’s picture

I'm wrong and can't read. It has to do with

  // If the triggering element wants to limit validation errors and the form is
  // not going to be submitted...
  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']))) {
    // Ensure this element wasn't specifically marked for validation in the
    // #limit_validation_errors sections array.
    $section_match = FALSE;

    foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) {
      // Because #limit_validation_errors sections force validation for any
      // element that matches the section or is a child of it, we can consider
      // it a match if the section completely matches the beginning of this
      // element's #parents array even if #parents contains additional elements.
      if (array_intersect_assoc($section, $element['#parents']) === $section) {
        $section_match = TRUE;
      }
    }

    // Exit this validate function, because the form is going to be rebuilt and
    // the data submitted may very well be incomplete.
    if (!$section_match) {
      form_set_value($element, array(), $form_state);
      return;
    }
  }
mglaman’s picture

Issue tags: +commerce-sprint

Tagging for the D7 sprint