uc_quote.module 31 KB

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