Newer
Older
Ryan Szrama
committed
<?php
/**
* @file
* Implements the shopping cart system and add to cart features.
Ryan Szrama
committed
*
* In Drupal Commerce, the shopping cart is really just an order that makes
* special considerations to associate it with a user and
*/
// Define a constant for the default cart provider.
define('COMMERCE_CART_DEFAULT_PROVIDER', 'session');
Ryan Szrama
committed
// Define constants for the shopping cart refresh modes.
define('COMMERCE_CART_REFRESH_ALWAYS', 'always');
define('COMMERCE_CART_REFRESH_OWNER_ONLY', 'owner_only');
define('COMMERCE_CART_REFRESH_ACTIVE_CART_ONLY', 'active_cart_only');
define('COMMERCE_CART_REFRESH_DEFAULT_FREQUENCY', 15);
Ryan Szrama
committed
/**
* Implements hook_menu().
*/
function commerce_cart_menu() {
$items = array();
Ryan Szrama
committed
$items['cart'] = array(
Ryan Szrama
committed
'access arguments' => array('access content'),
'file' => 'includes/commerce_cart.pages.inc',
);
$items['cart/my'] = array(
'title' => 'Shopping cart (# items)',
'title callback' => 'commerce_cart_menu_item_title',
'title arguments' => array(TRUE),
Andrei Mateescu
committed
'page callback' => 'commerce_cart_menu_item_redirect',
'access arguments' => array('access content'),
'type' => MENU_SUGGESTED_ITEM,
);
Ryan Szrama
committed
$items['checkout'] = array(
'title' => 'Checkout',
'page callback' => 'commerce_cart_checkout_router',
'access arguments' => array('access checkout'),
Ryan Szrama
committed
'type' => MENU_CALLBACK,
'file' => 'includes/commerce_cart.pages.inc',
);
// If the Order UI module is installed, add a local action to it that lets an
// administrator execute a cart order refresh on the order. Modules that
// define their own order edit menu item are also responsible for defining
// their own local action menu items if needed.
if (module_exists('commerce_order_ui')) {
$items['admin/commerce/orders/%commerce_order/edit/refresh'] = array(
'title' => 'Apply pricing rules',
'description' => 'Executes the cart order refresh used to apply all current pricing rules on the front end.',
'page callback' => 'drupal_get_form',
'page arguments' => array('commerce_cart_order_refresh_form', 3),
'access callback' => 'commerce_cart_order_refresh_form_access',
'access arguments' => array(3),
'type' => MENU_LOCAL_ACTION,
'file' => 'includes/commerce_cart.admin.inc',
);
}
Ryan Szrama
committed
return $items;
}
/**
* Returns the title of the shopping cart menu item with an item count.
*/
function commerce_cart_menu_item_title() {
global $user;
Ryan Szrama
committed
// Default to a static title.
$title = t('Shopping cart');
// If the user actually has a cart order...
if ($order = commerce_cart_order_load($user->uid)) {
// Count the number of product line items on the order.
$wrapper = entity_metadata_wrapper('commerce_order', $order);
$quantity = commerce_line_items_quantity($wrapper->commerce_line_items, commerce_product_line_item_types());
// If there are more than 0 product line items on the order...
if ($quantity > 0) {
// Use the dynamic menu item title.
$title = format_plural($quantity, 'Shopping cart (1 item)', 'Shopping cart (@count items)');
}
Ryan Szrama
committed
return $title;
Ryan Szrama
committed
}
Andrei Mateescu
committed
/**
* Redirects a valid page request to cart/my to the cart page.
*/
function commerce_cart_menu_item_redirect() {
drupal_goto('cart');
}
/**
* Access callback: determines access to the "Apply pricing rules" local action.
*/
function commerce_cart_order_refresh_form_access($order) {
// Do not show the link for cart orders as they're refreshed automatically.
if (commerce_cart_order_is_cart($order)) {
return FALSE;
}
// Returns TRUE if the link is enabled via the order settings form and the
// user has access to update the order.
return variable_get('commerce_order_apply_pricing_rules_link', TRUE) && commerce_order_access('update', $order);
}
Ryan Szrama
committed
/**
* Implements hook_hook_info().
*/
function commerce_cart_hook_info() {
$hooks = array(
'commerce_cart_order_id' => array(
'group' => 'commerce',
),
Ryan Szrama
committed
'commerce_cart_order_is_cart' => array(
'group' => 'commerce',
),
'commerce_cart_order_convert' => array(
'group' => 'commerce',
),
'commerce_cart_line_item_refresh' => array(
Ryan Szrama
committed
'group' => 'commerce',
),
'commerce_cart_order_refresh' => array(
Ryan Szrama
committed
'group' => 'commerce',
),
'commerce_cart_order_empty' => array(
'group' => 'commerce',
),
'commerce_cart_attributes_refresh_alter' => array(
'group' => 'commerce',
),
'commerce_cart_product_comparison_properties_alter' => array(
'group' => 'commerce',
),
'commerce_cart_product_prepare' => array(
'group' => 'commerce',
),
'commerce_cart_product_add' => array(
'group' => 'commerce',
),
'commerce_cart_product_remove' => array(
'group' => 'commerce',
),
Ryan Szrama
committed
);
return $hooks;
/**
* Implements hook_commerce_order_state_info().
*/
function commerce_cart_commerce_order_state_info() {
$order_states = array();
$order_states['cart'] = array(
'name' => 'cart',
'title' => t('Shopping cart'),
'description' => t('Orders in this state have not been completed by the customer yet.'),
'weight' => -5,
'default_status' => 'cart',
);
return $order_states;
}
/**
* Implements hook_commerce_order_status_info().
*/
function commerce_cart_commerce_order_status_info() {
$order_statuses = array();
$order_statuses['cart'] = array(
'name' => 'cart',
'title' => t('Shopping cart'),
'state' => 'cart',
Ryan Szrama
committed
'cart' => TRUE,
);
return $order_statuses;
}
/**
* Implements hook_commerce_checkout_pane_info().
*/
function commerce_cart_commerce_checkout_pane_info() {
$checkout_panes = array();
$checkout_panes['cart_contents'] = array(
'title' => t('Shopping cart contents'),
'base' => 'commerce_cart_contents_pane',
Ryan Szrama
committed
'file' => 'includes/commerce_cart.checkout_pane.inc',
Ryan Szrama
committed
'page' => 'checkout',
Ryan Szrama
committed
'weight' => -10,
);
return $checkout_panes;
}
Ryan Szrama
committed
/**
* Implements hook_commerce_checkout_complete().
*/
Ryan Szrama
committed
function commerce_cart_commerce_checkout_complete($order) {
Ryan Szrama
committed
// Move the cart order ID to a completed order ID.
if (commerce_cart_order_session_exists($order->order_id)) {
commerce_cart_order_session_save($order->order_id, TRUE);
commerce_cart_order_session_delete($order->order_id);
Ryan Szrama
committed
}
}
Ryan Szrama
committed
/**
* Implements hook_commerce_line_item_summary_link_info().
*/
function commerce_cart_commerce_line_item_summary_link_info() {
return array(
'view_cart' => array(
'title' => t('View cart'),
'href' => 'cart',
'attributes' => array('rel' => 'nofollow'),
'weight' => 0,
),
'checkout' => array(
'title' => t('Checkout'),
'href' => 'checkout',
'attributes' => array('rel' => 'nofollow'),
'weight' => 5,
Ryan Szrama
committed
'access' => user_access('access checkout'),
Ryan Szrama
committed
),
);
}
/**
* Implements hook_form_alter().
*/
function commerce_cart_form_alter(&$form, &$form_state, $form_id) {
Bojan Živanović
committed
if (strpos($form_id, 'views_form_commerce_cart_form_') === 0) {
Helior Colorado
committed
// Only alter buttons if the cart form View shows line items.
$view = reset($form_state['build_info']['args']);
if (!empty($view->result)) {
// Change the Save button to say Update cart.
$form['actions']['submit']['#value'] = t('Update cart');
$form['actions']['submit']['#submit'] = array_merge($form['#submit'], array('commerce_cart_line_item_views_form_submit'));
// Change any Delete buttons to say Remove.
if (!empty($form['edit_delete'])) {
foreach(element_children($form['edit_delete']) as $key) {
// Load and wrap the line item to have the title in the submit phase.
if (!empty($form['edit_delete'][$key]['#line_item_id'])) {
$line_item_id = $form['edit_delete'][$key]['#line_item_id'];
$form_state['line_items'][$line_item_id] = commerce_line_item_load($line_item_id);
$form['edit_delete'][$key]['#value'] = t('Remove');
$form['edit_delete'][$key]['#submit'] = array_merge($form['#submit'], array('commerce_cart_line_item_delete_form_submit'));
}
}
}
}
Helior Colorado
committed
else {
// Otherwise go ahead and remove any buttons from the View.
unset($form['actions']);
}
Ryan Szrama
committed
elseif (strpos($form_id, 'commerce_checkout_form_') === 0 && !empty($form['buttons']['cancel'])) {
// Override the submit handler for changing the order status on checkout cancel.
foreach ($form['buttons']['cancel']['#submit'] as $key => &$value) {
if ($value == 'commerce_checkout_form_cancel_submit') {
$value = 'commerce_cart_checkout_form_cancel_submit';
}
}
}
Bojan Živanović
committed
elseif (strpos($form_id, 'views_form_commerce_cart_block') === 0) {
// No point in having a "Save" button on the shopping cart block.
unset($form['actions']);
}
Ryan Szrama
committed
}
/**
* Submit handler to take back the order to cart status on cancel in checkout.
*/
function commerce_cart_checkout_form_cancel_submit($form, &$form_state) {
// Update the order to the cart status.
$order = commerce_order_load($form_state['order']->order_id);
$form_state['order'] = commerce_order_status_update($order, 'cart', TRUE);
// Skip saving in the status update and manually save here to force a save
// even when the status doesn't actually change.
Ivo Van Geertruyen
committed
if (variable_get('commerce_order_auto_revision', TRUE)) {
$form_state['order']->revision = TRUE;
$form_state['order']->log = t('Customer manually canceled the checkout process.');
}
Ryan Szrama
committed
commerce_order_save($form_state['order']);
drupal_set_message(t('Checkout of your current order has been canceled and may be resumed when you are ready.'));
// Redirect to cart on cancel.
$form_state['redirect'] = 'cart';
Ryan Szrama
committed
/**
* Submit handler to show the shopping cart updated message.
*/
function commerce_cart_line_item_views_form_submit($form, &$form_state) {
// Reset the status of the order to cart.
Ryan Szrama
committed
$order = commerce_order_load($form_state['order']->order_id);
Ryan Szrama
committed
Matt Glaman
committed
// Check order state for stale orders (cancelled, completed.)
$status = commerce_order_status_load($order->status);
if (in_array($status['state'], array('cart', 'checkout'))) {
$form_state['order'] = commerce_order_status_update($order, 'cart', TRUE);
Matt Glaman
committed
// Skip saving in the status update and manually save here to force a save
// even when the status doesn't actually change.
if (variable_get('commerce_order_auto_revision', TRUE)) {
$form_state['order']->revision = TRUE;
$form_state['order']->log = t('Customer updated the order via the shopping cart form.');
}
Ryan Szrama
committed
Matt Glaman
committed
commerce_order_save($form_state['order']);
drupal_set_message(t('Your shopping cart has been updated.'));
}
Ryan Szrama
committed
}
/**
* Submit handler to show the line item delete message.
*/
function commerce_cart_line_item_delete_form_submit($form, &$form_state) {
Bojan Živanović
committed
$line_item_id = $form_state['triggering_element']['#line_item_id'];
// Get the corresponding wrapper to show the correct title.
$line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $form_state['line_items'][$line_item_id]);
Ryan Szrama
committed
// If the deleted line item is a product...
if (in_array($line_item_wrapper->getBundle(), commerce_product_line_item_types())) {
Ryan Szrama
committed
$title = $line_item_wrapper->commerce_product->title->value();
}
else {
$title = $line_item_wrapper->line_item_label->value();
}
drupal_set_message(t('%title removed from your cart.', array('%title' => $title)));
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Adds a checkbox to the order settings form to enable the local action on
* order edit forms to apply pricing rules.
*/
function commerce_cart_form_commerce_order_settings_form_alter(&$form, &$form_state) {
$form['commerce_order_apply_pricing_rules_link'] = array(
'#type' => 'checkbox',
'#title' => t('Enable the local action link on order edit forms to apply pricing rules.'),
'#description' => t('Even if enabled the link will not appear on shopping cart order edit forms.'),
'#default_value' => variable_get('commerce_order_apply_pricing_rules_link', TRUE),
'#weight' => 10,
);
$cart_providers = array();
foreach (commerce_cart_get_providers() as $key => $cart_provider) {
$cart_providers[$key] = $cart_provider['title'];
}
$form['commerce_cart_provider'] = array(
'#title' => t('Cart provider'),
'#type' => 'select',
'#options' => $cart_providers,
'#default_value' => variable_get('commerce_cart_provider', COMMERCE_CART_DEFAULT_PROVIDER),
'#access' => count($cart_providers) > 1,
);
// Add a fieldset for settings pertaining to the shopping cart refresh.
$form['cart_refresh'] = array(
'#type' => 'fieldset',
'#title' => t('Shopping cart refresh'),
Josh Miller
committed
'#description' => t('Shopping cart orders comprise orders in shopping cart and some checkout related order statuses. These settings let you control how the shopping cart orders are refreshed, the process during which product prices are recalculated, to improve site performance in the case of excessive refreshes on sites with less dynamic pricing needs.'),
'#weight' => 40,
);
$form['cart_refresh']['commerce_cart_refresh_mode'] = array(
'#type' => 'radios',
'#title' => t('Shopping cart refresh mode'),
'#options' => array(
COMMERCE_CART_REFRESH_ALWAYS => t('Refresh a shopping cart when it is loaded regardless of who it belongs to.'),
COMMERCE_CART_REFRESH_OWNER_ONLY => t('Only refresh a shopping cart when it is loaded if it belongs to the current user.'),
COMMERCE_CART_REFRESH_ACTIVE_CART_ONLY => t("Only refresh a shopping cart when it is loaded if it is the current user's active shopping cart."),
),
'#default_value' => variable_get('commerce_cart_refresh_mode', COMMERCE_CART_REFRESH_OWNER_ONLY),
);
$form['cart_refresh']['commerce_cart_refresh_frequency'] = array(
'#type' => 'textfield',
'#title' => t('Shopping cart refresh frequency'),
'#description' => t('Shopping carts will only be refreshed if more than the specified number of seconds have passed since they were last refreshed.'),
'#default_value' => variable_get('commerce_cart_refresh_frequency', COMMERCE_CART_REFRESH_DEFAULT_FREQUENCY),
'#required' => TRUE,
'#size' => 32,
'#field_suffix' => t('seconds'),
'#element_validate' => array('commerce_cart_validate_refresh_frequency'),
);
$form['cart_refresh']['commerce_cart_refresh_force'] = array(
'#type' => 'checkbox',
'#title' => t('Always refresh shopping cart orders on shopping cart and checkout form pages regardless of other settings.'),
'#description' => t('Note: this option only applies to the core /cart and /checkout/* paths.'),
'#default_value' => variable_get('commerce_cart_refresh_force', TRUE),
);
}
/**
* Form element validation handler for the cart refresh frequency value.
*/
function commerce_cart_validate_refresh_frequency($element, &$form_state) {
$value = $element['#value'];
if ($value !== '' && (!is_numeric($value) || intval($value) != $value || $value < 0)) {
form_error($element, t('%name must be 0 or a positive integer.', array('%name' => $element['#title'])));
}
}
/**
* Implements hook_form_FORM_ID_alter().
Ryan Szrama
committed
*
* Alter the order edit form so administrators cannot attempt to alter line item
* unit prices for orders still in a shopping cart status. On order load, the
* cart module refreshes these prices based on the current product price and
* pricing rules, so any alterations would not be persistent anyways.
*
* @see commerce_cart_commerce_order_load()
Ryan Szrama
committed
function commerce_cart_form_commerce_order_ui_order_form_alter(&$form, &$form_state) {
$order = $form_state['commerce_order'];
Ryan Szrama
committed
// If the order being edited is in a shopping cart status and the form has the
// commerce_line_items element present...
if (commerce_cart_order_is_cart($order) && !empty($form['commerce_line_items'])) {
// Grab the instance info for commerce_line_items and only alter the form if
// it's using the line item manager widget.
$instance = field_info_instance('commerce_order', 'commerce_line_items', field_extract_bundle('commerce_order', $order));
if ($instance['widget']['type'] == 'commerce_line_item_manager') {
// Loop over the line items on the form...
foreach ($form['commerce_line_items'][$form['commerce_line_items']['#language']]['line_items'] as &$line_item) {
// Disable the unit price amount and currency code fields.
$language = $line_item['commerce_unit_price']['#language'];
$line_item['commerce_unit_price'][$language][0]['amount']['#disabled'] = TRUE;
$line_item['commerce_unit_price'][$language][0]['currency_code']['#disabled'] = TRUE;
}
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
/**
* Implements hook_form_FORM_ID_alter().
*
* Alters the Field UI field edit form to add per-instance settings for fields
* on product types governing the use of product fields as attribute selection
* fields on the Add to Cart form.
*/
function commerce_cart_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
// Extract the instance info from the form.
$instance = $form['#instance'];
// If the current field instance is not locked, is attached to a product type,
// and of a field type that defines an options list...
if (empty($form['locked']) && $instance['entity_type'] == 'commerce_product' &&
function_exists($form['#field']['module'] . '_options_list')) {
// Get the current instance's attribute settings for use as default values.
$commerce_cart_settings = commerce_cart_field_instance_attribute_settings($instance);
$form['instance']['commerce_cart_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Attribute field settings'),
'#description' => t('Single value fields attached to products can function as attribute selection fields on Add to Cart forms. When an Add to Cart form contains multiple products, attribute field data can be used to allow customers to select a product based on the values of the field instead of just from a list of product titles.'),
'#weight' => 5,
'#collapsible' => FALSE,
);
$form['instance']['commerce_cart_settings']['attribute_field'] = array(
'#type' => 'checkbox',
'#title' => t('Enable this field to function as an attribute field on Add to Cart forms.'),
'#default_value' => $commerce_cart_settings['attribute_field'],
);
$form['instance']['commerce_cart_settings']['attribute_widget'] = array(
'#type' => 'radios',
'#title' => t('Attribute selection widget'),
'#description' => t('The type of element used to select an option if used on an Add to Cart form.'),
'#options' => array(
'select' => t('Select list'),
'radios' => t('Radio buttons'),
),
'#default_value' => $commerce_cart_settings['attribute_widget'],
'#states' => array(
'visible' => array(
':input[name="instance[commerce_cart_settings][attribute_field]"]' => array('checked' => TRUE),
),
),
);
Jonathan Sacksick
committed
// Determine the default attribute widget title.
$attribute_widget_title = $commerce_cart_settings['attribute_widget_title'];
if (empty($attribute_widget_title)) {
$attribute_widget_title = $instance['label'];
}
$form['instance']['commerce_cart_settings']['attribute_widget_title'] = array(
'#type' => 'textfield',
'#title' => t('Attribute widget title'),
'#description' => t('Specify the title to use for the attribute widget on the Add to Cart form.'),
'#default_value' => $attribute_widget_title,
'#states' => array(
'visible' => array(
':input[name="instance[commerce_cart_settings][attribute_field]"]' => array('checked' => TRUE),
),
),
);
$form['field']['cardinality']['#description'] .= '<br />' . t('Must be 1 for this field to function as an attribute selection field on Add to Cart forms.');
}
Ryan Szrama
committed
// If the current field instance is not locked and is attached to a product
// line item type...
if (empty($form['locked']) && $instance['entity_type'] == 'commerce_line_item' &&
Ryan Szrama
committed
in_array($instance['bundle'], commerce_product_line_item_types())) {
Ryan Szrama
committed
// Get the current instance's line item form settings for use as default values.
$commerce_cart_settings = commerce_cart_field_instance_access_settings($instance);
$form['instance']['commerce_cart_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Add to Cart form settings'),
'#description' =>t('Fields attached to product line item types can be included in the Add to Cart form to collect additional information from customers in conjunction with their purchase of particular products.'),
'#weight' => 5,
'#collapsible' => FALSE,
);
$form['instance']['commerce_cart_settings']['field_access'] = array(
'#type' => 'checkbox',
'#title' => t('Include this field on Add to Cart forms for line items of this type.'),
'#default_value' => $commerce_cart_settings['field_access'],
);
}
}
/**
* Implements hook_commerce_order_delete().
*/
function commerce_cart_commerce_order_delete($order) {
commerce_cart_order_session_delete($order->order_id);
commerce_cart_order_session_delete($order->order_id, TRUE);
}
Ryan Szrama
committed
/**
* Implements hook_commerce_product_calculate_sell_price_line_item_alter().
*/
function commerce_cart_commerce_product_calculate_sell_price_line_item_alter($line_item) {
global $user;
// Reference the current shopping cart order in the line item if it isn't set.
Taihao Zhang
committed
// We load the complete order at this time to ensure it primes the order cache
// and avoid any untraceable recursive loops.
// @see http://drupal.org/node/1268472
Ryan Szrama
committed
if (empty($line_item->order_id)) {
Taihao Zhang
committed
$order = commerce_cart_order_load($user->uid);
Ryan Szrama
committed
if ($order) {
$line_item->order_id = $order->order_id;
}
Ryan Szrama
committed
}
}
/**
* Implements hook_views_api().
*/
function commerce_cart_views_api() {
return array(
'path' => drupal_get_path('module', 'commerce_cart') . '/includes/views',
);
}
Ryan Szrama
committed
/**
* Implements hook_theme().
*/
function commerce_cart_theme() {
return array(
'commerce_cart_empty_block' => array(
'variables' => array(),
),
Ryan Szrama
committed
'commerce_cart_empty_page' => array(
'variables' => array(),
),
Ryan Szrama
committed
'commerce_cart_block' => array(
Ryan Szrama
committed
'variables' => array('order' => NULL, 'contents_view' => NULL),
Ryan Szrama
committed
'path' => drupal_get_path('module', 'commerce_cart') . '/theme',
'template' => 'commerce-cart-block',
Ryan Szrama
committed
),
);
}
Ryan Szrama
committed
/**
* Implements hook_user_login().
*
* When a user logs into the site, if they have a shopping cart order it should
* be updated to belong to their user account.
Ryan Szrama
committed
*/
function commerce_cart_user_login(&$edit, $account) {
// Get the user's anonymous shopping cart order if it exists.
if ($order = commerce_cart_order_load()) {
// Convert it to an authenticated cart.
commerce_cart_order_convert($order, $account);
Ryan Szrama
committed
}
Ryan Szrama
committed
}
/**
* Implements hook_user_update().
*
* When a user account e-mail address is updated, update any shopping cart
* orders owned by the user account to use the new e-mail address.
*/
function commerce_cart_user_update(&$edit, $account, $category) {
// If the e-mail address was changed...
if (!empty($edit['original']->mail) && $account->mail != $edit['original']->mail) {
// Load the user's shopping cart orders.
$query = new EntityFieldQuery();
$query
->entityCondition('entity_type', 'commerce_order', '=')
->propertyCondition('uid', $account->uid, '=')
->propertyCondition('status', array_keys(commerce_order_statuses(array('cart' => TRUE))), 'IN');
$result = $query->execute();
if (!empty($result['commerce_order'])) {
foreach (commerce_order_load_multiple(array_keys($result['commerce_order'])) as $order) {
if ($order->mail != $account->mail) {
$order->mail = $account->mail;
commerce_order_save($order);
}
}
}
}
}
Ryan Szrama
committed
/**
* Implements hook_block_info().
*/
function commerce_cart_block_info() {
$blocks = array();
// Define the basic shopping cart block and hide it on the checkout pages.
$blocks['cart'] = array(
'info' => t('Shopping cart'),
'cache' => DRUPAL_NO_CACHE,
'visibility' => 0,
'pages' => 'checkout*',
);
return $blocks;
Ryan Szrama
committed
}
/**
* Implements hook_block_view().
*/
function commerce_cart_block_view($delta) {
global $user;
// Prepare the display of the default Shopping Cart block.
if ($delta == 'cart') {
Pedro Cambra
committed
// First check to ensure there are products in the shopping cart.
Ryan Szrama
committed
if ($order = commerce_cart_order_load($user->uid)) {
$wrapper = entity_metadata_wrapper('commerce_order', $order);
// If there are one or more products in the cart...
if (commerce_line_items_quantity($wrapper->commerce_line_items, commerce_product_line_item_types()) > 0) {
Ryan Szrama
committed
Ryan Szrama
committed
// Build the variables array to send to the cart block template.
$variables = array(
'order' => $order,
'contents_view' => commerce_embed_view('commerce_cart_block', 'default', array($order->order_id), $_GET['q']),
Ryan Szrama
committed
);
Ryan Szrama
committed
Ryan Szrama
committed
$content = theme('commerce_cart_block', $variables);
}
if (empty($content)) {
// Default to an empty cart block message.
$content = theme('commerce_cart_empty_block');
}
return array('subject' => t('Shopping cart'), 'content' => $content);
}
}
/**
* Checks if a cart order should be refreshed based on the shopping cart refresh
* settings on the order settings form.
*
* @param $order
* The cart order to check.
*
* @return
* Boolean indicating whether or not the cart order can be refreshed.
*/
function commerce_cart_order_can_refresh($order) {
Jonathan Sacksick
committed
$can_refresh = module_invoke_all('commerce_cart_order_can_refresh', $order);
// We refresh the cart order if both of the following conditions are met:
// - No modules say to skip the cart refresh.
// - At least one module allows it.
// If no module specified either allow or skip, we fall back to the
// logic below.
if (in_array(FALSE, $can_refresh, TRUE)) {
return FALSE;
}
elseif (in_array(TRUE, $can_refresh, TRUE)) {
return TRUE;
}
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
global $user;
// Force the shopping cart refresh on /cart and /checkout/* paths if enabled.
if (variable_get('commerce_cart_refresh_force', TRUE) &&
(current_path() == 'cart' || strpos(current_path(), 'checkout/') === 0)) {
return TRUE;
}
// Prevent refresh for orders that don't match the current refresh mode.
switch (variable_get('commerce_cart_refresh_mode', COMMERCE_CART_REFRESH_OWNER_ONLY)) {
case COMMERCE_CART_REFRESH_OWNER_ONLY:
// If the order is anonymous, check the session to see if the order
// belongs to the current user. Otherwise just check that the order uid
// matches the current user.
if ($order->uid == 0 && !commerce_cart_order_session_exists($order->order_id)) {
return FALSE;
}
elseif ($order->uid != $user->uid) {
return FALSE;
}
break;
case COMMERCE_CART_REFRESH_ACTIVE_CART_ONLY:
// Check to see if the order ID matches the current user's cart order ID.
if (commerce_cart_order_id($user->uid) != $order->order_id) {
return FALSE;
}
break;
case COMMERCE_CART_REFRESH_ALWAYS:
default:
// Continue on if shopping cart orders should always refresh.
break;
}
// Check to see if the last cart refresh happened long enough ago.
$seconds = variable_get('commerce_cart_refresh_frequency', COMMERCE_CART_REFRESH_DEFAULT_FREQUENCY);
if (!empty($seconds) && !empty($order->data['last_cart_refresh']) &&
REQUEST_TIME - $order->data['last_cart_refresh'] < $seconds) {
return FALSE;
}
return TRUE;
}
/**
* Implements hook_commerce_order_load().
*
* Because shopping carts are merely a special case of orders, we work through
* the Order API to ensure that products in shopping carts are kept up to date.
* Therefore, each time a cart is loaded, we calculate afresh the unit and total
* prices of product line items and save them if any values have changed.
*/
function commerce_cart_commerce_order_load($orders) {
$refreshed = &drupal_static(__FUNCTION__, array());
foreach ($orders as $order) {
Andrei Mateescu
committed
// Refresh only if this order object represents the latest revision of a
// shopping cart order, it hasn't been refreshed already in this request
// and it meets the criteria in the shopping cart refresh settings.
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
if (isset($refreshed[$order->order_id]) ||
commerce_entity_is_unchanged('commerce_order', $order) ||
!commerce_cart_order_is_cart($order) ||
!commerce_order_is_latest_revision($order) ||
!commerce_cart_order_can_refresh($order)) {
continue;
}
// Update the last cart refresh timestamp and record the order's current
// changed timestamp to detect if the order is actually updated.
$order->data['last_cart_refresh'] = REQUEST_TIME;
$unchanged_data = $order->data;
$last_changed = $order->changed;
// Refresh the order and add its ID to the refreshed array.
$refreshed[$order->order_id] = TRUE;
commerce_cart_order_refresh($order);
// If order wasn't updated during the refresh, we need to manually update
// the last cart refresh timestamp in the database.
if ($order->changed == $last_changed) {
db_update('commerce_order')
->fields(array('data' => serialize($unchanged_data)))
->condition('order_id', $order->order_id)
->execute();
db_update('commerce_order_revision')
->fields(array('data' => serialize($unchanged_data)))
->condition('order_id', $order->order_id)
->condition('revision_id', $order->revision_id)
->execute();
}
}
}
/**
* Themes an empty shopping cart block's contents.
*/
function theme_commerce_cart_empty_block() {
Ryan Szrama
committed
return '<div class="cart-empty-block">' . t('Your shopping cart is empty.') . '</div>';
}
/**
* Themes an empty shopping cart page.
*/
function theme_commerce_cart_empty_page() {
return '<div class="cart-empty-page">' . t('Your shopping cart is empty.') . '</div>';
Ryan Szrama
committed
/**
* Loads the shopping cart order for the specified user.
*
* @param $uid
Ryan Szrama
committed
* The uid of the customer whose cart to load. If left 0, attempts to load
Ryan Szrama
committed
* an anonymous order from the session.
Ryan Szrama
committed
*
Ryan Szrama
committed
* @return
Taihao Zhang
committed
* The fully loaded shopping cart order or FALSE if nonexistent.
Ryan Szrama
committed
*/
function commerce_cart_order_load($uid = 0) {
// Retrieve the order ID for the specified user's current shopping cart.
$order_id = commerce_cart_order_id($uid);
Ryan Szrama
committed
Ryan Szrama
committed
// If a valid cart order ID exists for the user, return it now.
if (!empty($order_id)) {
return commerce_order_load($order_id);
Ryan Szrama
committed
}
Ryan Szrama
committed
Ryan Szrama
committed
return FALSE;
Ryan Szrama
committed
}
Ryan Szrama
committed
/**
* Returns the current cart order ID for the given user.
*
* @param $uid
* The uid of the customer whose cart to load. If left 0, attempts to load
* an anonymous order from the session.
*
* @return
* The requested cart order ID or FALSE if none was found.
Ryan Szrama
committed
*/
function commerce_cart_order_id($uid = 0) {
// Cart order IDs will be cached keyed by $uid.
$cart_order_ids = &drupal_static(__FUNCTION__);
// Cache the user's cart order ID if it hasn't been set already.
if (isset($cart_order_ids[$uid])) {
return $cart_order_ids[$uid];
}
// First let other modules attempt to provide a valid order ID for the given
// uid. Instead of invoking hook_commerce_cart_order_id() directly, we invoke
// it in each module implementing the hook and return the first valid order ID
// returned (if any).
foreach (module_implements('commerce_cart_order_id') as $module) {
$order_id = module_invoke($module, 'commerce_cart_order_id', $uid);
// If a hook said the user should not have a cart, that overrides any other
// potentially valid order ID. Return FALSE now.
if ($order_id === FALSE) {
$cart_order_ids[$uid] = FALSE;
return FALSE;
}
// Otherwise only return a valid order ID.
if (!empty($order_id) && is_int($order_id)) {
$cart_order_ids[$uid] = $order_id;
return $order_id;
}
}
Ryan Szrama
committed
// Create an array of valid shopping cart order statuses.
$status_ids = array_keys(commerce_order_statuses(array('cart' => TRUE)));
// If a customer uid was specified...
if ($uid) {
// Look for the user's most recent shopping cart order, although they
// should never really have more than one.
$cart_order_ids[$uid] = db_query('SELECT order_id FROM {commerce_order} WHERE uid = :uid AND status IN (:status_ids) ORDER BY order_id DESC', array(':uid' => $uid, ':status_ids' => $status_ids))->fetchField();
Ryan Szrama
committed
}
else {
// Otherwise look for a shopping cart order ID in the session.
if (commerce_cart_order_session_exists()) {
// We can't trust a user's IP address to remain the same, especially since
// it may be derived from a proxy server and not the actual client. As of
// Commerce 1.4, this query no longer restricts order IDs based on IP
// address, instead trusting Drupal to prevent session hijacking.
$cart_order_ids[$uid] = db_query('SELECT order_id FROM {commerce_order} WHERE order_id IN (:order_ids) AND uid = 0 AND status IN (:status_ids) ORDER BY order_id DESC', array(':order_ids' => commerce_cart_order_session_order_ids(), ':status_ids' => $status_ids))->fetchField();
}
else {
$cart_order_ids[$uid] = FALSE;
Ryan Szrama
committed
}
}
return $cart_order_ids[$uid];
Ryan Szrama
committed
}
Ryan Szrama
committed
/**
* Resets the cached array of shopping cart orders.
*/
Ryan Szrama
committed
function commerce_cart_order_ids_reset() {
$cart_order_ids = &drupal_static('commerce_cart_order_id');
Ryan Szrama
committed
$cart_order_ids = NULL;
Ryan Szrama
committed
}
/**
* Creates a new shopping cart order for the specified user.
*
* @param $uid
Ryan Szrama
committed
* The uid of the user for whom to create the order. If left 0, the order will
* be created for an anonymous user and associated with the current session
* if it is anonymous.
* @param $type
* The type of the order; defaults to the standard 'commerce_order' type.
Ryan Szrama
committed
*
Ryan Szrama
committed
* @return
* The newly created shopping cart order object.
*/
function commerce_cart_order_new($uid = 0, $type = 'commerce_order') {
global $user;
// Create the new order with the customer's uid and the cart order status.
$order = commerce_order_new($uid, 'cart', $type);
$order->log = t('Created as a shopping cart order.');
Ryan Szrama
committed
// Save it so it gets an order ID and return the full object.
commerce_order_save($order);
Ryan Szrama
committed
// Reset the cart cache
Ryan Szrama
committed
commerce_cart_order_ids_reset();
// If the user is not logged in, ensure the order ID is stored in the session.
if (!$uid && empty($user->uid)) {
commerce_cart_order_session_save($order->order_id);
Ryan Szrama
committed
}
return $order;
Ryan Szrama
committed
}
Ryan Szrama
committed
Ryan Szrama
committed
/**
* Determines whether or not the given order is a shopping cart order.
*/
function commerce_cart_order_is_cart($order) {
// If the order is in a shopping cart order status, assume it is a cart.
$is_cart = array_key_exists($order->status, commerce_order_statuses(array('cart' => TRUE)));
Ryan Szrama
committed
// Allow other modules to make the judgment based on some other criteria.
Damien Tournoud
committed
foreach (module_implements('commerce_cart_order_is_cart') as $module) {
$function = $module . '_commerce_cart_order_is_cart';
// As of Drupal Commerce 1.2, $is_cart should be accepted by reference and
// manipulated directly, but we still check for a return value to preserve
// backward compatibility with the hook. In future versions, we will
// deprecate hook_commerce_cart_order_is_cart() and force modules to update
// to hook_commerce_cart_order_is_cart_alter().
if ($function($order, $is_cart) === FALSE) {
$is_cart = FALSE;
}
Ryan Szrama
committed
}
Damien Tournoud
committed
drupal_alter('commerce_cart_order_is_cart', $is_cart, $order);
Ryan Szrama
committed
return $is_cart;
}
Ryan Szrama
committed
* Implements hook_commerce_entity_access_condition_commerce_order_alter().
Ryan Szrama
committed
* This alter hook allows the Cart module to add conditions to the query used to
* determine if a user has view access to a given order. The Cart module will
* always grant users access to view their own carts (independent of any
* permission settings) and also grants anonymous users access to view their
* completed orders if they've been given the permission.
*/
function commerce_cart_commerce_entity_access_condition_commerce_order_alter(&$conditions, $context) {