diff --git a/payment/uc_payment/uc_payment.module b/payment/uc_payment/uc_payment.module index b5d0363..8755070 100644 --- a/payment/uc_payment/uc_payment.module +++ b/payment/uc_payment/uc_payment.module @@ -310,7 +310,9 @@ function uc_payment_method_free_order($op, &$order) { * The formatted HTML of the order total preview if $return is set to TRUE. */ function uc_payment_get_totals($form, $form_state) { - return $form['panes']['payment']['line_items']; + $commands[] = ajax_command_replace('#line-items-div', trim(drupal_render($form['panes']['payment']['line_items']))); + + return array('#type' => 'ajax', '#commands' => $commands); } /** diff --git a/payment/uc_payment/uc_payment_order_pane.inc b/payment/uc_payment/uc_payment_order_pane.inc index 7681c11..bdfa60b 100644 --- a/payment/uc_payment/uc_payment_order_pane.inc +++ b/payment/uc_payment/uc_payment_order_pane.inc @@ -122,7 +122,7 @@ function uc_order_pane_payment($op, $order, &$form = NULL, &$form_state = NULL) * AJAX callback to render the payment method pane. */ function uc_payment_order_pane_ajax_callback($form, &$form_state) { - $commands[] = ajax_command_replace('#payment-details', drupal_render($form['payment']['payment_details'])); + $commands[] = ajax_command_replace('#payment-details', trim(drupal_render($form['payment']['payment_details']))); $commands[] = ajax_command_prepend('#payment-details', theme('status_messages')); return array('#type' => 'ajax', '#commands' => $commands); } diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module index b02f4e1..c8c4325 100644 --- a/shipping/uc_quote/uc_quote.module +++ b/shipping/uc_quote/uc_quote.module @@ -241,75 +241,6 @@ function uc_quote_form_alter(&$form, &$form_state, $form_id) { } /** - * Implements hook_form_FORM_ID_alter() for uc_cart_checkout_form(). - * - * Adds Ajax shipping quote functionality to the checkout form. - */ -function uc_quote_form_uc_cart_checkout_form_alter(&$form, &$form_state) { - if (isset($form['panes']['delivery']['address'])) { - $form['panes']['delivery']['address']['#process'] = array('uc_store_process_address_field', 'uc_quote_process_checkout_address'); - } - if (isset($form['panes']['delivery']['select_address'])) { - $form['panes']['delivery']['select_address']['#ajax']['callback'] = 'uc_quote_select_address'; - } - // @todo: Figure out what needs to be done when copy-address box is checked. - - if (isset($form['panes']['payment']['line_items'])) { - $form['panes']['quotes']['quotes']['quote_option']['#ajax'] = array( - 'callback' => 'uc_payment_get_totals', - 'wrapper' => 'line-items-div', - ); - } -} - -/** - * Ajax callback: Updates quotes when a saved address is selected. - */ -function uc_quote_select_address($form, $form_state) { - $return = uc_quote_checkout_returned_rates($form, $form_state); - $address = uc_checkout_pane_address_render($form, $form_state); - - $return['#commands'][] = ajax_command_replace('#' . $form_state['triggering_element']['#ajax']['wrapper'], drupal_render($address)); - - return $return; -} - -/** - * Ajax callback: Updates shipping quotes when the delivery country changes. - */ -function uc_quote_country_change_wrapper(&$form, &$form_state) { - $return = uc_quote_checkout_returned_rates($form, $form_state); - $return['#commands'][] = ajax_command_replace('#uc-store-address-delivery-zone-wrapper', drupal_render(uc_store_update_address_field_zones($form, $form_state))); - return $return; -} - -/** - * Process callback: Adds Ajax functionality to delivery address fields. - */ -function uc_quote_process_checkout_address($element, $form_state) { - $ajax = array( - 'callback' => 'uc_quote_checkout_returned_rates', - 'effect' => 'slide', - 'progress' => array( - 'type' => 'throbber', - 'message' => t('Receiving shipping quotes...'), - ), - ); - - if (isset($element['delivery_postal_code'])) { - $element['delivery_postal_code']['#ajax'] = $ajax; - } - // The following replaces "uc_store_process_address_field" from uc_store - // with a wrapper that will update the available quotes when the country - // is changed. - if (isset($element['delivery_country'])) { - $element['delivery_country']['#ajax']['callback'] = 'uc_quote_country_change_wrapper'; - } - - return $element; -} - -/** * Implements hook_uc_cart_pane(). */ function uc_quote_uc_cart_pane($items) { @@ -596,7 +527,6 @@ function uc_cart_pane_quotes($form, &$form_state, $items) { * Adds a line item to the order that records the chosen shipping quote. * * @see uc_quote_checkout_pane_quotes_submit() - * @see uc_quote_checkout_returned_rates() */ function uc_checkout_pane_quotes($op, &$order, $form = NULL, &$form_state = NULL) { global $user; @@ -617,7 +547,6 @@ function uc_checkout_pane_quotes($op, &$order, $form = NULL, &$form_state = NULL '#submit' => array('uc_quote_checkout_pane_quotes_submit'), '#weight' => 0, '#ajax' => array( - 'callback' => 'uc_quote_checkout_returned_rates', 'effect' => 'slide', 'progress' => array( 'type' => 'bar', @@ -636,6 +565,14 @@ function uc_checkout_pane_quotes($op, &$order, $form = NULL, &$form_state = NULL $contents['quotes'] += $order->quote_form; + $form_state['uc_ajax']['uc_quote']['panes][quotes][quote_button'] = array( + 'payment-pane' => 'uc_ajax_replace_checkout_pane', + 'quotes-pane' => 'uc_ajax_replace_checkout_pane' + ); + $form_state['uc_ajax']['uc_quote']['panes][quotes][quotes][quote_option'] = array( + 'payment-pane' => 'uc_ajax_replace_checkout_pane', + ); + return array('description' => $description, 'contents' => $contents); case 'prepare': @@ -736,7 +673,8 @@ function uc_order_pane_quotes($op, $order, &$form = NULL, &$form_state = NULL) { '#value' => t('Get shipping quotes'), '#submit' => array('uc_quote_order_pane_quotes_submit'), '#ajax' => array( - 'callback' => 'uc_quote_order_returned_rates', + 'callback' => 'uc_quote_replace_order_quotes', + 'wrapper' => 'quote', 'effect' => 'slide', 'progress' => array( 'type' => 'bar', @@ -778,6 +716,10 @@ function uc_order_pane_quotes($op, $order, &$form = NULL, &$form_state = NULL) { ); } + $form_state['uc_ajax']['uc_quote']['ship_to][delivery_country'] = array( + 'quote' => 'uc_quote_replace_order_quotes', + ); + return $form; case 'edit-theme': @@ -930,8 +872,8 @@ function uc_quote_build_quote_form($order, $show_errors = TRUE) { * Ajax callback: Shows estimated shipping quotes on the cart page. */ function uc_quote_cart_returned_rates($form, $form_state) { - $commands[] = ajax_command_replace('#quote', drupal_render($form['quote'])); - $commands[] = ajax_command_prepend('#quote', theme('status_messages')); + $commands[] = ajax_command_replace('#quote', trim(drupal_render($form['quote']))); + $commands[] = ajax_command_prepend('#quote', trim(theme('status_messages'))); return array('#type' => 'ajax', '#commands' => $commands); } @@ -958,29 +900,10 @@ function _uc_quote_extract_default_option($quote_form) { } /** - * AJAX callback for calculated shipping rates. + * Ajax callback to update the quotes on the order edit form. */ -function uc_quote_checkout_returned_rates($form, $form_state) { - $commands[] = ajax_command_replace('#quote', drupal_render($form['panes']['quotes']['quotes'])); - $commands[] = ajax_command_prepend('#quote', theme('status_messages')); - - // Show default shipping rate as a line item. - if (isset($form['panes']['payment']['line_items'])) { - $commands[] = ajax_command_replace('#line-items-div', drupal_render($form['panes']['payment']['line_items'])); - $commands[] = ajax_command_prepend('#line-items-div', theme('status_messages')); - } - - return array('#type' => 'ajax', '#commands' => $commands); -} - -/** - * AJAX callback for calculated shipping rates. - */ -function uc_quote_order_returned_rates($form, $form_state) { - $commands[] = ajax_command_replace('#quote', drupal_render($form['quotes']['quotes'])); - $commands[] = ajax_command_prepend('#quote', theme('status_messages')); - - return array('#type' => 'ajax', '#commands' => $commands); +function uc_quote_replace_order_quotes($form, $form_state) { + return $form['quotes']['quotes']; } /** diff --git a/uc_ajax_admin/uc_ajax_admin.info b/uc_ajax_admin/uc_ajax_admin.info new file mode 100644 index 0000000..edba546 --- /dev/null +++ b/uc_ajax_admin/uc_ajax_admin.info @@ -0,0 +1,5 @@ +name = Ubercart Ajax Administration +description = Administrative interface for ajax updates to Ubercart forms. +dependencies[] = uc_cart +package = Ubercart - extra +core = 7.x diff --git a/uc_ajax_admin/uc_ajax_admin.module b/uc_ajax_admin/uc_ajax_admin.module new file mode 100644 index 0000000..14de566 --- /dev/null +++ b/uc_ajax_admin/uc_ajax_admin.module @@ -0,0 +1,240 @@ +' . t('Use this page to configure Ajax behaviors on the form. The table below associates triggering form input elements with panes. The contents of each associated pane will be refreshed whenever the customer clicks on or changes the triggering form element. For example, you can cause the available payment methods to be refreshed when the customer changes their billing zone.') . '

'; + $output .= '

' . t("Note that the triggering elements you can choose are listed based on the form as it would be displayed to you right now. For example, if none of your shipping methods apply to the current cart contents, you won't see the shipping quote selection element. If you don't see the form element you wish to use as a trigger, try adding some products to the shopping cart or otherwise simulating the customer experience, and verify that those elements are present on the form itself.") . '

'; + return $output; + } +} + +/** + * Implements hook_menu(). + */ +function uc_ajax_admin_menu() { + $items = array(); + $items['admin/store/settings/checkout/ajax'] = array( + 'title' => 'Ajax', + 'description' => 'Administer ajax updates on checkout form.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('uc_ajax_admin_form', 'checkout'), + 'access arguments' => array('administer store'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 3, + ); + return $items; +} + +/** + * Administration form for uc_ajax. + * + * @param $target_form + * The form for which ajax behaviors are to be administered. Currently only + * 'checkout' is supported. + */ +function uc_ajax_admin_form($form, &$form_state, $target_form = 'checkout') { + module_load_include('inc', 'uc_store', 'includes/uc_ajax_attach'); + switch ($target_form) { + case 'checkout': + $triggers = _uc_ajax_admin_checkout_trigger_options(_uc_ajax_admin_build_checkout_form()); + $panes = _uc_checkout_pane_list(); + $wrappers = array(); + foreach ($panes as $id => $pane) { + $wrappers["$id-pane"] = _uc_checkout_pane_data($id, 'title'); + } + break; + + default: + drupal_not_found(); + } + $form['#uc_ajax_target'] = $target_form; + $form['#uc_ajax_config'] = variable_get('uc_ajax_' . $target_form, _uc_ajax_defaults($target_form)); + + $form['table'] = tapir_get_table('uc_ajax_admin_table', $triggers, $wrappers, $form['#uc_ajax_config']); + $form['actions'] = array( + '#type' => 'actions', + 'submit' => array( + '#type' => 'submit', + '#value' => t('Submit'), + ), + ); + return $form; +} + +/** + * Submit handler for the uc_ajax_admin form. + */ +function uc_ajax_admin_form_submit($form, &$form_state) { + $config = $form['#uc_ajax_config']; + foreach ($form_state['values']['table'] as $index => $entry) { + $key = $entry['key']; + if ($index === '_new') { + if (!empty($key) && !empty($entry['panes'])) { + $config[$key] = $entry['panes']; + } + } + elseif ($entry['remove'] || empty($entry['panes'])) { + unset($config[$key]); + } + else { + $config[$key] = $entry['panes']; + } + } + variable_set('uc_ajax_' . $form['#uc_ajax_target'], $config); + drupal_set_message(t('Your changes have been saved.')); +} + +/** + * TAPIr table callback for the uc_ajax administrative form. + * + * @param $trigger_options + * The select options for triggering elements. + * @param $wrapper_options + * The select options for wrappers. + * @param $config + * The existing configuration. + */ +function uc_ajax_admin_table($trigger_options, $wrapper_options, $config) { + $rows = array(); + foreach ($config as $key => $panes) { + list(, $pane) = explode('][', $key); + $rows[] = array( + 'key' => array( + '#type' => 'hidden', + '#value' => $key, + '#suffix' => empty($trigger_options[ucfirst($pane)][$key]) ? $key : ucfirst($pane) . ': ' . $trigger_options[ucfirst($pane)][$key], + ), + 'panes' => array( + '#type' => 'select', + '#options' => $wrapper_options, + '#default_value' => $panes, + '#multiple' => TRUE, + ), + 'remove' => array( + '#type' => 'checkbox', + '#default_value' => FALSE, + ), + ); + } + $rows['_new'] = array( + 'key' => array( + '#type' => 'select', + '#options' => array(0 => t('--Add a new element--')) + $trigger_options, + ), + 'panes' => array( + '#type' => 'select', + '#options' => $wrapper_options, + '#multiple' => TRUE, + ), + 'remove' => array( + '#type' => 'hidden', + '#value' => 0, + ), + ); + + $table = array( + '#type' => 'tapir_table', + '#tree' => TRUE, + '#columns' => array( + 'remove' => array( + 'cell' => t('Remove'), + 'weight' => 3, + ), + 'key' => array( + 'cell' => t('Triggering form element'), + 'weight' => 1, + ), + 'panes' => array( + 'cell' => t('Panes to update'), + 'weight' => 2, + ), + ), + ) + $rows; + + return $table; +} + +/** + * Recursively builds a list of all form elements which are suitable triggers + * for ajax updates. + * + * @param $element + * The element to check. + * @param $list + * The list being built. When complete will be an array of the form + * 'element_name' => 'Element title' + * where 'element_name' is the name of the element as would be specified for + * form_set_error(), and 'Element title' is a best guess at the human readable + * name of the element. + */ +function _uc_ajax_admin_list_triggers($element, &$list) { + if (!empty($element['#input']) && !in_array($element['#type'], array('hidden', 'uc_address'))) { + $key = implode('][', $element['#array_parents']); + switch ($element['#type']) { + case 'button': case 'submit': + $title = empty($element['#value']) ? $key : $element['#value']; + break; + default: + $title = empty($element['#title']) ? $key : $element['#title']; + } + $list[$key] = $title; + } + if (empty($element['#type']) || !in_array($element['#type'], array('radios', 'checkboxes'))) { + foreach (element_children($element) as $child) { + _uc_ajax_admin_list_triggers($element[$child], $list); + } + } +} + +/** + * Builds a hierarchical list of possible ajax triggers for the checkout form. + * + * @param $form + * The fully processed checkout form to search for triggers. + * + * @return + * An hierarchical array of select options, categorized by pane. + */ +function _uc_ajax_admin_checkout_trigger_options($form) { + $list = array(); + foreach (element_children($form['panes']) as $name) { + $group = ucfirst($name); + $list[$group] = array(); + _uc_ajax_admin_list_triggers($form['panes'][$name], $list[$group]); + if (empty($list[$group])) { + unset($list[$group]); + } + } + return $list; +} + +/** + * Builds the checkout form, using the cart order if it exists, or a default + * shippable order if not. + */ +function _uc_ajax_admin_build_checkout_form() { + module_load_include('inc', 'uc_cart', 'uc_cart.pages'); + $order = empty($_SESSION['cart_order']) ? FALSE : uc_order_load($_SESSION['cart_order']); + if (!$order) { + $order = new UcOrder(); + $order->products = array((object) array( + 'cart_item_id' => 0, + 'title' => 'fake', + 'nid' => 0, + 'qty' => 1, + 'price' => 1, + 'data' => array('shippable' => TRUE), + 'model' => 0, + 'weight' => 0 + )); + } + return drupal_get_form('uc_cart_checkout_form', $order); +} diff --git a/uc_cart/uc_cart.pages.inc b/uc_cart/uc_cart.pages.inc index 14c6b69..f3fa31a 100644 --- a/uc_cart/uc_cart.pages.inc +++ b/uc_cart/uc_cart.pages.inc @@ -158,8 +158,9 @@ function uc_cart_checkout() { * @param $order * The order that is being checked out. * + * @see uc_cart_checkout_form_process() * @see uc_cart_checkout_form_validate() - * @see uc_cart_checkout_form_review() + * @see uc_cart_checkout_form_submit() * @see uc_cart_checkout_review() * @see theme_uc_cart_checkout_form() * @ingroup forms @@ -279,6 +280,9 @@ function uc_cart_checkout_form($form, &$form_state, $order) { '#value' => t('Review order'), ); + form_load_include($form_state, 'inc', 'uc_store', 'includes/uc_ajax_attach'); + $form['#process'][] = 'uc_ajax_process_form'; + unset($_SESSION['uc_checkout'][$order->order_id]); return $form; diff --git a/uc_order/uc_order.admin.inc b/uc_order/uc_order.admin.inc index ec06c97..62df9d7 100644 --- a/uc_order/uc_order.admin.inc +++ b/uc_order/uc_order.admin.inc @@ -1058,6 +1058,9 @@ function uc_order_edit_form($form, &$form_state, $order) { field_attach_form('uc_order', $order, $form, $form_state); + form_load_include($form_state, 'inc', 'uc_store', 'includes/uc_ajax_attach'); + $form['#process'][] = 'uc_ajax_process_form'; + return $form; } diff --git a/uc_order/uc_order.order_pane.inc b/uc_order/uc_order.order_pane.inc index 4918061..6a6b8b4 100644 --- a/uc_order/uc_order.order_pane.inc +++ b/uc_order/uc_order.order_pane.inc @@ -591,13 +591,13 @@ function uc_order_edit_products_remove($form, &$form_state) { * AJAX callback to render the order product controls. */ function uc_order_pane_products_ajax_callback($form, &$form_state) { - $commands[] = ajax_command_replace('#product-controls', drupal_render($form['product_controls'])); - $commands[] = ajax_command_prepend('#product-controls', theme('status_messages')); + $commands[] = ajax_command_replace('#product-controls', trim(drupal_render($form['product_controls']))); + $commands[] = ajax_command_prepend('#product-controls', trim(theme('status_messages'))); if (isset($form_state['refresh_products']) && $form_state['refresh_products']) { - $commands[] = ajax_command_replace('#order-edit-products', drupal_render($form['products'])); - $commands[] = ajax_command_replace('#order-line-items', drupal_render($form['line_items'])); - $commands[] = ajax_command_prepend('#order-edit-products', theme('status_messages')); + $commands[] = ajax_command_replace('#order-edit-products', trim(drupal_render($form['products']))); + $commands[] = ajax_command_replace('#order-line-items', trim(drupal_render($form['line_items']))); + $commands[] = ajax_command_prepend('#order-edit-products', trim(theme('status_messages'))); } return array('#type' => 'ajax', '#commands' => $commands); @@ -824,7 +824,7 @@ function uc_order_pane_line_items_remove($form, &$form_state) { * AJAX callback to render the line items. */ function uc_order_pane_line_items_update($form, &$form_state) { - $commands[] = ajax_command_replace('#order-line-items', drupal_render($form['line_items'])); + $commands[] = ajax_command_replace('#order-line-items', trim(drupal_render($form['line_items']))); $commands[] = ajax_command_prepend('#order-line-items', theme('status_messages')); return array('#type' => 'ajax', '#commands' => $commands); diff --git a/uc_store/includes/uc_ajax_attach.inc b/uc_store/includes/uc_ajax_attach.inc new file mode 100644 index 0000000..973721a --- /dev/null +++ b/uc_store/includes/uc_ajax_attach.inc @@ -0,0 +1,231 @@ + 'uc_ajax_replace_checkout_pane', + * ); + * @endcode + * + * This will cause the contents of 'quotes-pane' to be replaced by the return + * value of uc_ajax_replace_checkout_pane(). Note that if more than one module + * assign a callback to the same wrapper key, the heavier module or pane will + * take precedence. + * + * Implementors need not provide a wrapper key for each callback, in which case + * the callback must return an array of ajax commands rather than a renderable + * form element. For example: + * + * @code + * $form_state['uc_ajax']['mymodule']['panes][quotes][quote_button'] = array('my_ajax_callback'); + * ... + * function my_ajax_callback($form, $form_state) { + * $commands[] = ajax_command_invoke('#my-input-element', 'val', 0); + * return array('#type' => 'ajax', '#commands' => $commands); + * } + * @endcode + * + * However, using a wrapper key where appropriate will reduce redundant + * replacements of the same element. + * + * NOTE: 'uc_ajax_replace_checkout_pane' is a convenience callback which will + * replace the contents of an entire checkout pane. It is generally preferable + * to use this when updating data on the checkout form, as this will + * further reduce the likelihood of redundant replacements. You should use + * your own callback only when behaviours other than replacement are + * desired, or when replacing data that lie outside a checkout pane. Note + * also that you may combine both formulations by mixing numeric and string keys. + * For example: + * + * @code + * $form_state['uc_ajax']['mymodule']['panes][quotes][quote_button'] = array( + * 'my_ajax_callback', + * 'quotes-pane' => 'uc_ajax_replace_checkout_pane', + * ); + * @endcode + */ + +/** + * Form process callback to allow multiple Ajax callbacks on form elements. + */ +function uc_ajax_process_form($form, &$form_state) { + // When processing the top level form, add any variable-defined pane wrappers. + if (isset($form['#form_id'])) { + switch ($form['#form_id']) { + case 'uc_cart_checkout_form': + $config = variable_get('uc_ajax_checkout', _uc_ajax_defaults('checkout')); + foreach ($config as $key => $panes) { + foreach (array_keys($panes) as $pane) { + $config[$key][$pane] = 'uc_ajax_replace_checkout_pane'; + } + } + $form_state['uc_ajax']['uc_ajax'] = $config; + break; + } + } + + if (!isset($form_state['uc_ajax'])) { + return $form; + } + + // We have to operate on the children rather than on the element itself, as + // #process functions are called *after* form_handle_input_elements(), + // which is where the triggering element is determined. If we haven't added + // an '#ajax' key by that time, Drupal won't be able to determine which + // callback to invoke. + foreach (element_children($form) as $child) { + $element =& $form[$child]; + + // Add this process function recursively to the children. + if (empty($element['#process']) && !empty($element['#type'])) { + // We want to be sure the default process functions for the element type are called. + $info = element_info($element['#type']); + if (!empty($info['#process'])) { + $element['#process'] = $info['#process']; + } + } + $element['#process'][] = 'uc_ajax_process_form'; + + // Multiplex any Ajax calls for this element. + $parents = $form['#array_parents']; + array_push($parents, $child); + $key = implode('][', $parents); + + $callbacks = array(); + foreach ($form_state['uc_ajax'] as $module => $fields) { + if (!empty($fields[$key])) { + if (is_array($fields[$key])) { + $callbacks = array_merge($callbacks, $fields[$key]); + } + else { + $callbacks[] = $fields[$key]; + } + } + } + + if (!empty($callbacks)) { + if (empty($element['#ajax'])) { + $element['#ajax'] = array(); + } + elseif (!empty($element['#ajax']['callback'])) { + if (!empty($element['#ajax']['wrapper'])) { + $callbacks[$element['#ajax']['wrapper']] = $element['#ajax']['callback']; + } + else { + array_unshift($callbacks, $element['#ajax']['callback']); + } + } + + $element['#ajax'] = array_merge($element['#ajax'], array( + 'callback' => 'uc_ajax_multiplex', + 'list' => $callbacks, + )); + } + } + + return $form; +} + +/** + * Ajax callback multiplexer. + * + * Processes a set of Ajax commands attached to the triggering element. + */ +function uc_ajax_multiplex($form, $form_state) { + $element = $form_state['triggering_element']; + if (!empty($element['#ajax']['list'])) { + $commands = array(); + foreach ($element['#ajax']['list'] as $wrapper => $callback) { + if (!empty($callback) && function_exists($callback) && $result = $callback($form, $form_state, $wrapper)) { + if (is_array($result) && !empty($result['#type']) && $result['#type'] == 'ajax') { + // If the callback returned an array of commands, simply add these to the list. + $commands = array_merge($commands, $result['#commands']); + } + elseif (is_string($wrapper)) { + // Otherwise, assume the callback returned a string or render-array, and insert it into the wrapper. + $html = is_string($result) ? $result : drupal_render($result); + $commands[] = ajax_command_replace('#' . $wrapper, trim($html)); + $commands[] = ajax_command_prepend('#' . $wrapper, theme('status_messages')); + } + } + } + } + if (!empty($commands)) { + return array('#type' => 'ajax', '#commands' => $commands); + } +} + +/** + * Ajax callback to replace a whole checkout pane. + * + * @param $form + * The checkout form. + * @param $form_state + * The current form state. + * @param $wrapper + * Special third parameter passed for uc_ajax callbacks containing the ajax + * wrapper for this callback. Here used to determine which pane to replace. + * + * @return + * The form element representing the pane, suitable for ajax rendering. If + * the pane does not exist, or if the wrapper does not refer to a checkout + * pane, returns nothing. + */ +function uc_ajax_replace_checkout_pane($form, $form_state, $wrapper = NULL) { + if (empty($wrapper) && !empty($form_state['triggering_element']['#ajax']['wrapper'])) { + // If $wrapper is absent, then we were not invoked by uc_ajax_multiplex, + // so try to use the wrapper of the triggering element's #ajax array. + $wrapper = $form_state['triggering_element']['#ajax']['wrapper']; + } + if (!empty($wrapper)) { + list($pane, $verify) = explode('-', $wrapper); + if ($verify === 'pane' && !empty($form['panes'][$pane])) { + return $form['panes'][$pane]; + } + } +} + +/** + * Retrieve the default ajax behaviors for a target form. + * + * @param $target_form + * The form whose default behaviors are to be retrieved. + * + * @return + * The array of default behaviors for the form. + */ +function _uc_ajax_defaults($target_form) { + switch ($target_form) { + case 'checkout': + $quotes_defaults = drupal_map_assoc(array('payment-pane', 'quotes-pane')); + return array( + 'panes][delivery][address][delivery_country' => $quotes_defaults, + 'panes][delivery][address][delivery_postal_code' => $quotes_defaults, + 'panes][delivery][select_address' => $quotes_defaults, + 'panes][billing][address][billing_country' => array('payment-pane' => 'payment-pane'), + ); + default: + return array(); + } +} diff --git a/uc_store/tests/uc_ajax.test b/uc_store/tests/uc_ajax.test new file mode 100644 index 0000000..7fad973 --- /dev/null +++ b/uc_store/tests/uc_ajax.test @@ -0,0 +1,103 @@ + 'Ajax functionality', + 'description' => 'Ajax update of checkout and order pages.', + 'group' => 'Ubercart', + ); + } + + /** + * Overrides DrupalWebTestCase::setUp(). + */ + public function setUp() { + module_load_include('inc', 'uc_store', 'includes/uc_ajax_attach'); + $modules = array('rules_admin', 'uc_payment', 'uc_payment_pack'); + $permissions = array('administer rules', 'bypass rules access'); + parent::setUp($modules, $permissions); + $this->drupalLogin($this->adminUser); + } + + /** + * Set a zone-based condition for a particular payment method. + * + * @param $method + * The method to set (e.g. 'check') + * @param $zone + * The zone id (numeric) to check for. + * @param $negate + * TRUE to negate the condition. + */ + function addPaymentZoneCondition($method, $zone, $negate = FALSE) { + $not = $negate ? 'NOT ' : ''; + $name = 'uc_payment_method_' . $method; + $label = ucfirst($method) . ' conditions'; + $condition = array( + 'LABEL' => $label, + 'PLUGIN' => 'and', + 'REQUIRES' => array('rules'), + 'USES VARIABLES' => array( + 'order' => array( + 'label' => 'Order', + 'type' => 'uc_order', + ), + ), + 'AND' => array( + array( + $not . 'data_is' => array( + 'data' => array('order:billing-address:zone'), + 'value' => $zone, + ), + ), + ), + ); + $newconfig = rules_import(array($name => $condition)); + $oldconfig = rules_config_load($name); + if ($oldconfig) { + $newconfig->id = $oldconfig->id; + unset($newconfig->is_new); + $newconfig->status = ENTITY_CUSTOM; + } + $newconfig->save(); + entity_flush_caches(); + //$this->drupalGet('admin/config/workflow/rules/components/manage/' . $newconfig->id); + } + + function testCheckoutAjax() { + // Enable two payment methods and set a condition on one. + variable_set('uc_payment_method_check_checkout', TRUE); + variable_set('uc_payment_method_other_checkout', TRUE); + $this->addPaymentZoneCondition('other', '26'); + + // Speciy that the billing zone should update the payment pane. + $config = variable_get('uc_ajax_checkout', _uc_ajax_defaults('checkout')); + $config['panes][billing][address][billing_zone'] = array('payment-pane' => 'payment-pane'); + variable_set('uc_ajax_checkout', $config); + + // Go to the checkout page, veriy that the conditional payment method is + // not available. + $product = $this->createProduct(array('shippable' => FALSE)); + $this->drupalPost('node/' . $product->nid, array(), t('Add to cart')); + $this->drupalPost('cart', array('items[0][qty]' => 1), t('Checkout')); + $this->assertNoText('Other'); + + // Change the billing zone and veriy that payment pane updates. + $edit = array(); + $edit['panes[billing][billing_zone]'] = '26'; + $result = $this->ucPostAjax(NULL, $edit, 'panes[billing][billing_zone]'); + $this->assertText("Other"); + $edit['panes[billing][billing_zone]'] = '1'; + $result = $this->ucPostAjax(NULL, $edit, 'panes[billing][billing_zone]'); + // Not in Kansas any more... + $this->assertNoText("Other"); + } +} diff --git a/uc_store/uc_store.info b/uc_store/uc_store.info index 508c2bd..3e90801 100644 --- a/uc_store/uc_store.info +++ b/uc_store/uc_store.info @@ -11,6 +11,7 @@ files[] = classes/encrypt.inc ; Test cases files[] = tests/uc_store.test files[] = tests/uc_address.test +files[] = tests/uc_ajax.test configure = admin/store/settings/store stylesheets[all][] = uc_store.css