commit c8f6ebe8eaa2768b3d1294545300d652e47528d8
Author: Damien Tournoud <damien@commerceguys.com>
Date:   Fri Oct 21 18:01:57 2011 +0200

    Issue #842292: process panes in validate and not in submit so that the form failing validation doesn't prevent panes from being processed.

diff --git a/modules/checkout/includes/commerce_checkout.pages.inc b/modules/checkout/includes/commerce_checkout.pages.inc
index f61f4fd..d742984 100644
--- a/modules/checkout/includes/commerce_checkout.pages.inc
+++ b/modules/checkout/includes/commerce_checkout.pages.inc
@@ -127,7 +127,7 @@ function commerce_checkout_form($form, &$form_state, $order, $checkout_page) {
       '#type' => 'submit',
       '#value' => $checkout_page['submit_value'],
       '#attributes' => array('class' => array('checkout-continue')),
-      '#submit' => array('commerce_checkout_form_submit'),
+      '#validate' => array('commerce_checkout_form_validate'),
     );
 
     // Add the cancel or back button where appropriate. We define button level
@@ -165,6 +165,9 @@ function commerce_checkout_form($form, &$form_state, $order, $checkout_page) {
     }
   }
 
+  // Remove the validation handlers.
+  $form['#validate'] = array();
+
   return $form;
 }
 
@@ -176,7 +179,7 @@ function commerce_checkout_form($form, &$form_state, $order, $checkout_page) {
  * fails validation, we then ask for the form to be rebuilt. Once all the panes
  * are happy, we move on to the next page.
  */
-function commerce_checkout_form_submit($form, &$form_state) {
+function commerce_checkout_form_validate($form, &$form_state) {
   global $user;
 
   $checkout_page = $form_state['checkout_page'];
@@ -187,15 +190,36 @@ function commerce_checkout_form_submit($form, &$form_state) {
   // Catch and clear already pushed messages.
   $previous_messages = drupal_get_messages();
 
+  // Load the validation errors for the elements.
+  $errors = array();
+  foreach ((array) form_get_errors() as $element_path => $error) {
+    list($pane_id, ) = explode('][', $element_path, 2);
+    $errors[$pane_id][$element_path] = $error;
+  }
+
   // Loop through the enabled checkout panes for the current page.
   $form_validate = TRUE;
   foreach (commerce_checkout_panes(array('enabled' => TRUE, 'page' => $checkout_page['page_id'])) as $pane_id => $checkout_pane) {
     $validate = TRUE;
 
+    // If any element in the pane failed validation, we mark the pane as
+    // unvalidated and replay the validation messages on top of it.
+    if (!empty($errors[$pane_id])) {
+      $validate = FALSE;
+      foreach ($errors[$pane_id] as $element_path => $message) {
+        if ($message) {
+          drupal_set_message($message, 'error');
+        }
+      }
+      if (isset($previous_messages['error'])) {
+        $previous_messages['error'] = array_diff($previous_messages['error'], $errors[$pane_id]);
+      }
+    }
+
     // If the pane has defined a checkout form validate handler...
     if ($callback = commerce_checkout_pane_callback($checkout_pane, 'checkout_form_validate')) {
       // Give it a chance to process the submitted data.
-      $validate = $callback($form, $form_state, $checkout_pane, $order);
+      $validate &= $callback($form, $form_state, $checkout_pane, $order);
     }
 
     // Catch and clear panes' messages.
@@ -215,7 +239,7 @@ function commerce_checkout_form_submit($form, &$form_state) {
   }
 
   // Restore messages and form errors.
-  $_SESSION['messages'] = $previous_messages;
+  $_SESSION['messages'] = array_filter($previous_messages);
   $form_errors = &drupal_static('form_set_error', array());
   $form_state['storage']['errors'] = $form_errors;
   $form_errors = array();
