<?php

/**
 * @file
 * Cart menu items.
 */

/**
 * Displays the cart view page.
 *
 * Show the products in the cart with a form to adjust cart contents or go to
 * checkout.
 */
function uc_cart_view() {
  // Failsafe so that this function only works when called with no arguments.
  // This prevents the accidental wiping of the cart_order session variable.
  if (func_num_args() > 0) {
    return drupal_not_found();
  }

  // Clear the cart order session variable if it exists.
  if (!empty($_SESSION['cart_order'])) {
    unset($_SESSION['cart_order']);
  }

  // Load the array of shopping cart items.
  $items = uc_cart_get_contents();

  // Display the empty cart page if there are no items in the cart.
  if (empty($items)) {
    return theme('uc_empty_cart');
  }

  // Load through the cart panes...
  $output = '';
  foreach (uc_cart_cart_pane_list($items) as $pane) {
    // If the pane is enabled...
    if ($pane['enabled']) {
      // Add its output to the cart view.
      $output .= $pane['body'];
    }
  }

  // Add a custom cart breadcrumb if specified.
  if (($text = variable_get('uc_cart_breadcrumb_text', '')) !== '') {
    $link = l($text, variable_get('uc_cart_breadcrumb_url', '<front>'));
    drupal_set_breadcrumb(array($link));
  }

  return $output;
}

/**
 * Displays the cart checkout page built of checkout panes from enabled modules.
 */
function uc_cart_checkout() {
  global $user;

  $items = uc_cart_get_contents();
  if (count($items) == 0 || !variable_get('uc_checkout_enabled', TRUE)) {
    drupal_goto('cart');
  }

  $context = array(
    'revision' => 'altered',
    'type' => 'amount',
  );

  if (($min = uc_price(variable_get('uc_minimum_subtotal', 0), $context)) > 0) {
    $subtotal = 0;
    if (is_array($items) && count($items) > 0) {
      foreach ($items as $item) {
        $data = module_invoke($item->module, 'cart_display', $item);
        if (!empty($data)) {
          $subtotal += $data['#total'];
        }
      }
    }
    if ($subtotal < $min) {
      $context = array(
        'revision' => 'formatted-original',
        'type' => 'amount',
      );
      drupal_set_message(variable_get('uc_minimum_subtotal_text', t('The minimum order subtotal for checkout is !min.', array('!min' => uc_price($min, $context)))), 'error');
      drupal_goto('cart');
    }
  }

  // Send anonymous users to login page when anonymous checkout is disabled.
  if (!$user->uid && !variable_get('uc_checkout_anonymous', TRUE)) {
    drupal_set_message(t('You must login before you can proceed to checkout.'));
    if (variable_get('user_register', 1) != 0) {
      drupal_set_message(t('If you do not have an account yet, you should <a href="!url">register now</a>.', array('!url' => url('user/register', array('query' => drupal_get_destination())))));
    }
    drupal_goto('user', drupal_get_destination());
  }

  $list = _line_item_list();
  foreach ($list as $line_item) {
    if (isset($line_item['callback']) && function_exists($line_item['callback'])) {
      $line_item['callback']('cart-preview', $items);
    }
  }

  drupal_add_js(drupal_get_path('module', 'uc_cart') .'/uc_cart.js');
  $output = drupal_get_form('uc_cart_checkout_form');

  return $output;
}

/**
 * The checkout form built up from the enabled checkout panes.
 *
 * @see uc_cart_checkout_form_validate()
 * @see uc_cart_checkout_form_review()
 * @see uc_cart_checkout_review()
 * @see theme_uc_cart_checkout_form()
 * @ingroup forms
 */
function uc_cart_checkout_form() {
  global $user;

  // Cancel an order when a customer clicks the 'Cancel' button.
  if (isset($_POST['op']) && $_POST['op'] == t('Cancel')) {
    if (isset($_SESSION['cart_order']) && intval($_SESSION['cart_order']) > 0) {
      uc_order_comment_save($_SESSION['cart_order'], 0, t('Customer cancelled this order from the checkout form.'));
      unset($_SESSION['cart_order']);
    }
    drupal_goto('cart');
  }

  if (isset($_SESSION['cart_order'])) {
    $order = uc_order_load($_SESSION['cart_order']);
  }
  else {
    $order = new UcOrder();
  }

  // Check the referer URI to clear order details and prevent identity theft.
  if (uc_referer_check(array('cart/checkout', 'cart/checkout/review'))) {
    if ($order == FALSE || uc_order_status_data($order->order_status, 'state') != 'in_checkout') {
      unset($_SESSION['cart_order']);
      $order = new UcOrder();
    }
    elseif (uc_order_status_data($order->order_status, 'state') != 'in_checkout' || ($user->uid > 0 && $user->uid != $order->uid)) {
      $order = new UcOrder();
    }
  }
  else {
    unset($_SESSION['cart_order']);
    $order = new UcOrder();
  }

  $form['panes'] = array('#tree' => TRUE);
  $panes = _checkout_pane_list();

  // If the cart isn't shippable, remove panes with shippable == TRUE.
  if (!uc_cart_is_shippable() && variable_get('uc_cart_delivery_not_shippable', TRUE)) {
    $panes = uc_cart_filter_checkout_panes($panes, array('shippable' => TRUE));
  }

  foreach ($panes as $pane) {
    if ($pane['enabled']) {
      $pane['prev'] = _uc_cart_checkout_prev_pane($panes, $pane['id']);
      $pane['next'] = _uc_cart_checkout_next_pane($panes, $pane['id']);

      if (!isset($pane['collapsed'])) {
        $collapsed = ($pane['prev'] === FALSE || empty($displayed[$pane['prev']])) ? FALSE : TRUE;
      }
      if (isset($_SESSION['expanded_panes'])) {
        if (is_array($_SESSION['expanded_panes']) &&
            in_array($pane['id'], $_SESSION['expanded_panes'])) {
          $collapsed = FALSE;
        }
      }

      $return = $pane['callback']('view', $order, NULL);

      // Add the pane if any display data is returned from the callback.
      if (is_array($return) && (!empty($return['description']) || !empty($return['contents']))) {
        // Create the fieldset for the pane.
        $form['panes'][$pane['id']] = array(
          '#type' => 'fieldset',
          '#title' => $pane['title'],
          '#description' => !empty($return['description']) ? $return['description'] : NULL,
          '#collapsible' => $pane['collapsible'],
          '#collapsed' => variable_get('uc_use_next_buttons', FALSE) ? $collapsed : FALSE,
          '#attributes' => array('id' => $pane['id'] .'-pane'),
          '#theme' => isset($return['theme']) ? $return['theme'] : NULL,
        );

        // Add the contents of the fieldset if any were returned.
        if (!empty($return['contents'])) {
          $form['panes'][$pane['id']] = array_merge($form['panes'][$pane['id']], $return['contents']);
        }

        // Add the 'Next' button if necessary.
        if ((!isset($return['next-button']) || $return['next-button'] !== FALSE) && $pane['next'] !== FALSE &&
            variable_get('uc_use_next_buttons', FALSE) != FALSE) {
          $opt = variable_get('uc_collapse_current_pane', FALSE) ? $pane['id'] : 'false';
          $form['panes'][$pane['id']]['next'] = array(
            '#type' => 'button',
            '#value' => t('Next'),
            '#weight' => variable_get("uc_pane_{$pane_id}_field_button_weight", 20),
            '#attributes' => array('onclick' => "return uc_cart_next_button_click(this, '". $pane['next'] ."', '". $opt ."');"),
            '#prefix' => '<div class="next-button show-onload">',
            '#suffix' => '</div>',
          );
        }

        // Log that this pane was actually displayed.
        $displayed[$pane['id']] = TRUE;
      }
    }
  }
  unset($_SESSION['expanded_panes']);

  $contents = uc_cart_get_contents();

  $form['cart_contents'] = array(
    '#type' => 'hidden',
    '#value' => serialize($contents),
  );

  $form['cancel'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel'),
    '#submit' => FALSE,
  );
  $form['continue'] = array(
    '#type' => 'submit',
    '#value' => t('Review order'),
  );

  return $form;
}

/**
 * Adds markup and styling to the checkout panes.
 *
 * @see uc_cart_checkout_form()
 * @ingroup themeable
 */
function theme_uc_cart_checkout_form($form) {
  drupal_add_css(drupal_get_path('module', 'uc_cart') .'/uc_cart.css');

  $output = '<div id="checkout-instructions">'. check_markup(variable_get('uc_checkout_instructions', ''), variable_get('uc_checkout_instructions_format', FILTER_FORMAT_DEFAULT), FALSE) .'</div>';

  foreach (element_children($form['panes']) as $pane_id) {
    if (function_exists(($func = _checkout_pane_data($pane_id, 'callback')))) {
      $result = $func('theme', $form['panes'][$pane_id], NULL);
      if (!empty($result)) {
        $output .= $result;
        $form['panes'][$pane_id] = array();
      }
      else {
        $output .= drupal_render($form['panes'][$pane_id]);
      }
    }
    else {
      $output .= drupal_render($form['panes'][$pane_id]);
    }
  }

  $output .= '<div id="checkout-form-bottom">'. drupal_render($form) .'</div>';

  return $output;
}

/**
 * Form validation for uc_cart_checkout_form().
 *
 * @see uc_cart_checkout_form()
 * @see uc_cart_checkout_form_submit()
 */
function uc_cart_checkout_form_validate($form, &$form_state) {
  global $user;

  if (empty($_SESSION['cart_order'])) {
    $order = uc_order_new($user->uid);
    $_SESSION['cart_order'] = $order->order_id;
  }
  else {
    $order = new stdClass();
    $order->uid = $user->uid;
    $order->order_id = $_SESSION['cart_order'];
    $order->order_status = uc_order_state_default('in_checkout');
  }

  db_query("DELETE FROM {uc_order_products} WHERE order_id = %d", $order->order_id);
  $order->products = unserialize($form_state['values']['cart_contents']);

  $context = array(
    'revision' => 'original',
    'type' => 'order_product',
  );
  foreach ($order->products as $key => $item) {
    $price_info = array(
      'price' => $item->price,
      'qty' => $item->qty,
    );
    $context['subject'] = array(
      'order' => $order,
      'product' => $item,
      'node' => node_load($item->nid),
    );

    // Get the altered price per unit, as ordered products have a locked-in
    // price. Price altering rules may change over time, but the amount paid
    // by the customer does not after the fact.
    $price = uc_price($price_info, $context) / $item->qty;
    if ($order->products[$key]->price != $price) {
      $order->products[$key]->data['altered_price'] = $price;
    }
  }

  $order->order_total = uc_order_get_total($order, TRUE);

  // Validate/process the cart panes.  A FALSE value results in failed checkout.
  $_SESSION['checkout_valid'] = TRUE;
  foreach (element_children($form_state['values']['panes']) as $pane_id) {
    $func = _checkout_pane_data($pane_id, 'callback');
    if (is_string($func) && function_exists($func)) {
      $isvalid = $func('process', $order, $form_state['values']['panes'][$pane_id]);
      if ($isvalid === FALSE) {
        $_SESSION['expanded_panes'][] = $pane_id;
        $_SESSION['checkout_valid'] = FALSE;
      }
    }
  }

  $order->line_items = uc_order_load_line_items($order, TRUE);

  uc_order_save($order);
}

/**
 * Form submission handler for uc_cart_checkout_form().
 *
 * @see uc_cart_checkout_form()
 * @see uc_cart_checkout_form_validate()
 */
function uc_cart_checkout_form_submit($form, &$form_state) {
  if ($_SESSION['checkout_valid'] === FALSE) {
    $url = 'cart/checkout';
  }
  else {
    $url = 'cart/checkout/review';
    $_SESSION['do_review'] = TRUE;
  }

  unset($_SESSION['checkout_valid']);

  $form_state['redirect'] = $url;
}

/**
 * Allows a customer to review their order before finally submitting it.
 *
 * @see uc_cart_checkout_form()
 */
function uc_cart_checkout_review() {
  drupal_add_js(drupal_get_path('module', 'uc_cart') .'/uc_cart.js');
  $form = drupal_get_form('uc_cart_checkout_review_form');

  if ($_SESSION['do_review'] !== TRUE && !uc_referer_check(array('cart/checkout'))) {
    drupal_goto('cart/checkout');
  }
  unset($_SESSION['do_review']);

  $order = uc_order_load($_SESSION['cart_order']);

  if ($order === FALSE || uc_order_status_data($order->order_status, 'state') != 'in_checkout') {
    unset($_SESSION['cart_order']);
    drupal_goto('cart/checkout');
  }

  $panes = _checkout_pane_list();

  // If the cart isn't shippable, bypass panes with shippable == TRUE.
  if (!uc_cart_is_shippable() && variable_get('uc_cart_delivery_not_shippable', TRUE)) {
    $panes = uc_cart_filter_checkout_panes($panes, array('shippable' => TRUE));
  }

  foreach ($panes as $pane) {
    if ($pane['enabled']) {
      $func = $pane['callback'];
      if (function_exists($func)) {
        $return = $func('review', $order, NULL);
        if (!is_null($return)) {
          $data[$pane['title']] = $return;
        }
      }
    }
  }

  $output = theme('uc_cart_checkout_review', $data, $form);

  return $output;
}

/**
 * Themes the checkout review order page.
 *
 * @param $panes
 *   An associative array for each checkout pane that has information to add to
 *   the review page.  The key is the pane's title and the value is either the
 *   data returned for that pane or an array of returned data.
 * @param $form
 *   The HTML version of the form that by default includes the 'Back' and
 *   'Submit order' buttons at the bottom of the review page.
 *
 * @return
 *   A string of HTML for the page contents.
 *
 * @ingroup themeable
 */
function theme_uc_cart_checkout_review($panes, $form) {
  drupal_add_css(drupal_get_path('module', 'uc_cart') .'/uc_cart.css');

  $output = check_markup(variable_get('uc_checkout_review_instructions', uc_get_message('review_instructions')), variable_get('uc_checkout_review_instructions_format', FILTER_FORMAT_DEFAULT), FALSE)
           .'<table class="order-review-table">';

  foreach ($panes as $title => $data) {
    $output .= '<tr class="pane-title-row">';
    $output .= '<td colspan="2">' . $title . '</td>';
    $output .= '</tr>';
    if (is_array($data)) {
      foreach ($data as $row) {
        if (is_array($row)) {
          if (isset($row['border'])) {
            $border = ' class="row-border-'. $row['border'] . '"';
          }
          else {
            $border = '';
          }
          $output .= '<tr valign="top"' . $border . '>';
          $output .= '<td class="title-col">' . $row['title'] . ':</td>';
          $output .= '<td class="data-col">' . $row['data'] . '</td>';
          $output .= '</tr>';
        }
        else {
          $output .= '<tr valign="top"><td colspan="2">' . $row . '</td></tr>';
        }
      }
    }
    else {
      $output .= '<tr valign="top"><td colspan="2">' . $data . '</td></tr>';
    }
  }

  $output .= '<tr class="review-button-row">';
  $output .= '<td colspan="2">' . $form . '</td>';
  $output .= '</tr>';

  $output .= '</table>';

  return $output;
}

/**
 * Gives customers the option to finish checkout or go revise their information.
 *
 * @see uc_cart_checkout_review_form_back()
 * @see uc_cart_checkout_review_form_submit()
 * @ingroup forms
 */
function uc_cart_checkout_review_form() {
  // Set the session variable to pass the redirect check on the pageload.
  if (isset($_POST['op']) && $_POST['op'] == t('Back')) {
    $_SESSION['do_review'] = TRUE;
  }

  $form['back'] = array(
    '#type' => 'submit',
    '#value' => t('Back'),
    '#submit' => array('uc_cart_checkout_review_form_back'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit order'),
  );

  return $form;
}

/**
 * Returns the customer to the checkout page to edit their information.
 *
 * @see uc_cart_checkout_review_form()
 */
function uc_cart_checkout_review_form_back($form, &$form_state) {
  unset($_SESSION['do_review']);
  $form_state['redirect'] = 'cart/checkout';
}

/**
 * Final checks to make sure the order can be completed.
 *
 * @see uc_cart_checkout_review_form()
 */
function uc_cart_checkout_review_form_submit($form, &$form_state) {
  // Invoke hook_order($op = 'submit') to test to make sure the order can
  // be completed... used for auto payment in uc_credit.module.
  $order = uc_order_load($_SESSION['cart_order']);
  $error = FALSE;

  // Invoke it on a per-module basis instead of all at once.
  foreach (module_list() as $module) {
    $function = $module .'_order';
    if (function_exists($function)) {
      // $order must be passed by reference.
      $result = $function('submit', $order, NULL);

      $msg_type = 'status';
      if ($result[0]['pass'] === FALSE) {
        $error = TRUE;
        $msg_type = 'error';
      }
      if (!empty($result[0]['message'])) {
        drupal_set_message($result[0]['message'], $msg_type);
      }

      // Stop invoking the hooks if there was an error.
      if ($error) {
        break;
      }
    }
  }

  if ($error) {
    $_SESSION['do_review'] = TRUE;
    $form_state['redirect'] = 'cart/checkout/review';
  }
  else {
    $_SESSION['do_complete'] = TRUE;
    $form_state['redirect'] = 'cart/checkout/complete';
  }
}

/**
 * Completes the sale and finishes checkout.
 */
function uc_cart_checkout_complete() {
  if (!$_SESSION['do_complete']) {
    drupal_goto('cart');
  }

  $order = uc_order_load(intval($_SESSION['cart_order']));

  if (empty($order)) {
    // Display messages to customers and the administrator if the order was lost.
    drupal_set_message(t("We're sorry.  An error occurred while processing your order that prevents us from completing it at this time. Please contact us and we will resolve the issue as soon as possible."), 'error');
    watchdog('uc_cart', 'An empty order made it to checkout! Cart order ID: @cart_order', array('@cart_order' => $_SESSION['cart_order']), WATCHDOG_ERROR);
    drupal_goto('cart');
  }

  $output = uc_cart_complete_sale($order, variable_get('uc_new_customer_login', FALSE));
  unset($_SESSION['do_complete'], $_SESSION['cart_order']);

  // Add a comment to let sales team know this came in through the site.
  uc_order_comment_save($order->order_id, 0, t('Order created through website.'), 'admin');

  $page = variable_get('uc_cart_checkout_complete_page', '');
  if (!empty($page)) {
    drupal_goto($page);
  }

  return $output;
}
