--- modules/ecommerce/shipping/shipping.module.orig 2006-05-18 13:02:41.000000000 -0400 +++ modules/ecommerce/shipping/shipping.module 2006-05-26 12:08:16.000000000 -0400 @@ -11,564 +11,746 @@ function shipping_help($section = 'admin/help#shipping') { switch ($section) { case 'admin/modules#description': - return t('Enable a shipping module API for ecommerce.'); - case 'admin/store/shipping': - return t("First, choose a method to calculate shipping costs, then configure the shipping details for the method selected."); - case 'admin/settings/shipping': - return t('Shipping calculation methods are here.', array('%shipping_calc_url' => url('admin/store/shipping'))); + return t('Enable a shipping module API for ecommerce. You must also install and enable at least one shipping module to use the API.'); } } /** - * Implementation of hook_menu() - */ -function shipping_menu($may_cache) { - $items = array(); - if ($may_cache) { - $items[] = array('path' => 'admin/store/settings/shipping', - 'title' => t('shipping'), 'callback' => 'shipping_admin', - 'access' => user_access('administer store')); - $items[] = array('path' => 'admin/store/shipping', - 'title' => t('shipping'), 'callback' => 'shipping_admin', - 'access' => user_access('administer store')); - $items[] = array('path' => 'admin/store/transaction/shipping', - 'title' => t('shipping'), 'callback' => 'shipping_shipping_notification', - 'access' => user_access('administer store'), 'type' => MENU_CALLBACK); - - } - return $items; -} - -/** - * Implementation of hook_settings() + * Callback for shipping settings. */ function shipping_settings() { - return store_email_form('shipping', 'shipping_notification', t("Shipping email"), t("This text will be emailed to customers after their order has been shipped.")); + $output = store_email_form('shipping', 'shipping_notification', t("Shipping email"), t("This text will be emailed to customers after their order has been shipped.")); + return $output; } -/******************************************************************** - * Module Functions - ********************************************************************/ +function shipping_admin() { + $output = shipping_shipfrom_table(); + $output .= shipping_shipfrom_edit_form(); + return $output; +} /** - * Controller. This callback is invoked via menu() when "admin/store/shipping*" is hit. + * Implementation of hook_menu(). */ -function shipping_admin($action = '') { - - $edit = $_POST['edit']; - $op = $_POST["op"]; - - switch ($op ? $op : $action) { - case t('Save configuration'): - variable_set("shipping_method", $edit["shipping_method"]); - drupal_set_message(t('shipping options have been saved')); - $output = shipping_admin_overview(); - break; - - case t('Save shipping details'): - - if (shipping_details_validate($edit)) { - - shipping_details_save($edit); - drupal_goto('admin/store/shipping'); - } - else { - $output = shipping_admin_overview($edit); - } - break; - - default: - $output = shipping_admin_overview(); +function shipping_menu($may_cache) { + $items = array(); + if ($may_cache) { + $items[] = array( + 'path' => 'admin/store/settings/shipping', + 'title' => t('shipping'), + 'callback' => 'shipping_admin', + 'access' => user_access('administer store') + ); + $items[] = array( + 'path' => 'admin/store/transaction/shipping', + 'title' => t('shipping'), + 'callback' => 'shipping_shipping_notification', + 'access' => user_access('administer store'), + 'type' => MENU_CALLBACK + ); } - - return $output; + return $items; } /** - * Displays the default configuration screen for managing shipping information. - * - * @return string HTML + * Form for email settings. */ -function shipping_admin_overview($edit = null) { - /* Grab shipping methods from other modules */ - $method = shipping_invoke_shippingapi($edit, 'method select'); - $radio['per_product'] = t('Shipping costs are calculated on a per product basis.'); - foreach ($method as $key => $desc) { - $radio[$key] = $desc; - } - $radio['subtotal'] = t('Shipping costs are calculated based on the total cost of items in the cart.'); - $radio["none"] = t("Do not calculate shipping costs."); - - $form['methods'] = array( - '#type' => 'fieldset', - '#title' => t("Shipping calculation methods"), - ); - - $form['methods']["shipping_method"] = array( - '#type' => 'radios', - '#title' => t('Choose one'), - '#default_value' => variable_get("shipping_method", "none"), - '#options' => $radio, +function shipping_email_settings_form() { + $form["shipping_subject"] = array( + '#type' => 'textfield', + '#title' => t('Subject of shipping e-mail'), + '#default_value' => variable_get("shipping_subject", t("Your %site order has shipped (#%txnid)")), + '#size' => 70, + '#maxlength' => 180 ); - $form[] = array( - '#type' => 'submit', - '#value' => t("Save configuration"), + $form["shipping_new"] = array( + '#type' => 'textarea', + '#title' => t("Shipping email"), + '#default_value' => variable_get("shipping_new", shipping_help("Shipping email")), + '#cols' => 70, + '#rows' => 10, + '#description' => t("This text will be emailed to customers after their order has been shipped.") ); - - $output = drupal_get_form('shipping_method', $form); + return system_settings_form('shipping_email_settings', $form); +} - unset ($form); - /* goodie goodie, now let's display the form specifc to the shipping method chosen. */ - $shipping_instance_settings = ''; - - if (variable_get("shipping_method", "none") != "none") { - $form['details'] = array( - //'#type' => 'fieldset', // This fieldset is no rendering properly. It gets shown after the table. to fix this we would need to use the #theme property - '#title' => t("Shipping details"), +/** + * Implementation of hook_form_alter() + */ +function shipping_form_alter($form_id, &$form) { + // On each product type's settings page, e.g., admin/settings/content-types/product/tangible, + // offer shipping methods for the type. + if (($form_id == 'product_type_settings') && product_is_shippable(NULL, $form['#ptype'])) { + $ptype = $form['#ptype']; + $form[$ptype . '_shipping_methods'] = array( + '#type' => 'fieldset', + '#title' => t('Shipping methods'), + '#description' => t('Select the shipping methods you wish to make available for this product type.'), + '#weight' => -10, '#tree' => TRUE - //'#theme' => 'shipping_details' // This needs to be used in order to render the table within the fieldset, however it's not working properly and needs some more work ); - $fields = module_invoke(variable_get("shipping_method", ""), "shippingapi", $edit, 'form'); - if ($fields) { - if (variable_get("shipping_method", "") == "subtotal") { - $output .= "

". t("Enter the subtotals a customer should have for a given shipping cost to be applied. This can be a single price (e.g. 1.00, 2.99, 5.34) or a range (e.g. 1.00-3.99, 4.00-10.26). Then, enter the cost of shipping for that subtotal (e.g. .99, 5.00). If conflicting shipping rules exist, the last rule that matches will always be executed. Only hyphens, dots and numbers are allowed.") ."

"; - } - else { - $output .= "

". t("Enter the number of items a customer should have for a given shipping cost to be applied. This can be a single digit (e.g. 1, 2, 5) or a range (e.g. 1-3, 4-10, 5-100). Then, enter the cost of shipping for that quantity (e.g. .99, 5.00). If conflicting shipping rules exist, the last rule that matches will always be executed. Only hyphens, dots and numbers are allowed.") ."

"; - } - $form['details'] = array_merge($form['details'], $fields); - - $form[] = array( - '#type' => 'submit', - '#value' => t("Save shipping details"), + $methods = shipping_get_methods(); + $settings = variable_get($ptype . '_shipping_methods', array()); + foreach ($methods as $service => $method) { + $options = array(); + foreach (element_children($method) as $key) { + $options[$key] = $method[$key]['#title']; + } + // Read title from method. + $form[$ptype . '_shipping_methods'][$service] = array( + '#title' => $methods[$service]['#title'], + '#type' => 'checkboxes', + '#default_value' => $settings[$service] ? array_filter($settings[$service]) : array(), + '#options' => $options ); - $output .= drupal_get_form('shipping_details', $form); - //$output .= form_render($form); // This is used instead of previous line in order to use the #theme property + // Read in the optional method description. + if ($methods[$service]['#description']) { + $form[$ptype . 'shipping_methods'][$service]['#description'] = $methods[$service]['#description']; + }; + } + } + // On the product add/edit form, add shipping method selection. + else if (isset($form['product']) && isset($form['product']['ptype'])) { + $ptype = $form['product']['ptype']['#value']; + $methods = shipping_get_methods(NULL, $ptype); + if (count($methods)) { + $form['shipping_data'] = array( + '#type' => 'fieldset', + '#title' => t('Shipping methods'), + '#description' => t('Select the shipping methods you wish to make available for this product.'), + '#collapsible' => TRUE + ); + $form['shipping_data']['shipping_methods'] = array( + '#tree' => TRUE + ); + foreach ($methods as $service => $method) { + $options = array(); + foreach (element_children($method) as $key) { + $options[$key] = $method[$key]['#title']; + } + // Read title from method. + // The value of the checkboxes comes from the shipping_methods array set at nodeapi load. + $form['shipping_data']['shipping_methods'][$service] = array( + '#title' => $methods[$service]['#title'], + '#type' => 'checkboxes', + '#default_value' => $form['#node']->shipping_methods[$service], + '#options' => $options + ); + // Read in the optional method description. + if ($methods[$service]['#description']) { + $form['shipping_data']['shipping_methods'][$service]['#description'] = $methods[$service]['#description']; + }; + } + // Allow shipping modules to add elements for collecting product attribute data. + // We need to call a hook rather than relying on form_alter because + // other modules need to add to the shipping_methods fieldset, which won't be available + // until shipping.module has been called. + $form['shipping_data']['product_attributes'] = module_invoke_all('shipping_product_attributes', $form, $methods); + $form['shipping_data']['product_attributes']['#tree'] = TRUE; } } - return $output; } -function shipping_shipping_notification() { - $t = store_transaction_load(arg(4)); - $variables = store_email_get_transaction_variables($t); - $from = variable_get("site_mail", ini_get("sendmail_from")); - - if ($t->mail && valid_email_address($t->mail)) { - $to = $t->mail; - } elseif ($t->uid != 0) { - $to = db_result(db_query("SELECT mail FROM {users} WHERE uid = '%d'", $t->uid)); +/** + * Implementation of hook_nodeapi(). + * + * On load, we add an array of shipping_methods to the node object. + */ +function shipping_nodeapi(&$node, $op, $teaser) { + // Only act on products. + if (!isset($node->ptype)) { + return; } - $form['info'] = array( - '#type' => 'markup', - '#value' => t("

This is a preview of the message to be sent from %from to %to.

", array('%from' => $from, '%to' => $to)) - ); - - $form['to'] = array( - '#type' => 'hidden', - '#value' => $to, - ); - - $form['from'] = array( - '#type' => 'hidden', - '#value' => $from, - ); - - $form['txnid'] = array( - '#type' => 'hidden', - '#value' => $t->txnid, - ); - - $form['message'] = store_email_form('shipping', 'shipping_notification', t('Shipping notification'), NULL, $variables); + switch ($op) { - if ($workflow = $_GET['wf']) { - $form['change_workflow'] = array( - '#type' => 'checkbox', - '#title' => t('Change Workflow'), - '#description' => t('Check here if you want to change the transaction workflow to %new_workflow.', array('%new_workflow' => transaction_get_workflow($workflow))), - '#default_value' => TRUE - ); - $form['wf'] = array('#type' => 'hidden', '#value' => $workflow); - } + case 'load': + // Load service and method data. + $result = db_query("SELECT service, method FROM {ec_shipping_product} WHERE vid = %d", $node->vid); + if (db_num_rows($result)) { + $node->shipping_methods = array(); + while ($method = db_fetch_object($result)) { + $node->shipping_methods[$method->service][] = $method->method; + } + } + + // Load product attribute data. + $result = db_query("SELECT field, value FROM {ec_shipping_product_attribute} WHERE vid = %d", $node->vid); + if (db_num_rows($result)) { + $node->product_attributes = array(); + while ($attribute = db_fetch_object($result)) { + $node->product_attributes[$attribute->field] = $attribute->value; + } + } + break; + case 'update': + // Delete service and method data. + if ($node->shipping_methods && !$node->revision) { + db_query('DELETE FROM {ec_shipping_product} WHERE vid = %d', $node->vid); + } + // Delete product attribute data. + db_query("DELETE FROM {ec_shipping_product_attribute} WHERE vid = %d", $node->vid); + // Fall through. + case 'insert': + // Insert service and method data. + if (is_array($node->shipping_methods)) { + foreach ($node->shipping_methods as $service => $methods) { + foreach ($methods as $method) { + if ($method) { + db_query("INSERT INTO {ec_shipping_product} (vid, nid, service, method) VALUES (%d, %d, '%s', '%s')", $node->vid, $node->nid, $service, $method); + } + } + } + } + // Insert product attribute data. + if (is_array($node->product_attributes)) { + foreach ($node->product_attributes as $field => $value) { + db_query("INSERT INTO {ec_shipping_product_attribute} (vid, nid, field, value) VALUES (%d, %d, '%s', '%s')", $node->vid, $node->nid, $field, $value); + } + } + break; - return confirm_form('shipping_shipping_notification', $form, t('Use this form to change the message that will be sent to the customer'), $_GET['destination'], '
', t('Send shipping notification'), t('Cancel')); -} + case 'delete': + db_query('DELETE FROM {ec_shipping_product} WHERE nid = %d', $node->nid); + db_query("DELETE FROM {ec_shipping_product_attribute} WHERE nid = %d", $node->nid); + break; -function shipping_shipping_notification_submit($form_id, $edit) { - if ($edit['confirm']) { - $mail_sent = store_email_send($edit['from'], $edit['to'], $edit['subject'], $edit['body'], $edit['headers']); - if ($mail_sent) { - drupal_set_message(t('The shipping notification has been sent to the customer.')); - if ($edit['change_workflow'] && is_numeric($edit['wf'])) { - db_query("UPDATE {ec_transaction} SET workflow = '%d' WHERE txnid = '%d'", $edit['wf'], $edit['txnid']); - drupal_set_message(t('The transaction workflow has been updated.')); - } - } + case 'delete revision': + db_query('DELETE FROM {ec_shipping_product} WHERE vid = %d', $node->vid); + db_query("DELETE FROM {ec_shipping_product_attribute} WHERE vid = %d", $node->vid); + break; } - return $edit['destination']; } -/** - * Implementation of hook_store_email_text() - */ -function shipping_store_email_text($messageid) { - if ($messageid == 'shipping_notification') { - return array( - 'subject' => t("Your %site order has shipped (#%txnid)"), - 'body' => t("Hello %first_name,\n\nWe have shipped the following item(s) from Order #%txnid, received %order_date.\n\nItems(s) Shipped:\n%items\n%shipping_to\nQuestions about your order? Please contact us at %email.\n\nThanks for shopping at %site. We hope to hear from you again real soon!\n\n%uri"), - 'variables' => array('%order_date', '%txnid', '%billing_name', '%first_name', '%user_data', '%billing_to', '%shipping_to', '%items', '%email', '%site', '%uri', '%uri_brief', '%date') - ); - } -} +/******************************************************************** + * Module Functions + ********************************************************************/ /** - * Run the validation routines + * Return shipping methods. */ -function shipping_details_validate(&$edit) { - /* We use this instead of module_invoke to avoid passing $edit by reference. */ - $f = variable_get("shipping_method", ""). "_shippingapi"; - if (function_exists($f)) { - $f($edit, 'validate'); +function shipping_get_methods($module = NULL, $ptype = NULL) { + // If a module is set, we want only its methods. Otherwise, we want all. + $methods = $module ? module_invoke($module, 'shipping_methods') : module_invoke_all('shipping_methods'); + // If a ptype is set, we want only its registered methods. + if ($ptype) { + // Load the product type settings. + $ptype_methods = variable_get($ptype . '_shipping_methods', array()); + // We filter the values because checkboxes saves unneeded zeros. + foreach ($ptype_methods as $service => $method) { + $ptype_methods[$service] = array_filter($method); + } + // Filter to unset any methods not applicable for this product type. + $methods = shipping_methods_filter($methods, $ptype_methods); } - - return ((form_get_errors()) ? false : true); + return $methods; } /** - * Save the shipping information. + * Filter a set of shipping methods. * - * This function doesn't save any information itself. Instead it calls other - * method save functions. + * Arrays of methods are generally in one of two different formats. + * The first is that returned by the _shipping_methods hook, e.g, + * + * + * $methods['ups'] = array( + * '#title' => t('UPS'), + * '#description' => t('United Parcel Service of America, Inc.') + * ); + * $methods['ups']['1DM'] = array( + * '#title' => t('Next Day Air Early AM'), + * ); + * * - * @return string shipping_admin_overview() - */ -function shipping_details_save($edit) { - module_invoke(variable_get("shipping_method", ""), "shippingapi", $edit, "save"); - drupal_set_message('Shipping information updated.'); -} - -/** - * Calculcate the shipping cost - * - * Invoke the methods pf the current shipping scheme to calculate shipping cost. + * The second is that used to pass product type and product shipping + * options, e.g., * - * @param array $item A sequential array of all items in a user's cart + * + * array('ups' => array('1DM', '1DA')); + * * - * @return float The actual cost of shipping - */ -function shipping_calculate_cost($item) { - return module_invoke(variable_get("shipping_method", ""), "shippingapi", $item, "calculate"); -} - -/** - * The controller for the plugin API hooks. It iterates through the enabled - * plugins and calls their *_shippingapi hook if one exists, passing it the - * action (via $op) to execute. - * - * @param object $items - * @param string $op The action to execute (e.g., validate, insert, update, delete) - * @param string $arg Optional argument to pass along. Currently being used to - * pass an array of errors during form display - * - * @return array Collection of output from the all *_shippingapi calls. - */ -function shipping_invoke_shippingapi(&$items, $op, $arg = 0) { - - $return = array(); - foreach (module_list() as $name) { - $function = $name ."_shippingapi"; - if (function_exists($function)) { - $result = $function($items, $op, $arg); - if (isset($result)) { - $return = array_merge($return, $result); + * This function is used to filter method arrays of the first type (the + * first argument), returning only items with corresponding values in the + * method of the second type (the second argument). + */ +function shipping_methods_filter($methods, $used_methods) { + + foreach ($methods as $service => $method) { + // First filter out base methods, e.g, 'ups'. + if (!in_array($service, array_keys($used_methods))) { + unset($methods[$service]); + } + // For applicable methods, filter out any sub-methods not used, e.g., for 'ups', 'firstday'. + else { + foreach (element_children($method) as $key) { + if (!in_array($key, $used_methods[$service])) { + unset($methods[$service][$key]); + } } } } - return $return; -} -/******************************************************************** - * Default Shipping Method Functions - * - * - I wanted to have a default shipping calculation method, and this - * seemed like the best place to put it. - ********************************************************************/ + return $methods; +} /** - * The Controller to create a new shipping interface. + * Use checkoutapi to calculate shipping costs. */ -function shipping_shippingapi(&$items, $op) { - +function shipping_checkoutapi(&$txn, $op, $arg3 = NULL, $arg4 = NULL) { + // cart_get_items() doesn't do a full node load, so we don't have + // access to shipping data and therefore need to manually add + // shipping methods data here. + if (isset($txn->items)) { + foreach (array_keys($txn->items) as $nid) { + shipping_nodeapi($txn->items[$nid], 'load', NULL); + } + } switch ($op) { - case 'method select': /* module name => desc */ - return array('shipping' => t('Shipping costs are calculated based on the total quantity of items in the cart.')); - - case 'calculate': - return shipping_calculate($items); - case 'form': - return shipping_form($items); - + if ($txn == 'shipping') { + return TRUE; + } + return shipping_checkout_form($txn); case 'validate': - shipping_validate($items); - break; - + if (!$txn->shipping_method) { + form_set_error('', t('Please select a shipping method.')); + } + break; case 'save': - - return shipping_save($items); + $method = split('__', $txn->shipping_method); + $txn->ship = array( + 'service' => $method[0], + 'method' => $method[1], + 'cost' => $method[2], + 'currency' => $method[3] + ); + $txn->screen++; + break; + case 'review': + if ($txn == 'shipping') { + return TRUE; + } + if (($key = store_search_misc(array('type' => 'shipping'), $txn)) !== FALSE) { + $txn->misc[$key]->price = $txn->ship['cost']; + } + else { + $misc = array( + 'type' => 'shipping', + 'description' => t('Shipping'), + 'price' => $txn->ship['cost'], + 'weight' => 9 + ); + $txn->misc[] = (object)$misc; + } + break; } } -function shipping_calculate($items) { - - $type = variable_get("shipping_method", ""); - $pivot_element = 0; - $cost = 0; - - if ($type == "subtotal") { - foreach ($items as $item) { - if (product_is_shippable($item->nid)) { - $pivot_element += $item->qty * $item->price; - } - } +/** + * Build the shipping checkout form, which lists all available shipping options. + * + * @param $txn + * The current transaction. + */ +function shipping_checkout_form(&$txn) { + if (!is_array($txn->items)) { + // No items are defined, we shouldn't be here, just return. + return; } else { - foreach ($items as $item) { - if (product_is_shippable($item->nid)) { - $pivot_element += $item->qty; - } - } - } - - $function = $type. "_load"; - $ship = $function(); - $numset = count($ship['ship_item_slot']); - for ($i = 0; $i < $numset; $i++) { - $item = $ship['ship_item_slot'][$i]; - $price = $ship['ship_price_slot'][$i]; - - /* Check for range match */ - if (strstr($item, '-')) { - list($low, $high) = explode('-', $item); - if ($pivot_element >= $low and $pivot_element <= $high) { - $cost = $price; + $shippable = FALSE; + foreach ($txn->items as $item) { + if (is_array($item->shipping_methods)) { + $shippable = TRUE; } } - /* Check for exact item quantity match */ - elseif ($item == $pivot_element) { - $cost = $price; + if (!$shippable) { + // No shippable items are defined, just return. + return; } } - return $cost; -} + drupal_set_title(t('Choose shipping method')); + + $form = array(); + $options = array(); -function shipping_validate(&$edit) { - $errors = array(); + $rates = module_invoke_all('shipping_checkout_rates', $txn); - if (variable_get("shipping_method", "") == "subtotal") { - $row_1_error = t("One of the subtotal columns contain an invalid format."); + if (sizeof($rates) == 1) { + // Only one shipping method, auto select + $rate = array_pop($rates); + $txn->shipping_method = $service .'__'. $rate['#key'] .'__'. $rate['#cost'] .'__'. $rate['#currency']; + shipping_checkoutapi($txn, 'save'); + return; } - else { - $row_1_error = t("One of the item quantities columns contain an invalid format."); + + foreach ($rates as $service => $rate) { + $options[$service .'__'. $rate['#key'] .'__'. $rate['#cost'] .'__'. $rate['#currency']] = t('%price %currency - %method', array('%price' => $rate['#cost'], '%currency' => $rate['#currency'], '%method' => $rate['#method'])); } + $form['shipping_method'] = array( + '#type' => 'radios', + '#options' => $options + ); - $numset = count($edit['ship_item_slot']); - for ($i = 0; $i < $numset; $i++) { + $form[] = array( + '#type' => 'submit', + '#default_value' => t('Continue') + ); - $item = $edit['ship_item_slot'][$i]; - $price = $edit['ship_price_slot'][$i]; + return $form; +} - if ($item != '' && $price != '') { - if (!preg_match("/^[0-9.]+([-]?[0-9.]+)*$/", $item)) { - $errors['ship_item_slot'] = $row_1_error; +/** + * Implementation of hook_ec_transactionapi(). + */ +function shipping_ec_transactionapi(&$txn, $op, $a3 = NULL, $a4 = NULL) { + switch ($op) { + case 'load': + $txn->ship = db_fetch_object(db_query('SELECT service, method, cost FROM {ec_shipping_transaction} WHERE txnid = %d', $txn->txnid)); + break; + case 'update': + case 'insert': + if (is_array($txn->ship) && is_numeric($txn->txnid)) { + db_query("UPDATE {ec_shipping_transaction} SET service = '%s', method = '%s', cost = '%s' WHERE txnid = %d", $txn->ship['service'], $txn->ship['method'], $txn->ship['cost'], $txn->txnid); + if (!db_affected_rows()) { + db_query("INSERT INTO {ec_shipping_transaction} (txnid, service, method, cost) VALUES (%d, '%s', '%s', '%s')", $txn->txnid, $txn->ship['service'], $txn->ship['method'], $txn->ship['cost']); + } } + break; + case 'delete': + db_query('DELETE FROM {ec_shipping_transaction} WHERE txnid = %d', $txn->txnid); + break; + } +} - /* Remove the currency symbol at the beginning of the price if it exists */ - if (substr($price, 0, 1) == variable_get('payment_symbol', '$')) { - $price = substr($price, count(variable_get('payment_symbol', '$'))); - $edit['ship_price_slot'][$i] = $price; - } - $price = str_replace(',', '', $price); - if (!is_numeric($price)) { - $errors['ship_price_slot'] = t("Please enter a numeric value for the shipping cost."); - } - } - elseif (($item == '' && $price != '') || ($item != '' && $price == '')) { - $errors['ship_blank_field'] = t("You need to enter a value for both columns of a row and not just one of the fields."); + +/** + * Build a table listing all configured fulfillment centers. + */ +function shipping_shipfrom_table() { + $form['header'] = array('#type' => 'value', '#value' => array( + array('data' => t('Name'), 'field' => 'title', 'sort' => 'asc'), + array('data' => t('Code'), 'field' => 'code'), + array('data' => t('City'), 'field' => 'city'), + array('data' => t('Region'), 'field' => 'region'), + array('data' => t('Country'), 'field' => 'country'), + array('data' => t('Default'), 'field' => 'status'), + array('data' => t('Operations'), 'colspan' => 2)) + ); + + $sql = 'SELECT * FROM {ec_shipping_shipfrom}'; + $sql .= tablesort_sql($form['header']['#value']); + $result = pager_query($sql, 25); + $i = 0; + while ($address = db_fetch_object($result)) { + $form['title'][$i] = array('#value' => htmlspecialchars($address->title)); + $form['code'][$i] = array('#value' => $address->code); + $form['city'][$i] = array('#value' => $address->city); + $form['region'][$i] = array('#value' => $address->region); + $form['country'][$i] = array('#value' => $address->country); + $form['status'][$i] = array('#value' => ($address->status ? t('default') : '')); + $form['operations'][$i] = array('#value' => l(t('edit'), "admin/store/settings/shipping/$address->sid/edit") . ' ' . l(t('delete'), "admin/store/settings/shipping/$address->sid/delete")); + $i++; + } + + return drupal_get_form('shipping_shipfrom_table', $form); +} + +/** + * Theme the table of fulfillment centers. + */ +function theme_shipping_shipfrom_table($form) { + if (isset($form['title']) && is_array($form['title'])) { + foreach (element_children($form['title']) as $key) { + $row = array(); + $row[] = form_render($form['title'][$key]); + $row[] = form_render($form['code'][$key]); + $row[] = form_render($form['city'][$key]); + $row[] = form_render($form['region'][$key]); + $row[] = form_render($form['country'][$key]); + $row[] = form_render($form['status'][$key]); + $row[] = form_render($form['operations'][$key]); + $rows[] = $row; } } + else { + $rows[] = array(array('data' => t('There are no fulfillment centers configured.'), 'colspan' => '99')); + } - foreach ($errors as $name => $message) { - form_set_error($name, $message); + $table = theme('table', $form['header']['#value'], $rows); + if ($form['pager']['#value']) { + $table .= form_render($form['pager']); } - return count($errors) == 0; + + $output = theme('fieldset', array( + '#title' => t('Fulfillment centers'), + '#prefix' => '
', + '#suffix' => '
', + '#children' => $table)); + $output .= form_render($form); + + return $output; } -function shipping_form($edit = null) { +/** + * Display a form to add/edit fulfillment centers. + */ +function shipping_shipfrom_edit_form() { + global $form_values; - if (!$edit) { - $edit = shipping_load(); + if (arg(5) == 'edit' && is_numeric(arg(4))) { + $edit = _shipping_shipfrom_load(arg(4)); } - - $type = variable_get("shipping_method", ""); - if ($type == "subtotal") { - $sentance_pre = t("A subtotal of "); - //$help = "

". t("Enter the subtotals a customer should have for a given shipping cost to be applied. This can be a single price (e.g. 1.00, 2.99, 5.34) or a range (e.g. 1.00-3.99, 4.00-10.26). Then, enter the cost of shipping for that subtotal (e.g. .99, 5.00). If conflicting shipping rules exist, the last rule that matches will always be executed. Only hyphens, dots and numbers are allowed.") ."

"; + else if (arg(5) == 'delete' && is_numeric(arg(4))) { + $edit = _shipping_shipfrom_load(arg(4)); + return confirm_form('shipping_shipfrom_delete', array(), t('Confirm deletion of fulfillment center %shipfrom', array('%shipfrom' => $edit['title'])), 'admin/store/settings/shipping', t('Click the Delete button to permanently delete the %shipfrom fulfillment center. This action cannot be undone.', array('%shipfrom' => $edit['title'])), t('Delete'), t('Cancel')); } - else { - $column_name = t("Item quantity"); - $sentance_pre = t("A quantity of "); - $sentance_post = t("items"); - //$help = "

". t("Enter the number of items a customer should have for a given shipping cost to be applied. This can be a single digit (e.g. 1, 2, 5) or a range (e.g. 1-3, 4-10, 5-100). Then, enter the cost of shipping for that quantity (e.g. .99, 5.00). If conflicting shipping rules exist, the last rule that matches will always be executed. Only hyphens, dots and numbers are allowed.") ."

"; + + $form['shipfrom'] = array( + '#type' => 'fieldset', + '#title' => ($edit['sid'] ? t('Edit fulfillment center') : t('Add fulfillment center')) + ); + + if (isset($edit['sid'])) { + $form['shipfrom']['sid'] = array( + '#type' => 'hidden', + '#value' => $edit['sid'] + ); } - $header = array($column_name, t("Shipping cost")); - $fields = 10; - - for ($i = 0; $i < $fields; $i++) { - - $form['ship_item_slot'][$i] = array( + $form['shipfrom']['status'] = array( + '#type' => 'checkbox', + '#title' => t('Default'), + '#default_value' => $edit['status'], + '#description' => t('Check this box if this is the default fulfillment center. (Currently all shipments are sent from the default fulfillment center.)') + ); + + $form['shipfrom']['title'] = array( + '#type' => 'textfield', + '#title' => t('Name'), + '#size' => 80, + '#maxlength' => 255, + '#required' => TRUE, + '#default_value' => $edit['title'], + '#description' => t('The name of the fulfillment center.') + ); + $form['shipfrom']['city'] = array( '#type' => 'textfield', - '#value' => $edit['ship_item_slot'][$i], - '#size' => 35, - '#maxlength' => 128, - '#required' => false, - '#prefix' => $sentance_pre, - '#suffix' => $sentance_post - ); - $form['ship_price_slot'][$i] = array( + '#title' => t('City'), + '#size' => 80, + '#maxlength' => 255, + '#required' => TRUE, + '#default_value' => $edit['city'], + '#description' => t('The city that the fulfillment center is located in.') + ); + $form['shipfrom']['region'] = array( '#type' => 'textfield', - '#value' => $edit['ship_price_slot'][$i], - '#size' => 35, - '#maxlength' => 128, - '#required' => false, - '#prefix' => t('cost'), - '#suffix' => t("in shipping.") - ); + '#title' => t('Region (State/Province)'), + '#size' => 80, + '#maxlength' => 255, + '#required' => TRUE, + '#default_value' => $edit['region'], + '#description' => t('The region (state/province) that the fulfillment center is located in.') + ); + $form['shipfrom']['code'] = array( + '#type' => 'textfield', + '#title' => t('Zip/Postal code'), + '#size' => 20, + '#maxlength' => 255, + '#default_value' => $edit['code'], + '#description' => t('The fulfillment center zipcode or postal code.') + ); + $form['shipfrom']['country'] = array( + '#type' => 'select', + '#title' => t('Country'), + '#default_value' => variable_get('ec_country', ''), + '#options' => store_build_countries(), + '#default_value' => $edit['country'], + '#description' => t('The country that the fulfillment center is located in.') + ); + + $form['shipfrom']['actions'] = array( + '#prefix' => '
', + '#suffice' => '
' + ); + $form['shipfrom']['actions']['submit'] = array( + '#type' => 'submit', + '#value' => ($edit['sid'] ? t('Edit fulfillment center') : t('Add fulfillment center')) + ); + if ($edit['sid']) { + $form['shipfrom']['actions']['cancel'] = array( + '#value' => l(t('Cancel'), 'admin/store/settings/shipping') + ); } - return $form; + + return drupal_get_form('shipping_shipfrom_edit_form', $form); } -function theme_shipping_details(&$form) { - for ($i = 0; $i <= 9; $i++) { - $row = array(); - $row[] = form_render($form['details']['ship_item_slot'][$i]); - $row[] = form_render($form['details']['ship_price_slot'][$i]); - $rows[] = $row; +/** + * Save fulfillment centers when added or edited. + */ +function shipping_shipfrom_edit_form_submit() { + global $form_values; + + $default = db_result(db_query('SELECT sid FROM {ec_shipping_shipfrom} WHERE status = 1')); + if (!$default) { + $form_values['status'] = 1; } - - $type = variable_get("shipping_method", ""); - if ($type == "subtotal") { - $column_name = t("Subtotal"); + // There can only be one default -- this will need to be updated when + // 'status' is expanded to provide more than just 'default'. + elseif ((int)$form_values['status'] > 0) { + db_query('UPDATE {ec_shipping_shipfrom} SET status = 0'); + } + + if (isset($form_values['sid'])) { + db_query("UPDATE {ec_shipping_shipfrom} SET title = '%s', code = '%s', city = '%s', region = '%s', country = '%s', status = %d WHERE sid = %d", $form_values['title'], $form_values['code'], $form_values['city'], $form_values['region'], $form_values['country'], $form_values['status'], $form_values['sid']); + drupal_set_message(t('Fulfillment center updated.')); } else { - $column_name = t("Item quantity"); + db_query("INSERT INTO {ec_shipping_shipfrom} (title, code, city, region, country, status) VALUES('%s', '%s', '%s', '%s', '%s', %d)", $form_values['title'], $form_values['code'], $form_values['city'], $form_values['region'], $form_values['country'], $form_values['status']); + drupal_set_message(t('Fulfillment center added.')); } - $header = array($column_name, t("Shipping cost")); - $output = theme('table', $header, $rows); - $output .= form_render($form); - return $output; -} -function shipping_save($edit) { + drupal_goto('admin/store/settings/shipping'); +} - $type = variable_get("shipping_method", ""). "_shipping"; - $numset = count($edit['details']['ship_item_slot']); - /* As a safeguard, don't delete the old shipping values unless we're sure we - got at least one update. */ - if ($numset) { - db_query("DELETE FROM {ec_$type}"); +/** + * Delete the selected shipfrom address. If it was a default address, set a + * new default. + * + * @param $form + */ +function shipping_shipfrom_delete_submit($form) { + if (arg(5) == 'delete' && is_numeric(arg(4))) { + $default = db_result(db_query('SELECT sid FROM {ec_shipping_shipfrom} WHERE status = 1')); + db_query('DELETE FROM {ec_shipping_shipfrom} WHERE sid = %d', arg(4)); + if ($default == arg(4)) { + // We just deleted the default, auto-set a new default + $new = db_result(db_query_range('SELECT sid FROM {ec_shipping_shipfrom} ORDER BY title', 0, 1)); + if ($new) { + db_query('UPDATE {ec_shipping_shipfrom} SET status = 1 WHERE sid = %d', $new); + } + } } + drupal_goto('admin/store/settings/shipping'); +} - for ($i = 0; $i < $numset; $i++) { - $item = $edit['details']['ship_item_slot'][$i]; - $price = $edit['details']['ship_price_slot'][$i]; - if ($item && $price) { - db_query("INSERT INTO {ec_$type} (item_slot, price_slot) VALUES ('%s', '%s')", $item, $price); - } +/** + * Return the default shipfrom address, useful to shipping partner .inc files. + */ +function shipping_default_shipfrom() { + $result = db_fetch_array(db_query('SELECT * FROM {ec_shipping_shipfrom} WHERE status = 1')); + if ($result == array()) { + drupal_set_message(t('No shipping default fulfillment center configured, shipping prices will not be accurate.'), 'error'); } + return $result; +} - drupal_set_message(t("the shipping options have been saved")); +/** + * Load the specified fulfillment center. + */ +function _shipping_shipfrom_load($sid) { + return db_fetch_array(db_query('SELECT * FROM {ec_shipping_shipfrom} WHERE sid = %d', $sid)); } -function shipping_load() { +/** + * Callback to send email. + */ +function shipping_email($action) { - $type = variable_get("shipping_method", ""). "_shipping"; - $result = db_query("SELECT * FROM {ec_$type}"); + switch ($action) { + case 'send': + drupal_set_title(t('Shipping notification')); + $output = shipping_preview_email(arg(4), 1); + break; - while ($data = db_fetch_array($result)) { - $output['ship_item_slot'][] = $data['item_slot']; - $output['ship_price_slot'][] = $data['price_slot']; + case t('Send shipping notification'): + if ($edit['destination'] && shipping_send_email($edit)) { + drupal_set_message(t('Shipping notification has been sent.')); + unset($_REQUEST['destination']); + unset($_REQUEST['edit']['destination']); + drupal_goto(base64_decode(urldecode($edit['destination']))); + } + else { + drupal_set_message(t('Unable to send email.'), 'error'); + } + break; } return $output; } -/** - * Some young whipper snapper voodoo. I have extended the shipping module with - * two plugins in this shipping API module. They are both so very similar that - * i didn't overlap code. Kids, don't try this at home. Technically only one - * shipping api should be in each module - */ -function subtotal_shippingapi(&$items, $op, $arg = '') { - return shipping_shippingapi($items, $op, $arg); -} +function shipping_shipping_notification() { + $t = store_transaction_load(arg(4)); + $variables = store_email_get_transaction_variables($t); + $from = variable_get("site_mail", ini_get("sendmail_from")); -function subtotal_load() { - return shipping_load(); -} + if ($t->mail && valid_email_address($t->mail)) { + $to = $t->mail; + } elseif ($t->uid != 0) { + $to = db_result(db_query("SELECT mail FROM {users} WHERE uid = '%d'", $t->uid)); + } -function per_product_shippingapi(&$items, $op) { + $form['info'] = array( + '#type' => 'markup', + '#value' => t("

This is a preview of the message to be sent from %from to %to.

", array('%from' => $from, '%to' => $to)) + ); - switch ($op) { - case 'calculate': - return per_product_calculate($items); - case 'per_product' : - return TRUE; - case 'per_product_load': - return db_fetch_object(db_query('SELECT * FROM {ec_per_product_shipping} WHERE nid = %d', $items->nid)); - - case 'per_product_form': - $form['shipping_fee'] = array( - '#type' => 'textfield', - '#title' => t('Shipping charge'), - '#default_value' => $items->shipping_fee, - '#size' => 25, - '#maxlength' => 50, - '#description' => t('Enter any shipping or handling charges for this product here.'), - ); - return $form; + $form['to'] = array( + '#type' => 'hidden', + '#value' => $to, + ); - case 'per_product_validate': - if ($items->shipping_fee != NULL && !is_numeric($items->shipping_fee)) { - form_set_error('shipping_fee', t('You must enter a numeric value for the shipping fee.')); - } - return form_get_errors(); + $form['from'] = array( + '#type' => 'hidden', + '#value' => $from, + ); - case 'per_product_update': - case 'per_product_insert': - return per_product_save($items); + $form['txnid'] = array( + '#type' => 'hidden', + '#value' => $t->txnid, + ); + + $form['message'] = store_email_form('shipping', 'shipping_notification', t('Shipping notification'), NULL, $variables); + + if ($workflow = $_GET['wf']) { + $form['change_workflow'] = array( + '#type' => 'checkbox', + '#title' => t('Change Workflow'), + '#description' => t('Check here if you want to change the transaction workflow to %new_workflow.', array('%new_workflow' => transaction_get_workflow($workflow))), + '#default_value' => TRUE + ); + $form['wf'] = array('#type' => 'hidden', '#value' => $workflow); } + + return confirm_form('shipping_shipping_notification', $form, t('Use this form to change the message that will be sent to the customer'), $_GET['destination'], '
', t('Send shipping notification'), t('Cancel')); } -function per_product_save($node) { - if ($node->per_product_shipping) { - db_query('DELETE FROM {ec_per_product_shipping} WHERE nid = %d', $node->nid); - db_query('INSERT INTO {ec_per_product_shipping} (nid, shipping_fee) VALUES (%d, %f)', $node->nid, $node->shipping_fee); +function shipping_shipping_notification_submit($form_id, $edit) { + if ($edit['confirm']) { + $mail_sent = store_email_send($edit['from'], $edit['to'], $edit['subject'], $edit['body'], $edit['headers']); + if ($mail_sent) { + drupal_set_message(t('The shipping notification has been sent to the customer.')); + if ($edit['change_workflow'] && is_numeric($edit['wf'])) { + db_query("UPDATE {ec_transaction} SET workflow = '%d' WHERE txnid = '%d'", $edit['wf'], $edit['txnid']); + drupal_set_message(t('The transaction workflow has been updated.')); + } + } } + return $edit['destination']; } /** - * An array of items is passed in. + * Implementation of hook_store_email_text() */ -function per_product_calculate($items) { - $shipping = 0; - foreach ($items as $nid => $i) { - $shipping += $i->qty * $i->shipping_fee; +function shipping_store_email_text($messageid) { + if ($messageid == 'shipping_notification') { + return array( + 'subject' => t("Your %site order has shipped (#%txnid)"), + 'body' => t("Hello %first_name,\n\nWe have shipped the following item(s) from Order #%txnid, received %order_date.\n\nItems(s) Shipped:\n%items\n%shipping_to\nQuestions about your order? Please contact us at %email.\n\nThanks for shopping at %site. We hope to hear from you again real soon!\n\n%uri"), + 'variables' => array('%order_date', '%txnid', '%billing_name', '%first_name', '%user_data', '%billing_to', '%shipping_to', '%items', '%email', '%site', '%uri', '%uri_brief', '%date') + ); } - return $shipping; } -?> --- modules/ecommerce/shipping/shipping.install.orig 2006-05-18 13:02:41.000000000 -0400 +++ modules/ecommerce/shipping/shipping.install 2006-05-25 13:58:15.000000000 -0400 @@ -8,63 +8,85 @@ switch ($GLOBALS['db_type']) { case 'mysql': case 'mysqli': - db_query("CREATE TABLE {ec_shipping_shipping} ( - item_slot varchar(25) NOT NULL default '0', - price_slot varchar(25) NOT NULL default '0', - KEY item_slot (item_slot), - KEY price_slot (price_slot) + db_query("CREATE TABLE {ec_shipping_product} ( + vid int(10) unsigned NOT NULL default '0', + nid int(10) unsigned NOT NULL default '0', + service varchar(40) NOT NULL default '0', + method varchar(40) NOT NULL default '0', + KEY (vid), + KEY nid (nid), + KEY (service), + KEY (method) ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;"); - db_query("INSERT INTO {ec_shipping_shipping} VALUES ('1', '0.00');"); - db_query("INSERT INTO {ec_shipping_shipping} VALUES ('2-4', '0.00');"); - db_query("INSERT INTO {ec_shipping_shipping} VALUES ('5', '0.00');"); - db_query("INSERT INTO {ec_shipping_shipping} VALUES ('6-10', '0.00');"); - db_query("INSERT INTO {ec_shipping_shipping} VALUES ('10-1000', '0.00');"); - - db_query("CREATE TABLE {ec_subtotal_shipping} ( - item_slot varchar(25) NOT NULL default '0', - price_slot varchar(25) NOT NULL default '0', - KEY item_slot (item_slot), - KEY price_slot (price_slot) + db_query("CREATE TABLE {ec_shipping_transaction} ( + txnid int(10) unsigned NOT NULL DEFAULT '0', + service varchar(255) NOT NULL DEFAULT '', + method varchar(255) NOT NULL DEFAULT '', + cost decimal(10,2) UNSIGNED NOT NULL DEFAULT '0.00', + PRIMARY KEY (txnid) ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;"); - db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('0-0.99', '0.00');"); - db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('1.00-10.99', '0.00');"); - db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('11.00-50.00', '0.00');"); - db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('50.00-10000', '0.00');"); - - db_query("CREATE TABLE {ec_per_product_shipping} ( - nid int(10) unsigned NOT NULL default '0', - shipping_fee decimal(10,2) unsigned NOT NULL default '0.00', - PRIMARY KEY (nid) + db_query("CREATE TABLE {ec_shipping_product_attribute} ( + pid int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + vid int(10) UNSIGNED NOT NULL DEFAULT '0', + nid int(10) UNSIGNED NOT NULL DEFAULT '0', + field varchar(255) NOT NULL DEFAULT '', + value varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY pid (pid), + KEY field (field), + KEY vid (vid), + KEY nid (nid) + ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;"); + db_query("CREATE TABLE {ec_shipping_shipfrom} ( + sid int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + title varchar(255) NOT NULL DEFAULT '', + code varchar(255) NOT NULL DEFAULT '', + city varchar(255) NOT NULL DEFAULT '', + region varchar(255) NOT NULL DEFAULT '', + country varchar(2) NOT NULL DEFAULT '', + status int(3) NOT NULL DEFAULT 0, + PRIMARY KEY sid (sid), + KEY title (title) ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;"); break; case 'pgsql': db_query("CREATE TABLE {ec_shipping_shipping} ( - item_slot varchar(25) NOT NULL default '0', - price_slot varchar(25) NOT NULL default '0' + vid integer(10) NOT NULL default '0', + nid integer(10) NOT NULL default '0', + service varchar(40) NOT NULL default '0', + method varchar(40) NOT NULL default '0', + KEY (vid), + KEY nid (nid), + KEY (service), + KEY (method) )"); - db_query("CREATE INDEX {ec_shipping_shipping}_item_slot ON {ec_shipping_shipping} (item_slot)"); - db_query("CREATE INDEX {ec_shipping_shipping}_price_slot ON {ec_shipping_shipping} (price_slot)"); - db_query("INSERT INTO {ec_shipping_shipping} VALUES ('1', '0.00');"); - db_query("INSERT INTO {ec_shipping_shipping} VALUES ('2-4', '0.00');"); - db_query("INSERT INTO {ec_shipping_shipping} VALUES ('5', '0.00');"); - db_query("INSERT INTO {ec_shipping_shipping} VALUES ('6-10', '0.00');"); - db_query("INSERT INTO {ec_shipping_shipping} VALUES ('10-1000', '0.00');"); - - db_query("CREATE TABLE {ec_subtotal_shipping} ( - item_slot varchar(25) NOT NULL default '0', - price_slot varchar(25) NOT NULL default '0' + db_query("CREATE TABLE {ec_shipping_transaction} ( + txnid int(10) unsigned NOT NULL DEFAULT '0', + service varchar(255) NOT NULL DEFAULT '', + method varchar(255) NOT NULL DEFAULT '', + cost decimal(10,2) UNSIGNED NOT NULL DEFAULT '0.00', + PRIMARY KEY (txnid) )"); - db_query("CREATE INDEX {ec_subtotal_shipping}_item_slot ON {ec_subtotal_shipping} (item_slot)"); - db_query("CREATE INDEX {ec_subtotal_shipping}_price_slot ON {ec_subtotal_shipping} (price_slot)"); - db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('0-0.99', '0.00');"); - db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('1.00-10.99', '0.00');"); - db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('11.00-50.00', '0.00');"); - db_query("INSERT INTO {ec_subtotal_shipping} VALUES ('50.00-10000', '0.00');"); - - db_query("CREATE TABLE {ec_per_product_shipping} ( - nid integer NOT NULL default '0', - shipping_fee decimal(10,2) NOT NULL default '0.00', - PRIMARY KEY (nid) + db_query("CREATE TABLE {ec_shipping_product_attribute} ( + pid int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + vid int(10) UNSIGNED NOT NULL DEFAULT '0', + nid int(10) UNSIGNED NOT NULL DEFAULT '0', + field varchar(255) NOT NULL DEFAULT '', + value varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY pid (pid), + KEY field (field), + KEY vid (vid), + KEY nid (nid) + )"); + db_query("CREATE TABLE {ec_shipping_shipfrom} ( + sid int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + title varchar(255) NOT NULL DEFAULT '', + code varchar(255) NOT NULL DEFAULT '', + city varchar(255) NOT NULL DEFAULT '', + region varchar(255) NOT NULL DEFAULT '', + country varchar(2) NOT NULL DEFAULT '', + status int(3) NOT NULL DEFAULT 0, + PRIMARY KEY sid (sid), + KEY title (title) )"); break; } @@ -84,3 +106,8 @@ } return array(); } + +function shipping_update_2() { + shipping_install(); +} + --- modules/ecommerce/product/product.module.orig 2006-05-25 13:04:50.000000000 -0400 +++ modules/ecommerce/product/product.module 2006-05-25 13:04:54.000000000 -0400 @@ -224,21 +224,6 @@ } } - // Handle shipping. - // Don't test for if it *is* 'per_product', test for if it *supports* per_product - if (function_exists(variable_get('shipping_method', 'none').'_shippingapi')) { - // we can't use module_invoke, because one module could define multiple types - $f = variable_get('shipping_method', 'none').'_shippingapi'; - $per_product = $f($edit, 'per_product'); - } - if ($per_product && in_array('is_shippable', (array)module_invoke($product->ptype, 'productapi', $product, 'attributes', 'is_shippable'))) { - if ($shipping = module_invoke(variable_get('shipping_method', 'none'), 'shippingapi', $product, 'per_product_load')) { - foreach ($shipping as $key => $value) { - $product->$key = $value; - } - } - } - $products[$node->vid] = $product; } @@ -296,6 +281,16 @@ ); } else { + + foreach(product_get_ptypes() as $type => $description) { + $items[] = array( + 'path' => 'admin/settings/content-types/product/' . $type, + 'title' => $description, + 'callback' => 'product_types_configure', + 'access' => user_access('administer products'), + 'type' => MENU_CALLBACK + ); + } if (arg(0) == 'node' && is_numeric(arg(1)) && user_access('administer products')) { // Only add the product-tab for non-product pages: if (db_result(db_query(db_rewrite_sql("SELECT COUNT(n.nid) FROM {node} n WHERE n.nid = %d AND n.type != 'product'"), arg(1))) > 0) { @@ -339,14 +334,56 @@ * Implementation of hook_form_alter() */ function product_form_alter($form_id, &$form) { - if (isset($form['type']) && $form_id == $form['type']['#value'] .'_node_form') { - foreach (array('ptype') as $key) { - $form[$key] = array('#type' => 'value', '#value' => $form['#node']->$key); + if (isset($form['type'])) { + if ($form_id == $form['type']['#value'] .'_node_form') { + foreach (array('ptype') as $key) { + $form[$key] = array('#type' => 'value', '#value' => $form['#node']->$key); + } + } + // Add a fieldset with links for configuring individual product types. + else if ($form_id == $form['type']['#value'] .'_node_settings') { + $form['product_types'] = array( + '#type' => 'fieldset', + '#title' => t('Configure product types'), + '#collapsible' => TRUE, + '#weight' => -10, + '#children' => product_types_configure() + ); } } } /** + * Menu callback; presents each product type configuration page. + */ +function product_types_configure() { + if (arg(4)) { + $form = array('#ptype' => arg(4)); + return system_settings_form('product_type_settings', $form); + } + else { + $header = array(t('Type'), t('Operations')); + + $rows = array(); + foreach (product_get_ptypes() as $type => $name) { + $form = array('#ptype' => $type); + // Only show this link if at least one module has added settings. + // We test for > 1 because we are sending one element, the ptype. + // We don't use module_invoke because we need to pass by reference. + foreach (module_implements('form_alter') as $module) { + $function = $module . '_form_alter'; + $function('product_type_settings', $form); + } + if (count($form) > 1) { + $rows[] = array($name, l(t('configure'), 'admin/settings/content-types/product/'. $type)); + } + } + + return theme('table', $header, $rows); + } +} + +/** * Implementation of hook_nodeapi(). * * Provide product functionality for nodes that are not products @@ -741,10 +778,6 @@ } } - if ($edit->per_product_shipping) { - module_invoke(variable_get('shipping_method', 'none'), 'shippingapi', $edit, 'per_product_validate'); - } - if (!$errors['ptype']) { $f = $edit->ptype. '_productapi'; if (function_exists($f)) { @@ -769,10 +802,6 @@ $node->is_recurring = ($node->price_interval) ? 1 : 0; $fields = product_fields(); - /* - TODO: ADD SHIPPING API CALLBACKS - */ - /* Be very sure that we have a product entry to update! */ if (db_result(db_query('SELECT COUNT(vid) FROM {ec_product} WHERE vid = %d', $node->vid))) { foreach ($node as $key => $value) { @@ -784,9 +813,6 @@ $v[] = $node->vid; db_query('UPDATE {ec_product} SET '. implode(', ', $q) ." WHERE vid = %d", $v); module_invoke($node->ptype, 'productapi', $node, 'update'); - if ($node->per_product_shipping) { - module_invoke(variable_get('shipping_method', 'none'), 'shippingapi', $node, 'per_product_update'); - } } else { foreach ($node as $key => $value) { @@ -799,9 +825,6 @@ db_query('INSERT INTO {ec_product} ('. implode(', ', $k) .') VALUES('. implode(', ', $s) .')', $v); module_invoke($node->ptype, 'productapi', $node, 'insert'); - if ($node->per_product_shipping) { - module_invoke(variable_get('shipping_method', 'none'), 'shippingapi', $node, 'per_product_insert'); - } } } } @@ -928,17 +951,30 @@ return $output; } -function product_is_shippable($vid) { +/** + * Determine if a product or a product type is shippable. + * + * If a vid is given, the test is for a particular product, + * whereas if a ptype is given it's for the ptype. + * + * @param $vid + * Version id of product. + * @param $ptype + * Product type. + * @return + * Boolean. + */ +function product_is_shippable($vid = NULL, $ptype = NULL) { - $product = db_fetch_object(db_query('SELECT * FROM {ec_product} WHERE vid = %d', $vid)); + $product = $vid ? db_fetch_object(db_query('SELECT ptype FROM {ec_product} WHERE vid = %d', $vid)) : (object) array('ptype' => $ptype); - $attributes = (array) module_invoke($product->ptype, 'productapi', $product, 'attributes', 'is_shippable'); + $attributes = (array) module_invoke($product->ptype, 'productapi', $product, 'attributes'); if (in_array('is_shippable', $attributes)) { - return true; + return TRUE; } - return false; + return FALSE; } /**