uc_quote.module 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  1. <?php
  2. /**
  3. * @file
  4. * The controller module for fulfillment modules that process physical goods.
  5. *
  6. * This module collects information that is necessary to transport products from
  7. * one place to another. Its hook system is used by fulfillment modules to get
  8. * their specific information so that a shipment may be quoted and requested.
  9. */
  10. /**
  11. * Implements hook_permission().
  12. */
  13. function uc_quote_permission() {
  14. return array(
  15. 'configure quotes' => array(
  16. 'title' => t('Configure quotes'),
  17. ),
  18. );
  19. }
  20. /**
  21. * Implements hook_menu().
  22. */
  23. function uc_quote_menu() {
  24. $items = array();
  25. $items['admin/store/settings/quotes'] = array(
  26. 'title' => 'Shipping quotes',
  27. 'description' => 'Configure shipping quotes.',
  28. 'page callback' => 'drupal_get_form',
  29. 'page arguments' => array('uc_quote_method_settings'),
  30. 'access arguments' => array('configure quotes'),
  31. 'file' => 'uc_quote.admin.inc',
  32. );
  33. $items['admin/store/settings/quotes/methods'] = array(
  34. 'title' => 'Methods',
  35. 'type' => MENU_DEFAULT_LOCAL_TASK,
  36. );
  37. $items['admin/store/settings/quotes/settings'] = array(
  38. 'title' => 'Settings',
  39. 'description' => 'Configure shipping quote options.',
  40. 'page callback' => 'drupal_get_form',
  41. 'page arguments' => array('uc_quote_admin_settings'),
  42. 'access arguments' => array('configure quotes'),
  43. 'type' => MENU_LOCAL_TASK,
  44. 'file' => 'uc_quote.admin.inc',
  45. );
  46. $items['admin/store/settings/quotes/settings/general'] = array(
  47. 'title' => 'Basic settings',
  48. 'type' => MENU_DEFAULT_LOCAL_TASK,
  49. );
  50. $items += rules_ui()->config_menu('admin/store/settings/quotes');
  51. return $items;
  52. }
  53. /**
  54. * Implements hook_init().
  55. */
  56. function uc_quote_init() {
  57. global $conf;
  58. $conf['i18n_variables'][] = 'uc_quote_err_msg';
  59. $conf['i18n_variables'][] = 'uc_quote_pane_description';
  60. }
  61. /**
  62. * Implements hook_theme().
  63. */
  64. function uc_quote_theme() {
  65. return array(
  66. 'uc_quote_method_settings' => array(
  67. 'render element' => 'form',
  68. 'file' => 'uc_quote.admin.inc',
  69. ),
  70. 'uc_cart_pane_quotes' => array(
  71. 'render element' => 'form',
  72. 'file' => 'uc_quote.theme.inc',
  73. ),
  74. 'uc_quote_returned_rates' => array(
  75. 'render element' => 'form',
  76. 'file' => 'uc_quote.theme.inc',
  77. ),
  78. );
  79. }
  80. /**
  81. * Implements hook_node_insert().
  82. */
  83. function uc_quote_node_insert($node) {
  84. uc_quote_node_update($node);
  85. }
  86. /**
  87. * Implements hook_node_update().
  88. */
  89. function uc_quote_node_update($node) {
  90. if (uc_product_is_product($node->type)) {
  91. if (isset($node->shipping_type)) {
  92. uc_quote_set_shipping_type('product', $node->nid, $node->shipping_type);
  93. }
  94. if (!empty($node->shipping_address['street1'])) {
  95. db_merge('uc_quote_product_locations')
  96. ->key(array('nid' => $node->nid))
  97. ->fields(array(
  98. 'first_name' => $node->shipping_address['first_name'],
  99. 'last_name' => $node->shipping_address['last_name'],
  100. 'company' => $node->shipping_address['company'],
  101. 'street1' => $node->shipping_address['street1'],
  102. 'street2' => $node->shipping_address['street2'],
  103. 'city' => $node->shipping_address['city'],
  104. 'zone' => $node->shipping_address['zone'],
  105. 'postal_code' => $node->shipping_address['postal_code'],
  106. 'country' => $node->shipping_address['country'],
  107. 'phone' => $node->shipping_address['phone'],
  108. ))
  109. ->execute();
  110. }
  111. else {
  112. db_delete('uc_quote_product_locations')
  113. ->condition('nid', $node->nid)
  114. ->execute();
  115. }
  116. }
  117. }
  118. /**
  119. * Implements hook_node_load().
  120. */
  121. function uc_quote_node_load($nodes, $types) {
  122. $product_types = array_intersect(uc_product_types(), $types);
  123. if (empty($product_types)) {
  124. return;
  125. }
  126. $shipping_type = variable_get('uc_store_shipping_type', 'small_package');
  127. $shipping_types = db_query("SELECT id, shipping_type FROM {uc_quote_shipping_types} WHERE id_type = :type AND id IN (:ids)", array(':type' => 'product', ':ids' => array_keys($nodes)))->fetchAllKeyed();
  128. $addresses = db_query("SELECT nid, first_name, last_name, company, street1, street2, city, zone, postal_code, country, phone FROM {uc_quote_product_locations} WHERE nid IN (:nids)", array(':nids' => array_keys($nodes)), array('fetch' => 'UcAddress'))->fetchAllAssoc('nid');
  129. foreach ($nodes as $nid => &$node) {
  130. if (!in_array($node->type, $product_types)) {
  131. continue;
  132. }
  133. if (isset($shipping_types[$nid])) {
  134. $node->shipping_type = $shipping_types[$nid];
  135. }
  136. else {
  137. $node->shipping_type = $shipping_type;
  138. }
  139. if (isset($addresses[$nid])) {
  140. $node->shipping_address = (array) $addresses[$nid];
  141. unset($node->shipping_address['nid']);
  142. }
  143. else {
  144. $node->shipping_address = (array) variable_get('uc_quote_store_default_address', new UcAddress());
  145. }
  146. }
  147. }
  148. /**
  149. * Implements hook_node_delete().
  150. */
  151. function uc_quote_node_delete($node) {
  152. db_delete('uc_quote_shipping_types')
  153. ->condition('id_type', 'product')
  154. ->condition('id', $node->nid)
  155. ->execute();
  156. db_delete('uc_quote_product_locations')
  157. ->condition('nid', $node->nid)
  158. ->execute();
  159. }
  160. /**
  161. * Implements hook_form_alter().
  162. *
  163. * Adds a default shipping origin address for products. If left blank, the
  164. * store's default origin address will be used.
  165. */
  166. function uc_quote_form_alter(&$form, &$form_state, $form_id) {
  167. // Alter the product node form.
  168. if (uc_product_is_product_form($form)) {
  169. // Get the shipping address.
  170. if (isset($form['#node']->shipping_address)) {
  171. $address = $form['#node']->shipping_address;
  172. }
  173. // Use the store default if the product does not have an address set.
  174. if (empty($address)) {
  175. $address = variable_get('uc_quote_store_default_address', new UcAddress());
  176. }
  177. // Initialize the shipping fieldset array.
  178. if (!isset($form['shipping'])) {
  179. $form['shipping'] = array();
  180. }
  181. $form['shipping'] += array(
  182. '#type' => 'fieldset',
  183. '#title' => t('Shipping settings'),
  184. '#collapsible' => TRUE,
  185. '#weight' => -3,
  186. '#attributes' => array('class' => array('product-shipping')),
  187. '#group' => 'additional_settings',
  188. );
  189. $form['shipping']['shipping_type'] = array(
  190. '#type' => 'select',
  191. '#title' => t('Default product shipping type'),
  192. '#empty_value' => '',
  193. '#empty_option' => t('- Store default -'),
  194. '#default_value' => isset($form['#node']->nid) ? uc_quote_get_shipping_type('product', $form['#node']->nid) : '',
  195. '#options' => uc_quote_shipping_type_options(),
  196. '#weight' => -7,
  197. );
  198. // Add the default pickup address fieldset.
  199. $form['shipping']['shipping_address'] = array(
  200. '#type' => 'fieldset',
  201. '#title' => t('Default product pickup address'),
  202. '#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 <a href="!url">store pickup address</a>.', array('!url' => url('admin/store/settings/quotes/settings'))),
  203. '#collapsible' => TRUE,
  204. '#collapsed' => TRUE,
  205. '#weight' => -6,
  206. );
  207. $form['shipping']['shipping_address']['#tree'] = TRUE;
  208. $form['shipping']['shipping_address']['address'] = array(
  209. '#type' => 'uc_address',
  210. '#default_value' => isset($form_state['values']['shipping_address']) ? $form_state['values']['shipping_address'] : $address,
  211. '#required' => FALSE,
  212. );
  213. }
  214. }
  215. /**
  216. * Implements hook_uc_cart_pane().
  217. */
  218. function uc_quote_uc_cart_pane($items) {
  219. if (arg(0) == 'cart') {
  220. if (!variable_get('uc_cap_quotes_enabled', FALSE) || (variable_get('uc_cart_delivery_not_shippable', TRUE) && !uc_cart_is_shippable())) {
  221. return array();
  222. }
  223. $body = drupal_get_form('uc_cart_pane_quotes', $items);
  224. }
  225. else {
  226. $body = '';
  227. }
  228. $panes['quotes'] = array(
  229. 'title' => t('Shipping quotes'),
  230. 'enabled' => FALSE,
  231. 'weight' => 5,
  232. 'body' => $body,
  233. );
  234. return $panes;
  235. }
  236. /**
  237. * Defines the shipping quote checkout pane.
  238. */
  239. function uc_quote_uc_checkout_pane() {
  240. $panes['quotes'] = array(
  241. 'callback' => 'uc_checkout_pane_quotes',
  242. 'title' => t('Calculate shipping cost'),
  243. 'desc' => t('Extra information necessary to ship.'),
  244. 'weight' => 5,
  245. 'shippable' => TRUE,
  246. );
  247. return $panes;
  248. }
  249. /**
  250. * Implements hook_uc_order_pane().
  251. *
  252. * Defines the shipping quote order pane.
  253. */
  254. function uc_quote_uc_order_pane() {
  255. $panes['quotes'] = array(
  256. 'callback' => 'uc_order_pane_quotes',
  257. 'title' => t('Shipping quote'),
  258. 'desc' => t('Get a shipping quote for the order from a quoting module.'),
  259. 'class' => 'pos-left',
  260. 'weight' => 7,
  261. 'show' => array('edit'),
  262. );
  263. return $panes;
  264. }
  265. /**
  266. * Implements hook_uc_order().
  267. */
  268. function uc_quote_uc_order($op, $order, $arg2) {
  269. switch ($op) {
  270. case 'save':
  271. if (isset($order->quote['method'])) {
  272. db_merge('uc_order_quotes')
  273. ->key(array('order_id' => $order->order_id))
  274. ->fields(array(
  275. 'method' => $order->quote['method'],
  276. 'accessorials' => $order->quote['accessorials'],
  277. 'rate' => $order->quote['rate'],
  278. ))
  279. ->execute();
  280. }
  281. break;
  282. case 'load':
  283. $quote = db_query("SELECT method, accessorials, rate FROM {uc_order_quotes} WHERE order_id = :id", array(':id' => $order->order_id))->fetchAssoc();
  284. $order->quote = $quote;
  285. $order->quote['accessorials'] = strval($quote['accessorials']);
  286. break;
  287. case 'delete':
  288. db_delete('uc_order_quotes')
  289. ->condition('order_id', $order->order_id)
  290. ->execute();
  291. break;
  292. }
  293. }
  294. /**
  295. * Implements hook_uc_line_item().
  296. */
  297. function uc_quote_uc_line_item() {
  298. $items['shipping'] = array(
  299. 'title' => t('Shipping'),
  300. 'weight' => 1,
  301. 'default' => FALSE,
  302. 'stored' => TRUE,
  303. 'calculated' => TRUE,
  304. 'display_only' => FALSE,
  305. 'add_list' => TRUE,
  306. );
  307. return $items;
  308. }
  309. /**
  310. * Implements hook_uc_shipping_type().
  311. */
  312. function uc_quote_uc_shipping_type() {
  313. $weight = variable_get('uc_quote_type_weight', array('small_package' => 0));
  314. $types = array();
  315. $types['small_package'] = array(
  316. 'id' => 'small_package',
  317. 'title' => t('Small package'),
  318. 'weight' => $weight['small_package'],
  319. );
  320. return $types;
  321. }
  322. /**
  323. * Stores the shipping type of products and manufacturers.
  324. *
  325. * Fulfillment modules are invoked for products that match their shipping type.
  326. * This function stores the shipping type of a product or a manufacturer.
  327. *
  328. * @param $id_type
  329. * Type can be 'product' or 'manufacturer'.
  330. * @param $id
  331. * Either the node id or term id of the object receiving the shipping type.
  332. * @param $shipping_type
  333. * The type of product that is fulfilled by various fulfillment modules.
  334. */
  335. function uc_quote_set_shipping_type($id_type, $id, $shipping_type) {
  336. if ($shipping_type !== '') {
  337. db_merge('uc_quote_shipping_types')
  338. ->key(array(
  339. 'id_type' => $id_type,
  340. 'id' => $id,
  341. ))
  342. ->fields(array('shipping_type' => $shipping_type))
  343. ->execute();
  344. }
  345. else {
  346. db_delete('uc_quote_shipping_types')
  347. ->condition('id_type', $id_type)
  348. ->condition('id', $id)
  349. ->execute();
  350. }
  351. }
  352. /**
  353. * Retrieves shipping type information from the database.
  354. *
  355. * @param $id_type
  356. * Type can be 'product' or 'manufacturer'.
  357. * @param $id
  358. * Either the node id or term id of the object that was assigned
  359. * the shipping type.
  360. *
  361. * @return
  362. * The shipping type.
  363. */
  364. function uc_quote_get_shipping_type($id_type, $id) {
  365. static $types = array();
  366. if (!isset($types[$id_type][$id])) {
  367. $types[$id_type][$id] = db_query("SELECT shipping_type FROM {uc_quote_shipping_types} WHERE id_type = :type AND id = :id", array(':type' => $id_type, ':id' => $id))->fetchField();
  368. }
  369. return $types[$id_type][$id];
  370. }
  371. /**
  372. * Gets a product's shipping type.
  373. *
  374. * @param $product
  375. * A product object.
  376. *
  377. * @return
  378. * The product's shipping type, or the store's default shipping type if
  379. * the product's is not set.
  380. */
  381. function uc_product_get_shipping_type($product) {
  382. $shipping_type = variable_get('uc_store_shipping_type', 'small_package');
  383. if (isset($product->nid) &&
  384. $type = uc_quote_get_shipping_type('product', $product->nid)) {
  385. $shipping_type = $type;
  386. }
  387. return $shipping_type;
  388. }
  389. /**
  390. * Gets a product's default shipping address.
  391. *
  392. * @param $nid
  393. * A product node id.
  394. *
  395. * @return
  396. * An address object containing the product's default shipping address, or
  397. * the store's shipping address if the product's is not set.
  398. */
  399. function uc_quote_get_default_shipping_address($nid) {
  400. $address = db_query("SELECT first_name, last_name, company, street1, street2, city, zone, postal_code, country, phone FROM {uc_quote_product_locations} WHERE nid = :nid", array(':nid' => $nid))->fetchObject('UcAddress');
  401. if (empty($address)) {
  402. $address = variable_get('uc_quote_store_default_address', new UcAddress());
  403. }
  404. return $address;
  405. }
  406. /**
  407. * Cart pane callback.
  408. *
  409. * @see theme_uc_cart_pane_quotes()
  410. * @ingroup forms
  411. */
  412. function uc_cart_pane_quotes($form, &$form_state, $items) {
  413. global $user;
  414. $order = new UcOrder($user->uid);
  415. $order->delivery_country = isset($form_state['values']['delivery_country']) ? $form_state['values']['delivery_country'] : uc_store_default_country();
  416. $order->delivery_zone = isset($form_state['values']['delivery_zone']) ? $form_state['values']['delivery_zone'] : '';
  417. $order->delivery_postal_code = isset($form_state['values']['delivery_postal_code']) ? $form_state['values']['delivery_postal_code'] : '';
  418. $order->products = $items;
  419. $form['#attached']['css'][] = drupal_get_path('module', 'uc_quote') . '/uc_quote.css';
  420. $form['address'] = array(
  421. '#type' => 'uc_address',
  422. '#default_value' => array(
  423. 'delivery_country' => $order->delivery_country,
  424. 'delivery_zone' => $order->delivery_zone,
  425. 'delivery_postal_code' => $order->delivery_postal_code,
  426. ),
  427. '#required' => TRUE,
  428. '#key_prefix' => 'delivery',
  429. );
  430. $form['get_quote'] = array(
  431. '#type' => 'button',
  432. '#value' => t('Calculate'),
  433. '#ajax' => array(
  434. 'callback' => 'uc_quote_cart_returned_rates',
  435. 'wrapper' => 'quote',
  436. ),
  437. );
  438. module_load_include('inc', 'uc_quote', 'uc_quote.pages');
  439. $quotes = uc_quote_assemble_quotes($order);
  440. $quote_options = array();
  441. if (!empty($quotes)) {
  442. foreach ($quotes as $method => $data) {
  443. foreach ($data as $accessorial => $quote) {
  444. $key = $method . '---' . $accessorial;
  445. if (isset($quote['rate'])) {
  446. $quote_options[$key] = t('!label: !price', array('!label' => $quote['option_label'], '!price' => $quote['format']));
  447. }
  448. }
  449. }
  450. }
  451. $form['quote'] = array(
  452. '#theme' => 'item_list',
  453. '#items' => $quote_options,
  454. '#prefix' => '<div id="quote">',
  455. '#suffix' => '</div>',
  456. );
  457. return $form;
  458. }
  459. /**
  460. * Shipping quote checkout pane callback.
  461. *
  462. * Selects a quoting method based on the enabled methods' weight and the types
  463. * of products in the cart. The "Get Quotes" button fires a callback that
  464. * returns a form for the customer to select a rate based on their needs and
  465. * preferences.
  466. *
  467. * Adds a line item to the order that records the chosen shipping quote.
  468. *
  469. * @see uc_quote_checkout_pane_quotes_submit()
  470. */
  471. function uc_checkout_pane_quotes($op, &$order, $form = NULL, &$form_state = NULL) {
  472. global $user;
  473. switch ($op) {
  474. case 'view':
  475. $description = filter_xss_admin(variable_get('uc_quote_pane_description', t('Shipping quotes are generated automatically when you enter your address and may be updated manually with the button below.')));
  476. $contents['#attached']['css'][] = drupal_get_path('module', 'uc_quote') . '/uc_quote.css';
  477. $contents['uid'] = array(
  478. '#type' => 'hidden',
  479. '#value' => $user->uid,
  480. );
  481. $contents['quote_button'] = array(
  482. '#type' => 'submit',
  483. '#value' => t('Click to calculate shipping'),
  484. '#submit' => array('uc_quote_checkout_pane_quotes_submit'),
  485. '#weight' => 0,
  486. '#ajax' => array(
  487. 'effect' => 'slide',
  488. 'progress' => array(
  489. 'type' => 'bar',
  490. 'message' => t('Receiving quotes...'),
  491. ),
  492. ),
  493. // Shipping quotes can be retrieved even if the form doesn't validate.
  494. '#limit_validation_errors' => array(),
  495. );
  496. $contents['quotes'] = array(
  497. '#tree' => TRUE,
  498. '#prefix' => '<div id="quote">',
  499. '#suffix' => '</div>',
  500. '#weight' => 1,
  501. );
  502. // If this was an Ajax request, we reinvoke the 'prepare' op to ensure
  503. // that we catch any changes in panes heavier than this one.
  504. if (isset($form_state['triggering_element'])) {
  505. uc_checkout_pane_quotes('prepare', $order, $form, $form_state);
  506. }
  507. $contents['quotes'] += $order->quote_form;
  508. $form_state['uc_ajax']['uc_quote']['panes][quotes][quote_button'] = array(
  509. 'payment-pane' => 'uc_ajax_replace_checkout_pane',
  510. 'quotes-pane' => 'uc_ajax_replace_checkout_pane'
  511. );
  512. $form_state['uc_ajax']['uc_quote']['panes][quotes][quotes][quote_option'] = array(
  513. 'payment-pane' => 'uc_ajax_replace_checkout_pane',
  514. );
  515. return array('description' => $description, 'contents' => $contents);
  516. case 'prepare':
  517. case 'process':
  518. // If a quote was explicitly selected, add it to the order.
  519. if (isset($form['panes']['quotes']['quotes']['quote_option']['#value']) && isset($form['panes']['quotes']['quotes']['quote_option']['#default_value'])
  520. && $form['panes']['quotes']['quotes']['quote_option']['#value'] !== $form['panes']['quotes']['quotes']['quote_option']['#default_value']) {
  521. $quote_option = explode('---', $form_state['values']['panes']['quotes']['quotes']['quote_option']);
  522. $order->quote['method'] = $quote_option[0];
  523. $order->quote['accessorials'] = $quote_option[1];
  524. $order->data['uc_quote_selected'] = TRUE;
  525. }
  526. // If the current quote was never explicitly selected, discard it and
  527. // use the default.
  528. if (empty($order->data['uc_quote_selected'])) {
  529. unset($order->quote);
  530. }
  531. // Ensure that the form builder uses the default value to decide which
  532. // radio button should be selected.
  533. unset($form_state['input']['panes']['quotes']['quotes']['quote_option']);
  534. $order->quote_form = uc_quote_build_quote_form($order, !empty($form_state['quote_requested']));
  535. $default_option = _uc_quote_extract_default_option($order->quote_form);
  536. if ($default_option) {
  537. $order->quote['rate'] = $order->quote_form[$default_option]['rate']['#value'];
  538. $quote_option = explode('---', $default_option);
  539. $order->quote['method'] = $quote_option[0];
  540. $order->quote['accessorials'] = $quote_option[1];
  541. $methods = uc_quote_methods();
  542. $method = $methods[$quote_option[0]];
  543. $label = $method['quote']['accessorials'][$quote_option[1]];
  544. $result = db_query("SELECT line_item_id FROM {uc_order_line_items} WHERE order_id = :id AND type = :type", array(':id' => $order->order_id, ':type' => 'shipping'));
  545. if ($lid = $result->fetchField()) {
  546. uc_order_update_line_item($lid,
  547. $label,
  548. $order->quote['rate']
  549. );
  550. }
  551. else {
  552. uc_order_line_item_add($order->order_id, 'shipping',
  553. $label,
  554. $order->quote['rate']
  555. );
  556. }
  557. }
  558. // If there is no default option, then no valid quote was selected.
  559. else {
  560. unset($order->quote);
  561. }
  562. if (!isset($order->quote) && $op == 'process' && variable_get('uc_quote_require_quote', TRUE)) {
  563. form_set_error('panes][quotes][quotes][quote_option', t('You must select a shipping option before continuing.'));
  564. return FALSE;
  565. }
  566. else {
  567. return TRUE;
  568. }
  569. case 'review':
  570. $review = array();
  571. $result = db_query("SELECT * FROM {uc_order_line_items} WHERE order_id = :id AND type = :type", array(':id' => $order->order_id, ':type' => 'shipping'));
  572. if ($line_item = $result->fetchAssoc()) {
  573. $review[] = array('title' => $line_item['title'], 'data' => theme('uc_price', array('price' => $line_item['amount'])));
  574. }
  575. return $review;
  576. }
  577. }
  578. /**
  579. * Form submission handler for uc_checkout_pane_quotes().
  580. *
  581. * @see uc_checkout_pane_quotes()
  582. */
  583. function uc_quote_checkout_pane_quotes_submit($form, &$form_state) {
  584. $form_state['rebuild'] = TRUE;
  585. $form_state['quote_requested'] = TRUE;
  586. }
  587. /**
  588. * Shipping quote order pane callback.
  589. *
  590. * @see uc_quote_order_pane_quotes_submit()
  591. * @see uc_quote_apply_quote_to_order()
  592. */
  593. function uc_order_pane_quotes($op, $order, &$form = NULL, &$form_state = NULL) {
  594. switch ($op) {
  595. case 'edit-form':
  596. $form['quotes']['quote_button'] = array(
  597. '#type' => 'submit',
  598. '#value' => t('Get shipping quotes'),
  599. '#submit' => array('uc_quote_order_pane_quotes_submit'),
  600. '#ajax' => array(
  601. 'callback' => 'uc_quote_replace_order_quotes',
  602. 'wrapper' => 'quote',
  603. 'effect' => 'slide',
  604. 'progress' => array(
  605. 'type' => 'bar',
  606. 'message' => t('Receiving quotes...'),
  607. ),
  608. ),
  609. );
  610. $form['quotes']['quotes'] = array(
  611. '#tree' => TRUE,
  612. '#prefix' => '<div id="quote">',
  613. '#suffix' => '</div>',
  614. );
  615. if (!empty($form_state['quote_requested'])) {
  616. // Rebuild form products, from uc_order_edit_form_submit()
  617. $order->products = array();
  618. if (isset($form_state['values']['products']) && is_array($form_state['values']['products'])) {
  619. foreach ($form_state['values']['products'] as $product) {
  620. $product['data'] = unserialize($product['data']);
  621. $product = (object)$product;
  622. $order->products[] = $product;
  623. }
  624. }
  625. $form['quotes']['quotes'] += uc_quote_build_quote_form($order);
  626. $form['quotes']['quotes']['add_quote'] = array(
  627. '#type' => 'submit',
  628. '#value' => t('Apply to order'),
  629. '#submit' => array('uc_quote_apply_quote_to_order'),
  630. '#ajax' => array(
  631. 'callback' => 'uc_quote_order_update_rates',
  632. 'effect' => 'fade',
  633. 'progress' => array(
  634. 'type' => 'throbber',
  635. 'message' => t('Applying quotes...'),
  636. ),
  637. ),
  638. );
  639. }
  640. $form_state['uc_ajax']['uc_quote']['ship_to][delivery_country'] = array(
  641. 'quote' => 'uc_quote_replace_order_quotes',
  642. );
  643. return $form;
  644. case 'edit-theme':
  645. return drupal_render($form['quotes']);
  646. }
  647. }
  648. /**
  649. * Form submission handler for uc_order_pane_quotes().
  650. *
  651. * @see uc_order_pane_quotes()
  652. */
  653. function uc_quote_order_pane_quotes_submit($form, &$form_state) {
  654. $form_state['quote_requested'] = ($form_state['triggering_element']['#value'] == $form['quotes']['quote_button']['#value']);
  655. $form_state['rebuild'] = TRUE;
  656. }
  657. /**
  658. * Ajax callback: Manually applies a shipping quote to an order.
  659. */
  660. function uc_quote_apply_quote_to_order($form, &$form_state) {
  661. if (isset($form_state['values']['quotes']['quote_option'])) {
  662. if ($order = $form_state['order']) {
  663. $quote_option = explode('---', $form_state['values']['quotes']['quote_option']);
  664. $order->quote['method'] = $quote_option[0];
  665. $order->quote['accessorials'] = $quote_option[1];
  666. $methods = uc_quote_methods();
  667. $method = $methods[$quote_option[0]];
  668. $label = $method['quote']['accessorials'][$quote_option[1]];
  669. $quote_option = $form_state['values']['quotes']['quote_option'];
  670. $order->quote['rate'] = $form_state['values']['quotes'][$quote_option]['rate'];
  671. $result = db_query("SELECT line_item_id FROM {uc_order_line_items} WHERE order_id = :id AND type = :type", array(':id' => $order->order_id, ':type' => 'shipping'));
  672. if ($lid = $result->fetchField()) {
  673. uc_order_update_line_item($lid,
  674. $label,
  675. $order->quote['rate']
  676. );
  677. $form_state['uc_quote'] = array(
  678. 'lid' => $lid,
  679. 'title' => $label,
  680. 'amount' => $order->quote['rate'],
  681. );
  682. }
  683. else {
  684. uc_order_line_item_add($order->order_id, 'shipping',
  685. $label,
  686. $order->quote['rate']
  687. );
  688. }
  689. // Save selected shipping
  690. uc_quote_uc_order('save', $order, '');
  691. // Update line items.
  692. $order->line_items = uc_order_load_line_items($order);
  693. $form_state['order'] = $order;
  694. $form_state['rebuild'] = TRUE;
  695. $form_state['quote_requested'] = FALSE;
  696. }
  697. }
  698. }
  699. /**
  700. * Calculates and returns the shipping quote selection form.
  701. */
  702. function uc_quote_build_quote_form($order, $show_errors = TRUE) {
  703. $return = array();
  704. module_load_include('inc', 'uc_quote', 'uc_quote.pages');
  705. $quotes = uc_quote_assemble_quotes($order);
  706. $quote_options = array();
  707. if (!empty($quotes)) {
  708. foreach ($quotes as $method => $data) {
  709. foreach ($data as $accessorial => $quote) {
  710. $key = $method . '---' . $accessorial;
  711. if (isset($quote['rate'])) {
  712. $quote_options[$key] = t('!label: !price', array('!label' => $quote['option_label'], '!price' => $quote['format']));
  713. $return[$key]['rate'] = array(
  714. '#type' => 'hidden',
  715. '#value' => $quote['rate'],
  716. );
  717. }
  718. if (!empty($quote['error'])) {
  719. $return[$key]['error'] = array(
  720. '#markup' => '<div class="quote-error">' . theme('item_list', array('items' => $quote['error'])) . '</div>',
  721. );
  722. }
  723. if (!empty($quote['notes'])) {
  724. $return[$key]['notes'] = array(
  725. '#markup' => '<div class="quote-notes">' . $quote['notes'] . '</div>',
  726. );
  727. }
  728. if (!empty($quote['debug'])) {
  729. $return[$key]['debug'] = array(
  730. '#markup' => '<pre>' . $quote['debug'] . '</pre>',
  731. );
  732. }
  733. if (!isset($quote['rate']) && isset($quote['label']) && count($return[$key])) {
  734. $return[$key]['#prefix'] = $quote['label'] . ': ';
  735. }
  736. }
  737. }
  738. }
  739. $num_quotes = count($quote_options);
  740. $default = key($quote_options);
  741. if ($num_quotes > 1) {
  742. if (isset($order->quote['method']) && isset($order->quote['accessorials'])) {
  743. $chosen = $order->quote['method'] . '---' . $order->quote['accessorials'];
  744. if (isset($quote_options[$chosen])) {
  745. $default = $chosen;
  746. }
  747. }
  748. $return['quote_option'] = array(
  749. '#type' => 'radios',
  750. '#options' => $quote_options,
  751. '#default_value' => $default,
  752. );
  753. }
  754. elseif ($num_quotes == 1) {
  755. $return['quote_option'] = array(
  756. '#type' => 'hidden',
  757. '#value' => $default,
  758. '#suffix' => $quote_options[$default],
  759. );
  760. }
  761. elseif ($show_errors) {
  762. $return['error'] = array(
  763. '#markup' => filter_xss_admin(variable_get('uc_quote_err_msg', t("There were problems getting a shipping quote. Please verify the delivery address and product information and try again.\nIf this does not resolve the issue, please call @phone to complete your order.", array('@phone' => variable_get('uc_store_phone', NULL))))),
  764. );
  765. }
  766. $return['#theme'] = 'uc_quote_returned_rates';
  767. return $return;
  768. }
  769. /**
  770. * Ajax callback: Shows estimated shipping quotes on the cart page.
  771. */
  772. function uc_quote_cart_returned_rates($form, $form_state) {
  773. $commands[] = ajax_command_replace('#quote', trim(drupal_render($form['quote'])));
  774. $commands[] = ajax_command_prepend('#quote', trim(theme('status_messages')));
  775. return array('#type' => 'ajax', '#commands' => $commands);
  776. }
  777. /**
  778. * Gets the default (selected) quote option from the built form element.
  779. *
  780. * @param $quote_form
  781. * The quotes form-element.
  782. *
  783. * @return
  784. * The default quote option, or FALSE if none exists.
  785. */
  786. function _uc_quote_extract_default_option($quote_form) {
  787. if (isset($quote_form['quote_option']['#value'])) {
  788. return $quote_form['quote_option']['#value'];
  789. }
  790. elseif (isset($quote_form['quote_option']['#default_value'])) {
  791. return $quote_form['quote_option']['#default_value'];
  792. }
  793. else {
  794. return FALSE;
  795. }
  796. }
  797. /**
  798. * Ajax callback to update the quotes on the order edit form.
  799. */
  800. function uc_quote_replace_order_quotes($form, $form_state) {
  801. return $form['quotes']['quotes'];
  802. }
  803. /**
  804. * AJAX callback for applying shipping rates.
  805. */
  806. function uc_quote_order_update_rates($form, $form_state) {
  807. // Update shipping line item.
  808. if (isset($form_state['uc_quote'])) {
  809. $lid = $form_state['uc_quote']['lid'];
  810. $form['line_items'][$lid]['title']['#value'] = $form_state['uc_quote']['title'];
  811. $form['line_items'][$lid]['amount']['#value'] = $form_state['uc_quote']['amount'];
  812. }
  813. $commands[] = ajax_command_replace('#order-line-items', drupal_render($form['line_items']));
  814. // Reset shipping form.
  815. $commands[] = ajax_command_replace('#quote', drupal_render($form['quotes']['quotes']));
  816. $commands[] = ajax_command_prepend('#quote', theme('status_messages'));
  817. return array('#type' => 'ajax', '#commands' => $commands);
  818. }
  819. /**
  820. * Returns an array of available shipping quote methods.
  821. *
  822. * @param $all
  823. * If FALSE, only enabled shipping methods are returned.
  824. */
  825. function uc_quote_methods($all = FALSE) {
  826. $enabled = variable_get('uc_quote_enabled', array());
  827. $weight = variable_get('uc_quote_method_weight', array());
  828. $methods = array();
  829. foreach (module_invoke_all('uc_shipping_method') as $id => $method) {
  830. // Set defaults.
  831. $method += array(
  832. 'enabled' => FALSE,
  833. 'weight' => 0,
  834. );
  835. // Override defaults with store configuration, if any.
  836. if (isset($enabled[$id])) {
  837. $method['enabled'] = $enabled[$id];
  838. }
  839. if (isset($weight[$id])) {
  840. $method['weight'] = $weight[$id];
  841. }
  842. if ($all || $method['enabled']) {
  843. $methods[$id] = $method;
  844. }
  845. }
  846. uasort($methods, '_uc_quote_type_sort');
  847. return $methods;
  848. }
  849. /**
  850. * Callback for uasort().
  851. */
  852. function _uc_quote_type_sort($a, $b) {
  853. $aw = $a['weight'];
  854. $bw = $b['weight'];
  855. if ($aw == $bw) {
  856. return strcasecmp($a['id'], $b['id']);
  857. }
  858. else {
  859. return ($aw < $bw) ? -1 : 1;
  860. }
  861. }
  862. /**
  863. * Callback for uasort().
  864. *
  865. * Sorts service rates by increasing price.
  866. */
  867. function uc_quote_price_sort($a, $b) {
  868. $ar = $a['rate'];
  869. $br = $b['rate'];
  870. if ($ar == $br) {
  871. return 0;
  872. }
  873. else {
  874. return ($ar < $br) ? -1 : 1;
  875. }
  876. }
  877. /**
  878. * Returns an options array of shipping types.
  879. */
  880. function uc_quote_shipping_type_options() {
  881. $types = array();
  882. $ship_types = uc_quote_get_shipping_types();
  883. uasort($ship_types, '_uc_quote_type_sort');
  884. foreach ($ship_types as $ship_type) {
  885. $types[$ship_type['id']] = $ship_type['title'];
  886. }
  887. if (empty($types)) {
  888. $types['small_package'] = t('Small package');
  889. }
  890. return $types;
  891. }
  892. /**
  893. * Returns an array of shipping types.
  894. */
  895. function uc_quote_get_shipping_types() {
  896. $args = array();
  897. $hook = 'uc_shipping_type';
  898. $return = array();
  899. foreach (module_implements($hook) as $module) {
  900. $function = $module . '_' . $hook;
  901. $result = call_user_func_array($function, $args);
  902. if (isset($result) && is_array($result)) {
  903. $return = array_merge($return, $result);
  904. }
  905. elseif (isset($result)) {
  906. $return[] = $result;
  907. }
  908. }
  909. return $return;
  910. }