uc_cart_checkout_pane.inc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. <?php
  2. /**
  3. * @file
  4. * Callbacks for the default Ubercart checkout panes plus helper functions.
  5. *
  6. * Checkout panes are defined using hook_uc_checkout_pane() and use a callback
  7. * to handle the different processes involved in completing the checkout form.
  8. * The default checkout panes are defined in uc_cart_uc_checkout_pane() in
  9. * uc_cart.module.
  10. */
  11. /**
  12. * Displays the cart contents for review during checkout.
  13. */
  14. function uc_checkout_pane_cart($op, $order, $form = NULL, &$form_state = NULL) {
  15. switch ($op) {
  16. case 'view':
  17. $contents['cart_review_table'] = array(
  18. '#theme' => 'uc_cart_review_table',
  19. '#items' => $order->products,
  20. '#weight' => variable_get('uc_pane_cart_field_cart_weight', 2),
  21. );
  22. return array('contents' => $contents, 'next-button' => FALSE);
  23. case 'review':
  24. //$review[] = theme('uc_checkout_pane_cart_review', array('items' => $order->products));
  25. $review[] = theme('uc_cart_review_table', array('items' => $order->products, 'show_subtotal' => FALSE));
  26. return $review;
  27. }
  28. }
  29. /**
  30. * Gets the user's email address for login.
  31. */
  32. function uc_checkout_pane_customer($op, $order, $form = NULL, &$form_state = NULL) {
  33. global $user;
  34. switch ($op) {
  35. case 'view':
  36. if ($user->uid) {
  37. $email = $user->mail;
  38. $description = t('Order information will be sent to your account e-mail listed below.');// . '<br />'
  39. $contents['primary_email'] = array('#type' => 'hidden', '#value' => $email);
  40. $contents['email_text'] = array(
  41. '#markup' => '<div>' . t('<b>E-mail address:</b> @email (<a href="!url">edit</a>)', array('@email' => $email, '!url' => url('user/' . $user->uid . '/edit', array('query' => drupal_get_destination())))) . '</div>',
  42. );
  43. }
  44. else {
  45. $email = $order->primary_email;
  46. $description = t('Enter a valid email address for this order or <a href="!url">click here</a> to login with an existing account and return to checkout.', array('!url' => url('user/login', array('query' => drupal_get_destination()))));
  47. $contents['primary_email'] = uc_textfield(t('E-mail address'), $email, TRUE, NULL, 64);
  48. }
  49. if (variable_get('uc_cart_email_validation', FALSE) && !$user->uid) {
  50. $contents['primary_email_confirm'] = uc_textfield(t('Confirm e-mail address'), $email, TRUE, NULL, 64);
  51. }
  52. if ($user->uid == 0) {
  53. $contents['new_account'] = array();
  54. if (variable_get('uc_cart_new_account_name', FALSE)) {
  55. $contents['new_account']['name'] = array(
  56. '#type' => 'textfield',
  57. '#title' => t('Username'),
  58. '#default_value' => isset($order->data['new_user']['name']) ? $order->data['new_user']['name'] : '',
  59. '#maxlength' => 60,
  60. '#size' => 32,
  61. );
  62. }
  63. if (variable_get('uc_cart_new_account_password', FALSE)) {
  64. $contents['new_account']['pass'] = array(
  65. '#type' => 'password',
  66. '#title' => t('Password'),
  67. '#maxlength' => 32,
  68. '#size' => 32,
  69. );
  70. $contents['new_account']['pass_confirm'] = array(
  71. '#type' => 'password',
  72. '#title' => t('Confirm password'),
  73. '#description' => t('Passwords must match to proceed.'),
  74. '#maxlength' => 32,
  75. '#size' => 32,
  76. );
  77. }
  78. if (!empty($contents['new_account'])) {
  79. $array = array(
  80. '#type' => 'fieldset',
  81. '#title' => t('New account details'),
  82. '#description' => variable_get('uc_cart_new_account_details', t('<b>Optional.</b> New customers may supply custom account details.<br />We will create these for you if no values are entered.')),
  83. '#collapsible' => FALSE,
  84. );
  85. $contents['new_account'] = array_merge($array, $contents['new_account']);
  86. }
  87. }
  88. return array('description' => $description, 'contents' => $contents);
  89. case 'process':
  90. $pane = $form_state['values']['panes']['customer'];
  91. if (!empty($pane['primary_email']) && !valid_email_address($pane['primary_email'])) {
  92. form_set_error('panes][customer][primary_email', t('You must enter a valid e-mail address.'));
  93. }
  94. $order->primary_email = $pane['primary_email'];
  95. if (variable_get('uc_cart_email_validation', FALSE) && !$user->uid &&
  96. $pane['primary_email'] !== $pane['primary_email_confirm']) {
  97. form_set_error('panes][customer][primary_email_confirm', t('The e-mail address did not match.'));
  98. }
  99. // Invalidate if an account already exists for this e-mail address, and the user is not logged into that account
  100. if (!variable_get('uc_cart_mail_existing', TRUE) && $user->uid == 0 && !empty($pane['primary_email'])) {
  101. if (db_query("SELECT uid FROM {users} WHERE mail LIKE :mail", array(':mail' => $pane['primary_email']))->fetchField() > 0) {
  102. form_set_error('panes][customer][primary_email', t('An account already exists for your e-mail address. You will either need to login with this e-mail address or use a different e-mail address.'));
  103. }
  104. }
  105. // If new users can specify names or passwords then...
  106. if ((variable_get('uc_cart_new_account_name', FALSE) ||
  107. variable_get('uc_cart_new_account_password', FALSE)) &&
  108. $user->uid == 0) {
  109. // Skip if an account already exists for this e-mail address.
  110. if (variable_get('uc_cart_mail_existing', TRUE) && db_query("SELECT uid FROM {users} WHERE mail LIKE :mail", array(':mail' => $pane['primary_email']))->fetchField() > 0) {
  111. drupal_set_message(t('An account already exists for your e-mail address. The new account details you entered will be disregarded.'));
  112. }
  113. else {
  114. // Validate the username.
  115. if (variable_get('uc_cart_new_account_name', FALSE) && !empty($pane['new_account']['name'])) {
  116. $message = user_validate_name($pane['new_account']['name']);
  117. if (!empty($message)) {
  118. form_set_error('panes][customer][new_account][name', $message);
  119. }
  120. elseif (db_query("SELECT uid FROM {users} WHERE name LIKE :name", array(':name' => $pane['new_account']['name']))->fetchField()) {
  121. form_set_error('panes][customer][new_account][name', t('The username %name is already taken. Please enter a different name or leave the field blank for your username to be your e-mail address.', array('%name' => $pane['new_account']['name'])));
  122. }
  123. else {
  124. $order->data['new_user']['name'] = $pane['new_account']['name'];
  125. }
  126. }
  127. // Validate the password.
  128. if (variable_get('uc_cart_new_account_password', FALSE)) {
  129. if (strcmp($pane['new_account']['pass'], $pane['new_account']['pass_confirm'])) {
  130. form_set_error('panes][customer][new_account][pass_confirm', t('The passwords you entered did not match. Please try again.'));
  131. }
  132. if (!empty($pane['new_account']['pass'])) {
  133. require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
  134. $order->data['new_user']['hash'] = user_hash_password(trim($pane['new_account']['pass']));
  135. }
  136. }
  137. }
  138. }
  139. if ($user->uid) {
  140. $order->uid = $user->uid;
  141. }
  142. return TRUE;
  143. case 'review':
  144. $review[] = array('title' => t('E-mail'), 'data' => check_plain($order->primary_email));
  145. return $review;
  146. case 'settings':
  147. $form['uc_cart_new_account_details'] = array(
  148. '#type' => 'textarea',
  149. '#title' => t('New account details help message'),
  150. '#description' => t('Enter the help message displayed in the new account details fieldset when shown.'),
  151. '#default_value' => variable_get('uc_cart_new_account_details', t('<b>Optional.</b> New customers may supply custom account details.<br />We will create these for you if no values are entered.')),
  152. );
  153. return $form;
  154. }
  155. }
  156. /**
  157. * Gets the delivery information.
  158. */
  159. function uc_checkout_pane_delivery($op, $order, $form = NULL, &$form_state = NULL) {
  160. $description = t('Enter your delivery address and information here.');
  161. $copy = t('My delivery information is the same as my billing information.');
  162. return uc_checkout_pane_address('delivery', $op, $order, $form_state, $description, $copy);
  163. }
  164. /**
  165. * Gets the billing information.
  166. */
  167. function uc_checkout_pane_billing($op, $order, $form = NULL, &$form_state = NULL) {
  168. $description = t('Enter your billing address and information here.');
  169. $copy = t('My billing information is the same as my delivery information.');
  170. return uc_checkout_pane_address('billing', $op, $order, $form_state, $description, $copy);
  171. }
  172. /**
  173. * Generic address pane handler.
  174. */
  175. function uc_checkout_pane_address($pane, $op, $order, &$form_state, $description, $copy) {
  176. global $user;
  177. // Source pane for "copy address" checkbox.
  178. static $source;
  179. if (!isset($source)) {
  180. $source = $pane;
  181. }
  182. switch ($op) {
  183. case 'view':
  184. if ($source != $pane) {
  185. $contents['copy_address'] = array(
  186. '#type' => 'checkbox',
  187. '#title' => $copy,
  188. '#default_value' => variable_get('uc_cart_default_same_address', FALSE),
  189. '#ajax' => array(
  190. 'callback' => 'uc_checkout_pane_address_render',
  191. 'wrapper' => $pane . '-address-pane',
  192. 'progress' => array(
  193. 'type' => 'throbber',
  194. ),
  195. ),
  196. );
  197. }
  198. if ($user->uid && $addresses = uc_select_addresses($user->uid, $pane)) {
  199. $contents['select_address'] = array(
  200. '#type' => 'select',
  201. '#title' => t('Saved addresses'),
  202. '#options' => $addresses['#options'],
  203. '#ajax' => array(
  204. 'callback' => 'uc_checkout_pane_address_render',
  205. 'wrapper' => $pane . '-address-pane',
  206. 'progress' => array(
  207. 'type' => 'throbber',
  208. ),
  209. ),
  210. '#states' => array(
  211. 'invisible' => array(
  212. 'input[name="panes[' . $pane . '][copy_address]"]' => array('checked' => TRUE),
  213. ),
  214. ),
  215. );
  216. }
  217. $contents['address'] = array(
  218. '#type' => 'uc_address',
  219. '#default_value' => $order,
  220. '#key_prefix' => $pane,
  221. '#prefix' => '<div id="' . $pane . '-address-pane">',
  222. '#suffix' => '</div>',
  223. );
  224. if (isset($form_state['values']['panes'][$pane]['copy_address'])) {
  225. $contents['address']['#hidden'] = !empty($form_state['values']['panes'][$pane]['copy_address']);
  226. }
  227. elseif (isset($contents['copy_address'])) {
  228. $contents['address']['#hidden'] = variable_get('uc_cart_default_same_address', FALSE);
  229. }
  230. // If this was an Ajax request, update form input values for the
  231. // copy and select address features.
  232. if (isset($form_state['triggering_element'])) {
  233. $element = &$form_state['triggering_element'];
  234. if ($element['#name'] == "panes[$pane][copy_address]") {
  235. $address = &$form_state['values']['panes'][$source];
  236. foreach ($address as $field => $value) {
  237. if (substr($field, 0, strlen($source)) == $source) {
  238. $field = str_replace($source, $pane, $field);
  239. $form_state['input']['panes'][$pane][$field] = $value;
  240. }
  241. }
  242. }
  243. if ($element['#name'] == "panes[$pane][select_address]" && isset($addresses[$element['#value']])) {
  244. $address = $addresses[$element['#value']];
  245. foreach ($address as $field => $value) {
  246. $form_state['input']['panes'][$pane][$pane . '_' . $field] = $value;
  247. }
  248. }
  249. // Forget any previous Ajax submissions, as we send new default values.
  250. unset($form_state['uc_address']);
  251. }
  252. return array('description' => $description, 'contents' => $contents);
  253. case 'process':
  254. $panes = &$form_state['values']['panes'];
  255. foreach ($panes[$pane] as $field => $value) {
  256. if (substr($field, 0, strlen($pane)) == $pane) {
  257. if (!empty($panes[$pane]['copy_address'])) {
  258. $value = $panes[$source][str_replace($pane, $source, $field)];
  259. }
  260. $order->$field = $value;
  261. }
  262. }
  263. if (isset($panes[$pane]['select_address']) && $panes[$pane]['select_address'] >= 0) {
  264. $addresses = uc_select_addresses($user->uid, $pane);
  265. $address = $addresses[$panes[$pane]['select_address']];
  266. foreach ($address as $field => $value) {
  267. $order->{$pane . '_' . $field} = $value;
  268. }
  269. }
  270. return TRUE;
  271. case 'review':
  272. $review[] = array('title' => t('Address'), 'data' => uc_order_address($order, $pane, FALSE));
  273. if (uc_address_field_enabled('phone') && !empty($order->{$pane . '_phone'})) {
  274. $review[] = array('title' => t('Phone'), 'data' => check_plain($order->{$pane . '_phone'}));
  275. }
  276. return $review;
  277. }
  278. }
  279. /**
  280. * Ajax callback to re-render the full address element.
  281. */
  282. function uc_checkout_pane_address_render($form, &$form_state) {
  283. $element = &$form;
  284. foreach (array_slice($form_state['triggering_element']['#array_parents'], 0, -1) as $field) {
  285. $element = &$element[$field];
  286. }
  287. return $element['address'];
  288. }
  289. /**
  290. * Allows a customer to make comments on the order.
  291. */
  292. function uc_checkout_pane_comments($op, $order, $form = NULL, &$form_state = NULL) {
  293. switch ($op) {
  294. case 'view':
  295. $description = t('Use this area for special instructions or questions regarding your order.');
  296. if (!empty($order->order_id)) {
  297. $default = db_query("SELECT message FROM {uc_order_comments} WHERE order_id = :id", array(':id' => $order->order_id))->fetchField();
  298. }
  299. else {
  300. $default = NULL;
  301. }
  302. $contents['comments'] = array(
  303. '#type' => 'textarea',
  304. '#title' => t('Order comments'),
  305. '#default_value' => $default,
  306. );
  307. return array('description' => $description, 'contents' => $contents);
  308. case 'process':
  309. db_delete('uc_order_comments')
  310. ->condition('order_id', $order->order_id)
  311. ->execute();
  312. if (strlen($form_state['values']['panes']['comments']['comments']) > 0) {
  313. uc_order_comment_save($order->order_id, 0, $form_state['values']['panes']['comments']['comments'], 'order', uc_order_state_default('post_checkout'), TRUE);
  314. }
  315. return TRUE;
  316. case 'review':
  317. $review = NULL;
  318. $result = db_query("SELECT message FROM {uc_order_comments} WHERE order_id = :id", array(':id' => $order->order_id));
  319. if ($comment = $result->fetchObject()) {
  320. $review[] = array('title' => t('Comment'), 'data' => check_plain($comment->message));
  321. }
  322. return $review;
  323. }
  324. }
  325. /**
  326. * Finds the collapsible pane displayed above the pane with an ID of $pane_id.
  327. */
  328. function _uc_cart_checkout_prev_pane($panes, $pane_id = NULL) {
  329. if (is_null($pane_id)) {
  330. return FALSE;
  331. }
  332. $prev = FALSE;
  333. foreach ($panes as $target) {
  334. if ($target['id'] == $pane_id) {
  335. return $prev;
  336. }
  337. if ($target['collapsible'] && $target['enabled']) {
  338. $prev = $target['id'];
  339. }
  340. }
  341. return FALSE;
  342. }
  343. /**
  344. * Finds the pane that displays below the pane with an ID of $pane_id.
  345. */
  346. function _uc_cart_checkout_next_pane($panes, $pane_id = NULL) {
  347. if (is_null($pane_id)) {
  348. return FALSE;
  349. }
  350. $next = FALSE;
  351. foreach ($panes as $target) {
  352. if ($next) {
  353. if ($target['collapsible'] && $target['enabled']) {
  354. return $target['id'];
  355. }
  356. }
  357. if ($target['id'] == $pane_id) {
  358. $next = TRUE;
  359. }
  360. }
  361. return FALSE;
  362. }
  363. /**
  364. * Builds a list of checkout panes defined in the enabled modules.
  365. */
  366. function _uc_checkout_pane_list($action = NULL) {
  367. static $panes = array();
  368. if (count($panes) > 0 && $action !== 'rebuild') {
  369. return $panes;
  370. }
  371. foreach (module_invoke_all('uc_checkout_pane') as $id => $pane) {
  372. // Preserve backward compatibility for panes with no key specified.
  373. if (is_numeric($id)) {
  374. $id = $pane['id'];
  375. }
  376. // Set defaults.
  377. $pane += array(
  378. 'id' => $id,
  379. 'enabled' => TRUE,
  380. 'weight' => 0,
  381. 'review' => TRUE,
  382. 'process' => TRUE,
  383. 'collapsible' => TRUE,
  384. );
  385. $pane['enabled'] = variable_get('uc_pane_' . $id . '_enabled', $pane['enabled']);
  386. $pane['weight'] = variable_get('uc_pane_' . $id . '_weight', $pane['weight']);
  387. $panes[$id] = $pane;
  388. }
  389. // Allow other modules to alter the defaults.
  390. drupal_alter('uc_checkout_pane', $panes);
  391. uasort($panes, 'uc_weight_sort');
  392. return $panes;
  393. }
  394. /**
  395. * Returns data from a checkout pane by pane ID and the array key.
  396. */
  397. function _uc_checkout_pane_data($pane_id, $key) {
  398. $panes = _uc_checkout_pane_list();
  399. return $panes[$pane_id][$key];
  400. }
  401. /**
  402. * Formats the cart contents table on the checkout page.
  403. *
  404. * @param $variables
  405. * An associative array containing:
  406. * - show_subtotal: TRUE or FALSE indicating if you want a subtotal row
  407. * displayed in the table.
  408. * - items: An associative array of cart item information containing:
  409. * - qty: Quantity in cart.
  410. * - title: Item title.
  411. * - price: Item price.
  412. * - desc: Item description.
  413. *
  414. * @return
  415. * The HTML output for the cart review table.
  416. *
  417. * @ingroup themeable
  418. */
  419. function theme_uc_cart_review_table($variables) {
  420. $items = $variables['items'];
  421. $show_subtotal = $variables['show_subtotal'];
  422. $subtotal = 0;
  423. // Set up table header.
  424. $header = array(
  425. array('data' => theme('uc_qty_label'), 'class' => array('qty')),
  426. array('data' => t('Products'), 'class' => array('products')),
  427. array('data' => t('Price'), 'class' => array('price')),
  428. );
  429. // Set up table rows.
  430. $display_items = uc_order_product_view_multiple($items);
  431. if (!empty($display_items['uc_order_product'])) {
  432. foreach (element_children($display_items['uc_order_product']) as $key) {
  433. $display_item = $display_items['uc_order_product'][$key];
  434. $subtotal += $display_item['total']['#price'];
  435. $rows[] = array(
  436. array('data' => $display_item['qty'], 'class' => array('qty')),
  437. array('data' => $display_item['product'], 'class' => array('products')),
  438. array('data' => $display_item['total'], 'class' => array('price')),
  439. );
  440. }
  441. }
  442. // Add the subtotal as the final row.
  443. if ($show_subtotal) {
  444. $rows[] = array(
  445. 'data' => array(
  446. // One cell
  447. array(
  448. 'data' => array(
  449. '#theme' => 'uc_price',
  450. '#prefix' => '<span id="subtotal-title">' . t('Subtotal:') . '</span> ',
  451. '#price' => $subtotal,
  452. ),
  453. // Cell attributes
  454. 'colspan' => 3,
  455. 'class' => array('subtotal'),
  456. ),
  457. ),
  458. // Row attributes
  459. 'class' => array('subtotal'),
  460. );
  461. }
  462. return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('cart-review'))));
  463. }
  464. /**
  465. * Themes cart items on the checkout review order page.
  466. *
  467. * @param $variables
  468. * An associative array containing:
  469. * - items: An associative array of cart item information containing:
  470. * - qty: Quantity in cart.
  471. * - title: Item title.
  472. * - price: Item price.
  473. * - desc: Item description.
  474. *
  475. * @return
  476. * A string of HTML for the page contents.
  477. *
  478. * @ingroup themeable
  479. */
  480. function theme_uc_checkout_pane_cart_review($variables) {
  481. $rows = array();
  482. $items = uc_order_product_view_multiple($variables['items']);
  483. foreach (element_children($items['uc_order_product']) as $key) {
  484. $item = $items['uc_order_product'][$key];
  485. $rows[] = array(
  486. array('data' => $item['qty'], 'class' => array('qty')),
  487. array('data' => $item['product'], 'class' => array('products')),
  488. array('data' => $item['total'], 'class' => array('price')),
  489. );
  490. }
  491. return theme('table', array('rows' => $rows, 'attributes' => array('class' => array('cart-review'))));
  492. }