'Shipping quote settings', 'description' => 'Configure the shipping quote settings.', 'page callback' => 'uc_quote_overview', 'access arguments' => array('configure quotes'), 'type' => MENU_NORMAL_ITEM, 'file' => 'uc_quote.admin.inc', ); $items['admin/store/settings/quotes/overview'] = array( 'title' => 'Overview', 'description' => 'View general shipping quote settings.', 'access arguments' => array('configure quotes'), 'weight' => -10, 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items['admin/store/settings/quotes/edit'] = array( 'title' => 'Quote settings', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_quote_admin_settings'), 'access arguments' => array('configure quotes'), 'weight' => -8, 'type' => MENU_LOCAL_TASK, 'file' => 'uc_quote.admin.inc', ); $items['admin/store/settings/quotes/methods'] = array( 'title' => 'Quote methods', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_quote_method_settings'), 'access arguments' => array('configure quotes'), 'weight' => -5, 'type' => MENU_LOCAL_TASK, 'file' => 'uc_quote.admin.inc', ); $items['admin/store/settings/quotes/methods/general'] = array( 'title' => 'General settings', 'weight' => -10, 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items['cart/checkout/shipping/quote'] = array( 'page callback' => 'uc_quote_request_quotes', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, 'file' => 'uc_quote.pages.inc', ); return $items; } /** * Implementation of hook_init(). */ function uc_quote_init() { drupal_add_css(drupal_get_path('module', 'uc_quote') .'/uc_quote.css', 'module'); global $conf; $conf['i18n_variables'][] = 'uc_quote_err_msg'; $conf['i18n_variables'][] = 'uc_quote_pane_description'; } /** * Implementation of hook_theme(). */ function uc_quote_theme() { return array( 'uc_quote_method_settings' => array( 'arguments' => array('form' => NULL), 'file' => 'uc_quote.admin.inc', ), 'uc_cart_pane_quotes' => array( 'arguments' => array('items' => NULL), ), ); } /** * Implementation of hook_nodeapi(). * * Load, save, and delete the shipping type and default shipping address of products. */ function uc_quote_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) { if (uc_product_is_product($node->type)) { switch ($op) { case 'insert': case 'update': if (isset($node->shipping_type)) { uc_quote_set_shipping_type('product', $node->nid, $node->shipping_type); } db_query("DELETE FROM {uc_quote_product_locations} WHERE nid = %d", $node->nid); if ($node->street1) { db_query("INSERT INTO {uc_quote_product_locations} (nid, first_name, last_name, company, street1, street2, city, zone, postal_code, country, phone) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', %d, '%s')", $node->nid, $node->first_name, $node->last_name, $node->company, $node->street1, $node->street2, $node->city, $node->zone, $node->postal_code, $node->country, $node->phone ); } break; case 'load': $shipping_type = uc_product_get_shipping_type($node); $address = db_fetch_object(db_query("SELECT first_name, last_name, company, street1, street2, city, zone, postal_code, country, phone FROM {uc_quote_product_locations} WHERE nid = %d", $node->nid)); if ($address === FALSE) { $address = variable_get('uc_quote_store_default_address', new stdClass()); } return array('shipping_type' => $shipping_type, 'shipping_address' => $address); break; case 'delete': db_query("DELETE FROM {uc_quote_shipping_types} WHERE id_type = 'product' AND id = %d", $node->nid); db_query("DELETE FROM {uc_quote_product_locations} WHERE nid = %d", $node->nid); break; } } } /** * Implementation of hook_form_alter(). * * Add a default shipping address for products and manufacturers. If it is left * blank, products default to their manufacturers', which default to the store's. */ function uc_quote_form_alter(&$form, $form_state, $form_id) { // Alter the product node form. if (uc_product_is_product_form($form)) { // Get the shipping address. $address = $form['#node']->shipping_address; // Use the store default if the product does not have an address set. if (empty($address)) { $address = variable_get('uc_quote_store_default_address', new stdClass()); } // Store the country to use for the zone select based on $_POST. // TODO: Fix this for D6! Neither the $_POST or $form_state are available // when the node form is being processed. : ( if (isset($_POST['country'])) { $country = $_POST['country']; } else { $country = $address->country; } // Initialize the shipping fieldset array. if (!isset($form['shipping'])) { $form['shipping'] = array(); } $form['shipping'] += array( '#type' => 'fieldset', '#title' => t('Shipping settings'), '#collapsible' => TRUE, '#weight' => module_exists('content') ? content_extra_field_weight($form['#node']->type, 'shipping') : 0, '#attributes' => array('class' => 'product-shipping'), ); // Build the options for the default shipping type. $options = array('' => t('- Store default -')) + uc_quote_shipping_type_options(); $form['shipping']['shipping_type'] = array( '#type' => 'select', '#title' => t('Default product shipping type'), '#default_value' => uc_quote_get_shipping_type('product', $form['#node']->nid), '#options' => $options, '#weight' => -7, ); // Add the default pickup address fieldset. $form['shipping']['default_address'] = array( '#type' => 'fieldset', '#title' => t('Default product pickup address'), '#description' => t('When delivering products to customers, the original location of the product must be known in order to accurately quote the shipping cost and set up a delivery. If this pickup address is left blank, this product will default to the store pickup address.', array('!url' => url('admin/store/settings/quotes'))), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => -6, ); $form['shipping']['default_address']['first_name'] = uc_textfield(uc_get_field_name('first_name'), $address->first_name, FALSE); $form['shipping']['default_address']['last_name'] = uc_textfield(uc_get_field_name('last_name'), $address->last_name, FALSE); $form['shipping']['default_address']['company'] = uc_textfield(uc_get_field_name('company'), $address->company, FALSE); $form['shipping']['default_address']['street1'] = uc_textfield(uc_get_field_name('street1'), $address->street1, FALSE, NULL, 64); $form['shipping']['default_address']['street2'] = uc_textfield(uc_get_field_name('street2'), $address->street2, FALSE, NULL, 64); $form['shipping']['default_address']['city'] = uc_textfield(uc_get_field_name('city'), $address->city, FALSE); $form['shipping']['default_address']['zone'] = uc_zone_select(uc_get_field_name('zone'), $address->zone, NULL, $country); $form['shipping']['default_address']['postal_code'] = uc_textfield(uc_get_field_name('postal_code'), $address->postal_code, FALSE, NULL, 10, 10); $form['shipping']['default_address']['country'] = uc_country_select(uc_get_field_name('country'), $address->country); } // Add quote selection form handlers to the checkout form. if ($form_id == 'uc_cart_checkout_form' && isset($form['panes']['quotes'])) { $form['#validate'][] = 'uc_quote_save_choice'; $form['#pre_render'][] = 'uc_quote_cache_quotes'; } } /****************************************************************************** * CCK Hooks * ******************************************************************************/ /** * Implementation of hook_content_extra_fields(). */ function uc_quote_content_extra_fields($type_name) { $type = node_get_types('type', $type_name); $extra = array(); if ($type->module == 'uc_product') { $extra['shipping'] = array( 'label' => t('Shipping'), 'description' => t('Shipping settings form.'), 'weight' => 0, ); } return $extra; } /****************************************************************************** * Conditional Actions Hooks * ******************************************************************************/ /** * Implementation of hook_ca_trigger(). * * Register an event for each shipping method. Enabled methods have active * configurations. */ function uc_quote_ca_trigger() { $methods = module_invoke_all('shipping_method'); $triggers = array(); foreach ($methods as $id => $method) { $triggers['get_quote_from_'. $id] = array( '#title' => t('Getting shipping quote via !method', array('!method' => $method['title'])), '#category' => t('Quote'), '#hidden' => TRUE, '#arguments' => array( 'order' => array('#entity' => 'uc_order', '#title' => t('Order')), 'method' => array('#entity' => 'quote_method', '#title' => t('Quote method')), 'account' => array('#entity' => 'user', '#title' => t('User account')), ), ); } return $triggers; } /** * Implementation of hook_ca_condition(). */ function uc_quote_ca_condition() { return array( 'uc_quote_condition_product_shipping_type' => array( '#title' => t("Order has a product of a particular shipping type"), '#category' => t('Order: Product'), '#callback' => 'uc_quote_condition_product_shipping_type', '#arguments' => array( 'order' => array('#entity' => 'uc_order', '#title' => t('Order')), ), ), 'uc_quote_condition_order_shipping_method' => array( '#title' => t("Order has a shipping quote from a particular method"), '#category' => t('Order: Shipping Quote'), '#callback' => 'uc_quote_condition_order_shipping_method', '#arguments' => array( 'order' => array('#entity' => 'uc_order', '#title' => t('Order')), ), ), ); } /** * Return TRUE if the order has a product of the chosen shipping type. * * @see uc_quote_condition_product_shipping_type_form() */ function uc_quote_condition_product_shipping_type($order, $settings) { $result = FALSE; foreach ($order->products as $product) { if ($product->nid && uc_product_get_shipping_type($product) == $settings['type']) { $result = TRUE; break; } } return $result; } /** * Settings form for uc_quote_condition_product_shipping_type(). * * @ingroup forms * @see uc_quote_condition_product_shipping_type() */ function uc_quote_condition_product_shipping_type_form($form_state, $settings = array()) { $form = array(); $options = array(); $types = uc_quote_get_shipping_types(); foreach ($types as $id => $type) { $options[$id] = $type['title']; } $form['type'] = array('#type' => 'select', '#title' => t('Shipping type'), '#options' => $options, '#default_value' => $settings['type'], ); return $form; } /** * Check an order's shipping method. * * @see uc_quote_condition_order_shipping_method_form() */ function uc_quote_condition_order_shipping_method($order, $settings) { // Check the easy way first. if (is_array($order->quote)) { return $order->quote['method'] == $settings['method']; } // Otherwise, look harder. if (is_array($order->line_items)) { $methods = module_invoke_all('shipping_method'); $accessorials = $methods[$settings['method']]['quote']['accessorials']; foreach ($order->line_items as $line_item) { if ($line_item['type'] == 'shipping' && in_array($line_item['title'], $accessorials)) { return TRUE; } } } return FALSE; } /** * @ingroup forms * @see uc_quote_condition_order_shipping_method() */ function uc_quote_condition_order_shipping_method_form($form_state, $settings = array()) { $form = array(); $methods = module_invoke_all('shipping_method'); $enabled = variable_get('uc_quote_enabled', array()); $options = array(); foreach ($methods as $id => $method) { $options[$id] = $method['title']; if (!$enabled[$id]) { $options[$id] .= ' '. t('(disabled)'); } } $form['method'] = array( '#type' => 'select', '#title' => t('Shipping quote method'), '#default_value' => $settings['method'], '#options' => $options, ); return $form; } /** * Implementation of hook_ca_action(). */ function uc_quote_ca_action() { return array( 'uc_quote_action_get_quote' => array( '#title' => t('Fetch a shipping quote'), '#category' => t('Quote'), '#arguments' => array( 'order' => array('#entity' => 'uc_order', '#title' => t('Order')), 'method' => array('#entity' => 'quote_method', '#title' => t('Quote method')), ), ), ); } /** * Retrieve shipping quote. * * @param $order * The order the quote is for. * @param $method * The shipping method to generate the quote. * @return * Array of shipping quotes. */ function uc_quote_action_get_quote($order, $method) { $details = array(); foreach ($order as $key => $value) { if (substr($key, 0, 9) == 'delivery_') { $field = substr($key, 9); $details[$field] = $value; } } ob_start(); // Load include file containing quote callback, if there is one if (isset($method['quote']['file'])) { $inc_file = drupal_get_path('module', $method['module']) .'/'. $method['quote']['file']; if (is_file($inc_file)) { require_once($inc_file); } } if (function_exists($method['quote']['callback'])) { // This feels wrong, but it's the only way I can ensure that shipping // methods won't mess up the products in their methods. $products = array(); foreach ($order->products as $key => $item) { if (uc_cart_product_is_shippable($item)) { $products[$key] = drupal_clone($item); } } $quote_data = call_user_func($method['quote']['callback'], $products, $details, $method); } $messages = ob_get_contents(); ob_end_clean(); //drupal_set_message('
'. print_r($quote_data, TRUE) .''); if ($messages && variable_get('uc_quote_log_errors', FALSE)) { watchdog('quote', '!messages', array('!messages' => $messages), WATCHDOG_WARNING); watchdog('quote', '
@data', array('@data' => print_r($quote_data, TRUE)), WATCHDOG_WARNING); } return $quote_data; } /****************************************************************************** * Ubercart Hooks * ******************************************************************************/ /** * Implementation of hook_cart_pane(). */ function uc_quote_cart_pane($items) { if (arg(0) == 'cart') { if (!variable_get('uc_cap_quotes_enabled', FALSE) || (variable_get('uc_cart_delivery_not_shippable', TRUE) && !uc_cart_is_shippable())) { return array(); } } $panes[] = array('id' => 'quotes', 'title' => t('Shipping quotes'), 'enabled' => FALSE, 'weight' => 5, 'body' => drupal_get_form('uc_cart_pane_quotes', $items), ); return $panes; } /** * Define the shipping quote checkout pane. */ function uc_quote_checkout_pane() { $panes[] = array('id' => 'quotes', 'callback' => 'uc_checkout_pane_quotes', 'title' => t('Calculate shipping cost'), 'desc' => t('Extra information necessary to ship.'), 'weight' => 5, 'shippable' => TRUE, ); return $panes; } /** * Defines the shipping quote order pane. */ function uc_quote_order_pane() { $panes = array(); $panes[] = array( 'id' => 'quotes', 'callback' => 'uc_order_pane_quotes', 'title' => t('Shipping quote'), 'desc' => t('Get a shipping quote for the order from a quoting module.'), 'class' => 'abs-left', 'weight' => 7, 'show' => array('edit'), ); return $panes; } /** * Implementation of hook_add_to_cart(). */ function uc_quote_add_to_cart() { unset($_SESSION['quote']); } /** * Implementation of hook_update_cart_item(). */ function uc_quote_update_cart_item() { unset($_SESSION['quote']); } /** * Implementation of hook_order(). */ function uc_quote_order($op, &$arg1, $arg2) { switch ($op) { case 'submit': unset($_SESSION['quote']); break; case 'save': db_query("DELETE FROM {uc_order_quotes} WHERE order_id = %d", $arg1->order_id); db_query("INSERT INTO {uc_order_quotes} (order_id, method, accessorials, rate, quote_form) VALUES (%d, '%s', '%s', %f, '%s')", $arg1->order_id, $arg1->quote['method'], $arg1->quote['accessorials'], $arg1->quote['rate'], $arg1->quote['quote_form']); break; case 'load': $quote = db_fetch_array(db_query("SELECT method, accessorials, rate, quote_form FROM {uc_order_quotes} WHERE order_id = %d", $arg1->order_id)); $arg1->quote = $quote; $arg1->quote['accessorials'] = strval($quote['accessorials']); break; case 'delete': db_query("DELETE FROM {uc_order_quotes} WHERE order_id = %d", $arg1->order_id); break; } } /** * Implementation of hook_line_item(). */ function uc_quote_line_item() { $items[] = array( 'id' => 'shipping', 'title' => t('Shipping'), 'weight' => 1, 'default' => FALSE, 'stored' => TRUE, 'calculated' => TRUE, 'display_only' => FALSE, 'add_list' => TRUE, ); return $items; } /** * Implementation of hook_shipping_type(). */ function uc_quote_shipping_type() { $weight = variable_get('uc_quote_type_weight', array('small_package' => 0)); $types = array(); $types['small_package'] = array( 'id' => 'small_package', 'title' => t('Small package'), 'weight' => $weight['small_package'], ); return $types; } /****************************************************************************** * Module Functions * ******************************************************************************/ /** * Store the shipping type of products and manufacturers. * * Fulfillment modules are invoked for products that match their shipping type. * This function stores the shipping type of a product or a manufacturer. * * @param $id_type * product | manufacturer * @param $id * Either the node id or term id of the object receiving the shipping type. * @param $shipping_type * The type of product that is fulfilled by various fulfillment modules. */ function uc_quote_set_shipping_type($id_type, $id, $shipping_type) { db_query("DELETE FROM {uc_quote_shipping_types} WHERE id_type = '%s' AND id = %d", $id_type, $id); if ($shipping_type !== '') { db_query("INSERT INTO {uc_quote_shipping_types} (id_type, id, shipping_type) VALUES ('%s', %d, '%s')", $id_type, $id, $shipping_type); } } /** * Retrieve a product's or manufacturer's shipping type from the database. * * @param $id_type * product | manufacturer * @param $id * Either the node id or term id of the object that was assigned the shipping type. * @return The shipping type. */ function uc_quote_get_shipping_type($id_type, $id) { static $types = array(); if (!isset($types[$id_type][$id])) { $types[$id_type][$id] = db_result(db_query("SELECT shipping_type FROM {uc_quote_shipping_types} WHERE id_type = '%s' AND id = %d", $id_type, $id)); } return $types[$id_type][$id]; } /** * Get a product's shipping type, defaulting to the store's if it doesn't exist. * * @param $product * The product object. * @return The shipping type. */ function uc_product_get_shipping_type($product) { $shipping_type = variable_get('uc_store_shipping_type', 'small_package'); if ($type = uc_quote_get_shipping_type('product', $product->nid)) { $shipping_type = $type; } return $shipping_type; } /** * Get a product's default shipping address. * * Load the default shipping address of a product, it's manufacturer's, or the * store's, whichever is available. * * @param $nid * A product node id. * @return * An address object. */ function uc_quote_get_default_shipping_address($nid) { $address = db_fetch_object(db_query("SELECT first_name, last_name, company, street1, street2, city, zone, postal_code, country, phone FROM {uc_quote_product_locations} WHERE nid = %d", $nid)); if (empty($address)) { $address = variable_get('uc_quote_store_default_address', new stdClass()); } return $address; } /** * Cart pane callback. * * @ingroup forms * @see theme_uc_cart_pane_quotes() */ function uc_cart_pane_quotes($items) { global $user; // Get all quote types neccessary to fulfill order. $shipping_types = array(); foreach ($items as $product) { $shipping_types[] = uc_product_get_shipping_type($product); } $shipping_types = array_unique($shipping_types); $all_types = uc_quote_get_shipping_types(); $shipping_type = ''; $type_weight = 1000; // arbitrary large number foreach ($shipping_types as $type) { if ($all_types[$type]['weight'] < $type_weight) { $shipping_type = $all_types[$type]['id']; $type_weight = $all_types[$type]['weight']; } } $methods = array_filter(module_invoke_all('shipping_method'), '_uc_quote_method_enabled'); uasort($methods, '_uc_quote_type_sort'); $method_choices = array(); foreach ($methods as $method) { if ($method['quote']['type'] == 'order' || $method['quote']['type'] == $shipping_type) { $method_choices[$method['id']] = $method['title']; } } $form['delivery_country'] = uc_country_select(uc_get_field_name('country'), uc_store_default_country(), NULL, 'name', TRUE); $country_id = isset($_POST['delivery_country']) ? intval($_POST['delivery_country']) : uc_store_default_country(); $form['delivery_zone'] = uc_zone_select(uc_get_field_name('zone'), NULL, NULL, $country_id, 'name', TRUE); $form['delivery_postal_code'] = uc_textfield(uc_get_field_name('postal_code'), '', TRUE, NULL, 10, 10); $form['quote_method'] = array('#type' => 'hidden', '#value' => key($method_choices), ); $form['get_quote'] = array('#type' => 'button', '#value' => t('Calculate'), ); $form['page'] = array('#type' => 'hidden', '#value' => 'cart', ); $form['uid'] = array('#type' => 'hidden', '#value' => $user->uid, ); $form['quote'] = array('#type' => 'markup', '#value' => '', ); drupal_add_js(array( 'uc_quote' => array( 'progress_msg' => t('Receiving quotes:'), 'err_msg' => check_markup(variable_get('uc_quote_err_msg', t("There were problems getting a shipping quote. Please verify the delivery and product information and try again.\nIf this does not resolve the issue, please call in to complete your order.")), variable_get('uc_quote_msg_format', FILTER_FORMAT_DEFAULT), FALSE), ), 'ucURL' => array( 'shippingQuotes' => url('cart/checkout/shipping/quote'), ), ), 'setting'); drupal_add_js('misc/progress.js'); drupal_add_js(drupal_get_path('module', 'uc_quote') .'/uc_quote.js'); $prod_string = ''; foreach ($items as $item) { $prod_string .= '|'. $item->nid; $prod_string .= '^'. $item->title; $prod_string .= '^'. $item->model; $prod_string .= '^'. $item->manufacturer; $prod_string .= '^'. $item->qty; $prod_string .= '^'. $item->cost; $prod_string .= '^'. $item->price; $prod_string .= '^'. $item->weight; $prod_string .= '^'. serialize($item->data); } $prod_string = substr($prod_string, 1); // If a previous quote gets loaded, make sure it gets saved again. // Also, make sure the previously checked option is checked by default. drupal_add_js('$(function() { setQuoteCallbacks("'. drupal_urlencode($prod_string) .'"); $("#uc-cart-pane-quotes").submit(function() { quoteCallback("'. drupal_urlencode($prod_string) .'"); return false; }); })', 'inline'); return $form; } /** * Display the formatted quote cart pane. * * @ingroup themeable */ function theme_uc_cart_pane_quotes($form) { $output = '
'. print_r($order, TRUE) .''); $quote_option = explode('---', $_POST['quote-option']); $order->quote['method'] = $quote_option[0]; $order->quote['accessorials'] = $quote_option[1]; $order->quote['quote_form'] = rawurldecode($_POST['quote-form']); $methods = array_filter(module_invoke_all('shipping_method'), '_uc_quote_method_enabled'); $method = $methods[$quote_option[0]]; $quote_data = array(); $predicate = ca_load_trigger_predicates('get_quote_from_'. $method['id']); $arguments = array( 'order' => array( '#entity' => 'uc_order', '#title' => t('Order'), '#data' => $arg1, ), 'method' => array( '#entity' => 'quote_method', '#title' => t('Quote method'), '#data' => $method, ), 'account' => array( '#entity' => 'user', '#title' => t('User'), '#data' => $user, ), ); if (ca_evaluate_conditions($predicate, $arguments)) { $quote_data = uc_quote_action_get_quote($order, $method, $user); } //drupal_set_message('Chosen quote method:
'. print_r($method, TRUE) .''); //drupal_set_message('Chosen quote data:
'. print_r($quote_data, TRUE) .''); if (!isset($quote_data[$quote_option[1]])) { drupal_set_message(t('Invalid option selected. Recalculate shipping quotes to continue.'), 'error'); break; } $label = $method['quote']['accessorials'][$quote_option[1]]; $order->quote['rate'] = $quote_data[$quote_option[1]]['rate']; $result = db_query("SELECT line_item_id FROM {uc_order_line_items} WHERE order_id = %d AND type = 'shipping'", $arg1['order_id']); if ($lid = db_result($result)) { uc_order_update_line_item($lid, $label, $order->quote['rate'] ); } else { uc_order_line_item_add($order->order_id, 'shipping', $label, $order->quote['rate'] ); } } } break; } } /** * Validate handler added to uc_cart_checkout_form(). * * Save the choice of shipping method in the customer's session. */ function uc_quote_save_choice($form, &$form_state) { $quote_option = explode('---', $_POST['quote-option']); $_SESSION['quote'] = array('method' => $quote_option[0], 'accessorials' => $quote_option[1], 'rate' => $_POST['rate'][$_POST['quote-option']], 'quote_form' => $_POST['quote-form'], ); } /** * Pre-render callback added to uc_cart_checkout_form(). * * Render the shipping quotes without an asynchronous call to create them if a * choice had been cached in the session. */ function uc_quote_cache_quotes($form) { if ($form['#id'] == 'uc-cart-checkout-form') { if (isset($_SESSION['quote']) && isset($_SESSION['quote']['rate'])) { $quote = $_SESSION['quote']; $form['panes']['quotes']['quote'] = array('#type' => 'markup', '#value' => '