Skip to content

Instantly share code, notes, and snippets.

@rfay
Created February 14, 2011 19:01
Show Gist options
  • Select an option

  • Save rfay/826357 to your computer and use it in GitHub Desktop.

Select an option

Save rfay/826357 to your computer and use it in GitHub Desktop.
DC forced push
.DS_Store
._*
*~
*.kpf
*.swp
*.swo
Drupal Commerce is packaged for download at http://drupal.org/project/commerce.
Learn more at http://www.drupalcommerce.org.
This directory contains files used by the Advanced Help module.
This directory contains Views related include files.
This directory contains the various core Drupal Commerce modules.
<?php
// $Id$
/**
* @file
* Default implementation of the shopping cart block template.
*
* Available variables:
* - $contents_view: A rendered View containing the contents of the cart.
*
* Helper variables:
* - $order: The full order object for the shopping cart.
*
* @see template_preprocess()
* @see template_process()
*/
?>
<div class="cart-contents">
<?php print $contents_view; ?>
</div>
<?php
// $Id$
/**
* @file
* Default implementation of a line item summary template.
*
* Available variables:
* - $quantity_raw: The number of items in the cart.
* - $quantity_label: The quantity appropriate label to use for the number of
* items in the shopping cart; "item" or "items" by default.
* - $quantity: A single string containing the number and label.
* - $total_raw: The raw numeric value of the total value of items in the cart.
* - $total_label: A text label for the total value; "Total:" by default.
* - $total: The currency formatted total value of items in the cart.
* - $links: A rendered links array with cart and checkout links.
*
* Helper variables:
* - $view: The View the line item summary is attached to.
*
* @see template_preprocess()
* @see template_process()
*/
?>
<div class="line-item-summary">
<?php if ($quantity_raw): ?>
<div class="line-item-quantity">
<span class="line-item-quantity-raw"><?php print $quantity_raw; ?></span> <span class="line-item-quantity-label"><?php print $quantity_label; ?></span>
</div>
<?php endif; ?>
<?php if ($total): ?>
<div class="line-item-total">
<span class="line-item-total-label"><?php print $total_label; ?></span> <span class="line-item-total-raw"><?php print $total; ?></span>
</div>
<?php endif; ?>
<?php print $links; ?>
</div>
<?php
// $Id$
/**
* @file
* Default implementation of a payment totals template.
*
* Available variables:
* - $rows: A rows array as used by theme_table(), potentially containing
* transaction totals, an order balance, and information specified by
* other modules.
* - $form: When present, a form for adding payments to an order pertinent to
* the display.
*
* Helper variables:
* - $totals: An array of transaction totals keyed by currency code.
* - $view: The View the line item summary is attached to.
* - $order: If present, the order represented by the totals.
*
* @see template_preprocess()
* @see template_process()
*/
?>
<div class="payment-totals">
<?php print theme('table', array('rows' => $rows, 'attributes' => array('class' => array('payment-totals-table')))); ?>
<?php print $form; ?>
</div>
<?php
// $Id$
/**
* @file
* Default theme implementation to present the SKU on a product page.
*
* Available variables:
* - $sku: The SKU to render.
* - $label: If present, the string to use as the SKU label.
*
* Helper variables:
* - $product: The fully loaded product object the SKU represents.
*/
?>
<?php if ($sku): ?>
<div class="commerce-product-sku">
<?php if ($label): ?>
<div class="sku-label">
<?php print $label; ?>
</div>
<?php endif; ?>
<?php print $sku; ?>
</div>
<?php endif; ?>
<?php
// $Id$
/**
* @file
* Default theme implementation to present the status on a product page.
*
* Available variables:
* - $status: The string representation of a product's status to render.
* - $label: If present, the string to use as the status label.
*
* Helper variables:
* - $product: The fully loaded product object the status belongs to.
*/
?>
<?php if ($status): ?>
<div class="commerce-product-status">
<?php if ($label): ?>
<div class="status-label">
<?php print $label; ?>
</div>
<?php endif; ?>
<?php print $status; ?>
</div>
<?php endif; ?>
<?php
// $Id$
/**
* @file
* Default theme implementation to present the title on a product page.
*
* Available variables:
* - $title: The title to render.
* - $label: If present, the string to use as the title label.
*
* Helper variables:
* - $product: The fully loaded product object the title belongs to.
*/
?>
<?php if ($title): ?>
<div class="commerce-product-title">
<?php if ($label): ?>
<div class="title-label">
<?php print $label; ?>
</div>
<?php endif; ?>
<?php print $title; ?>
</div>
<?php endif; ?>
<?php
// $Id$
/**
* @file
* This file contains no working PHP code; it exists to provide additional
* documentation for doxygen as well as to document hooks in the standard
* Drupal manner.
*/
/**
* Defines currencies available to commerce.
*
* By default commerce provides all active currencies according to ISO 4217.
* Make sure to use the translate function t() for translatable properties.
*
* @return
* An array of information about the currencies commerce should provide.
* The array contains a sub-array for each currency, with the currency name
* as the key.
* Possible attributes for each sub-array are:
* - code: The uppercase alphabetic currency code.
* For example USD.
* - numeric_code: The nummeric currceny code. According to ISO4217 this code
* consists of three digits and first digit can be a zero.
* - symbol: The currency symbol. For example $.
* - name: The name of the currency. Translatable.
* - symbol_placement: Defines where the currency symbol has to be placed for
* display. Allowed values: before, after, hidden.
* - code_placement: Defines where the currency code has to be placed for
* display. Allowed values: before, after, hidden.
* - minor_unit: Name of the minor unit of the currency. For example Cent.
* Translatable
* - major_unit: Name of the major unit of the currency. For example Dollar.
* Translatable
* - rounding_step: Defines which stepping has to is used for price rounding.
* For example Swiss Francs use a rounding_step of 0.05. This means a
* price like 10.93 is converted to 10.95. Currently only the steps
* 0.5,0.05... and 0.2, 0.02 ... are supported. This value has to be
* defined as string, otherwise the rounding results can be unpredictable.
* Default: 0 (no special rounding)
* - decimals: The number of decimals to display.
* Default: 2
* - thousands_separator: The char to split the value in groups of thousand.
* Default: ,
* - decimal_separator: The char to split the integer from the decimal part.
* Default: .
* - format_callback: Custom callback function to format a price value.
* - conversion_rate: The conversion rate of this currency calculated against
* the base currency, expressed as a decimal value denoting the value of
* one majur unit of this currency when converted to the base currency.
* Default: 1
*
* @see hook_commerce_currency_info_alter()
*/
function hook_commerce_currency_info() {
return array (
'CHF' => array(
'code' => 'CHF',
'numeric_code' => '756',
'symbol' => 'Fr.',
'name' => t('Swiss Franc'),
'symbol_placement' => 'before',
'code_placement' => 'before',
'minor_unit' => t('Rappen'),
'major_unit' => t('Franc'),
'rounding_step' => '0.05',
),
);
}
/**
* Alter commerce currencies.
*
* By default Commerce provides all active currencies according to ISO 4217.
* This hook allows you to change the formatting properties of existing
* definitions.
*
* Additionally, because every currency's default conversion rate is 1, this
* hook can be used to populate currency conversion rates with meaningful
* values. Conversion rates can be calculated using any currency as the base
* currency as long as the same base currency is used for every rate.
*
* @see hook_commerce_currency_info()
*/
function hook_commerce_currency_info_alter(&$currencies, $langcode) {
$currencies['CHF']['code_placement'] = 'after';
}
<?php
// $Id$
/**
* @file
* Defines the default currency list of active currencies from ISO 4217.
*/
/**
* Implements hook_commerce_currency_info().
*/
function commerce_commerce_currency_info() {
return array(
'AFN' => array(
'code' => 'AFN',
'symbol' => 'Af',
'name' => t('Afghan Afghani'),
'decimals' => 0,
'numeric_code' => '971',
'minor_unit' => t('Pul'),
'major_unit' => t('Afghani'),
),
'ANG' => array(
'code' => 'ANG',
'symbol' => 'NAf.',
'name' => t('Netherlands Antillean Guilder'),
'numeric_code' => '532',
'minor_unit' => t('Cent'),
'major_unit' => t('Guilder'),
),
'AOA' => array(
'code' => 'AOA',
'symbol' => 'Kz',
'name' => t('Angolan Kwanza'),
'numeric_code' => '973',
'minor_unit' => t('Cรชntimo'),
'major_unit' => t('Kwanza'),
),
'ARM' => array(
'code' => 'ARM',
'symbol' => 'm$n',
'name' => t('Argentine Peso Moneda Nacional'),
'minor_unit' => t('Centavos'),
'major_unit' => t('Peso'),
),
'ARS' => array(
'code' => 'ARS',
'symbol' => 'AR$',
'name' => t('Argentine Peso'),
'numeric_code' => '032',
'minor_unit' => t('Centavo'),
'major_unit' => t('Peso'),
),
'AUD' => array(
'code' => 'AUD',
'symbol' => 'AU$',
'name' => t('Australian Dollar'),
'numeric_code' => '036',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'AWG' => array(
'code' => 'AWG',
'symbol' => 'Afl.',
'name' => t('Aruban Florin'),
'numeric_code' => '533',
'minor_unit' => t('Cent'),
'major_unit' => t('Guilder'),
),
'AZN' => array(
'code' => 'AZN',
'symbol' => 'man.',
'name' => t('Azerbaijanian Manat'),
'minor_unit' => t('Qษ™pik'),
'major_unit' => t('New Manat'),
),
'BAM' => array(
'code' => 'BAM',
'symbol' => 'KM',
'name' => t('Bosnia-Herzegovina Convertible Mark'),
'numeric_code' => '977',
'minor_unit' => t('Fening'),
'major_unit' => t('Convertible Marka'),
),
'BBD' => array(
'code' => 'BBD',
'symbol' => 'Bds$',
'name' => t('Barbadian Dollar'),
'numeric_code' => '052',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'BDT' => array(
'code' => 'BDT',
'symbol' => 'Tk',
'name' => t('Bangladeshi Taka'),
'numeric_code' => '050',
'minor_unit' => t('Paisa'),
'major_unit' => t('Taka'),
),
'BHD' => array(
'code' => 'BHD',
'symbol' => 'BD',
'name' => t('Bahraini Dinar'),
'decimals' => 3,
'numeric_code' => '048',
'minor_unit' => t('Fils'),
'major_unit' => t('Dinar'),
),
'BIF' => array(
'code' => 'BIF',
'symbol' => 'FBu',
'name' => t('Burundian Franc'),
'decimals' => 0,
'numeric_code' => '108',
'minor_unit' => t('Centime'),
'major_unit' => t('Franc'),
),
'BMD' => array(
'code' => 'BMD',
'symbol' => 'BD$',
'name' => t('Bermudan Dollar'),
'numeric_code' => '060',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'BND' => array(
'code' => 'BND',
'symbol' => 'BN$',
'name' => t('Brunei Dollar'),
'numeric_code' => '096',
'minor_unit' => t('Sen'),
'major_unit' => t('Dollar'),
),
'BOB' => array(
'code' => 'BOB',
'symbol' => 'Bs',
'name' => t('Bolivian Boliviano'),
'numeric_code' => '068',
'minor_unit' => t('Centavo'),
'major_unit' => t('Bolivianos'),
),
'BRL' => array(
'code' => 'BRL',
'symbol' => 'R$',
'name' => t('Brazilian Real'),
'numeric_code' => '986',
'minor_unit' => t('Centavo'),
'major_unit' => t('Reais'),
),
'BSD' => array(
'code' => 'BSD',
'symbol' => 'BS$',
'name' => t('Bahamian Dollar'),
'numeric_code' => '044',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'BTN' => array(
'code' => 'BTN',
'symbol' => 'Nu.',
'name' => t('Bhutanese Ngultrum'),
'numeric_code' => '064',
'minor_unit' => t('Chetrum'),
'major_unit' => t('Ngultrum'),
),
'BWP' => array(
'code' => 'BWP',
'symbol' => 'BWP',
'name' => t('Botswanan Pula'),
'numeric_code' => '072',
'minor_unit' => t('Thebe'),
'major_unit' => t('Pulas'),
),
'BZD' => array(
'code' => 'BZD',
'symbol' => 'BZ$',
'name' => t('Belize Dollar'),
'numeric_code' => '084',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'CAD' => array(
'code' => 'CAD',
'symbol' => 'CA$',
'name' => t('Canadian Dollar'),
'numeric_code' => '124',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'CDF' => array(
'code' => 'CDF',
'symbol' => 'CDF',
'name' => t('Congolese Franc'),
'numeric_code' => '976',
'minor_unit' => t('Centime'),
'major_unit' => t('Franc'),
),
'CHF' => array(
'code' => 'CHF',
'symbol' => 'Fr.',
'name' => t('Swiss Franc'),
'rounding_step' => '0.05',
'numeric_code' => '756',
'minor_unit' => t('Rappen'),
'major_unit' => t('Franc'),
),
'CLE' => array(
'code' => 'CLE',
'symbol' => 'Eยบ',
'name' => t('Chilean Escudo'),
'minor_unit' => t('Centรฉsimo'),
'major_unit' => t('Escudo'),
),
'CLP' => array(
'code' => 'CLP',
'symbol' => 'CL$',
'name' => t('Chilean Peso'),
'decimals' => 0,
'numeric_code' => '152',
'minor_unit' => t('Centavo'),
'major_unit' => t('Peso'),
),
'CNY' => array(
'code' => 'CNY',
'symbol' => 'CNยฅ',
'name' => t('Chinese Yuan Renminbi'),
'numeric_code' => '156',
'minor_unit' => t('Fe'),
'major_unit' => t('Yuan Renminbi'),
),
'COP' => array(
'code' => 'COP',
'symbol' => 'CO$',
'name' => t('Colombian Peso'),
'decimals' => 0,
'numeric_code' => '170',
'minor_unit' => t('Centavo'),
'major_unit' => t('Peso'),
),
'CRC' => array(
'code' => 'CRC',
'symbol' => 'ยข',
'name' => t('Costa Rican Colรณn'),
'decimals' => 0,
'numeric_code' => '188',
'minor_unit' => t('Cรฉntimo'),
'major_unit' => t('Colรณn'),
),
'CUC' => array(
'code' => 'CUC',
'symbol' => 'CUC$',
'name' => t('Cuban Convertible Peso'),
'minor_unit' => t('Centavo'),
'major_unit' => t('Peso'),
),
'CUP' => array(
'code' => 'CUP',
'symbol' => 'CU$',
'name' => t('Cuban Peso'),
'numeric_code' => '192',
'minor_unit' => t('Centavo'),
'major_unit' => t('Peso'),
),
'CVE' => array(
'code' => 'CVE',
'symbol' => 'CV$',
'name' => t('Cape Verdean Escudo'),
'numeric_code' => '132',
'minor_unit' => t('Centavo'),
'major_unit' => t('Escudo'),
),
'CZK' => array(
'code' => 'CZK',
'symbol' => 'Kc',
'name' => t('Czech Republic Koruna'),
'numeric_code' => '203',
'minor_unit' => t('Halรฉr'),
'major_unit' => t('Koruny'),
),
'DJF' => array(
'code' => 'DJF',
'symbol' => 'Fdj',
'name' => t('Djiboutian Franc'),
'numeric_code' => '262',
'decimals' => 0,
'minor_unit' => t('Centime'),
'major_unit' => t('Franc'),
),
'DKK' => array(
'code' => 'DKK',
'symbol' => 'kr.',
'name' => t('Danish Krone'),
'numeric_code' => '208',
'thousands_separator' => ' ',
'decimal_separator' => ',',
'symbol_placement' => 'after',
'code_placement' => '',
'minor_unit' => t('ร˜re'),
'major_unit' => t('Kroner'),
),
'DOP' => array(
'code' => 'DOP',
'symbol' => 'RD$',
'name' => t('Dominican Peso'),
'numeric_code' => '214',
'minor_unit' => t('Centavo'),
'major_unit' => t('Peso'),
),
'DZD' => array(
'code' => 'DZD',
'symbol' => 'DA',
'name' => t('Algerian Dinar'),
'numeric_code' => '012',
'minor_unit' => t('Santeem'),
'major_unit' => t('Dinar'),
),
'EEK' => array(
'code' => 'EEK',
'symbol' => 'Ekr',
'name' => t('Estonian Kroon'),
'thousands_separator' => ' ',
'decimal_separator' => ',',
'numeric_code' => '233',
'minor_unit' => t('Sent'),
'major_unit' => t('Krooni'),
),
'EGP' => array(
'code' => 'EGP',
'symbol' => 'EGยฃ',
'name' => t('Egyptian Pound'),
'numeric_code' => '818',
'minor_unit' => t('Piastr'),
'major_unit' => t('Pound'),
),
'ERN' => array(
'code' => 'ERN',
'symbol' => 'Nfk',
'name' => t('Eritrean Nakfa'),
'numeric_code' => '232',
'minor_unit' => t('Cent'),
'major_unit' => t('Nakfa'),
),
'ETB' => array(
'code' => 'ETB',
'symbol' => 'Br',
'name' => t('Ethiopian Birr'),
'numeric_code' => '230',
'minor_unit' => t('Santim'),
'major_unit' => t('Birr'),
),
'EUR' => array(
'code' => 'EUR',
'symbol' => 'โ‚ฌ',
'name' => t('Euro'),
'thousands_separator' => ' ',
'decimal_separator' => ',',
'symbol_placement' => 'after',
'code_placement' => '',
'numeric_code' => '978',
'minor_unit' => t('Cent'),
'major_unit' => t('Euro'),
),
'FJD' => array(
'code' => 'FJD',
'symbol' => 'FJ$',
'name' => t('Fijian Dollar'),
'numeric_code' => '242',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'FKP' => array(
'code' => 'FKP',
'symbol' => 'FKยฃ',
'name' => t('Falkland Islands Pound'),
'numeric_code' => '238',
'minor_unit' => t('Penny'),
'major_unit' => t('Pound'),
),
'GBP' => array(
'code' => 'GBP',
'symbol' => 'ยฃ',
'name' => t('British Pound Sterling'),
'numeric_code' => '826',
'symbol_placement' => 'before',
'code_placement' => '',
'minor_unit' => t('Penny'),
'major_unit' => t('Pound'),
),
'GHS' => array(
'code' => 'GHS',
'symbol' => 'GHโ‚ต',
'name' => t('Ghanaian Cedi'),
'minor_unit' => t('Pesewa'),
'major_unit' => t('Cedi'),
),
'GIP' => array(
'code' => 'GIP',
'symbol' => 'GIยฃ',
'name' => t('Gibraltar Pound'),
'numeric_code' => '292',
'minor_unit' => t('Penny'),
'major_unit' => t('Pound'),
),
'GMD' => array(
'code' => 'GMD',
'symbol' => 'GMD',
'name' => t('Gambian Dalasi'),
'numeric_code' => '270',
'minor_unit' => t('Butut'),
'major_unit' => t('Dalasis'),
),
'GNF' => array(
'code' => 'GNF',
'symbol' => 'FG',
'name' => t('Guinean Franc'),
'decimals' => 0,
'numeric_code' => '324',
'minor_unit' => t('Centime'),
'major_unit' => t('Franc'),
),
'GTQ' => array(
'code' => 'GTQ',
'symbol' => 'GTQ',
'name' => t('Guatemalan Quetzal'),
'numeric_code' => '320',
'minor_unit' => t('Centavo'),
'major_unit' => t('Quetzales'),
),
'GYD' => array(
'code' => 'GYD',
'symbol' => 'GY$',
'name' => t('Guyanaese Dollar'),
'decimals' => 0,
'numeric_code' => '328',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'HKD' => array(
'code' => 'HKD',
'symbol' => 'HK$',
'name' => t('Hong Kong Dollar'),
'numeric_code' => '344',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'HNL' => array(
'code' => 'HNL',
'symbol' => 'HNL',
'name' => t('Honduran Lempira'),
'numeric_code' => '340',
'minor_unit' => t('Centavo'),
'major_unit' => t('Lempiras'),
),
'HRK' => array(
'code' => 'HRK',
'symbol' => 'kn',
'name' => t('Croatian Kuna'),
'numeric_code' => '191',
'minor_unit' => t('Lipa'),
'major_unit' => t('Kuna'),
),
'HTG' => array(
'code' => 'HTG',
'symbol' => 'HTG',
'name' => t('Haitian Gourde'),
'numeric_code' => '332',
'minor_unit' => t('Centime'),
'major_unit' => t('Gourde'),
),
'HUF' => array(
'code' => 'HUF',
'symbol' => 'Ft',
'name' => t('Hungarian Forint'),
'numeric_code' => '348',
'decimals' => 0,
'symbol_placement' => 'after',
'code_placement' => '',
'major_unit' => t('Forint'),
),
'IDR' => array(
'code' => 'IDR',
'symbol' => 'Rp',
'name' => t('Indonesian Rupiah'),
'decimals' => 0,
'numeric_code' => '360',
'minor_unit' => t('Sen'),
'major_unit' => t('Rupiahs'),
),
'ILS' => array(
'code' => 'ILS',
'symbol' => 'โ‚ช',
'name' => t('Israeli New Sheqel'),
'numeric_code' => '376',
'minor_unit' => t('Agora'),
'major_unit' => t('New Shekels'),
),
'INR' => array(
'code' => 'INR',
'symbol' => 'Rs',
'name' => t('Indian Rupee'),
'numeric_code' => '356',
'minor_unit' => t('Paisa'),
'major_unit' => t('Rupee'),
),
'ISK' => array(
'code' => 'ISK',
'symbol' => 'Ikr',
'name' => t('Icelandic Krรณna'),
'decimals' => 0,
'thousands_separator' => ' ',
'numeric_code' => '352',
'minor_unit' => t('Eyrir'),
'major_unit' => t('Kronur'),
),
'JMD' => array(
'code' => 'JMD',
'symbol' => 'J$',
'name' => t('Jamaican Dollar'),
'numeric_code' => '388',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'JOD' => array(
'code' => 'JOD',
'symbol' => 'JD',
'name' => t('Jordanian Dinar'),
'decimals' => 3,
'numeric_code' => '400',
'minor_unit' => t('Piastr'),
'major_unit' => t('Dinar'),
),
'JPY' => array(
'code' => 'JPY',
'symbol' => 'ยฅ',
'name' => t('Japanese Yen'),
'decimals' => 0,
'numeric_code' => '392',
'minor_unit' => t('Sen'),
'major_unit' => t('Yen'),
),
'KES' => array(
'code' => 'KES',
'symbol' => 'Ksh',
'name' => t('Kenyan Shilling'),
'numeric_code' => '404',
'minor_unit' => t('Cent'),
'major_unit' => t('Shilling'),
),
'KMF' => array(
'code' => 'KMF',
'symbol' => 'CF',
'name' => t('Comorian Franc'),
'decimals' => 0,
'numeric_code' => '174',
'minor_unit' => t('Centime'),
'major_unit' => t('Franc'),
),
'KRW' => array(
'code' => 'KRW',
'symbol' => 'โ‚ฉ',
'name' => t('South Korean Won'),
'decimals' => 0,
'numeric_code' => '410',
'minor_unit' => t('Jeon'),
'major_unit' => t('Won'),
),
'KWD' => array(
'code' => 'KWD',
'symbol' => 'KD',
'name' => t('Kuwaiti Dinar'),
'decimals' => 3,
'numeric_code' => '414',
'minor_unit' => t('Fils'),
'major_unit' => t('Dinar'),
),
'KYD' => array(
'code' => 'KYD',
'symbol' => 'KY$',
'name' => t('Cayman Islands Dollar'),
'numeric_code' => '136',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'LAK' => array(
'code' => 'LAK',
'symbol' => 'โ‚ญN',
'name' => t('Laotian Kip'),
'decimals' => 0,
'numeric_code' => '418',
'minor_unit' => t('Att'),
'major_unit' => t('Kips'),
),
'LBP' => array(
'code' => 'LBP',
'symbol' => 'LBยฃ',
'name' => t('Lebanese Pound'),
'decimals' => 0,
'numeric_code' => '422',
'minor_unit' => t('Piastre'),
'major_unit' => t('Pound'),
),
'LKR' => array(
'code' => 'LKR',
'symbol' => 'SLRs',
'name' => t('Sri Lanka Rupee'),
'numeric_code' => '144',
'minor_unit' => t('Cent'),
'major_unit' => t('Rupee'),
),
'LRD' => array(
'code' => 'LRD',
'symbol' => 'L$',
'name' => t('Liberian Dollar'),
'numeric_code' => '430',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'LSL' => array(
'code' => 'LSL',
'symbol' => 'LSL',
'name' => t('Lesotho Loti'),
'numeric_code' => '426',
'minor_unit' => t('Sente'),
'major_unit' => t('Loti'),
),
'LTL' => array(
'code' => 'LTL',
'symbol' => 'Lt',
'name' => t('Lithuanian Litas'),
'numeric_code' => '440',
'minor_unit' => t('Centas'),
'major_unit' => t('Litai'),
),
'LVL' => array(
'code' => 'LVL',
'symbol' => 'Ls',
'name' => t('Latvian Lats'),
'numeric_code' => '428',
'minor_unit' => t('Santims'),
'major_unit' => t('Lati'),
),
'LYD' => array(
'code' => 'LYD',
'symbol' => 'LD',
'name' => t('Libyan Dinar'),
'decimals' => 3,
'numeric_code' => '434',
'minor_unit' => t('Dirham'),
'major_unit' => t('Dinar'),
),
'MMK' => array(
'code' => 'MMK',
'symbol' => 'MMK',
'name' => t('Myanma Kyat'),
'decimals' => 0,
'numeric_code' => '104',
'minor_unit' => t('Pya'),
'major_unit' => t('Kyat'),
),
'MNT' => array(
'code' => 'MNT',
'symbol' => 'โ‚ฎ',
'name' => t('Mongolian Tugrik'),
'decimals' => 0,
'numeric_code' => '496',
'minor_unit' => t('Mรถngรถ'),
'major_unit' => t('Tugriks'),
),
'MOP' => array(
'code' => 'MOP',
'symbol' => 'MOP$',
'name' => t('Macanese Pataca'),
'numeric_code' => '446',
'minor_unit' => t('Avo'),
'major_unit' => t('Pataca'),
),
'MRO' => array(
'code' => 'MRO',
'symbol' => 'UM',
'name' => t('Mauritanian Ouguiya'),
'decimals' => 0,
'numeric_code' => '478',
'minor_unit' => t('Khoums'),
'major_unit' => t('Ouguiya'),
),
'MTP' => array(
'code' => 'MTP',
'symbol' => 'MTยฃ',
'name' => t('Maltese Pound'),
'minor_unit' => t('Shilling'),
'major_unit' => t('Pound'),
),
'MUR' => array(
'code' => 'MUR',
'symbol' => 'MURs',
'name' => t('Mauritian Rupee'),
'decimals' => 0,
'numeric_code' => '480',
'minor_unit' => t('Cent'),
'major_unit' => t('Rupee'),
),
'MXP' => array(
'code' => 'MXP',
'symbol' => 'MX$',
'name' => t('Mexican Silver Peso (1861-1992)'),
'minor_unit' => t('Centavo'),
'major_unit' => t('Peso'),
),
'MYR' => array(
'code' => 'MYR',
'symbol' => 'RM',
'name' => t('Malaysian Ringgit'),
'numeric_code' => '458',
'minor_unit' => t('Sen'),
'major_unit' => t('Ringgits'),
),
'MZN' => array(
'code' => 'MZN',
'symbol' => 'MTn',
'name' => t('Mozambican Metical'),
'minor_unit' => t('Centavo'),
'major_unit' => t('Metical'),
),
'NAD' => array(
'code' => 'NAD',
'symbol' => 'N$',
'name' => t('Namibian Dollar'),
'numeric_code' => '516',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'NGN' => array(
'code' => 'NGN',
'symbol' => 'โ‚ฆ',
'name' => t('Nigerian Naira'),
'numeric_code' => '566',
'minor_unit' => t('Kobo'),
'major_unit' => t('Naira'),
),
'NIO' => array(
'code' => 'NIO',
'symbol' => 'C$',
'name' => t('Nicaraguan Cordoba Oro'),
'numeric_code' => '558',
'minor_unit' => t('Centavo'),
'major_unit' => t('Cordoba'),
),
'NOK' => array(
'code' => 'NOK',
'symbol' => 'Nkr',
'name' => t('Norwegian Krone'),
'thousands_separator' => ' ',
'decimal_separator' => ',',
'numeric_code' => '578',
'minor_unit' => t('ร˜re'),
'major_unit' => t('Krone'),
),
'NPR' => array(
'code' => 'NPR',
'symbol' => 'NPRs',
'name' => t('Nepalese Rupee'),
'numeric_code' => '524',
'minor_unit' => t('Paisa'),
'major_unit' => t('Rupee'),
),
'NZD' => array(
'code' => 'NZD',
'symbol' => 'NZ$',
'name' => t('New Zealand Dollar'),
'numeric_code' => '554',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'PAB' => array(
'code' => 'PAB',
'symbol' => 'B/.',
'name' => t('Panamanian Balboa'),
'numeric_code' => '590',
'minor_unit' => t('Centรฉsimo'),
'major_unit' => t('Balboa'),
),
'PEI' => array(
'code' => 'PEI',
'symbol' => 'I/.',
'name' => t('Peruvian Inti'),
'minor_unit' => t('Cรฉntimo'),
'major_unit' => t('Inti'),
),
'PEN' => array(
'code' => 'PEN',
'symbol' => 'S/.',
'name' => t('Peruvian Nuevo Sol'),
'numeric_code' => '604',
'minor_unit' => t('Cรฉntimo'),
'major_unit' => t('Nuevos Sole'),
),
'PGK' => array(
'code' => 'PGK',
'symbol' => 'PGK',
'name' => t('Papua New Guinean Kina'),
'numeric_code' => '598',
'minor_unit' => t('Toea'),
'major_unit' => t('Kina '),
),
'PHP' => array(
'code' => 'PHP',
'symbol' => 'โ‚ฑ',
'name' => t('Philippine Peso'),
'numeric_code' => '608',
'minor_unit' => t('Centavo'),
'major_unit' => t('Peso'),
),
'PKR' => array(
'code' => 'PKR',
'symbol' => 'PKRs',
'name' => t('Pakistani Rupee'),
'decimals' => 0,
'numeric_code' => '586',
'minor_unit' => t('Paisa'),
'major_unit' => t('Rupee'),
),
'PLN' => array(
'code' => 'PLN',
'symbol' => 'zl',
'name' => t('Polish Zloty'),
'numeric_code' => '985',
'minor_unit' => t('Grosz'),
'major_unit' => t('Zlotych'),
),
'PYG' => array(
'code' => 'PYG',
'symbol' => 'โ‚ฒ',
'name' => t('Paraguayan Guarani'),
'decimals' => 0,
'numeric_code' => '600',
'minor_unit' => t('Cรฉntimo'),
'major_unit' => t('Guarani'),
),
'QAR' => array(
'code' => 'QAR',
'symbol' => 'QR',
'name' => t('Qatari Rial'),
'numeric_code' => '634',
'minor_unit' => t('Dirham'),
'major_unit' => t('Rial'),
),
'RHD' => array(
'code' => 'RHD',
'symbol' => 'RH$',
'name' => t('Rhodesian Dollar'),
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'RON' => array(
'code' => 'RON',
'symbol' => 'RON',
'name' => t('Romanian Leu'),
'minor_unit' => t('Ban'),
'major_unit' => t('Leu'),
),
'RSD' => array(
'code' => 'RSD',
'symbol' => 'din.',
'name' => t('Serbian Dinar'),
'decimals' => 0,
'minor_unit' => t('Para'),
'major_unit' => t('Dinars'),
),
'RUB' => array(
'code' => 'RUB',
'symbol' => 'ั€ัƒะฑ.',
'name' => t('Russian Ruble'),
'numeric_code' => '643',
'symbol_placement' => 'after',
'code_placement' => '',
'minor_unit' => t('Kopek'),
'major_unit' => t('Ruble'),
),
'SAR' => array(
'code' => 'SAR',
'symbol' => 'SR',
'name' => t('Saudi Riyal'),
'numeric_code' => '682',
'minor_unit' => t('Hallallah'),
'major_unit' => t('Riyals'),
),
'SBD' => array(
'code' => 'SBD',
'symbol' => 'SI$',
'name' => t('Solomon Islands Dollar'),
'numeric_code' => '090',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'SCR' => array(
'code' => 'SCR',
'symbol' => 'SRe',
'name' => t('Seychellois Rupee'),
'numeric_code' => '690',
'minor_unit' => t('Cent'),
'major_unit' => t('Rupee'),
),
'SDD' => array(
'code' => 'SDD',
'symbol' => 'LSd',
'name' => t('Old Sudanese Dinar'),
'numeric_code' => '736',
'minor_unit' => t('None'),
'major_unit' => t('Dinar'),
),
'SEK' => array(
'code' => 'SEK',
'symbol' => 'Skr',
'name' => t('Swedish Krona'),
'numeric_code' => '752',
'thousands_separator' => ' ',
'decimal_separator' => ',',
'minor_unit' => t('ร–re'),
'major_unit' => t('Kronor'),
),
'SGD' => array(
'code' => 'SGD',
'symbol' => 'S$',
'name' => t('Singapore Dollar'),
'numeric_code' => '702',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'SHP' => array(
'code' => 'SHP',
'symbol' => 'SHยฃ',
'name' => t('Saint Helena Pound'),
'numeric_code' => '654',
'minor_unit' => t('Penny'),
'major_unit' => t('Pound'),
),
'SLL' => array(
'code' => 'SLL',
'symbol' => 'Le',
'name' => t('Sierra Leonean Leone'),
'decimals' => 0,
'numeric_code' => '694',
'minor_unit' => t('Cent'),
'major_unit' => t('Leone'),
),
'SOS' => array(
'code' => 'SOS',
'symbol' => 'Ssh',
'name' => t('Somali Shilling'),
'decimals' => 0,
'numeric_code' => '706',
'minor_unit' => t('Cent'),
'major_unit' => t('Shilling'),
),
'SRD' => array(
'code' => 'SRD',
'symbol' => 'SR$',
'name' => t('Surinamese Dollar'),
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'SRG' => array(
'code' => 'SRG',
'symbol' => 'Sf',
'name' => t('Suriname Guilder'),
'numeric_code' => '740',
'minor_unit' => t('Cent'),
'major_unit' => t('Guilder'),
),
'STD' => array(
'code' => 'STD',
'symbol' => 'Db',
'name' => t('Sรฃo Tomรฉ and Prรญncipe Dobra'),
'decimals' => 0,
'numeric_code' => '678',
'minor_unit' => t('Cรชntimo'),
'major_unit' => t('Dobra'),
),
'SYP' => array(
'code' => 'SYP',
'symbol' => 'SYยฃ',
'name' => t('Syrian Pound'),
'decimals' => 0,
'numeric_code' => '760',
'minor_unit' => t('Piastre'),
'major_unit' => t('Pound'),
),
'SZL' => array(
'code' => 'SZL',
'symbol' => 'SZL',
'name' => t('Swazi Lilangeni'),
'numeric_code' => '748',
'minor_unit' => t('Cent'),
'major_unit' => t('Lilangeni'),
),
'THB' => array(
'code' => 'THB',
'symbol' => 'เธฟ',
'name' => t('Thai Baht'),
'numeric_code' => '764',
'minor_unit' => t('Satang'),
'major_unit' => t('Baht'),
),
'TND' => array(
'code' => 'TND',
'symbol' => 'DT',
'name' => t('Tunisian Dinar'),
'decimals' => 3,
'numeric_code' => '788',
'minor_unit' => t('Millime'),
'major_unit' => t('Dinar'),
),
'TOP' => array(
'code' => 'TOP',
'symbol' => 'T$',
'name' => t('Tongan Paสปanga'),
'numeric_code' => '776',
'minor_unit' => t('Senit'),
'major_unit' => t('Paสปanga'),
),
'TRY' => array(
'code' => 'TRY',
'symbol' => 'TL',
'name' => t('Turkish Lira'),
'minor_unit' => t('Kurus'),
'major_unit' => t('Lira'),
),
'TTD' => array(
'code' => 'TTD',
'symbol' => 'TT$',
'name' => t('Trinidad and Tobago Dollar'),
'numeric_code' => '780',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'TWD' => array(
'code' => 'TWD',
'symbol' => 'NT$',
'name' => t('New Taiwan Dollar'),
'numeric_code' => '901',
'minor_unit' => t('Cent'),
'major_unit' => t('New Dollar'),
),
'TZS' => array(
'code' => 'TZS',
'symbol' => 'TSh',
'name' => t('Tanzanian Shilling'),
'decimals' => 0,
'numeric_code' => '834',
'minor_unit' => t('Senti'),
'major_unit' => t('Shilling'),
),
'UAH' => array(
'code' => 'UAH',
'symbol' => 'โ‚ด',
'name' => t('Ukrainian Hryvnia'),
'numeric_code' => '980',
'minor_unit' => t('Kopiyka'),
'major_unit' => t('Hryvnia'),
),
'UGX' => array(
'code' => 'UGX',
'symbol' => 'USh',
'name' => t('Ugandan Shilling'),
'decimals' => 0,
'numeric_code' => '800',
'minor_unit' => t('Cent'),
'major_unit' => t('Shilling'),
),
'USD' => array(
'code' => 'USD',
'symbol' => '$',
'name' => t('United States Dollar'),
'numeric_code' => '840',
'symbol_placement' => 'before',
'code_placement' => '',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'UYU' => array(
'code' => 'UYU',
'symbol' => '$U',
'name' => t('Uruguayan Peso'),
'numeric_code' => '858',
'minor_unit' => t('Centรฉsimo'),
'major_unit' => t('Peso'),
),
'VEF' => array(
'code' => 'VEF',
'symbol' => 'Bs.F.',
'name' => t('Venezuelan Bolรญvar Fuerte'),
'minor_unit' => t('Cรฉntimo'),
'major_unit' => t('Bolivares Fuerte'),
),
'VND' => array(
'code' => 'VND',
'symbol' => 'โ‚ซ',
'name' => t('Vietnamese Dong'),
'decimals' => 0,
'numeric_code' => '704',
'minor_unit' => t('Hร '),
'major_unit' => t('Dong'),
),
'VUV' => array(
'code' => 'VUV',
'symbol' => 'VT',
'name' => t('Vanuatu Vatu'),
'decimals' => 0,
'numeric_code' => '548',
'minor_unit' => t('None'),
'major_unit' => t('Vatu'),
),
'WST' => array(
'code' => 'WST',
'symbol' => 'WS$',
'name' => t('Samoan Tala'),
'numeric_code' => '882',
'minor_unit' => t('Sene'),
'major_unit' => t('Tala'),
),
'XAF' => array(
'code' => 'XAF',
'symbol' => 'FCFA',
'name' => t('CFA Franc BEAC'),
'decimals' => 0,
'numeric_code' => '950',
'minor_unit' => t('Centime'),
'major_unit' => t('Franc'),
),
'XCD' => array(
'code' => 'XCD',
'symbol' => 'EC$',
'name' => t('East Caribbean Dollar'),
'numeric_code' => '951',
'minor_unit' => t('Cent'),
'major_unit' => t('Dollar'),
),
'XOF' => array(
'code' => 'XOF',
'symbol' => 'CFA',
'name' => t('CFA Franc BCEAO'),
'decimals' => 0,
'numeric_code' => '952',
'minor_unit' => t('Centime'),
'major_unit' => t('Franc'),
),
'XPF' => array(
'code' => 'XPF',
'symbol' => 'CFPF',
'name' => t('CFP Franc'),
'decimals' => 0,
'numeric_code' => '953',
'minor_unit' => t('Centime'),
'major_unit' => t('Franc'),
),
'YER' => array(
'code' => 'YER',
'symbol' => 'YR',
'name' => t('Yemeni Rial'),
'decimals' => 0,
'numeric_code' => '886',
'minor_unit' => t('Fils'),
'major_unit' => t('Rial'),
),
'ZAR' => array(
'code' => 'ZAR',
'symbol' => 'R',
'name' => t('South African Rand'),
'numeric_code' => '710',
'minor_unit' => t('Cent'),
'major_unit' => t('Rand'),
),
'ZMK' => array(
'code' => 'ZMK',
'symbol' => 'ZK',
'name' => t('Zambian Kwacha'),
'decimals' => 0,
'numeric_code' => '894',
'minor_unit' => t('Ngwee'),
'major_unit' => t('Kwacha'),
),
);
}
; $Id$
name = Commerce
description = Defines features and functions common to the Commerce modules.
package = Commerce
core = 7.x
; Tests
files[] = tests/commerce_base.test
files[] = tests/commerce.test
<?php
// $Id$
/**
* @file
* Defines features and functions common to the Commerce modules.
*/
/**
* Implements hook_permission().
*/
function commerce_permission() {
$permissions = array(
'configure store' => array(
'title' => t('Configure store settings'),
'description' => t('Allows users to update store currency and contact settings.'),
'restrict access' => TRUE,
),
);
return $permissions;
}
/**
* Finds all fields of a particular field type.
*
* @param $field_type
* The type of field to search for.
* @param $entity_type
* Optional entity type to restrict the search to.
*
* @return
* An array of the matching fields keyed by the field name.
*/
function commerce_info_fields($field_type, $entity_type = NULL) {
$fields = array();
// Loop through the fields looking for any product reference fields.
foreach (field_info_fields() as $field_name => $field) {
if ($field['type'] == $field_type) {
// Add this field to the return array if no entity type was specified or
// if the specified type exists in the field's bundles array.
if (empty($entity_type) || in_array($entity_type, array_keys($field['bundles']))) {
$fields[$field_name] = $field;
}
}
}
return $fields;
}
/**
* Makes any required form elements in a form unrequired.
*
* @param $form
* The form array to search for required elements.
*/
function commerce_unrequire_form_elements(&$form) {
array_walk_recursive($form, '_commerce_unrequire_element');
}
/**
* array_walk_recursive callback: makes an individual element unrequired.
*
* @param &$value
* The value of the form array being walked.
* @param $key
* The key of the form array corresponding to the present value.
*/
function _commerce_unrequire_element(&$value, $key) {
if ($key === '#required') {
$value = FALSE;
}
}
/**
* Returns the callback for a form ID as defined by hook_forms().
*
* @param $form_id
* The form ID to find the callback for.
* @return
* A string containing the form's callback function name.
*
* @see drupal_retrieve_form()
* @see hook_forms()
*/
function commerce_form_callback($form_id, $form_state) {
// If a function named after the $form_id does not exist, look for its
// definition in hook_forms().
if (!function_exists($form_id)) {
$forms = &drupal_static(__FUNCTION__);
// In cases where many form_ids need to share a central builder function,
// such as the product editing form, modules can implement hook_forms(). It
// maps one or more form_ids to the correct constructor functions.
if (!isset($forms) || !isset($forms[$form_id])) {
$forms = module_invoke_all('forms', $form_id, $form_state['build_info']['args']);
}
if (isset($forms[$form_id]['callback'])) {
return $forms[$form_id]['callback'];
}
}
return $form_id;
}
/**
* Renders a View for display in some other element.
*
* @param $view_key
* The ID of the View to embed.
* @param $display_id
* The ID of the display of the View that will actually be rendered.
* @param $arguments
* An array of arguments to pass to the View.
*
* @return
* The rendered output of the chosen View display.
*/
function commerce_embed_view($view_id, $display_id, $arguments) {
// Load the cart line item View.
$view = views_get_view($view_id);
$view->set_display($display_id);
// Set the specific line items for this order.
$view->set_arguments($arguments);
// Prepare and execute the View query.
$view->pre_execute();
$view->execute();
// Return the rendered View.
return $view->render();
}
/**
* Returns the e-mail address from which to send commerce related e-mails.
*
* Currently this is just using the site's e-mail address, but this may be
* updated to use a specific e-mail address when we add a settings form for the
* store's physical address and contact information.
*/
function commerce_email_from() {
return variable_get('site_mail', ini_get('sendmail_from'));
}
/**
* Returns the currency code of the site's default currency.
*/
function commerce_default_currency() {
return variable_get('commerce_default_currency', 'USD');
}
/**
* Returns a single currency array.
*
* @param $currency_code
* The code of the currency to return or NULL to return the default currency.
*
* @return
* The specified currency array or FALSE if it does not exist.
*/
function commerce_currency_load($currency_code = NULL) {
$currencies = commerce_currencies();
// Check to see if we should return the default currency.
if (empty($currency_code)) {
$currency_code = commerce_default_currency();
}
return isset($currencies[$currency_code]) ? $currencies[$currency_code] : FALSE;
}
/**
* Returns an array of all available currencies.
*
* @param $enabled
* Boolean indicating whether or not to return only enabled currencies.
* @param $reset
* Boolean indicating whether or not the cache should be reset before currency
* data is loaded and returned.
*
* @return
* An array of altered currency arrays keyed by the currency code.
*/
function commerce_currencies($enabled = FALSE, $reset = FALSE) {
global $language;
// Attempt to load currency data from the cache first.
if (!$reset && $currencies = cache_get('commerce_currencies:' . $language->language)) {
$currencies = $currencies->data;
}
else {
// Establish some default values for currencies.
$defaults = array(
'symbol' => '',
'minor_unit' => '',
'decimals' => 2,
'rounding_step' => 0,
'thousands_separator' => ',',
'decimal_separator' => '.',
'symbol_placement' => '',
'code_placement' => 'after',
'format_callback' => '',
'conversion_rate' => 1,
);
// Include the currency file and invoke the currency info hook.
module_load_include('inc', 'commerce', 'includes/commerce.currency');
$currencies = module_invoke_all('commerce_currency_info');
drupal_alter('commerce_currency_info', $currencies);
// Add default values if they don't exist and convert to objects.
foreach ($currencies as $currency_code => $currency) {
$currencies[$currency_code] = array_merge($defaults, $currency);
}
cache_set('commerce_currencies:' . $language->language, $currencies);
}
// Return only enabled currencies if specified.
if ($enabled) {
// Form an array of enabled currencies based on the variable set by the
// checkboxes element on the currency settings form.
$enabled_currencies = array_diff(array_values(variable_get('commerce_enabled_currencies', array('USD' => 'USD'))), array(0));
return array_intersect_key($currencies, drupal_map_assoc($enabled_currencies));
}
return $currencies;
}
/**
* Returns an associative array of the specified currency codes.
*
* @param $enabled
* Boolean indicating whether or not to include only enabled currencies.
*/
function commerce_currency_get_code($enabled = FALSE) {
return drupal_map_assoc(array_keys(commerce_currencies($enabled)));
}
/**
* Wraps commerce_currency_get_code() for use by the Entity module.
*/
function commerce_currency_code_options_list() {
return commerce_currency_get_code(TRUE);
}
/**
* Returns the symbol of any or all currencies.
*
* @param $code
* Optional parameter specifying the code of the currency whose symbol to return.
*
* @return
* Either an array of all currency symbols keyed by the currency code or a
* string containing the symbol for the specified currency. If a currency is
* specified that does not exist, this function returns FALSE.
*/
function commerce_currency_get_symbol($currency_code = NULL) {
$currencies = commerce_currencies();
// Return a specific currency symbol if specified.
if (!empty($currency_code)) {
if (isset($currencies[$currency_code])) {
return $currencies[$currency_code]['symbol'];
}
else {
return FALSE;
}
}
// Otherwise turn the array values into the type name only.
foreach ($currencies as $currency_code => $currency) {
$currencies[$currency_code] = $currency['symbol'];
}
return $currencies;
}
/**
* Formats a price for a particular currency.
*
* @param $price
* A numeric price value.
* @param $currency_code
* The three character code of the currency.
* @param $object
* When present, the object to which the price is attached.
*
* @return
* A fully formatted currency.
*/
function commerce_currency_format($price, $currency_code, $object = NULL) {
// First load the currency array.
$currency = commerce_currency_load($currency_code);
// Invoke the custom format callback if specified.
if (!empty($currency['format_callback'])) {
return $currency['format_callback']($price, $currency, $entity);
}
// Separate the negative symbol from the number itself.
if ($price < 0) {
$negative = TRUE;
$price = abs($price);
}
else {
$negative = FALSE;
}
// Format the price as a number.
$price = number_format(commerce_currency_round($price, $currency), $currency['decimals'], $currency['decimal_separator'], $currency['thousands_separator']);
// Establish the replacement values to format this price for its currency.
$replacements = array(
'@code_before' => $currency['code_placement'] == 'before' ? $currency['code'] : '',
'@symbol_before' => $currency['symbol_placement'] == 'before' ? $currency['symbol'] : '',
'@price' => $price,
'@symbol_after' => $currency['symbol_placement'] == 'after' ? $currency['symbol'] : '',
'@code_after' => $currency['code_placement'] == 'after' ? $currency['code'] : '',
'@negative' => $negative ? '-' : '',
);
return t('@code_before @negative@symbol_before@price @symbol_after @code_after', $replacements);
}
/**
* Rounds a price amount for the specified currency.
*
* Rounding of the minor unit with a currency specific step size. For example,
* Swiss Francs are rounded using a step size of 0.05. This means a price of
* 10.93 is converted to 10.95.
*
* @param $amount
* The numeric amount value of the price to be rounded.
* @param $currency
* The currency array containing the rounding information pertinent to this
* price. Specifically, this function looks for the 'rounding_step' property
* for the step size to round to, supporting '0.05' and '0.02'. If the value
* is 0, this function performs normal rounding to the nearest supported
* decimal value.
*
* @return
* The rounded numeric amount value for the price.
*/
function commerce_currency_round($amount, $currency) {
if (!$currency['rounding_step']) {
return round($amount, $currency['decimals']);
}
$modifier = 1 / $currency['rounding_step'];
return round($amount * $modifier) / $modifier;
}
/**
* Converts a price amount from a currency to the target currency based on the
* current currency conversion rates.
*
* The Commerce module establishes a default conversion rate for every currency
* as 1, so without any additional information there will be a 1:1 conversion
* from one currency to the next. Other modules can provide UI based or web
* service based alterations to the conversion rate of the defined currencies as
* long as every rate is calculated relative to a single base currency. It does
* not matter which currency is the base currency as long as the same one is
* used for every rate calculation.
*
* To convert an amount from one currency to another, we simply take the amount
* value and multiply it by the current currency's conversion rate divided by
* the target currency's conversion rate.
*
* @param $amount
* The numeric amount value of the price to be rounded.
* @param $currency_code
* The currency code for the current currency of the price.
* @param $target_currency_code
* The currency code for the target currency of the price.
*
* @return
* The numeric amount value converted to its equivalent in the target currency.
*/
function commerce_currency_convert($amount, $currency_code, $target_currency_code) {
$currency = commerce_currency_load($currency_code);
$target_currency = commerce_currency_load($target_currency_code);
return $amount * ($currency['conversion_rate'] / $target_currency['conversion_rate']);
}
/**
* Converts a price amount to an integer value for storage in the database.
*
* @param $amount
* The price amount to convert to an integer.
* @param $currency_code
* The currency code of the price whose decimals value will be used to
* multiply by the proper factor when converting the amount.
*
* @return
* The integer value of the price amount.
*/
function commerce_currency_amount_to_integer($amount, $currency_code) {
static $factors;
// If the divisor for this currency hasn't been calculated yet...
if (empty($factors[$currency_code])) {
// Load the currency and calculate its factor as a power of 10.
$currency = commerce_currency_load($currency_code);
$factors[$currency_code] = pow(10, $currency['decimals']);
}
return $amount * $factors[$currency_code];
}
/**
* Converts an integer value to a price amount when loading from the database.
*
* @param $integer
* The integer to convert to a price amount.
* @param $currency_code
* The currency code of the price whose decimals value will be used to
* divide by the proper divisor when converting the integer.
*
* @return
* The price amount depending on the number of decimals the currency uses.
*/
function commerce_currency_integer_to_amount($integer, $currency_code) {
static $divisors;
// If the divisor for this currency hasn't been calculated yet...
if (empty($divisors[$currency_code])) {
// Load the currency and calculate its divisor as a power of 10.
$currency = commerce_currency_load($currency_code);
$divisors[$currency_code] = pow(10, $currency['decimals']);
}
return $integer / $divisors[$currency_code];
}
/**
* Returns an associative array of month names keyed by numeric representation.
*/
function commerce_months() {
return array(
'01' => t('January'),
'02' => t('February'),
'03' => t('March'),
'04' => t('April'),
'05' => t('May'),
'06' => t('June'),
'07' => t('July'),
'08' => t('August'),
'09' => t('September'),
'10' => t('October'),
'11' => t('November'),
'12' => t('December'),
);
}
<?php
// $Id$
/**
* @file
* Hooks provided by the Cart module.
*/
/**
* Determines whether or not the given order is a shopping cart order.
*
* When determining if an order should be considered a shopping cart order, the
* Cart module provides a simple order status comparison but allows other
* modules to make the decision based on some other criteria. Any module can
* invalidate the cart status of an order by returning FALSE from this hook, but
* a module can also opt to treat an order in a non-cart status as a cart by
* receiving the second argument by reference and setting it to TRUE. It should
* just be noted that this value could be returned to FALSE by some other
* module implementing the same hook.
*
* @param $order
* The order whose cart status is being determined.
* @param $is_cart
* Boolean indicating whether or not the order should be considered a cart
* order; initialized based on the order status.
*
* @return
* FALSE to indicate that an order should not be treated as a cart.
*
* @see commerce_cart_order_is_cart()
*/
function hook_commerce_cart_order_is_cart($order, $is_cart) {
// No example.
}
<?php
// $Id$
/**
* @file
* Checkout pane callback functions for the cart module.
*/
/**
* Checkout pane callback: returns the cart contents pane's settings form.
*/
function commerce_cart_contents_pane_settings_form($checkout_pane) {
$form = array();
// Build an options array of Views available for the cart contents pane.
$options = array();
// Generate an option list from all user defined and module defined views.
foreach (views_get_all_views() as $view_id => $view_value) {
// Only include line item Views.
if ($view_value->base_table == 'commerce_order') {
foreach ($view_value->display as $display_id => $display_value) {
$options[check_plain($view_id)][$view_id .'|'. $display_id] = check_plain($display_value->display_title);
}
}
}
$form['commerce_cart_contents_pane_view'] = array(
'#type' => 'select',
'#title' => t('Cart contents View'),
'#description' => t('Specify the View to use in the cart contents pane.'),
'#options' => $options,
'#default_value' => variable_get('commerce_cart_contents_pane_view', 'commerce_cart_summary|default'),
);
return $form;
}
/**
* Checkout pane callback: returns the cart contents View for inclusion in the
* checkout form.
*/
function commerce_cart_contents_pane_checkout_form($form, &$form_state, $checkout_pane, $order) {
$pane_form = array();
// Extract the View and display keys from the cart contents pane setting.
list($view_id, $display_id) = explode('|', variable_get('commerce_cart_contents_pane_view', 'commerce_cart_summary|default'));
$pane_form['cart_contents_view'] = array(
'#markup' => commerce_embed_view($view_id, $display_id, array($order->order_id)),
);
return $pane_form;
}
/**
* Checkout pane callback: returns the cart contents review data for the
* Review checkout pane.
*/
function commerce_cart_contents_pane_review($form, $form_state, $checkout_pane, $order) {
// Extract the View and display keys from the cart contents pane setting.
list($view_id, $display_id) = explode('|', variable_get('commerce_cart_contents_pane_view', 'commerce_cart_summary|default'));
return commerce_embed_view($view_id, $display_id, array($order->order_id));
}
; $Id$
name = Cart
description = Implements the shopping cart system and add to cart features.
package = Commerce
dependencies[] = commerce_checkout
dependencies[] = commerce_line_item
dependencies[] = commerce_order
dependencies[] = commerce_product
dependencies[] = commerce_product_reference
dependencies[] = views
core = 7.x
; Views handlers
files[] = includes/views/handlers/commerce_cart_handler_field_add_to_cart_form.inc
; Simple tests
files[] = tests/commerce_cart.test
<?php
// $Id$
/**
* @file
* Provides metadata for the shopping cart order.
*/
/**
* Implements hook_entity_property_info_alter().
*/
function commerce_cart_entity_property_info_alter(&$info) {
// Add the current user's shopping cart to the site information.
$info['site']['properties']['current_cart_order'] = array(
'label' => t("User's shopping cart order"),
'description' => t('The shopping cart order belonging to the current user.'),
'getter callback' => 'commerce_cart_get_properties',
'type' => 'commerce_order',
);
}
<?php
// $Id$
/**
* @file
* Implements the shopping cart system and add to cart features.
*
* In Drupal Commerce, the shopping cart is really just an order that makes
* special considerations to associate it with a user and
*/
/**
* Implements hook_menu().
*/
function commerce_cart_menu() {
$items = array();
$items['cart'] = array(
'title' => 'Shopping cart',
'page callback' => 'commerce_cart_view',
'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),
'page callback' => 'drupal_goto',
'page arguments' => array('cart'),
'access arguments' => array('access content'),
'type' => MENU_SUGGESTED_ITEM,
);
$items['checkout'] = array(
'title' => 'Checkout',
'page callback' => 'commerce_cart_checkout_router',
'access arguments' => array('access checkout'),
'type' => MENU_CALLBACK,
'file' => 'includes/commerce_cart.pages.inc',
);
return $items;
}
/**
* Returns the title of the shopping cart menu item with an item count.
*/
function commerce_cart_menu_item_title() {
global $user;
// Load the current user's cart order.
$order = commerce_cart_order_load($user->uid);
// If it's empty or has no line items, return a static title.
if (empty($order) || empty($order->commerce_line_items)) {
return t('Shopping cart');
}
// 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, 'product');
// Return the title with an item count appended.
return format_plural($quantity, 'Shopping cart (1 item)', 'Shopping cart (@count items)');
}
/**
* 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',
'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',
'file' => 'includes/commerce_cart.checkout_pane.inc',
'page' => 'checkout',
'weight' => -10,
);
return $checkout_panes;
}
/**
* Implements hook_commerce_checkout_complete().
*/
function commerce_cart_commerce_checkout_complete($order) {
// 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);
}
}
/**
* 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,
'access' => user_access('access checkout'),
),
);
}
/**
* Implements hook_form_alter().
*/
function commerce_cart_form_alter(&$form, &$form_values, $form_id) {
if (strpos($form_id, 'commerce_line_item_views_form_commerce_cart_form_') === 0) {
// Change the Save button to say Update cart.
$form['actions']['update']['#value'] = t('Update cart');
$form['actions']['update']['#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 $line_item_id) {
$form['edit_delete'][$line_item_id]['#value'] = t('Remove');
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* 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()
*/
function commerce_cart_form_commerce_order_ui_order_form_alter(&$form, &$form_state) {
$order = $form_state['commerce_order'];
// If the order being edited is in a shopping cart status...
if (commerce_cart_order_is_cart($order)) {
// 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;
}
}
}
/**
* Submit handler to show the shopping cart updated message.
*/
function commerce_cart_line_item_views_form_submit($form, &$form_state) {
drupal_set_message(t('Your shopping cart has been updated.'));
}
/**
* 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);
}
/**
* Implements hook_views_api().
*/
function commerce_cart_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'commerce_cart') . '/includes/views',
);
}
/**
* Implements hook_theme().
*/
function commerce_cart_theme() {
return array(
'commerce_cart_empty_block' => array(
'variables' => array(),
),
'commerce_cart_empty_page' => array(
'variables' => array(),
),
'commerce_cart_block' => array(
'variables' => array('order' => NULL, 'contents_view' => NULL),
'path' => drupal_get_path('module', 'commerce_cart') . '/theme',
'template' => 'commerce-cart-block',
),
);
}
/**
* Implements hook_user_login().
*/
function commerce_cart_user_login(&$edit, $account) {
// Merge any items in the anonymous cart with the authenticated user's cart.
// Get the anonymous cart if it exists.
if ($anonymous_order = commerce_cart_order_load()) {
$anonymous_order_wrapper = entity_metadata_wrapper('commerce_order', $anonymous_order);
// If the anonymous user had any items in the cart, add them to the user's
// authenticated cart.
if ($anonymous_order_wrapper->commerce_line_items->count() > 0) {
// Add each of the anonymous cart items to the authenticated user's cart.
foreach ($anonymous_order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {
commerce_cart_product_add($account->uid, $line_item_wrapper->commerce_product->product_id->value(), $line_item_wrapper->quantity->value());
}
// Delete the anonymous order from the database and clear the reference.
commerce_order_delete($anonymous_order->order_id);
}
}
// Set the uid for any completed orders if they exist.
if (commerce_cart_order_session_exists(NULL, TRUE)) {
foreach (commerce_cart_order_session_order_ids(TRUE) as $order_id) {
if ($order = commerce_order_load($order_id)) {
if ($order->uid == 0 && $order->hostname == ip_address()) {
$order->uid = $account->uid;
commerce_order_save($order);
}
}
}
commerce_cart_order_session_delete(NULL, TRUE);
}
}
/**
* 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;
}
/**
* Implements hook_block_view().
*/
function commerce_cart_block_view($delta) {
global $user;
// Prepare the display of the default Shopping Cart block.
if ($delta == 'cart') {
// First check for items in the shopping cart.
$order = commerce_cart_order_load($user->uid);
// If the cart is empty...
if (!$order || empty($order->commerce_line_items)) {
// Display an appropriate message.
$content = theme('commerce_cart_empty_block');
}
else {
// Build the variables array to send to the cart block template.
$variables = array(
'order' => $order,
'contents_view' => commerce_embed_view('commerce_cart_block', 'defaults', array($order->order_id)),
);
$content = theme('commerce_cart_block', $variables);
}
return array('subject' => t('Shopping cart'), 'content' => $content);
}
}
/**
* 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) {
foreach ($orders as $order) {
if (commerce_cart_order_is_cart($order)) {
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
// Loop over every line item on the order...
foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {
// If the line item is a product line item...
if ($line_item_wrapper->type->value() == 'product') {
// Load the product unchanged from the database so any existing price
// adjustments aren't duplicated in this line item.
$product = entity_load_unchanged('commerce_product', $line_item_wrapper->commerce_product->product_id->value());
// Create a new product line item referencing the same product as though
// we had not added it to the cart yet, but use the current quantity.
$line_item = commerce_product_line_item_new($product, $line_item_wrapper->quantity->value(), $order->order_id);
// Process the unit price through Rules so it reflects the user's actual
// current purchase price.
rules_invoke_event('commerce_product_calculate_sell_price', $line_item);
}
else {
$line_item = clone($line_item_wrapper->value());
}
// Allow other modules alter line items on a shopping cart refresh.
module_invoke_all('commerce_cart_line_item_refresh', $line_item, $order_wrapper);
// Compare the refreshed unit price to the original unit price.
$current_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
if ($line_item_wrapper->commerce_unit_price->amount->value() != $current_line_item_wrapper->commerce_unit_price->amount->value() ||
$line_item_wrapper->commerce_unit_price->currency_code->value() != $current_line_item_wrapper->commerce_unit_price->currency_code->value()) {
// Adjust the unit price accordingly if necessary.
$line_item_wrapper->commerce_unit_price->amount = $current_line_item_wrapper->commerce_unit_price->amount->value();
$line_item_wrapper->commerce_unit_price->currency_code = $current_line_item_wrapper->commerce_unit_price->currency_code->value();
// Save the updated line item and clear the entity cache.
commerce_line_item_save($line_item_wrapper->value());
entity_get_controller('commerce_line_item')->resetCache(array($line_item_wrapper->line_item_id->value()));
}
}
// Allow other modules to alter the entire order on a shopping cart refresh.
module_invoke_all('commerce_cart_order_refresh', $order_wrapper);
}
}
}
/**
* Implements hook_commerce_order_presave().
*
* Resets the shopping cart order status when the cart contents are changed,
* which we check on order save by seeing if line items are added or removed.
*/
function commerce_cart_commerce_order_presave($order) {
// Exit if this is a new order or not in a cart status.
if (empty($order->order_id) || !commerce_cart_order_is_cart($order)) {
return;
}
$wrapper = entity_metadata_wrapper('commerce_order', $order);
// Get the order directly from the database and compare with the current one.
$unchanged_order = reset(commerce_order_load_multiple(array($order->order_id), array(), TRUE));
$unchanged_wrapper = entity_metadata_wrapper('commerce_order', $unchanged_order);
// If the number of line items has changed, reset the status.
if (count($unchanged_wrapper->commerce_line_items) != count($wrapper->commerce_line_items)) {
$order->status = 'cart';
}
else {
// Otherwise compare the actual line item IDs.
$unchanged_line_item_ids = array();
foreach ($unchanged_wrapper->commerce_line_items as $key => $value) {
$unchanged_line_item_ids[] = $key;
}
foreach ($wrapper->commerce_line_items as $key => $value) {
if (!in_array($key, $unchanged_line_item_ids)) {
$order->status = 'cart';
}
}
}
}
/**
* Implements hook_commerce_line_item_presave().
*
* Resets the pertinent shopping cart order status when line items are changed,
* which we check on line item save.
*/
function commerce_cart_commerce_line_item_presave($line_item) {
// Exit now if the line item is being inserted for the first time, as it won't
// be referenced by an order yet.
if (empty($line_item->line_item_id)) {
return;
}
// If the line item is attached to an existing order...
if ($order = commerce_order_load($line_item->order_id)) {
// Get the line item directly from the database and compare with the current one.
$unchanged_line_item = reset(commerce_line_item_load_multiple(array($line_item->line_item_id), array(), TRUE));
// If the line item's quantity has changed and it's attached to a shopping
// cart order...
if ($line_item->quantity != $unchanged_line_item->quantity && commerce_cart_order_is_cart($order)) {
// Return the order to the cart status.
$order->status = 'cart';
commerce_order_save($order);
}
}
}
/**
* Implements hook_commerce_line_item_delete().
*
* Resets the pertinent shopping cart order status when a line item is deleted.
*/
function commerce_cart_commerce_line_item_delete($line_item) {
// If the order for this line item exists...
if ($order = commerce_order_load($line_item->order_id)) {
// And it's a shopping cart order...
if (commerce_cart_order_is_cart($order)) {
// Reset the order to the Cart status directly.
$order->status = 'cart';
commerce_order_save($order);
}
}
}
/**
* Themes an empty shopping cart block's contents.
*/
function theme_commerce_cart_empty_block() {
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>';
}
/**
* Loads the shopping cart order for the specified 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 fully loaded shopping cart order or FALSE if non-existent.
*/
function commerce_cart_order_load($uid = 0) {
// Loaded orders will be cached keyed by $uid.
$cart_orders = &drupal_static(__FUNCTION__);
// First return the cached order if it has been loaded already.
if (!isset($cart_orders[$uid])) {
$order_id = FALSE;
// 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.
$order_id = 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();
}
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 session to contain only their order IDs, as
// they could've edited their session to try and gain access to someone
// else's order data. Therefore, this query restricts its search to
// orders assigned to anonymous users using the same IP address to try
// and mitigate risk as much as possible.
$order_id = db_query('SELECT order_id FROM {commerce_order} WHERE order_id IN (:order_ids) AND uid = 0 AND hostname = :hostname AND status IN (:status_ids) ORDER BY order_id DESC', array(':order_ids' => commerce_cart_order_session_order_ids(), ':hostname' => ip_address(), ':status_ids' => $status_ids))->fetchField();
}
}
// If a valid order ID was found, load the order now.
if ($order_id) {
$cart_orders[$uid] = commerce_order_load($order_id);
}
else {
$cart_orders[$uid] = FALSE;
}
}
return $cart_orders[$uid];
}
/**
* Resets the cached array of shopping cart orders.
*/
function commerce_cart_orders_reset() {
$cart_orders = &drupal_static('commerce_cart_order_load');
$cart_orders = NULL;
}
/**
* Creates a new shopping cart order for the specified user.
*
* @param $uid
* The uid of the user for whom to create the order. If left 0, the order will
* be created for the current user and associated with his or her session.
*
* @return
* The newly created shopping cart order object.
*/
function commerce_cart_order_new($uid = 0) {
// Create the new order with the customer's uid and the cart order status.
$order = commerce_order_new($uid, 'cart');
$order->log = t('Created as a shopping cart order.');
// Save it so it gets an order ID and return the full object.
$order = commerce_order_save($order);
// Reset the cart cache
commerce_cart_orders_reset();
// If the user is not logged in, ensure the order ID is stored in the session.
if (!$uid) {
commerce_cart_order_session_save($order->order_id);
}
return $order;
}
/**
* 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 = in_array($order->status, array_keys(commerce_order_statuses(array('cart' => TRUE))));
// Allow other modules to make the judgment based on some other criteria.
if (in_array(FALSE, module_invoke_all('commerce_cart_order_is_cart', $order, $is_cart))) {
$is_cart = FALSE;
}
return $is_cart;
}
/**
* Entity metadata callback: returns the current user's shopping cart order.
*
* @see commerce_cart_entity_property_info_alter()
*/
function commerce_cart_get_properties($data = FALSE, array $options, $name) {
global $user;
switch ($name) {
case 'current_cart_order':
if ($order = commerce_cart_order_load($user->uid)) {
return $order;
}
else {
return commerce_order_new($user->uid, 'cart');
}
}
}
/**
* Returns an array of cart order IDs stored in the session.
*
* @param $completed
* Boolean indicating whether or not the operation should save to the
* completed orders array instead of the active cart orders array.
*
* @return
* An array of applicable cart order IDs or an empty array if none exist.
*/
function commerce_cart_order_session_order_ids($completed = FALSE) {
$key = $completed ? 'commerce_cart_completed_orders' : 'commerce_cart_orders';
return empty($_SESSION[$key]) ? array() : $_SESSION[$key];
}
/**
* Saves an order ID to the appropriate cart orders session variable.
*
* @param $order_id
* The order ID to save to the array.
* @param $completed
* Boolean indicating whether or not the operation should save to the
* completed orders array instead of the active cart orders array.
*/
function commerce_cart_order_session_save($order_id, $completed = FALSE) {
$key = $completed ? 'commerce_cart_completed_orders' : 'commerce_cart_orders';
if (empty($_SESSION[$key])) {
$_SESSION[$key] = array($order_id);
}
elseif (!in_array($order_id, $_SESSION[$key])) {
$_SESSION[$key][] = $order_id;
}
}
/**
* Checks to see if any order ID or a specific order ID exists in the session.
*
* @param $order_id
* Optionally specify an order ID to look for in the commerce_cart_orders
* session variable; defaults to NULL.
* @param $completed
* Boolean indicating whether or not the operation should look in the
* completed orders array instead of the active cart orders array.
*
* @return
* Boolean indicating whether or not any cart order ID exists in the session
* or if the specified order ID exists in the session.
*/
function commerce_cart_order_session_exists($order_id = NULL, $completed = FALSE) {
$key = $completed ? 'commerce_cart_completed_orders' : 'commerce_cart_orders';
// If an order was specified, look for it in the array.
if (!empty($order_id)) {
return !empty($_SESSION[$key]) && in_array($order_id, $_SESSION[$key]);
}
else {
// Otherwise look for any value.
return !empty($_SESSION[$key]);
}
}
/**
* Deletes all order IDs or a specific order ID from the cart orders session
* variable.
*
* @param $order_id
* The order ID to remove from the array or NULL to delete the variable.
* @param $completed
* Boolean indicating whether or not the operation should delete from the
* completed orders array instead of the active cart orders array.
*/
function commerce_cart_order_session_delete($order_id = NULL, $completed = FALSE) {
$key = $completed ? 'commerce_cart_completed_orders' : 'commerce_cart_orders';
if (!empty($_SESSION[$key])) {
if (!empty($order_id)) {
$_SESSION[$key] = array_diff($_SESSION[$key], array($order_id));
}
else {
unset($_SESSION[$key]);
}
}
}
/**
* Adds the specified product to a customer's shopping cart.
*
* @param $uid
* The uid of the user whose cart you are adding the product to.
* @param $product_id
* The ID of the product to add to the cart.
* @param $quantity
* The quantity of this product to add to the cart.
*
* @return
* The entire shopping cart order object or FALSE on failure.
*/
function commerce_cart_product_add($uid, $product_id, $quantity) {
// Load and validate the specified product ID.
$product = commerce_product_load($product_id);
// Fail if the product does not exist or is disabled.
if (empty($product) || !$product->status) {
return FALSE;
}
// First attempt to load the customer's shopping cart order.
$order = commerce_cart_order_load($uid);
// If no order existed, create one now.
if (empty($order)) {
$order = commerce_cart_order_new($uid);
}
// Wrap the order for easy access to field data.
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
// Invoke the product prepare event with the shopping cart order.
rules_invoke_all('commerce_cart_product_prepare', $order, $product, $quantity);
// Determine if the product already exists on the order and increment its
// quantity instead of adding a new line if it does.
$line_item = NULL;
// Loop through the line items looking for products.
foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {
// If this line item matches the product being added...
if (empty($line_item) &&
$line_item_wrapper->type->value() == 'product' &&
$line_item_wrapper->commerce_product->product_id->value() == $product_id) {
// Exit this loop with the $line_item intact so it gets updated.
$line_item = $line_item_wrapper->value();
}
}
// If no matching line item was found...
if (empty($line_item)) {
// Create the new product line item.
$line_item = commerce_product_line_item_new($product, $quantity, $order->order_id);
// Process the unit price through Rules so it reflects the user's actual
// purchase price.
rules_invoke_event('commerce_product_calculate_sell_price', $line_item);
// Save the line item now so we get its ID.
$line_item = commerce_line_item_save($line_item);
// Add it to the order's line item reference value.
$order_wrapper->commerce_line_items[] = $line_item;
}
else {
// Increment the quantity of the line item and save it.
$line_item->quantity += $quantity;
commerce_line_item_save($line_item);
// Clear the line item cache so the updated quantity will be available to
// the ensuing load instead of the original quantity as loaded above.
entity_get_controller('commerce_line_item')->resetCache(array($line_item->line_item_id));
}
// Save the updated order.
commerce_order_save($order);
// Invoke the product add event with the newly saved or updated line item.
rules_invoke_all('commerce_cart_product_add', $order, $product, $quantity, $line_item);
// Return the order.
return $order;
}
/**
* Deletes a product line item from a shopping cart order.
*
* @param $order
* The shopping cart order to delete from.
* @param $line_item_id
* The ID of the product line item to delete from the order.
* @param $skip_save
* TRUE to skip saving the order after deleting the line item; used when the
* order would otherwise be saved or to delete multiple product line items
* from the order and then save.
*
* @return
* The order with the matching product line item deleted from the line item
* reference field.
*/
function commerce_cart_order_product_line_item_delete($order, $line_item_id, $skip_save = FALSE) {
$line_item = commerce_line_item_load($line_item_id);
// Check to ensure the line item exists and is a product line item.
if (!$line_item || $line_item->type != 'product') {
return $order;
}
// Remove the line item from the line item reference field.
$order = commerce_line_item_reference_delete('commerce_order', $order, 'commerce_line_items', $line_item_id);
// Wrap the line item to be deleted and extract the product from it.
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$product = $wrapper->commerce_product->value();
// Invoke the product removal event with the line item about to be deleted.
rules_invoke_all('commerce_cart_product_remove', $order, $product, $line_item->quantity, $line_item);
// Delete the actual line item.
commerce_line_item_delete($line_item->line_item_id);
return $skip_save ? $order : commerce_order_save($order);
}
/**
* Deletes every product line item from a shopping cart order.
*
* @param $order
* The shopping cart order to empty.
*
* @return
* The order with the product line items all removed.
*/
function commerce_cart_order_empty($order) {
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
// Build an array of product line item IDs.
$line_item_ids = array();
foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {
if ($line_item_wrapper->type->value() == 'product') {
$line_item_ids[] = $line_item_wrapper->line_item_id->value();
}
}
// Delete each line item one by one from the order. This is done this way
// instead of unsetting each as we find it to ensure that changing delta
// values don't prevent an item from being removed from the order.
foreach ($line_item_ids as $line_item_id) {
$order = commerce_cart_order_product_line_item_delete($order, $line_item_id, TRUE);
}
return commerce_order_save($order);
}
/**
* Builds an appropriate cart form ID based on the products on the form.
*
* @see commerce_cart_forms().
*/
function commerce_cart_add_to_cart_form_id($product_ids, $qty = 0) {
// Make sure the length of the form id is limited.
$data = implode('_', $product_ids);
if (strlen($data) > 50) {
$data = drupal_hash_base64($data);
}
return 'commerce_cart_add_to_cart_form_' . $data;
}
/**
* Implements hook_forms().
*
* To provide distinct form IDs for add to cart forms, the product IDs
* referenced by the form are appended to the base ID,
* commerce_cart_add_to_cart_form. When such a form is built or submitted, this
* function will return the proper callback function to use for the given form.
*/
function commerce_cart_forms($form_id, $args) {
$forms = array();
// Construct a valid cart form ID from the arguments.
if (strpos($form_id, 'commerce_cart_add_to_cart_form_') === 0) {
$forms[$form_id] = array(
'callback' => 'commerce_cart_add_to_cart_form',
);
}
return $forms;
}
/**
* Builds an Add to Cart form for a set of products.
*
* @param $product_ids
* An array of product IDs that will be included in the form.
* @param $show_quantity
* Boolean indicating whether or not the quantity field should be shown;
* defaults to FALSE resulting in a hidden field.
* @param $default_quantity
* The value to place in the quantity field, defaults to 1.
* @param $context
* Information on the context of the form's placement, allowing it to update
* product fields on the page based on the currently selected default
* product. Should be an associative array containing the following keys:
* - class_prefix: a prefix used to target HTML containers for replacement
* with rendered fields as the default product is updated. For example,
* nodes display product fields in their context wrapped in spans with the
* class node-#-product-field_name. The class_prefix for the add to cart
* form displayed on a node would be node-# with this form's AJAX refresh
* adding the suffix -product-field_name.
* - view_mode: a product view mode that tells the AJAX refresh how to
* render the replacement fields.
* If no context is specified, AJAX replacement of rendered fields will not
* happen. This parameter only affects forms containing multiple products.
*
* @return
* The form array.
*/
function commerce_cart_add_to_cart_form($form, &$form_state, $product_ids, $show_quantity = FALSE, $default_quantity = 1, $context = array()) {
global $user;
// Store the form ID as a class of the form to avoid the incrementing form ID
// from causing the AJAX refresh not to work.
$form['#attributes']['class'][] = drupal_html_class(commerce_cart_add_to_cart_form_id($product_ids, $default_quantity));
// Store the customer uid in the form so other modules can override with a
// selection widget if necessary.
$form['uid'] = array(
'#type' => 'value',
'#value' => $user->uid,
);
// Load all the products intended for sale on this form.
$products = commerce_product_load_multiple($product_ids);
// If no products were returned...
if (count($products) == 0) {
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Product not available'),
'#disabled' => TRUE,
);
}
else {
$form_state['products'] = $products;
// If the form is for a single product, store the product_id in a hidden
// form field for use by the submit handler.
if (count($products) == 1) {
$form['product_id'] = array(
'#type' => 'hidden',
'#value' => key($products),
);
}
else {
// However, if more than one products are represented on it, attempt to
// use smart select boxes for the product selection. If the products are
// all of the same type and there are qualifying fields on that product
// type, display their options for customer selection.
$same_type = TRUE;
$qualifying_fields = array();
$type = '';
// Find the default product so we know how to set default options on the
// various Add to Cart form widgets and an array of any matching product
// based on attribute selections so we can add a selection widget.
$matching_products = array();
$default_product = NULL;
$attribute_names = array();
$unchanged_attributes = array();
foreach ($products as $product_id => $product) {
$product_wrapper = entity_metadata_wrapper('commerce_product', $product);
// Store the first product type.
if (empty($type)) {
$type = $product->type;
}
// If the current product type is different from the first, we are not
// dealing with a set of same typed products.
if ($product->type != $type) {
$same_type = FALSE;
}
// If the form state contains a set of attribute data, use it to try
// and determine the default product.
$changed_attribute = NULL;
if (!empty($form_state['values']['attributes'])) {
$match = TRUE;
// Set an array of checked attributes for later comparison against the
// default matching product.
if (empty($attribute_names)) {
$attribute_names = (array) array_diff_key($form_state['values']['attributes'], array('product_select' => ''));
$unchanged_attributes = $form_state['values']['unchanged_attributes'];
}
foreach ($attribute_names as $key => $value) {
// If this is the attribute widget that was changed...
if ($value != $unchanged_attributes[$key]) {
// Store the field name.
$changed_attribute = $key;
}
// If a field name has been stored and we've moved past it to
// compare the next attribute field...
if (!empty($changed_attribute) && $changed_attribute != $key) {
// Wipe subsequent values from the form state so the attribute
// widgets can use the default values from the new default product.
unset($form_state['input']['attributes'][$key]);
// Don't accept this as a matching product.
continue;
}
if ($product_wrapper->{$key}->value() != $value) {
$match = FALSE;
}
}
// If the changed field name has already been stored, only accept the
// first matching product by ignoring the rest that would match. An
// exception is granted for additional matching products that share
// the exact same attribute values as the first.
if ($match && !empty($changed_attribute) && !empty($matching_products)) {
reset($matching_products);
$matching_product = $matching_products[key($matching_products)];
foreach ($attribute_names as $key => $value) {
if ($product_wrapper->{$key}->value() != $matching_product->{$key}->value()) {
$match = FALSE;
}
}
}
if ($match) {
$matching_products[$product_id] = $product;
}
}
}
// Set the default product now if it isn't already set.
if (empty($matching_products)) {
// If a product ID value was passed in, use that product if it exists.
if (!empty($form_state['values']['product_id']) &&
!empty($products[$form_state['values']['product_id']])) {
$default_product = $products[$form_state['values']['product_id']];
}
else {
reset($products);
$default_product = $products[key($products)];
}
}
else {
// If the product selector has a value, use that.
if (!empty($form_state['values']['attributes']['product_select']) &&
!empty($products[$form_state['values']['attributes']['product_select']]) &&
in_array($products[$form_state['values']['attributes']['product_select']], $matching_products)) {
$default_product = $products[$form_state['values']['attributes']['product_select']];
}
else {
reset($matching_products);
$default_product = $matching_products[key($matching_products)];
}
}
// Wrap the default product for later use.
$default_product_wrapper = entity_metadata_wrapper('commerce_product', $default_product);
$form_state['default_product'] = $default_product;
$form_state['context'] = $context;
// If all the products are of the same type...
if ($same_type) {
// Loop through all the field instances on that product type.
foreach (field_info_instances('commerce_product', $type) as $name => $instance) {
// A field qualifies if it is single value, required and uses a widget
// with a definite set of options. For the sake of simplicity, this is
// currently restricted to fields defined by the options module.
$field = field_info_field($instance['field_name']);
if ($field['cardinality'] == 1 && $instance['required'] && $instance['widget']['module'] == 'options') {
// Get the options properties from the options module and store the
// options for the instance in select list format in the array of
// qualifying fields.
$properties = _options_properties('select', FALSE, TRUE, TRUE);
$qualifying_fields[$name] = array(
'field' => $field,
'instance' => $instance,
'options' => _options_get_options($field, $instance, $properties),
'weight' => $instance['widget']['weight'],
);
}
}
}
// Otherwise for products of varying types, display a simple select list
// by product title.
if (!empty($qualifying_fields)) {
$used_options = array();
// Sort the fields by weight.
uasort($qualifying_fields, 'drupal_sort_weight');
foreach ($qualifying_fields as $field_name => $data) {
// Build an options array of widget options used by referenced products.
foreach ($products as $product_id => $product) {
$product_wrapper = entity_metadata_wrapper('commerce_product', $product);
// Only add options to the present array that appear on products that
// match the default value of the previously added attribute widgets.
foreach ($used_options as $used_field_name => $unused) {
// Don't apply this check for the current field being evaluated.
if ($used_field_name == $field_name) {
continue;
}
if ($product_wrapper->{$used_field_name}->value() != $form['attributes'][$used_field_name]['#default_value']) {
continue 2;
}
}
// With our hard dependency on widgets provided by the Options
// module, we can make assumptions about where the data is stored.
$used_options[$field_name][] = $product_wrapper->{$field_name}->value();
}
// If for some reason no options for this field are used, remove it
// from the qualifying fields array.
if (empty($used_options[$field_name])) {
unset($qualifying_fields[$field_name]);
}
else {
$form['attributes'][$field_name] = array(
'#type' => 'select',
'#title' => check_plain($data['instance']['label']),
'#options' => array_intersect_key($data['options'], drupal_map_assoc($used_options[$field_name])),
'#default_value' => $default_product_wrapper->{$field_name}->value(),
'#weight' => $data['instance']['widget']['weight'],
'#ajax' => array(
'callback' => 'commerce_cart_add_to_cart_form_attributes_refresh',
),
);
$form['unchanged_attributes'][$field_name] = array(
'#type' => 'value',
'#value' => $default_product_wrapper->{$field_name}->value(),
);
}
}
if (!empty($form['attributes'])) {
$form['attributes'] += array(
'#tree' => 'TRUE',
'#prefix' => '<div class="attribute-widgets">',
'#suffix' => '</div>',
'#weight' => 0,
);
$form['unchanged_attributes'] += array(
'#tree' => 'TRUE',
);
// If the matching products array is empty, it means this is the first
// time the form is being built. We should populate it now with
// products that match the default attribute options.
if (empty($matching_products)) {
foreach ($products as $product_id => $product) {
$product_wrapper = entity_metadata_wrapper('commerce_product', $product);
$match = TRUE;
foreach (element_children($form['attributes']) as $field_name) {
if ($product_wrapper->{$field_name}->value() != $form['attributes'][$field_name]['#default_value']) {
$match = FALSE;
}
}
if ($match) {
$matching_products[$product_id] = $product;
}
}
}
// If there were more than one matching products for the current
// attribute selection, add a product selection widget.
if (count($matching_products) > 1) {
$options = array();
foreach ($matching_products as $product_id => $product) {
$options[$product_id] = check_plain($product->title);
}
$form['attributes']['product_select'] = array(
'#type' => 'select',
'#title' => t('Select a product'),
'#options' => $options,
'#default_value' => $default_product->product_id,
'#weight' => 40,
'#ajax' => array(
'callback' => 'commerce_cart_add_to_cart_form_attributes_refresh',
),
);
}
$form['product_id'] = array(
'#type' => 'hidden',
'#value' => $default_product->product_id,
);
}
}
// If the products referenced were of different types or did not posess
// any qualifying attribute fields, add a product selection widget.
if (!$same_type || empty($qualifying_fields)) {
$options = array();
foreach ($products as $product_id => $product) {
$options[$product_id] = check_plain($product->title);
}
$form['product_id'] = array(
'#type' => 'select',
'#options' => $options,
'#default_value' => $default_product->product_id,
'#weight' => 0,
'#ajax' => array(
'callback' => 'commerce_cart_add_to_cart_form_attributes_refresh',
),
);
}
}
// Render the quantity field as either a textfield if shown or a hidden
// field if not.
if ($show_quantity) {
$form['quantity'] = array(
'#type' => 'textfield',
'#title' => t('Quantity'),
'#default_value' => $default_quantity,
'#datatype' => 'integer',
'#size' => 5,
'#weight' => 5,
);
}
else {
$form['quantity'] = array(
'#type' => 'hidden',
'#value' => $default_quantity,
'#datatype' => 'integer',
'#weight' => 5,
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Add to cart'),
'#weight' => 10,
);
}
// Add the handlers manually since we're using hook_forms() to associate this
// form with form IDs based on the $product_ids.
$form['#validate'][] = 'commerce_cart_add_to_cart_form_validate';
$form['#submit'][] = 'commerce_cart_add_to_cart_form_submit';
return $form;
}
/**
* Form validate handler: validate the product and quantity to add to the cart.
*/
function commerce_cart_add_to_cart_form_validate($form, &$form_state) {
if (!is_numeric($form_state['values']['quantity']) || $form_state['values']['quantity'] <= 0) {
form_set_error('quantity', t('You must specify a valid quantity to add to the cart.'));
}
// If the custom data type attribute of the quantity element is integer,
// ensure we only accept whole number values.
if ($form['quantity']['#datatype'] == 'integer' &&
(int) $form_state['values']['quantity'] != $form_state['values']['quantity']) {
form_set_error('quantity', t('You must specify a whole number for the quantity.'));
}
// If the attributes matching product selector was used, set the value of the
// product_id field to match; this will be fixed on rebuild when the actual
// default product will be selected based on the product selector value.
if (!empty($form_state['values']['attributes']['product_select'])) {
form_set_value($form['product_id'], $form_state['values']['attributes']['product_select'], $form_state);
}
}
/**
* Ajax callback: returns AJAX commands when an attribute widget is changed.
*/
function commerce_cart_add_to_cart_form_attributes_refresh($form, $form_state) {
$commands = array();
// Render the form afresh to capture any changes to the available widgets
// based on the latest selection.
$commands[] = ajax_command_replace('.' . drupal_html_class($form['#form_id']), drupal_render($form));
// Then render and return the various product fields that might need to be
// updated on the page.
if (!empty($form_state['context'])) {
$product = $form_state['default_product'];
foreach (field_info_instances('commerce_product', $product->type) as $product_field_name => $product_field) {
$class = drupal_html_class(implode('-', array($form_state['context']['class_prefix'], 'product', $product_field_name)));
$element = field_view_field('commerce_product', $product, $product_field_name, $form_state['context']['view_mode']);
$element += array(
'#prefix' => '<span class="' . $class . '">',
'#suffix' => '</span>',
);
$commands[] = ajax_command_replace('.' . $class, drupal_render($element));
}
}
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Form submit handler: add the selected product to the cart.
*/
function commerce_cart_add_to_cart_form_submit($form, &$form_state) {
$product_id = $form_state['values']['product_id'];
$product = $form_state['products'][$product_id];
// Add the product to the specified shopping cart.
commerce_cart_product_add($form_state['values']['uid'], $product_id, $form_state['values']['quantity']);
// TODO: Accommodate multiple product Add to Cart forms better; i.e. should it
// display the product title or the product display node title?
drupal_set_message(t('%title added to <a href="!cart-url">your cart</a>.', array('%title' => $product->title, '!cart-url' => url('cart'))));
}
/**
* Implements hook_field_info_alter().
*/
function commerce_cart_field_info_alter(&$info) {
// Set the default display formatter for product reference fields to the Add
// to Cart form.
$info['commerce_product_reference']['default_formatter'] = 'commerce_cart_add_to_cart_form';
}
/**
* Implements hook_field_formatter_info().
*/
function commerce_cart_field_formatter_info() {
return array(
'commerce_cart_add_to_cart_form' => array(
'label' => t('Add to Cart form'),
'description' => t('Display an Add to Cart form for the referenced product.'),
'field types' => array('commerce_product_reference'),
'settings' => array(
'show_quantity' => FALSE,
'default_quantity' => 1,
),
),
);
}
/**
* Implements hook_field_formatter_settings_form().
*/
function commerce_cart_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$element = array();
if ($display['type'] == 'commerce_cart_add_to_cart_form') {
$element['show_quantity'] = array(
'#type' => 'checkbox',
'#title' => t('Display a textfield quantity widget on the add to cart form.'),
'#default_value' => $settings['show_quantity'],
);
$element['default_quantity'] = array(
'#type' => 'textfield',
'#title' => t('Default quantity'),
'#default_value' => $settings['default_quantity'] <= 0 ? 1 : $settings['default_quantity'],
'#element_validate' => array('commerce_cart_field_formatter_settings_form_quantity_validate'),
'#size' => 16,
);
}
return $element;
}
/**
* Element validate callback: ensure a valid quantity is entered.
*/
function commerce_cart_field_formatter_settings_form_quantity_validate($element, &$form_state, $form) {
if (!is_numeric($element['#value']) || $element['#value'] <= 0) {
form_set_error(implode('][', $element['#parents']), t('You must enter a positive numeric default quantity value.'));
}
}
/**
* Implements hook_field_formatter_settings_summary().
*/
function commerce_cart_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$summary = array();
if ($display['type'] == 'commerce_cart_add_to_cart_form') {
$summary = array(
t('Quantity widget: !status', array('!status' => $settings['show_quantity'] ? t('Enabled') : t('Disabled'))),
t('Default quantity: @quantity', array('@quantity' => $settings['default_quantity'])),
);
}
return implode('<br />', $summary);
}
/**
* Implements hook_field_formatter_view().
*/
function commerce_cart_field_formatter_view($entity_type, $object, $field, $instance, $langcode, $items, $display) {
$result = array();
// Collect the list of product IDs.
$product_ids = array();
foreach ($items as $delta => $item) {
$product_ids[$item['product_id']] = $item['product_id'];
}
if ($display['type'] == 'commerce_cart_add_to_cart_form') {
$settings = $display['settings'];
$result[] = array(
'#arguments' => array(
'form_id' => commerce_cart_add_to_cart_form_id($product_ids),
'product_ids' => $product_ids,
'show_quantity' => $settings['show_quantity'],
'default_quantity' => $settings['default_quantity'],
),
);
}
return $result;
}
/**
* Implements hook_field_attach_view_alter().
*
* When a field is formatted for display, the display formatter does not know
* what view mode it is being displayed for. Unfortunately, the Add to Cart form
* display formatter needs this information when displaying product reference
* fields on nodes to provide adequate context for product field replacement on
* multi-value product reference fields. This hook is used to transform a set of
* arguments into a form using the arguments and the extra context information
* gleaned from the parameters passed into this function.
*/
function commerce_cart_field_attach_view_alter(&$output, $context) {
// Loop through the fields passed in looking for any product reference fields
// formatted with the Add to Cart form display formatter.
foreach ($output as $field_name => $element) {
if (!empty($element['#formatter']) && $element['#formatter'] == 'commerce_cart_add_to_cart_form') {
// Prepare the context information needed by the cart form.
$context = array();
// Add the context for displaying product fields in the context of a node
// that references the product by looking at the entity this product
// reference field is attached to.
if ($element['#entity_type'] == 'node') {
$context = array(
'class_prefix' => 'node-' . $element['#object']->nid,
'view_mode' => 'node_' . $element['#view_mode'],
);
}
foreach (element_children($element) as $key) {
// Extract the drupal_get_form() arguments array from the element.
$arguments = $element[$key]['#arguments'];
// Replace the array containing the arguments with the return value of
// drupal_get_form(). It will be rendered when the rest of the object is
// rendered for display.
$output[$field_name][$key] = drupal_get_form($arguments['form_id'], $arguments['product_ids'], $arguments['show_quantity'], $arguments['default_quantity'], $context);
}
}
}
}
<?php
// $Id$
/**
* @file
* The page and form callbacks for use by the shopping cart.
*/
/**
* Redirects invalid checkout attempts or displays the checkout form if valid.
*/
function commerce_cart_checkout_router() {
global $user;
// Load the shopping cart order.
$order = commerce_cart_order_load($user->uid);
$wrapper = entity_metadata_wrapper('commerce_order', $order);
// If no shopping cart order could be found, redirect away from checkout.
// TODO: Redirect to the cart page instead which would then appear as an
// empty shopping cart page.
if (empty($order) || commerce_line_items_quantity($wrapper->commerce_line_items, 'product') == 0) {
drupal_set_message(t('Add some items to your cart and then try checking out.'));
drupal_goto(variable_get('commerce_checkout_empty_redirect', ''));
}
drupal_goto('checkout/' . $order->order_id);
}
/**
* Displays the shopping cart form and associated information.
*/
function commerce_cart_view() {
global $user;
// First check for items in the shopping cart.
$order = commerce_cart_order_load($user->uid);
// If the cart is empty...
if (!$order || empty($order->commerce_line_items)) {
// Display an appropriate message.
$content = theme('commerce_cart_empty_page');
}
else {
// Add the form for editing the cart contents.
$content = commerce_embed_view('commerce_cart_form', 'default', array($order->order_id));
}
return $content;
}
<?php
// $Id$
/**
* @file
* Rules integration for shopping carts.
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_event_info().
*/
function commerce_cart_rules_event_info() {
$events = array();
$events['commerce_cart_product_prepare'] = array(
'label' => t('Before adding a product to the cart'),
'group' => t('Commerce Cart'),
'variables' => commerce_cart_rules_event_variables(),
'access callback' => 'commerce_order_rules_access',
);
$events['commerce_cart_product_add'] = array(
'label' => t('After adding a product to the cart'),
'group' => t('Commerce Cart'),
'variables' => commerce_cart_rules_event_variables(TRUE),
'access callback' => 'commerce_order_rules_access',
);
$events['commerce_cart_product_remove'] = array(
'label' => t('After removing a product from the cart'),
'group' => t('Commerce Cart'),
'variables' => commerce_cart_rules_event_variables(TRUE),
'access callback' => 'commerce_order_rules_access',
);
return $events;
}
/**
* Returns a variables array for shopping cart events.
*
* @param $line_item
* Boolean indicating whether or not to include product line item variables.
*/
function commerce_cart_rules_event_variables($line_item = FALSE) {
$variables = array(
'order' => array(
'type' => 'commerce_order',
'label' => t('Shopping cart order'),
),
'product' => array(
'label' => t('Product'),
'type' => 'commerce_product',
),
'quantity' => array(
'label' => t('Quantity'),
'type' => 'integer',
),
);
if ($line_item) {
$variables += array(
'line_item' => array(
'label' => t('Product line item'),
'type' => 'commerce_line_item',
),
'line_item_unchanged' => array(
'label' => t('Unchanged product line item'),
'type' => 'commerce_line_item',
'skip save' => TRUE,
'handler' => 'rules_events_entity_unchanged',
),
);
}
return $variables;
}
/**
* @}
*/
<?php
// $Id$
/**
* @file
* Unit tests for the commerce cart module.
*/
/**
* Test cart features for a product display that only has one product attached.
*/
class CommerceCartTestCaseSimpleProduct extends CommerceBaseTestCase {
/**
* Product that is being added to the cart.
*/
protected $product;
/**
* Product display.
*/
protected $product_node;
/**
* Normal user (without admin or store permissions) for testing.
*/
protected $normal_user;
/**
* Cart url.
*/
protected $cart_url;
/**
* Implementation of getInfo().
*/
public static function getInfo() {
return array(
'name' => 'Commerce cart functional tests for simple products',
'description' => 'Tests cart features like add to cart, remove from cart, update quantities...',
'group' => 'Drupal Commerce',
);
}
/**
* Implementation of setUp().
*/
function setUp() {
$modules = parent::setUpHelper('all');
parent::setUp($modules);
$this->site_admin = $this->createSiteAdmin();
$this->drupalLogin($this->site_admin);
// Create a dummy product display content type.
$this->createDummyProductDisplayContentType();
// Create dummy product display nodes (and their corresponding product
// entities).
$sku = 'PROD-01';
$product_name = 'Product One';
$this->product = $this->createDummyProduct($sku, $product_name);
$this->product_node = $this->createDummyProductNode(array($this->product->product_id), $product_name);
// Log as a normal user to test cart process.
// User has to have the access checkout permission to check the Checkout
// button integrity.
$this->normal_user = $this->drupalCreateUser(array('access checkout'));
$this->drupalLogin($this->normal_user);
// Submit the add to cart form.
$this->drupalPost('node/' . $this->product_node->nid, array(), t('Add to cart'));
// Get the cart url.
$links = commerce_line_item_summary_links();
$this->cart_url = $links['view_cart']['href'];
}
/**
* Test if the product form has the correct structure.
*/
public function testCommerceCartProductFormStructure() {
// Go to cart url.
$this->drupalGet('node/' . $this->product_node->nid);
$this->assertField('edit-submit', t('Add to cart button exists'));
}
/**
* Test if the product has been correctly added to the cart.
*/
public function testCommerceCartAdd() {
// We check the cart message.
$this->assertRaw($this->product_node->title.'</em> added to <a href="/cart">your cart</a>', t('Message of product added to the cart'));
// Go to cart url.
$this->drupalGet($this->cart_url);
// Test if the page resolves and there is something in the cart.
$this->assertResponse(200);
$this->assertNoText(t('Your shopping cart is empty.'), t('Cart is not empty'));
$this->assertText($this->product->title, t('Product was added to the cart'));
}
/**
* Test if the cart form has the correct fields and structure.
*/
public function testCommerceCartFormStructure() {
// Check if the form is present and it has the quantity field, remove and
// submit buttons.
// Go to cart url.
$this->drupalGet($this->cart_url);
// Check remove button.
$this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-delete')]", NULL, t('Remove button present'));
// Check quantity field.
$this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-quantity')]", NULL, t('Quantity field present'));
$this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-quantity')]", 1, t('Quantity field has correct number of items'));
// Check if the Update cart and Checkout buttons are present.
$this->assertField("edit-update", t('Update cart button present'));
$this->assertField("edit-checkout", t('Checkout button present'));
}
/**
* Test if the product is present in the order stored in db.
*/
public function testCommerceCartOrder() {
// Load the current order of the user.
$order = commerce_cart_order_load($this->normal_user->uid);
$products = array();
$this->assertTrue(commerce_cart_order_is_cart($order), t('User has currently an order in cart status.'));
// Get the products out of the order and store them in an array.
foreach (entity_metadata_wrapper('commerce_order', $order)->commerce_line_items as $delta => $line_item_wrapper) {
if ($line_item_wrapper->type->value() == 'product') {
$product = $line_item_wrapper->commerce_product->value();
$products[$product->product_id]= $product;
}
}
// Check if the product is in the products array for the order.
$this->assertTrue(in_array($this->product->product_id, array_keys($products)), t('Product is actually in the cart'));
}
/**
* Test the quantity changes in the cart.
*/
public function testCommerceCartChangeQty() {
// Go to cart url.
$this->drupalGet($this->cart_url);
// Change quantity in the cart view form.
// We search for the first quantity field in the html and change the
// amount there.
$qty = $this->xpath("//input[starts-with(@name, 'edit_quantity')]");
$this->drupalPost($this->cart_url, array((string) $qty[0]['name'] => 2), t('Update cart'));
// Check if the amount has been really changed.
$this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-quantity')]", 2, t('Cart updated with new quantity'));
}
/**
* Test removing a product from the cart.
*/
public function testCommerceCartRemove() {
// Go to cart url.
$this->drupalGet($this->cart_url);
// Remove the product from the cart.
$this->drupalPost($this->cart_url, array(), t('Remove'));
// Test if the page resolves and there is something in the cart.
$this->assertText(t('Your shopping cart is empty.'), t('Removed product and cart is empty'));
}
}
/**
* Test cart features for a product display that has several products attached.
*/
class CommerceCartTestCaseMultiProducts extends CommerceBaseTestCase {
/**
* Products that are being added to the cart.
*/
protected $products = array();
/**
* Titles of the products that are being added to the cart.
*/
protected $product_titles = array();
/**
* Product display.
*/
protected $product_node;
/**
* Normal user (without admin or store permissions) for testing.
*/
protected $normal_user;
/**
* Cart url.
*/
protected $cart_url;
/**
* Implementation of getInfo().
*/
public static function getInfo() {
return array(
'name' => 'Commerce cart functional tests with several products in a product display',
'description' => 'Tests cart features, add to cart, select other product...',
'group' => 'Drupal Commerce',
);
}
/**
* Implementation of setUp().
*/
function setUp() {
$modules = parent::setUpHelper('all');
parent::setUp($modules);
$this->site_admin = $this->createSiteAdmin();
$this->drupalLogin($this->site_admin);
// Create a dummy product display content type.
$this->createDummyProductDisplayContentType('product_display', TRUE, 'field_product', 2);
// Create dummy product display nodes (and their corresponding product
// entities).
$products = array();
$sku = 'PROD-01';
$product_name = 'Product One';
$this->product_titles[] = $product_name;
$product = $this->createDummyProduct($sku, $product_name);
$this->products[$product->product_id] = $product;
$sku = 'PROD-02';
$product_name = 'Product Two';
$this->product_titles[] = $product_name;
$product = $this->createDummyProduct($sku, $product_name);
$this->products[$product->product_id] = $product;
$this->product_node = $this->createDummyProductNode(array_keys($this->products), 'Combined Product');
// Log as a normal user to test cart process.
// User has to have the access checkout permission to check the Checkout
// button integrity.
$this->normal_user = $this->drupalCreateUser(array('access checkout'));
$this->drupalLogin($this->normal_user);
// Submit the add to cart form.
$this->drupalPost('node/' . $this->product_node->nid, array('product_id' => $this->products[2]->product_id), t('Add to cart'));
// Get the cart url.
$links = commerce_line_item_summary_links();
$this->cart_url = $links['view_cart']['href'];
}
/**
* Test the structure of the product form.
*/
public function testCommerceCartProductFormStructure() {
$option_titles = array();
// Get the options of the product's select.
$options = $this->xpath("//select[@id='edit-product-id']//option");
foreach ($options as $option) {
$option_titles[] = (string) $option;
}
// Check if the selector exists.
$this->assertField('edit-product-id', t('The selector of products exists'));
// Check if the products actually are present in the selector.
$this->assertTrue($this->product_titles == $option_titles, t('Correct products are present in the selector'));
// Look for the Add to cart button.
$this->assertField('edit-submit', t('Add to cart button exists'));
}
/**
* Test to select one product and check if it has been correctly added to
* the cart.
*/
public function testCommerceCartSelectProductAdd() {
// Check the cart message.
$this->assertRaw($this->product_titles[1].'</em> added to <a href="/cart">your cart</a>', t('Message of product added to the cart'));
// Go to cart url.
$this->drupalGet($this->cart_url);
// Test if the page resolves and there is something in the cart.
$this->assertResponse(200);
$this->assertNoText(t('Your shopping cart is empty.'), t('Cart is not empty'));
$this->assertText($this->product_titles[1], t('Product was added to the cart'));
}
/**
* Test if the cart form has the correct fields and structure.
*/
public function testCommerceCartFormStructure() {
// Check if the form is present and it has the quantity field, remove and
// submit buttons.
// Go to cart url.
$this->drupalGet($this->cart_url);
// Check remove button.
$this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-delete')]", NULL, t('Remove button present'));
// Check quantity field.
$this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-quantity')]", NULL, t('Quantity field present'));
$this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-quantity')]", 1, t('Quantity field has correct number of items'));
// Check if the Update cart and Checkout buttons are present.
$this->assertField("edit-update", t('Update cart button present'));
$this->assertField("edit-checkout", t('Checkout button present'));
}
/**
* Test if the product is present in the order stored in db.
*/
public function testCommerceCartOrder() {
$products_in_cart = array();
// Load the current order of the user.
$order = commerce_cart_order_load($this->normal_user->uid);
// Check if the user has at least one order in cart status.
$this->assertTrue(commerce_cart_order_is_cart($order), t('User has currently an order in cart status.'));
// Get the products out of the order and store them in an array.
foreach (entity_metadata_wrapper('commerce_order', $order)->commerce_line_items as $delta => $line_item_wrapper) {
if ($line_item_wrapper->type->value() == 'product') {
$product = $line_item_wrapper->commerce_product->value();
$products_in_cart[$product->product_id]= $product;
}
}
// Check if the product is in the products array for the order.
$this->assertTrue(in_array($this->products[2]->product_id, array_keys($products_in_cart)), t('Product is actually in the cart'));
}
}
/**
* Test cart features for a product with attributes.
*/
class CommerceCartTestCaseAttributes extends CommerceBaseTestCase {
/**
* Products that are being added to the cart.
*/
protected $products = array();
/**
* Product display.
*/
protected $product_node;
/**
* Normal user (without admin or store permissions) for testing.
*/
protected $normal_user;
/**
* Cart url.
*/
protected $cart_url;
/**
* Implementation of getInfo().
*/
public static function getInfo() {
return array(
'name' => 'Commerce cart functional tests with attributes',
'description' => 'Tests cart features like switch attributes, add to cart...',
'group' => 'Drupal Commerce',
);
}
/**
* Implementation of setUp().
*/
function setUp() {
$modules = parent::setUpHelper('all');
parent::setUp($modules);
$this->site_admin = $this->createSiteAdmin();
$this->drupalLogin($this->site_admin);
// Create a dummy product display content type.
$this->createDummyProductDisplayContentType('product_display', TRUE, 'field_product', FIELD_CARDINALITY_UNLIMITED);
// Create the fields and bind them to the product.
$this->fields['field_1'] = array(
'field_name' => 'field_1',
'type' => 'list_text',
'cardinality' => 1,
'settings' => array(
'allowed_values' => array('field_1_value_1' => 'field_1_value_1', 'field_1_value_2' => 'field_1_value_2'),
),
);
$this->fields['field_1'] = field_create_field($this->fields['field_1']);
$this->fields['field_2'] = array(
'field_name' => 'field_2',
'type' => 'list_text',
'cardinality' => 1,
'settings' => array(
'allowed_values' => array('field_2_value_1' => 'field_2_value_1', 'field_2_value_2' => 'field_2_value_2'),
),
);
$this->fields['field_2'] = field_create_field($this->fields['field_2']);
foreach ($this->fields as $field) {
$instance = array(
'field_name' => $field['field_name'],
'entity_type' => 'commerce_product',
'bundle' => 'product',
'label' => $field['field_name']. '_label',
'description' => $field['field_name'] . '_description',
'required' => TRUE,
'widget' => array(
'module' => 'options',
'type' => 'options_select',
),
);
field_create_instance($instance);
}
// Populate the different values for the fields and create products.
foreach ($this->fields['field_1']['settings']['allowed_values'] as $field_1_value) {
foreach ($this->fields['field_2']['settings']['allowed_values'] as $field_2_value) {
$product = $this->createDummyProduct('PROD-' . $field_1_value . '-' . $field_2_value , $field_1_value.'_'.$field_2_value);
$product->field_1[LANGUAGE_NONE][0]['value'] = $field_1_value;
$product->field_2[LANGUAGE_NONE][0]['value'] = $field_2_value;
field_attach_update('commerce_product', $product);
$this->products[$product->product_id] = $product;
}
}
// Create dummy product display node.
$this->product_node = $this->createDummyProductNode(array_keys($this->products), 'Combined Product');
// Log as a normal user to test cart process.
// User has to have the access checkout permission to check the Checkout
// button integrity.
$this->normal_user = $this->drupalCreateUser(array('access checkout'));
$this->drupalLogin($this->normal_user);
// Get the cart url.
$links = commerce_line_item_summary_links();
$this->cart_url = $links['view_cart']['href'];
}
/**
* Test the add to cart functional process with attributes.
*/
public function AAtestCommerceCartSelectProductAdd() {
// Go to product page.
$this->drupalGet('node/' . $this->product_node->nid);
// Set the product that we are checking.
$product = entity_metadata_wrapper('commerce_product', $this->products[3]);
// Select one of the attributes.
$this->drupalPostAJAX(NULL, array('attributes[field_1]' => $product->field_1->value()), 'attributes[field_1]');
// Add product to the cart.
$this->drupalPost(NULL, array(), t('Add to cart'));
// We check the cart message.
$this->assertRaw('field_1_value_2_field_2_value_1'.'</em> added to <a href="/cart">your cart</a>', t('Message of product added to the cart'));
// Go to cart url.
$this->drupalGet($this->cart_url);
// Test if the page resolves and there is something in the cart.
$this->assertResponse(200);
$this->assertNoText(t('Your shopping cart is empty.'), t('Cart is not empty'));
$this->assertText('field_1_value_2_field_2_value_1', t('Product was added to the cart'));
}
/**
* Test the form structure of the Product.
*/
public function testCommerceCartProductFormStructure() {
$this->drupalGet('node/' . $this->product_node->nid);
// Check whether the attribute selectors exist.
$this->assertField('edit-attributes-field-1', t('First attribute selector exists'));
$this->assertField('edit-attributes-field-2', t('Second attribute selector exists'));
// Check the number of attributes.
$options = $this->xpath("//select[@id='edit-attributes-field-1']//option");
$this->assertEqual(count($options), count($this->fields['field_1']['settings']['allowed_values']), t('Number of options for first attribute match'));
$options = $this->xpath("//select[@id='edit-attributes-field-2']//option");
$this->assertEqual(count($options), count($this->fields['field_2']['settings']['allowed_values']), t('Number of options for second attribute match'));
// Look for the Add to cart button.
$this->assertField('edit-submit', t('Add to cart button exists'));
}
}
<?php
// $Id$
/**
* Provide shopping cart related Views integration.
*/
/**
* Implements hook_views_data()
*/
function commerce_cart_views_data() {
$data = array();
$data['commerce_product']['add_to_cart_form'] = array(
'field' => array(
'title' => t('Add to Cart form'),
'help' => t('Display an Add to Cart form for the product.'),
'handler' => 'commerce_cart_handler_field_add_to_cart_form',
),
);
return $data;
}
<?php
// $Id$
/**
* Views for the shopping cart.
*/
/**
* Implements hook_views_default_views().
*/
function commerce_cart_views_default_views() {
$views = array();
// Define the shopping cart update form as a View.
$view = new view;
$view->name = 'commerce_cart_form';
$view->description = 'Display a shopping cart update form.';
$view->tag = 'commerce';
$view->base_table = 'commerce_order';
$view->human_name = '';
$view->core = 0;
$view->api_version = '3.0-alpha1';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
/* Display: Defaults */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->display->display_options['title'] = 'Shopping cart';
$handler->display->display_options['access']['type'] = 'none';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'none';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'edit_delete' => 'edit_delete',
'title' => 'title',
'line_item_label' => 'line_item_label',
'entity_id_1' => 'entity_id_1',
'edit_quantity' => 'edit_quantity',
'entity_id' => 'entity_id',
);
$handler->display->display_options['style_options']['default'] = '-1';
$handler->display->display_options['style_options']['info'] = array(
'edit_delete' => array(
'align' => '',
'separator' => '',
),
'title' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
),
'line_item_label' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
),
'entity_id_1' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
),
'edit_quantity' => array(
'align' => '',
'separator' => '',
),
'entity_id' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
),
);
$handler->display->display_options['style_options']['override'] = 1;
$handler->display->display_options['style_options']['sticky'] = 0;
/* Footer: Commerce Line Item: Line item summary */
$handler->display->display_options['footer']['line_item_summary']['id'] = 'line_item_summary';
$handler->display->display_options['footer']['line_item_summary']['table'] = 'commerce_line_item';
$handler->display->display_options['footer']['line_item_summary']['field'] = 'line_item_summary';
$handler->display->display_options['footer']['line_item_summary']['label'] = 'Cart summary';
$handler->display->display_options['footer']['line_item_summary']['info'] = array(
'quantity' => 0,
'total' => 'total',
);
/* Footer: Commerce Line Item: Line item actions */
$handler->display->display_options['footer']['line_item_actions']['id'] = 'line_item_actions';
$handler->display->display_options['footer']['line_item_actions']['table'] = 'commerce_line_item';
$handler->display->display_options['footer']['line_item_actions']['field'] = 'line_item_actions';
$handler->display->display_options['footer']['line_item_actions']['label'] = 'Cart actions';
$handler->display->display_options['footer']['line_item_actions']['buttons'] = array(
'update' => 'update',
'checkout' => 'checkout',
);
/* Relationship: Fields: Referenced line item */
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['id'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['table'] = 'field_data_commerce_line_items';
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['field'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['required'] = 1;
/* Relationship: Fields: Referenced product */
$handler->display->display_options['relationships']['commerce_product_product_id']['id'] = 'commerce_product_product_id';
$handler->display->display_options['relationships']['commerce_product_product_id']['table'] = 'field_data_commerce_product';
$handler->display->display_options['relationships']['commerce_product_product_id']['field'] = 'commerce_product_product_id';
$handler->display->display_options['relationships']['commerce_product_product_id']['relationship'] = 'commerce_line_items_line_item_id';
/* Field: Commerce Line Item: Remove */
$handler->display->display_options['fields']['edit_delete']['id'] = 'edit_delete';
$handler->display->display_options['fields']['edit_delete']['table'] = 'commerce_line_item';
$handler->display->display_options['fields']['edit_delete']['field'] = 'edit_delete';
$handler->display->display_options['fields']['edit_delete']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['fields']['edit_delete']['label'] = '';
$handler->display->display_options['fields']['edit_delete']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['edit_delete']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['edit_delete']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['edit_delete']['alter']['trim'] = 0;
$handler->display->display_options['fields']['edit_delete']['alter']['nl2br'] = 0;
$handler->display->display_options['fields']['edit_delete']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['edit_delete']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['edit_delete']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['edit_delete']['alter']['html'] = 0;
$handler->display->display_options['fields']['edit_delete']['element_label_colon'] = 1;
$handler->display->display_options['fields']['edit_delete']['element_default_classes'] = 1;
$handler->display->display_options['fields']['edit_delete']['hide_empty'] = 0;
$handler->display->display_options['fields']['edit_delete']['empty_zero'] = 0;
/* Field: Commerce Line Item: Line item label */
$handler->display->display_options['fields']['line_item_label']['id'] = 'line_item_label';
$handler->display->display_options['fields']['line_item_label']['table'] = 'commerce_line_item';
$handler->display->display_options['fields']['line_item_label']['field'] = 'line_item_label';
$handler->display->display_options['fields']['line_item_label']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['fields']['line_item_label']['label'] = 'SKU';
$handler->display->display_options['fields']['line_item_label']['exclude'] = TRUE;
$handler->display->display_options['fields']['line_item_label']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['trim'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['word_boundary'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['ellipsis'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['html'] = 0;
$handler->display->display_options['fields']['line_item_label']['hide_empty'] = 0;
$handler->display->display_options['fields']['line_item_label']['empty_zero'] = 0;
/* Field: Commerce Product: Title */
$handler->display->display_options['fields']['title']['id'] = 'title';
$handler->display->display_options['fields']['title']['table'] = 'commerce_product';
$handler->display->display_options['fields']['title']['field'] = 'title';
$handler->display->display_options['fields']['title']['relationship'] = 'commerce_product_product_id';
$handler->display->display_options['fields']['title']['label'] = 'Product';
$handler->display->display_options['fields']['title']['alter']['alter_text'] = 1;
$handler->display->display_options['fields']['title']['alter']['text'] = '<div>[title] ([line_item_label])</div>';
$handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['title']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['title']['alter']['trim'] = 0;
$handler->display->display_options['fields']['title']['alter']['nl2br'] = 0;
$handler->display->display_options['fields']['title']['alter']['word_boundary'] = 0;
$handler->display->display_options['fields']['title']['alter']['ellipsis'] = 0;
$handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['title']['alter']['html'] = 0;
$handler->display->display_options['fields']['title']['element_label_colon'] = 1;
$handler->display->display_options['fields']['title']['element_default_classes'] = 1;
$handler->display->display_options['fields']['title']['hide_empty'] = 0;
$handler->display->display_options['fields']['title']['empty_zero'] = 0;
$handler->display->display_options['fields']['title']['link_to_product'] = 0;
/* Field: Fields: commerce_unit_price */
$handler->display->display_options['fields']['entity_id_1']['id'] = 'entity_id_1';
$handler->display->display_options['fields']['entity_id_1']['table'] = 'field_data_commerce_unit_price';
$handler->display->display_options['fields']['entity_id_1']['field'] = 'entity_id';
$handler->display->display_options['fields']['entity_id_1']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['fields']['entity_id_1']['label'] = 'Price';
$handler->display->display_options['fields']['entity_id_1']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['trim'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['word_boundary'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['ellipsis'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['html'] = 0;
$handler->display->display_options['fields']['entity_id_1']['hide_empty'] = 0;
$handler->display->display_options['fields']['entity_id_1']['empty_zero'] = 0;
$handler->display->display_options['fields']['entity_id_1']['type'] = 'commerce_price_formatted_amount';
/* Field: Commerce Line Item: Quantity edit */
$handler->display->display_options['fields']['edit_quantity']['id'] = 'edit_quantity';
$handler->display->display_options['fields']['edit_quantity']['table'] = 'commerce_line_item';
$handler->display->display_options['fields']['edit_quantity']['field'] = 'edit_quantity';
$handler->display->display_options['fields']['edit_quantity']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['fields']['edit_quantity']['label'] = 'Quantity';
$handler->display->display_options['fields']['edit_quantity']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['edit_quantity']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['edit_quantity']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['edit_quantity']['alter']['trim'] = 0;
$handler->display->display_options['fields']['edit_quantity']['alter']['word_boundary'] = 0;
$handler->display->display_options['fields']['edit_quantity']['alter']['ellipsis'] = 0;
$handler->display->display_options['fields']['edit_quantity']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['edit_quantity']['alter']['html'] = 0;
$handler->display->display_options['fields']['edit_quantity']['element_label_colon'] = 0;
$handler->display->display_options['fields']['edit_quantity']['element_default_classes'] = 0;
$handler->display->display_options['fields']['edit_quantity']['hide_empty'] = 0;
$handler->display->display_options['fields']['edit_quantity']['empty_zero'] = 0;
/* Field: Fields: commerce_total */
$handler->display->display_options['fields']['entity_id']['id'] = 'entity_id';
$handler->display->display_options['fields']['entity_id']['table'] = 'field_data_commerce_total';
$handler->display->display_options['fields']['entity_id']['field'] = 'entity_id';
$handler->display->display_options['fields']['entity_id']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['fields']['entity_id']['label'] = 'Total';
$handler->display->display_options['fields']['entity_id']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['trim'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['word_boundary'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['ellipsis'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['html'] = 0;
$handler->display->display_options['fields']['entity_id']['hide_empty'] = 0;
$handler->display->display_options['fields']['entity_id']['empty_zero'] = 0;
$handler->display->display_options['fields']['entity_id']['type'] = 'commerce_price_formatted_amount';
/* Sort criterion: Commerce Line Item: Line item ID */
$handler->display->display_options['sorts']['line_item_id']['id'] = 'line_item_id';
$handler->display->display_options['sorts']['line_item_id']['table'] = 'commerce_line_item';
$handler->display->display_options['sorts']['line_item_id']['field'] = 'line_item_id';
$handler->display->display_options['sorts']['line_item_id']['relationship'] = 'commerce_line_items_line_item_id';
/* Argument: Commerce Order: Order ID */
$handler->display->display_options['arguments']['order_id']['id'] = 'order_id';
$handler->display->display_options['arguments']['order_id']['table'] = 'commerce_order';
$handler->display->display_options['arguments']['order_id']['field'] = 'order_id';
$handler->display->display_options['arguments']['order_id']['default_action'] = 'empty';
$handler->display->display_options['arguments']['order_id']['style_plugin'] = 'default_summary';
$handler->display->display_options['arguments']['order_id']['wildcard'] = '';
$handler->display->display_options['arguments']['order_id']['wildcard_substitution'] = '';
$handler->display->display_options['arguments']['order_id']['default_argument_type'] = 'fixed';
$handler->display->display_options['arguments']['order_id']['break_phrase'] = 0;
$handler->display->display_options['arguments']['order_id']['not'] = 0;
/* Filter: Commerce Line Item: Type */
$handler->display->display_options['filters']['type']['id'] = 'type';
$handler->display->display_options['filters']['type']['table'] = 'commerce_line_item';
$handler->display->display_options['filters']['type']['field'] = 'type';
$handler->display->display_options['filters']['type']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['filters']['type']['value'] = array(
'product' => 'product',
);
$translatables['commerce_cart_form'] = array(
t('Defaults'),
t('Shopping cart'),
t('more'),
t('Apply'),
t('Reset'),
t('Sort By'),
t('Asc'),
t('Desc'),
t('Cart summary'),
t('Cart actions'),
t('Line Item'),
t('Product'),
t('SKU'),
t('<div>[title] ([line_item_label])</div>'),
t('Price'),
t('Quantity'),
t('Total'),
);
$views[$view->name] = $view;
// Shopping cart view for the block and checkout pane.
$view = new view;
$view->name = 'commerce_cart_block';
$view->description = 'Display a list of line items addded to cart.';
$view->tag = 'commerce';
$view->view_php = '';
$view->base_table = 'commerce_order';
$view->is_cacheable = FALSE;
$view->api_version = '3.0-alpha1';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
/* Display: Defaults */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->display->display_options['title'] = 'Shopping cart';
$handler->display->display_options['access']['type'] = 'none';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'none';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'line_item_label' => 'line_item_label',
'quantity' => 'quantity',
'entity_id' => 'entity_id',
);
$handler->display->display_options['style_options']['default'] = '-1';
$handler->display->display_options['style_options']['info'] = array(
'line_item_label' => array(
'sortable' => 0,
'align' => '',
'separator' => '',
),
'quantity' => array(
'sortable' => 0,
'align' => '',
'separator' => '',
),
'entity_id' => array(
'sortable' => 0,
'align' => 'views-align-right',
'separator' => '',
),
);
$handler->display->display_options['style_options']['override'] = 1;
$handler->display->display_options['style_options']['sticky'] = 0;
/* Footer: Commerce Line Item: Line item summary */
$handler->display->display_options['footer']['line_item_summary']['id'] = 'line_item_summary';
$handler->display->display_options['footer']['line_item_summary']['table'] = 'commerce_line_item';
$handler->display->display_options['footer']['line_item_summary']['field'] = 'line_item_summary';
$handler->display->display_options['footer']['line_item_summary']['links'] = array(
'view_cart' => 'view_cart',
'checkout' => 'checkout',
);
/* Relationship: Fields: Referenced line item */
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['id'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['table'] = 'field_data_commerce_line_items';
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['field'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['required'] = 1;
/* Field: Commerce Line Item: Quantity */
$handler->display->display_options['fields']['quantity']['id'] = 'quantity';
$handler->display->display_options['fields']['quantity']['table'] = 'commerce_line_item';
$handler->display->display_options['fields']['quantity']['field'] = 'quantity';
$handler->display->display_options['fields']['quantity']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['fields']['quantity']['label'] = '';
$handler->display->display_options['fields']['quantity']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['trim'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['quantity']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['quantity']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['html'] = 0;
$handler->display->display_options['fields']['quantity']['hide_empty'] = 0;
$handler->display->display_options['fields']['quantity']['empty_zero'] = 0;
$handler->display->display_options['fields']['quantity']['set_precision'] = 0;
$handler->display->display_options['fields']['quantity']['precision'] = '0';
$handler->display->display_options['fields']['quantity']['suffix'] = ' ร— ';
/* Field: Commerce Line Item: Line item label */
$handler->display->display_options['fields']['line_item_label']['id'] = 'line_item_label';
$handler->display->display_options['fields']['line_item_label']['table'] = 'commerce_line_item';
$handler->display->display_options['fields']['line_item_label']['field'] = 'line_item_label';
$handler->display->display_options['fields']['line_item_label']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['fields']['line_item_label']['label'] = '';
$handler->display->display_options['fields']['line_item_label']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['trim'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['line_item_label']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['line_item_label']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['html'] = 0;
$handler->display->display_options['fields']['line_item_label']['hide_empty'] = 0;
$handler->display->display_options['fields']['line_item_label']['empty_zero'] = 0;
/* Field: Fields: commerce_total */
$handler->display->display_options['fields']['entity_id']['id'] = 'entity_id';
$handler->display->display_options['fields']['entity_id']['table'] = 'field_data_commerce_total';
$handler->display->display_options['fields']['entity_id']['field'] = 'entity_id';
$handler->display->display_options['fields']['entity_id']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['fields']['entity_id']['label'] = '';
$handler->display->display_options['fields']['entity_id']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['trim'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['entity_id']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['entity_id']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['html'] = 0;
$handler->display->display_options['fields']['entity_id']['hide_empty'] = 0;
$handler->display->display_options['fields']['entity_id']['empty_zero'] = 0;
$handler->display->display_options['fields']['entity_id']['type'] = 'commerce_price_formatted_amount';
/* Sort criterion: Commerce Line Item: Line item ID */
$handler->display->display_options['sorts']['line_item_id']['id'] = 'line_item_id';
$handler->display->display_options['sorts']['line_item_id']['table'] = 'commerce_line_item';
$handler->display->display_options['sorts']['line_item_id']['field'] = 'line_item_id';
$handler->display->display_options['sorts']['line_item_id']['relationship'] = 'commerce_line_items_line_item_id';
/* Argument: Commerce Order: Order ID */
$handler->display->display_options['arguments']['order_id']['id'] = 'order_id';
$handler->display->display_options['arguments']['order_id']['table'] = 'commerce_order';
$handler->display->display_options['arguments']['order_id']['field'] = 'order_id';
$handler->display->display_options['arguments']['order_id']['style_plugin'] = 'default_summary';
$handler->display->display_options['arguments']['order_id']['default_argument_type'] = 'fixed';
$handler->display->display_options['arguments']['order_id']['break_phrase'] = 0;
$handler->display->display_options['arguments']['order_id']['not'] = 0;
/* Filter: Commerce Line Item: Type */
$handler->display->display_options['filters']['type']['id'] = 'type';
$handler->display->display_options['filters']['type']['table'] = 'commerce_line_item';
$handler->display->display_options['filters']['type']['field'] = 'type';
$handler->display->display_options['filters']['type']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['filters']['type']['value'] = array(
'product' => 'product',
);
$views[$view->name] = $view;
// Now add a summary version that don't use links in the line item summary for
// use during checkout.
$view = new view;
$view->name = 'commerce_cart_summary';
$view->description = 'Cart line item summary displayed during checkout.';
$view->tag = 'commerce';
$view->base_table = 'commerce_order';
$view->human_name = '';
$view->core = 0;
$view->api_version = '3.0-alpha1';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
/* Display: Defaults */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->display->display_options['title'] = 'Shopping cart';
$handler->display->display_options['access']['type'] = 'none';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'none';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'line_item_label' => 'line_item_label',
'quantity' => 'quantity',
'entity_id' => 'entity_id',
);
$handler->display->display_options['style_options']['default'] = '-1';
$handler->display->display_options['style_options']['info'] = array(
'line_item_label' => array(
'sortable' => 0,
'align' => '',
'separator' => '',
),
'quantity' => array(
'sortable' => 0,
'align' => '',
'separator' => '',
),
'entity_id' => array(
'sortable' => 0,
'align' => 'views-align-right',
'separator' => '',
),
);
$handler->display->display_options['style_options']['override'] = 1;
$handler->display->display_options['style_options']['sticky'] = 0;
/* Footer: Commerce Line Item: Line item summary */
$handler->display->display_options['footer']['line_item_summary']['id'] = 'line_item_summary';
$handler->display->display_options['footer']['line_item_summary']['table'] = 'commerce_line_item';
$handler->display->display_options['footer']['line_item_summary']['field'] = 'line_item_summary';
/* Relationship: Fields: Referenced line item */
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['id'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['table'] = 'field_data_commerce_line_items';
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['field'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['relationships']['commerce_line_items_line_item_id']['required'] = 1;
/* Relationship: Fields: Referenced product */
$handler->display->display_options['relationships']['commerce_product_product_id']['id'] = 'commerce_product_product_id';
$handler->display->display_options['relationships']['commerce_product_product_id']['table'] = 'field_data_commerce_product';
$handler->display->display_options['relationships']['commerce_product_product_id']['field'] = 'commerce_product_product_id';
$handler->display->display_options['relationships']['commerce_product_product_id']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['relationships']['commerce_product_product_id']['required'] = 0;
/* Field: Commerce Product: Title */
$handler->display->display_options['fields']['title']['id'] = 'title';
$handler->display->display_options['fields']['title']['table'] = 'commerce_product';
$handler->display->display_options['fields']['title']['field'] = 'title';
$handler->display->display_options['fields']['title']['relationship'] = 'commerce_product_product_id';
$handler->display->display_options['fields']['title']['label'] = 'Product';
$handler->display->display_options['fields']['title']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['title']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['title']['alter']['trim'] = 0;
$handler->display->display_options['fields']['title']['alter']['nl2br'] = 0;
$handler->display->display_options['fields']['title']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['title']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['title']['alter']['html'] = 0;
$handler->display->display_options['fields']['title']['element_label_colon'] = 1;
$handler->display->display_options['fields']['title']['element_default_classes'] = 1;
$handler->display->display_options['fields']['title']['hide_empty'] = 0;
$handler->display->display_options['fields']['title']['empty_zero'] = 0;
$handler->display->display_options['fields']['title']['link_to_product'] = 0;
/* Field: Fields: commerce_unit_price */
$handler->display->display_options['fields']['entity_id_1']['id'] = 'entity_id_1';
$handler->display->display_options['fields']['entity_id_1']['table'] = 'field_data_commerce_unit_price';
$handler->display->display_options['fields']['entity_id_1']['field'] = 'entity_id';
$handler->display->display_options['fields']['entity_id_1']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['fields']['entity_id_1']['label'] = 'Price';
$handler->display->display_options['fields']['entity_id_1']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['trim'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['nl2br'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['entity_id_1']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['entity_id_1']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['html'] = 0;
$handler->display->display_options['fields']['entity_id_1']['element_label_colon'] = 1;
$handler->display->display_options['fields']['entity_id_1']['element_default_classes'] = 1;
$handler->display->display_options['fields']['entity_id_1']['hide_empty'] = 0;
$handler->display->display_options['fields']['entity_id_1']['empty_zero'] = 0;
$handler->display->display_options['fields']['entity_id_1']['click_sort_column'] = 'amount';
$handler->display->display_options['fields']['entity_id_1']['type'] = 'commerce_price_formatted_amount';
$handler->display->display_options['fields']['entity_id_1']['settings'] = array(
'calculation' => FALSE,
);
/* Field: Commerce Line Item: Quantity */
$handler->display->display_options['fields']['quantity']['id'] = 'quantity';
$handler->display->display_options['fields']['quantity']['table'] = 'commerce_line_item';
$handler->display->display_options['fields']['quantity']['field'] = 'quantity';
$handler->display->display_options['fields']['quantity']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['fields']['quantity']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['trim'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['nl2br'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['quantity']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['quantity']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['html'] = 0;
$handler->display->display_options['fields']['quantity']['element_label_colon'] = 1;
$handler->display->display_options['fields']['quantity']['element_default_classes'] = 1;
$handler->display->display_options['fields']['quantity']['hide_empty'] = 0;
$handler->display->display_options['fields']['quantity']['empty_zero'] = 0;
$handler->display->display_options['fields']['quantity']['set_precision'] = 0;
$handler->display->display_options['fields']['quantity']['precision'] = '0';
$handler->display->display_options['fields']['quantity']['format_plural'] = 0;
/* Field: Fields: commerce_total */
$handler->display->display_options['fields']['entity_id']['id'] = 'entity_id';
$handler->display->display_options['fields']['entity_id']['table'] = 'field_data_commerce_total';
$handler->display->display_options['fields']['entity_id']['field'] = 'entity_id';
$handler->display->display_options['fields']['entity_id']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['fields']['entity_id']['label'] = 'Total';
$handler->display->display_options['fields']['entity_id']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['trim'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['nl2br'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['entity_id']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['entity_id']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['html'] = 0;
$handler->display->display_options['fields']['entity_id']['element_label_colon'] = 1;
$handler->display->display_options['fields']['entity_id']['element_default_classes'] = 1;
$handler->display->display_options['fields']['entity_id']['hide_empty'] = 0;
$handler->display->display_options['fields']['entity_id']['empty_zero'] = 0;
$handler->display->display_options['fields']['entity_id']['click_sort_column'] = 'amount';
$handler->display->display_options['fields']['entity_id']['type'] = 'commerce_price_formatted_amount';
$handler->display->display_options['fields']['entity_id']['settings'] = array(
'calculation' => FALSE,
);
/* Sort criterion: Commerce Line Item: Line item ID */
$handler->display->display_options['sorts']['line_item_id']['id'] = 'line_item_id';
$handler->display->display_options['sorts']['line_item_id']['table'] = 'commerce_line_item';
$handler->display->display_options['sorts']['line_item_id']['field'] = 'line_item_id';
$handler->display->display_options['sorts']['line_item_id']['relationship'] = 'commerce_line_items_line_item_id';
/* Argument: Commerce Order: Order ID */
$handler->display->display_options['arguments']['order_id']['id'] = 'order_id';
$handler->display->display_options['arguments']['order_id']['table'] = 'commerce_order';
$handler->display->display_options['arguments']['order_id']['field'] = 'order_id';
$handler->display->display_options['arguments']['order_id']['style_plugin'] = 'default_summary';
$handler->display->display_options['arguments']['order_id']['default_argument_type'] = 'fixed';
$handler->display->display_options['arguments']['order_id']['break_phrase'] = 0;
$handler->display->display_options['arguments']['order_id']['not'] = 0;
/* Filter: Commerce Line Item: Type */
$handler->display->display_options['filters']['type']['id'] = 'type';
$handler->display->display_options['filters']['type']['table'] = 'commerce_line_item';
$handler->display->display_options['filters']['type']['field'] = 'type';
$handler->display->display_options['filters']['type']['relationship'] = 'commerce_line_items_line_item_id';
$handler->display->display_options['filters']['type']['value'] = array(
'product' => 'product',
);
$translatables['commerce_cart_summary'] = array(
t('Defaults'),
t('Shopping cart'),
t('more'),
t('Apply'),
t('Reset'),
t('Sort By'),
t('Asc'),
t('Desc'),
t('Line Item'),
t('Product'),
t('Price'),
t('Quantity'),
t('.'),
t(','),
t('Total'),
t('All'),
);
$views[$view->name] = $view;
return $views;
}
<?php
// $Id$
/**
* Field handler to present an add to cart form for the product..
*/
class commerce_cart_handler_field_add_to_cart_form extends views_handler_field {
function construct() {
parent::construct();
$this->additional_fields['product_id'] = 'product_id';
}
function option_definition() {
$options = parent::option_definition();
$options['show_quantity'] = array('default' => FALSE);
$options['default_quantity'] = array('default' => 1);
return $options;
}
/**
* Provide the add to cart display options.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['show_quantity'] = array(
'#type' => 'checkbox',
'#title' => t('Display a textfield quantity widget on the add to cart form.'),
'#default_value' => $this->options['show_quantity'],
);
$form['default_quantity'] = array(
'#type' => 'textfield',
'#title' => t('Default quantity'),
'#default_value' => $this->options['default_quantity'] <= 0 ? 1 : $this->options['default_quantity'],
'#element_validate' => array('commerce_cart_field_formatter_settings_form_quantity_validate'),
'#size' => 16,
);
}
function query() {
$this->ensure_my_table();
$this->add_additional_fields();
}
function render($values) {
if (!empty($values->{$this->aliases['product_id']})) {
$product_ids = array($values->{$this->aliases['product_id']});
$default_quantity = $this->options['default_quantity'] <= 0 ? 1 : $this->options['default_quantity'];
// Generate a form ID for this add to cart form.
$form_id = commerce_cart_add_to_cart_form_id($product_ids, $default_quantity);
return drupal_render(drupal_get_form($form_id, $product_ids, $this->options['show_quantity'], $default_quantity));
}
}
}
<?php
// $Id$
/**
* @file
* Administrative callbacks for the Checkout module.
*/
/**
* Build the checkout form builder, adding in data for the checkout pages and
* the appropriate fields to enable tabledrag on the checkout panes.
*/
function commerce_checkout_builder_form($form, &$form_state) {
// Load an array of all available checkout pages.
$checkout_pages = commerce_checkout_pages();
// Add a "disabled" pseudo-page.
$checkout_pages['disabled'] = array('page_id' => 'disabled', 'title' => t('Disabled'));
$form['checkout_pages'] = array(
'#type' => 'value',
'#value' => $checkout_pages,
);
// Create arrays for checkout panes in each of the pages.
foreach (array_keys($checkout_pages) as $page_id) {
$form['page'][$page_id]['panes'] = array('#tree' => TRUE);
}
// Loop through all the checkout panes on the site.
$panes = commerce_checkout_panes();
foreach ($panes as $pane_id => $checkout_pane) {
// Determine a checkout pane's current checkout page.
$page_id = $checkout_pane['enabled'] ? $checkout_pane['page'] : 'disabled';
// If the page no longer exists, place the pane on the first page.
if (empty($checkout_pages[$page_id])) {
reset($checkout_pages);
$page_id = key($checkout_pages);
}
// Add the pane's name to the form array.
$form['page'][$page_id]['panes'][$pane_id]['name'] = array(
'#markup' => check_plain($checkout_pane['name']),
);
// Add the select field for the pane's checkout page.
$form['page'][$page_id]['panes'][$pane_id]['page'] = array(
'#type' => 'select',
'#options' => drupal_map_assoc(array_keys($checkout_pages)),
'#default_value' => $checkout_pane['page'],
'#attributes' => array('class' => array('checkout-pane-page checkout-pane-page-'. $checkout_pane['page'])),
);
// Add the select field for the pane's weight.
$form['page'][$page_id]['panes'][$pane_id]['weight'] = array(
'#type' => 'weight',
'#delta' => 20,
'#default_value' => $checkout_pane['weight'],
'#attributes' => array('class' => array('checkout-pane-weight checkout-pane-weight-'. $checkout_pane['page'])),
);
// Add a configuration link for the pane.
$form['page'][$page_id]['panes'][$pane_id]['ops'] = array(
'#markup' => l(t('configure'), 'admin/commerce/config/checkout/form/pane/'. $pane_id),
);
}
$form['actions'] = array(
'#type' => 'container',
'#tree' => FALSE,
'#attributes' => array('class' => array('form-actions')),
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save configuration'),
'#submit' => array('commerce_checkout_builder_form_save_submit'),
);
$form['actions']['reset'] = array(
'#type' => 'submit',
'#value' => t('Reset to defaults'),
'#submit' => array('commerce_checkout_builder_form_reset_submit'),
);
return $form;
}
/**
* Submit handler for the checkout builder form's save button.
*/
function commerce_checkout_builder_form_save_submit($form, &$form_state) {
// Loop through each of the checkout panes.
if (!empty($form_state['values']['panes'])) {
foreach ($form_state['values']['panes'] as $pane_id => $data) {
// Load and update the checkout pane array.
$checkout_pane = commerce_checkout_pane_load($pane_id);
$checkout_pane['weight'] = $data['weight'];
// Accommodate the "Disabled" pseudo-page in the form.
if ($data['page'] == 'disabled') {
$checkout_pane['enabled'] = FALSE;
$checkout_pane['page'] = 'disabled';
}
else {
$checkout_pane['enabled'] = TRUE;
$checkout_pane['page'] = $data['page'];
}
commerce_checkout_pane_save($checkout_pane);
}
}
drupal_set_message(t('Checkout pane positions saved.'));
}
/**
* Submit handler for the checkout builder form's reset button.
*/
function commerce_checkout_builder_form_reset_submit($form, &$form_state) {
// Empty the checkout pane table of configuration data.
db_query("TRUNCATE TABLE {commerce_checkout_pane}");
drupal_set_message(t('Checkout pane positions reset.'));
}
/**
* Theme the checkout pages form to enable tabledrag.
*/
function theme_commerce_checkout_builder_form($variables) {
$form = $variables['form'];
drupal_add_css(drupal_get_path('module', 'commerce_checkout') .'/theme/commerce_checkout_admin.css');
drupal_add_js(drupal_get_path('module', 'commerce_checkout') .'/commerce_checkout_admin.js');
// Build the full table header; Page and Weight will be hidden by tabledrag.
$header = array(t('Checkout pane'), t('Page'), t('Weight'), t('Operations'));
$rows = array();
// Loop through each page array in the form.
foreach ($form['checkout_pages']['#value'] as $page_id => $checkout_page) {
// Initialize the tabledrag for this page.
drupal_add_tabledrag('panes', 'match', 'sibling', 'checkout-pane-page', 'checkout-pane-page-'. $page_id);
drupal_add_tabledrag('panes', 'order', 'sibling', 'checkout-pane-weight', 'checkout-pane-weight-'. $page_id);
// Add a non-draggable header row for each checkout page.
$row = array(
array('data' => $checkout_page['title'], 'colspan' => 4),
);
$rows[] = array('data' => $row, 'class' => array('page-header'));
// Determine whether the page currently has any panes in it.
$class = count(element_children($form['page'][$page_id]['panes'])) == 0 ? 'page-empty' : 'page-populated';
// Add a row to the table that will be automatically shown or hidden as a
// placeholder for pages that do not have any panes.
$rows[] = array(
'data' => array(
array('data' => $page_id != 'disabled' ? t('No panes on this page.') : t('No disabled panes.'), 'colspan' => 4),
),
'class' => array('page-message page-'. $page_id .'-message', $class),
);
// Loop through each checkout pane currently assigned to this page.
foreach (element_children($form['page'][$page_id]['panes']) as $pane_id) {
$row = array(
drupal_render($form['page'][$page_id]['panes'][$pane_id]['name']),
drupal_render($form['page'][$page_id]['panes'][$pane_id]['page']),
drupal_render($form['page'][$page_id]['panes'][$pane_id]['weight']),
drupal_render($form['page'][$page_id]['panes'][$pane_id]['ops']),
);
$rows[] = array('data' => $row, 'class' => array('draggable'));
}
}
$variables = array(
'header' => $header,
'rows' => $rows,
'attributes' => array('id' => 'panes'),
);
return theme('table', $variables) . drupal_render_children($form);
}
/**
* Build the configuration form for a checkout pane.
*/
function commerce_checkout_pane_settings_form($form, &$form_state, $checkout_pane) {
// Build the form array with the bare minimum fields.
$form['checkout_pane'] = array(
'#type' => 'value',
'#value' => $checkout_pane,
);
$form['display'] = array(
'#type' => 'fieldset',
'#title' => t('Display settings'),
'#description' => t('These settings are common to all checkout panes and affect their appearance on the checkout form.'),
);
$form['display']['collapsibility'] = array(
'#type' => 'radios',
'#title' => t('Checkout form fieldset display'),
'#description' => t('Checkout panes are rendered on the checkout form in individual fieldsets.') .'<br />'. t('Specify here how the fieldset for this pane will appear.'),
'#options' => array(
'default' => t('Display this pane in a non-collapsible fieldset.'),
'collapsible' => t('Display this pane in a collapsible fieldset.'),
'collapsed' => t('Display this pane in a collapsed fieldset.'),
),
'#default_value' => $checkout_pane['collapsible'] ? ($checkout_pane['collapsed'] ? 'collapsed' : 'collapsible') : 'default',
);
$form['display']['review'] = array(
'#type' => 'checkbox',
'#title' => t('Include this pane on the Review checkout pane.'),
'#default_value' => $checkout_pane['review'],
);
// If a settings form exists for the specified checkout pane...
if ($callback = commerce_checkout_pane_callback($checkout_pane, 'settings_form')) {
// Create a fieldset to hold the checkout pane settings.
$form['settings'] = array(
'#type' => 'fieldset',
'#title' => t('Checkout pane configuration'),
'#description' => t('These settings are specific to this checkout pane.'),
);
// Add the settings from the callback to the form.
$form['settings'] += $callback($checkout_pane);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save configuration'),
'#submit' => array('commerce_checkout_pane_settings_form_save_submit'),
);
$form['reset'] = array(
'#type' => 'submit',
'#value' => t('Reset to defaults'),
'#suffix' => l(t('Cancel'), 'admin/commerce/config/checkout/form'),
'#submit' => array('commerce_checkout_pane_settings_form_reset_submit'),
);
return $form;
}
/**
* Submit handler for the checkout pane settings form's save button.
*/
function commerce_checkout_pane_settings_form_save_submit($form, &$form_state) {
// Load and update the pane with values from the form.
$checkout_pane = commerce_checkout_pane_load($form_state['values']['checkout_pane']['pane_id']);
// Update the fieldset collapsibility variables.
switch ($form_state['values']['collapsibility']) {
case 'collapsible':
$checkout_pane['collapsible'] = TRUE;
$checkout_pane['collapsed'] = FALSE;
break;
case 'collapsed':
$checkout_pane['collapsible'] = TRUE;
$checkout_pane['collapsed'] = TRUE;
break;
case 'default':
default:
$checkout_pane['collapsible'] = FALSE;
$checkout_pane['collapsed'] = FALSE;
break;
}
// Update the pane's review page visibility.
$checkout_pane['review'] = $form_state['values']['review'];
// Save the changes.
commerce_checkout_pane_save($checkout_pane);
// Save this checkout pane's settings as if this was a system settings form.
if (!empty($form['settings'])) {
foreach (element_children($form['settings']) as $field) {
variable_set($field, $form_state['values'][$field]);
}
}
drupal_set_message(t('Checkout pane saved.'));
// Redirect to the main checkout form builder page on save.
$form_state['redirect'] = 'admin/commerce/config/checkout/form';
}
/**
* Submit handler for the checkout pane settings form's reset button.
*/
function commerce_checkout_pane_settings_form_reset_submit($form, &$form_state) {
// Reset the display settings for the checkout pane.
commerce_checkout_pane_reset($form_state['values']['checkout_pane']['pane_id']);
// Reset this checkout pane's settings as if this was a system settings form.
if (!empty($form['settings'])) {
foreach (element_children($form['settings']) as $field) {
variable_del($field);
}
}
drupal_set_message(t('Checkout pane reset.'));
}
/**
* Builds the checkout completion Rules Overview page.
*/
function commerce_checkout_complete_rules() {
RulesPluginUI::$basePath = 'admin/commerce/config/checkout/rules';
$content['enabled']['title']['#markup'] = '<h3>' . t('Enabled checkout complete rules') . '</h3>';
$conditions = array('event' => 'commerce_checkout_complete', 'plugin' => 'reaction rule', 'active' => TRUE);
$content['enabled']['rules'] = RulesPluginUI::overviewTable($conditions);
$content['enabled']['rules']['#empty'] = t('There are no active checkout completion rules.');
$content['disabled']['title']['#markup'] = '<h3>' . t('Disabled checkout completion rules') . '</h3>';
$conditions['active'] = FALSE;
$content['disabled']['rules'] = RulesPluginUI::overviewTable($conditions);
$content['disabled']['rules']['#empty'] = t('There are no disabled checkout rules.');
return $content;
}
<?php
// $Id$
/**
* @file
* Hooks provided by the Checkout module.
*/
/**
* Routes checkout/%commerce_order* to an alternate URL if necessary.
*
* The checkout module uses two URLs for the checkout form, displaying a form
* specific to the checkout page indicated by the URL.
*
* - checkout/%commerce_order: used for the first checkout page
* - checkout/%commerce_order/%commerce_checkout_page: used for all subsequent
* checkout pages
*
* The page callback for these URLs checks the user's access to the requested
* checkout page for the given order and to make sure the order has line items.
* After these two checks, it gives other modules an opportunity to evaluate the
* order and checkout page to see if any other redirection is necessary. This
* hook should not be used to alter the output at the actual checkout URL.
*
* @param $order
* The order object specified by the checkout URL.
* @param $checkout_page
* The checkout page array specified by the checkout URL.
*
* @see commerce_checkout_router()
*/
function hook_commerce_checkout_router($order, $checkout_page) {
global $user;
// Redirect anonymous users to a custom login page instructing them to login
// prior to checkout. (Note that Drupal Commerce does not require users to
// login prior to checkout as an e-commerce best practice.)
if (!$user->uid) {
drupal_set_message(t('Please login or create an account now to continue checkout.'));
drupal_goto('checkout/login/' . $order->order_id);
}
}
<?php
// $Id$
/**
* @file
* Checkout pane callback functions for the checkout module.
*/
/**
* Checkout pane callback: returns a pane allowing the customer to review the
* details of the order.
*/
function commerce_checkout_review_pane_checkout_form($form, &$form_state, $checkout_pane, $order) {
$pane_form = array();
// Otherwise add any enabled checkout panes that are visible on review.
$pane_form['review'] = array(
'#theme' => 'commerce_checkout_review',
'#data' => array(),
);
// Load all the checkout panes enabled for the review page.
$checkout_panes = commerce_checkout_panes(array('enabled' => TRUE, 'review' => TRUE));
// Loop through all the pages before the review page...
foreach (commerce_checkout_pages() as $page_id => $checkout_page) {
// Exit the loop once the review page is reached.
if ($page_id == 'review') {
break;
}
// Then loop through all the panes...
foreach (commerce_checkout_panes(array('page' => $page_id)) as $pane_id => $checkout_pane_local) {
// If the pane is on the review callback...
if ($callback = commerce_checkout_pane_callback($checkout_pane_local, 'review')) {
// Add a row for it in the review data.
$pane_form['review']['#data'][$pane_id] = array(
'title' => $checkout_pane_local['title'],
);
// Get the review data for this pane.
$pane_form['review']['#data'][$pane_id]['data'] = $callback($form, $form_state, $checkout_pane_local, $order);
}
}
}
return $pane_form;
}
/**
* Checkout pane callback: presents a completion message on the complete page.
*/
function commerce_checkout_completion_message_pane_checkout_form($form, &$form_state, $checkout_pane, $order) {
$pane_form = array();
$pane_form['message'] = array(
'#markup' => '<div class="checkout-completion-message">' . t('Thank you for your order. Your order number is @order-number.', array('@order-number' => $order->order_number)) . '</div>',
);
$pane_form['link'] = array(
'#markup' => '<div class="checkout-completion-link">' . l(t('Return to the front page.'), '<front>') . '</div>',
);
return $pane_form;
}
/* $Id$ */
fieldset.checkout-buttons {
text-align: right;
}
fieldset.checkout-buttons .form-button, fieldset.checkout-buttons .form-submit {
margin-left: 1em;
}
fieldset.checkout-buttons .checkout-cancel, fieldset.checkout-buttons .checkout-back {
float: left;
}
table.checkout-review {
width: auto;
margin-left: auto;
margin-right: auto;
min-width: 40%;
}
fieldset.checkout-buttons.review {
width: 40%;
margin-left: auto;
margin-right: auto;
}
table.checkout-review .pane-title td {
font-weight: bold;
text-align: center;
}
table.checkout-review tr.pane-data {
vertical-align: top;
}
table.checkout-review .pane-data-key {
font-weight: bold;
text-align: right;
white-space: nowrap;
padding-left: 3em;
width: 50%;
}
table.checkout-review .pane-data-value {
padding-right: 3em;
}
table.checkout-review .pane-data-full {
padding-left: 1em;
padding-right: 1em;
}
.checkout-processing {
padding-right: 18px !important;
background: url(../images/status-active.gif) right center no-repeat;
}
; $Id$
name = Checkout
description = Enable checkout as a multi-step form with customizable checkout pages.
package = Commerce
dependencies[] = commerce
dependencies[] = commerce_ui
dependencies[] = commerce_order
core = 7.x
; Simple tests
files[] = tests/commerce_checkout.test
<?php
// $Id$
/**
* Implements hook_schema().
*/
function commerce_checkout_schema() {
$schema = array();
$schema['commerce_checkout_pane'] = array(
'description' => 'Checkout pane configuration data.',
'fields' => array(
'pane_id' => array(
'description' => 'The machine readable name of the order state.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'page' => array(
'description' => 'The ID of the checkout page on which this pane appears.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '1',
),
'collapsible' => array(
'description' => 'Boolean value indicating whether or not the pane should appear collapsed.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
),
'collapsed' => array(
'description' => 'Boolean value indicating whether or not the pane should appear collapsed.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
),
'weight' => array(
'description' => 'The sorting weight of the status for lists of statuses.',
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
),
'enabled' => array(
'description' => 'Boolean value indicating whether or not the pane is enabled.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 1,
),
'review' => array(
'description' => 'Boolean value indicating whether or not the pane should appear on the checkout review.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 1,
),
),
'primary key' => array('pane_id'),
);
return $schema;
}
// $Id$
;(function($) {
/**
* Disable the continue buttons in the checkout process once they are clicked
* and provide a notification to the user.
*/
Drupal.behaviors.commerceCheckout = {
attach: function (context, settings) {
// When the buttons to move from page to page in the checkout process are
// clicked we disable them so they are not accidently clicked twice.
$('input.checkout-continue', context).click(function() {
var $this = $(this);
$this.clone().insertAfter(this).attr('disabled', true).after(Drupal.theme('checkoutProcessing'));
$this.hide();
});
}
}
/**
* Theme function for checkout button clicked processing indication.
*/
Drupal.theme.prototype.checkoutProcessing = function() {
return '<span class="checkout-processing"></span>';
}
})(jQuery);
<?php
// $Id$
/**
* @file
* Enable checkout as a multi-step form with customizable pages and a simple
* checkout pane API.
*/
/**
* Implements hook_menu().
*/
function commerce_checkout_menu() {
$items = array();
$items['checkout/%commerce_order'] = array(
'title' => 'Checkout',
'page callback' => 'commerce_checkout_router',
'page arguments' => array(1),
'access arguments' => array('access checkout'),
'type' => MENU_CALLBACK,
'file' => 'includes/commerce_checkout.pages.inc',
);
$items['checkout/%commerce_order/%commerce_checkout_page'] = array(
'title' => 'Checkout',
'page callback' => 'commerce_checkout_router',
'page arguments' => array(1, 2),
'access arguments' => array('access checkout'),
'type' => MENU_CALLBACK,
'file' => 'includes/commerce_checkout.pages.inc',
);
$items['admin/commerce/config/checkout'] = array(
'title' => 'Checkout settings',
'description' => 'Customize the checkout form and configure checkout rules.',
'page callback' => 'drupal_get_form',
'page arguments' => array('commerce_checkout_builder_form'),
'access arguments' => array('administer checkout'),
'type' => MENU_NORMAL_ITEM,
'file' => 'includes/commerce_checkout.admin.inc',
);
$items['admin/commerce/config/checkout/form'] = array(
'title' => 'Checkout form',
'description' => 'Build your checkout pages using module defined checkout form elements.',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
'file' => 'includes/commerce_checkout.admin.inc',
);
$items['admin/commerce/config/checkout/rules'] = array(
'title' => 'Checkout rules',
'description' => 'Enable and configure checkout completion rules.',
'page callback' => 'commerce_checkout_complete_rules',
'access arguments' => array('administer checkout'),
'type' => MENU_LOCAL_TASK,
'weight' => 5,
'file' => 'includes/commerce_checkout.admin.inc',
);
// Add the menu items for the various Rules forms.
$controller = new RulesUIController();
$items += $controller->config_menu('admin/commerce/config/checkout/rules');
$items['admin/commerce/config/checkout/rules/add'] = array(
'title' => 'Add a checkout rule',
'description' => 'Adds an additional checkout completion rule configuration.',
'page callback' => 'drupal_get_form',
'page arguments' => array('commerce_checkout_add_complete_rule_form', 'admin/commerce/config/checkout/rules'),
'access arguments' => array('administer checkout'),
'file path' => drupal_get_path('module', 'rules_admin'),
'file' => 'rules_admin.inc',
'type' => MENU_LOCAL_ACTION,
);
$items['admin/commerce/config/checkout/form/pane/%commerce_checkout_pane'] = array(
'title callback' => 'commerce_checkout_pane_settings_title',
'title arguments' => array(6),
'description' => 'Configure the settings for a checkout pane.',
'page callback' => 'drupal_get_form',
'page arguments' => array('commerce_checkout_pane_settings_form', 6),
'access arguments' => array('administer checkout'),
'file' => 'includes/commerce_checkout.admin.inc',
);
return $items;
}
/**
* Implements hook_permission().
*/
function commerce_checkout_permission() {
$permissions = array(
'administer checkout' => array(
'title' => t('Administer checkout'),
'description' => t('Access checkout for any order and configure checkout settings including the layout of the checkout form.'),
'restrict access' => TRUE,
),
'access checkout' => array(
'title' => t('Access checkout'),
'description' => t('Complete a purchase through the checkout form or be (or be redirected to login based on checkout settings).'),
),
);
return $permissions;
}
/**
* Implements hook_help().
*/
function commerce_checkout_help($path, $arg) {
switch ($path) {
case 'admin/commerce/config/checkout':
case 'admin/commerce/config/checkout/form':
return t('Use the table below to build your checkout form using the available checkout panes defined by modules enabled on your site. You may configure the checkout pane settings using the operations links below. You may also add additional pages to the checkout process using the tab above and edit them using the operations links below.');
case 'admin/commerce/config/checkout/rules':
return t('When a customer advances to the checkout completion page, rules reacting on the <em>Completing the checkout process</em> are evaluated. Default rules handle standard tasks like updating the order status, sending order e-mails, and creating accounts for anonymous users. You can edit these or add additional rules to customize your checkout workflow.');
}
}
/**
* Implements hook_theme().
*/
function commerce_checkout_theme() {
return array(
'commerce_checkout_builder_form' => array(
'render element' => 'form',
'file' => 'includes/commerce_checkout.admin.inc',
),
'commerce_checkout_review' => array(
'render element' => 'form',
'file' => 'includes/commerce_checkout.pages.inc',
),
);
}
/**
* Implements hook_entity_property_info_alter().
*
* Currently the Entity module does not add query callbacks for various core
* entity properties. Checkout depends on being able to load a user account by
* e-mail address, so we must alter in the callback until Entity is updated.
*
* @see http://drupal.org/node/1044312
*/
function commerce_checkout_entity_property_info_alter(&$info) {
// TODO: Switch to using entity_metadata_table_query() when
// http://drupal.org/node/1041240 is fixed.
$info['user']['properties']['mail']['query callback'] = 'commerce_checkout_entity_metadata_table_query';
}
/**
* Entity callback: queries the database for an entity by a given property.
*/
function commerce_checkout_entity_metadata_table_query($entity_type, $property, $value, $limit) {
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', $entity_type, '=')
->propertyCondition($property, $value, '=')
->range(0, $limit);
$result = $query->execute();
return !empty($result[$entity_type]) ? array_keys($result[$entity_type]) : array();
}
/**
* Implements hook_forms().
*
* Each page of the checkout form is actually a unique form as opposed to a
* single multistep form. To accommodate this, we map any form ID beginning with
* commerce_checkout_form_ to the same form builder assuming the remainder of
* the form ID matches a valid checkout page ID.
*/
function commerce_checkout_forms($form_id, $args) {
$forms = array();
// All checkout page forms should be built using the same function.
if (strpos($form_id, 'commerce_checkout_form_') === 0) {
// Ensure the checkout page is valid.
if (commerce_checkout_page_load(substr($form_id, 23))) {
$forms[$form_id] = array(
'callback' => 'commerce_checkout_form',
);
}
}
$forms['commerce_checkout_add_complete_rule_form'] = array(
'callback' => 'rules_admin_add_reaction_rule',
);
return $forms;
}
/**
* Implements hook_form_alter().
*/
function commerce_checkout_form_alter(&$form, &$form_values, $form_id) {
if (strpos($form_id, 'commerce_line_item_views_form_commerce_cart_form_') === 0) {
$form['actions']['checkout'] = array(
'#type' => 'submit',
'#value' => t('Checkout'),
'#weight' => 5,
'#access' => user_access('access checkout'),
'#submit' => array_merge($form['#submit'], array('commerce_checkout_line_item_views_form_submit')),
);
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* The Checkout module instantiates the Rules Admin rule configuration add form
* at a particular path in the Commerce IA. It uses its own form ID to do so and
* alters the form here to select the necessary Rules event.
*
* @see rules_admin_add_reaction_rule()
*/
function commerce_checkout_form_commerce_checkout_add_complete_rule_form_alter(&$form, &$form_state) {
unset($form['settings']['help']);
$form['settings']['event']['#type'] = 'value';
$form['settings']['event']['#value'] = 'commerce_checkout_complete';
$form['submit']['#suffix'] = l(t('Cancel'), 'admin/commerce/config/checkout/rules');
}
/**
* Implements hook_commerce_order_state_info().
*/
function commerce_checkout_commerce_order_state_info() {
$order_states = array();
$order_states['checkout'] = array(
'name' => 'checkout',
'title' => t('Checkout'),
'description' => t('Orders in this state have begun but not completed the checkout process.'),
'weight' => -3,
'default_status' => 'checkout_checkout',
);
return $order_states;
}
/**
* Implements hook_commerce_order_status_info().
*/
function commerce_checkout_commerce_order_status_info() {
$order_statuses = array();
// Create an order status to correspond with every checkout page.
foreach (commerce_checkout_pages() as $page_id => $checkout_page) {
$order_statuses['checkout_' . $page_id] = array(
'name' => 'checkout_' . $page_id,
'title' => t('Checkout: @page', array('@page' => $checkout_page['name'])),
'state' => 'checkout',
'checkout_page' => $page_id,
'cart' => $checkout_page['status_cart'],
'weight' => $checkout_page['weight'],
);
}
return $order_statuses;
}
/**
* Submit handler used to redirect to the checkout page.
*/
function commerce_checkout_line_item_views_form_submit($form, &$form_state) {
// Redirect to the checkout page if specified.
if ($form_state['clicked_button']['#value'] == $form['actions']['checkout']['#value']) {
$form_state['redirect'] = 'checkout';
}
}
/**
* Implements hook_commerce_checkout_page_info().
*/
function commerce_checkout_commerce_checkout_page_info() {
$checkout_pages = array();
// Define a primary checkout page as the first step.
$checkout_pages['checkout'] = array(
'title' => t('Checkout'),
'weight' => 0,
);
// Define a page for reviewing the data entered during checkout.
$checkout_pages['review'] = array(
'name' => t('Review'),
'title' => t('Review order'),
'help' => t('Review your order before continuing.'),
'weight' => 10,
);
// Define a page for checkout completion with no submit buttons on it.
$checkout_pages['complete'] = array(
'name' => t('Complete'),
'title' => t('Checkout complete'),
'weight' => 50,
'status_cart' => FALSE,
'buttons' => FALSE,
);
return $checkout_pages;
}
/**
* Implements hook_commerce_checkout_pane_info().
*/
function commerce_checkout_commerce_checkout_pane_info() {
$checkout_panes = array();
$checkout_panes['checkout_review'] = array(
'title' => t('Review'),
'file' => 'includes/commerce_checkout.checkout_pane.inc',
'base' => 'commerce_checkout_review_pane',
'page' => 'review',
);
$checkout_panes['checkout_completion_message'] = array(
'title' => t('Completion message'),
'file' => 'includes/commerce_checkout.checkout_pane.inc',
'base' => 'commerce_checkout_completion_message_pane',
'page' => 'complete',
);
return $checkout_panes;
}
/**
* Returns an array of checkout pages defined by enabled modules.
*
* @return
* An associative array of checkout page objects keyed by the page_id.
*/
function commerce_checkout_pages() {
$checkout_pages = &drupal_static(__FUNCTION__);
// If the checkout pages haven't been defined yet, do so now.
if (empty($checkout_pages)) {
$checkout_pages = module_invoke_all('commerce_checkout_page_info');
drupal_alter('commerce_checkout_page_info', $checkout_pages);
$count = 0;
foreach ($checkout_pages as $page_id => $checkout_page) {
$defaults = array(
'page_id' => $page_id,
'name' => $checkout_page['title'],
'title' => '',
'help' => '',
'status_cart' => TRUE,
'buttons' => TRUE,
'back_value' => t('Back'),
'submit_value' => t('Continue'),
'prev_page' => NULL,
'next_page' => NULL,
);
$checkout_pages[$page_id] += $defaults;
// Set a weight that preserves the order of 0 weighted pages.
if (empty($checkout_page['weight'])) {
$checkout_pages[$page_id]['weight'] = $count++ / count($checkout_pages);
}
}
uasort($checkout_pages, 'drupal_sort_weight');
// Initialize the previous and next pages.
$previous_page_id = NULL;
foreach ($checkout_pages as &$checkout_page) {
// Look for any checkout panes assigned to this page.
$checkout_panes = commerce_checkout_panes(array('page' => $checkout_page['page_id']));
// If this is the completion page or at least one pane was found...
if ($checkout_page['page_id'] == 'complete' || !empty($checkout_panes)) {
// If a page has been stored as the previous page...
if ($previous_page_id) {
// Set the current page's previous page and the previous page's next.
$checkout_page['prev_page'] = $previous_page_id;
$checkout_pages[$previous_page_id]['next_page'] = $checkout_page['page_id'];
}
// Set the current page as the previous page for the next iteration.
$previous_page_id = $checkout_page['page_id'];
}
}
}
return $checkout_pages;
}
/**
* Returns a checkout page object.
*
* @param $page_id
* The ID of the page to return.
*
* @return
* The fully loaded page object or FALSE if not found.
*/
function commerce_checkout_page_load($page_id) {
$checkout_pages = commerce_checkout_pages();
// If a page was specified that does not exist, return FALSE.
if (empty($checkout_pages[$page_id])) {
return FALSE;
}
// Otherwise, return the specified page.
return $checkout_pages[$page_id];
}
/**
* Return a filtered array of checkout pane objects.
*
* @param $conditions
* An array of conditions to filter the returned list by; for example, if you
* specify 'enabled' => TRUE in the array, then only checkout panes with an
* enabled value equal to TRUE would be included.
*
* @return
* The array of checkout pane objects, keyed by pane ID.
*/
function commerce_checkout_panes($conditions = array()) {
$checkout_panes = &drupal_static(__FUNCTION__);
// Cache the saved checkout pane data if it hasn't been loaded yet.
if (!isset($checkout_panes)) {
$saved_panes = db_query('SELECT * FROM {commerce_checkout_pane}')->fetchAllAssoc('pane_id', PDO::FETCH_ASSOC);
// Load panes defined by modules.
$checkout_panes = array();
foreach (module_implements('commerce_checkout_pane_info') as $module) {
foreach (module_invoke($module, 'commerce_checkout_pane_info') as $pane_id => $checkout_pane) {
$checkout_pane['pane_id'] = $pane_id;
$checkout_pane['module'] = $module;
$checkout_panes[$pane_id] = $checkout_pane;
}
}
drupal_alter('commerce_checkout_pane_info', $checkout_panes);
// Merge in defaults.
foreach ($checkout_panes as $pane_id => $checkout_pane) {
// Update the pane with saved data.
if (!empty($saved_panes[$pane_id])) {
$checkout_pane = array_merge($checkout_pane, $saved_panes[$pane_id]);
$checkout_pane['saved'] = TRUE;
}
// Set some defaults for the checkout pane.
$defaults = array(
'base' => $pane_id,
'name' => $checkout_pane['title'],
'page' => 'checkout',
'collapsible' => FALSE,
'collapsed' => FALSE,
'weight' => 0,
'enabled' => TRUE,
'review' => TRUE,
'callbacks' => array(),
);
$checkout_pane += $defaults;
// Merge in default callbacks.
foreach (array('settings_form', 'checkout_form', 'checkout_form_validate', 'checkout_form_submit', 'review') as $callback) {
if (!isset($checkout_pane['callbacks'][$callback])) {
$checkout_pane['callbacks'][$callback] = $checkout_pane['base'] . '_' . $callback;
}
}
$checkout_panes[$pane_id] = $checkout_pane;
}
// Sort the panes by their weight value.
uasort($checkout_panes, 'drupal_sort_weight');
}
// Apply conditions to the returned panes if specified.
if (!empty($conditions)) {
$matching_panes = array();
foreach ($checkout_panes as $pane_id => $checkout_pane) {
// Check the pane against the conditions array to determine whether to add
// it to the return array or not.
$valid = TRUE;
foreach ($conditions as $property => $value) {
// If the current value for the specified property on the pane does not
// match the filter value...
if ($checkout_pane[$property] != $value) {
// Do not add it to the temporary array.
$valid = FALSE;
}
}
if ($valid) {
$matching_panes[$pane_id] = $checkout_pane;
}
}
return $matching_panes;
}
return $checkout_panes;
}
/**
* Saves a checkout pane's configuration to the database.
*
* @param $checkout_pane
* The fully loaded checkout pane object.
*
* @return
* The return value of the call to drupal_write_record() to save the checkout
* pane, either FALSE on failure or SAVED_NEW or SAVED_UPDATED indicating
* the type of query performed to save the checkout pane.
*/
function commerce_checkout_pane_save($checkout_pane) {
return drupal_write_record('commerce_checkout_pane', $checkout_pane, !empty($checkout_pane['saved']) ? 'pane_id' : array());
}
/**
* Loads the data for a specific checkout pane.
*
* @param $pane_id
* The machine readable ID of the checkout pane.
*
* @return
* The requested checkout pane array or FALSE if not found.
*/
function commerce_checkout_pane_load($pane_id) {
// Loads the entire list of panes.
$checkout_panes = commerce_checkout_panes();
// Return FALSE if the pane does not exist.
if (empty($checkout_panes[$pane_id])) {
return FALSE;
}
return $checkout_panes[$pane_id];
}
/**
* Return the title of a checkout pane settings form page.
*
* @param $checkout_pane
* The checkout pane object represented on the settings form.
*/
function commerce_checkout_pane_settings_title($checkout_pane) {
return t("'@pane' checkout pane", array('@pane' => $checkout_pane['name']));
}
/**
* Resets a checkout pane by pane_id to its module defined defaults.
*/
function commerce_checkout_pane_reset($pane_id) {
db_delete('commerce_checkout_pane')
->condition('pane_id', $pane_id)
->execute();
}
/**
* Returns the specified callback for the given checkout pane if it's available,
* loading the checkout pane include file if specified.
*
* @param $checkout_pane
* The checkout pane array.
* @param $callback
* The callback function to return, one of:
* - settings_form
* - checkout_form
* - checkout_form_validate
* - checkout_form_submit
* - review
*
* @return
* A string containing the name of the callback function or FALSE if it could
* not be found.
*/
function commerce_checkout_pane_callback($checkout_pane, $callback) {
// Include the checkout pane file if specified.
if (!empty($checkout_pane['file'])) {
$parts = explode('.', $checkout_pane['file']);
module_load_include(array_pop($parts), $checkout_pane['module'], implode('.', $parts));
}
// If the specified callback function exists, return it.
if (!empty($checkout_pane['callbacks'][$callback]) &&
function_exists($checkout_pane['callbacks'][$callback])) {
return $checkout_pane['callbacks'][$callback];
}
// Otherwise return FALSE.
return FALSE;
}
/**
* Checks the current user's access to the specified checkout page and order.
*
* @param $order
* The fully loaded order object represented on the checkout form.
* @param $checkout_page
* The fully loaded checkout page object representing the current step in the
* checkout process.
* @param $account
* Alternately provide an account object whose access to check instead of the
* current user.
*
* @return
* TRUE or FALSE indicating access.
*/
function commerce_checkout_access($order, $checkout_page, $account = NULL) {
global $user;
// Default to the current user as the account whose access we're checking.
if (empty($account)) {
$account = clone($user);
}
// First, if this order doesn't belong to the account return FALSE.
if ($account->uid) {
if ($account->uid != $order->uid ) {
return FALSE;
}
}
elseif (empty($_SESSION['commerce_cart_completed_orders']) ||
!in_array($order->order_id, $_SESSION['commerce_cart_completed_orders'])) {
// Return FALSE if the order does have a uid.
if ($order->uid) {
return FALSE;
}
// And then return FALSE if the anonymous user's session doesn't specify
// this order ID.
if (empty($_SESSION['commerce_cart_orders']) || !in_array($order->order_id, $_SESSION['commerce_cart_orders'])) {
return FALSE;
}
}
// Load the order status object for the current order.
$order_status = commerce_order_status_load($order->status);
// If the order is not in a checkout status, return FALSE for any page but the
// completion page unless the order is still a shopping cart.
if ($order_status['state'] != 'checkout' && $checkout_page['page_id'] != 'complete') {
if ($order_status['state'] == 'cart') {
$checkout_pages = commerce_checkout_pages();
$first_page = key($checkout_pages);
if ($checkout_page['page_id'] != $first_page) {
return FALSE;
}
}
else {
return FALSE;
}
}
// If the order is still in checkout, only allow access to pages that it is
// currently on or has previously completed.
if ($order_status['state'] == 'checkout') {
$status_checkout_page = commerce_checkout_page_load($order_status['checkout_page']);
// However, if buttons aren't present on the status's checkout page, don't
// allow access unless the order status matches the page.
if (!$status_checkout_page['buttons'] && $status_checkout_page['page_id'] != $checkout_page['page_id']) {
// The exception here for the checkout completion page allows customers to
// access this page for any order status the order might be moved to when
// the order is completed.
if ($checkout_page['page_id'] != 'complete') {
return FALSE;
}
}
else {
// Compare the weights of the currently requested page against the weight
// of the order status's page and return FALSE if it's greater.
if ($checkout_page['weight'] > $status_checkout_page['weight']) {
return FALSE;
}
}
}
// We've now handled above cases where the user is trying to access a checkout
// page other than the completion page for an order that is not in a checkout
// status. We then handled cases where the user is trying to access any
// checkout page for orders in a checkout status. We now turn to cases where
// the user is accessing the complete page for any other order state.
elseif ($checkout_page['page_id'] == 'complete') {
// Don't allow completion page access for orders in the cart or canceled states.
if (in_array($order_status['state'], array('canceled', 'cart'))) {
return FALSE;
}
}
return TRUE;
}
/**
* Completes the checkout process for the given order.
*/
function commerce_checkout_complete($order) {
rules_invoke_all('commerce_checkout_complete', $order);
}
/**
* Creates a new user account with the specified parameters and notification.
*
* @param $name
* The new account username.
* @param $mail
* The e-mail address associated with the new account.
* @param $pass
* The new account password. If left empty, a password will be generated.
* @param $status
* TRUE or FALSE indicating the active / blocked status of the account.
* @param $notify
* TRUE or FALSE indicating whether or not to e-mail the new account details
* to the user.
*
* @return
* The account user object.
*/
function commerce_checkout_create_account($name, $mail, $pass, $status, $notify = FALSE) {
// Setup the account fields array and save it as a new user.
$fields = array(
'name' => $name,
'mail' => $mail,
'init' => $mail,
'pass' => empty($pass) ? user_password(variable_get('commerce_password_length', 8)) : $pass,
'roles' => array(),
'status' => $status,
);
$account = user_save('', $fields);
// Manually set the password so it appears in the e-mail.
$account->password = $fields['pass'];
// Send the customer their account details if enabled.
if ($notify) {
// Send the e-mail through the user module.
drupal_mail('user', 'register_no_approval_required', $mail, NULL, array('account' => $account), commerce_email_from());
}
return $account;
}
<?php
// $Id$
/**
* @file
* The page and form callbacks for use in the checkout form.
*/
/**
* Redirects invalid checkout attempts or displays the checkout form if valid.
*/
function commerce_checkout_router($order, $checkout_page = NULL) {
$checkout_pages = commerce_checkout_pages();
// If no checkout page is specified, default to the first one.
if (empty($checkout_page)) {
$checkout_page = reset($checkout_pages);
}
// If the user does not have access to checkout the order, return a 404. We
// could return a 403, but then the user would know they've identified a
// potentially valid checkout URL.
if (!commerce_checkout_access($order, $checkout_page)) {
return drupal_not_found();
}
// If there are no line items on the order, redirect away.
$wrapper = entity_metadata_wrapper('commerce_order', $order);
if (commerce_line_items_quantity($wrapper->commerce_line_items, 'product') == 0) {
drupal_goto('<front>');
}
// Prior to displaying the checkout form, allow other modules to route the
// checkout form.
module_invoke_all('commerce_checkout_router', $order, $checkout_page);
// Update the page title if specified.
if (!empty($checkout_page['title'])) {
drupal_set_title($checkout_page['title']);
}
return drupal_get_form('commerce_checkout_form_' . $checkout_page['page_id'], $order, $checkout_page);
}
/**
* Builds the checkout form based on the current step in checkout.
*
* @param $order
* The fully loaded order object being checked out.
* @param $checkout_page
* The checkout page object representing the current step in checkout.
*/
function commerce_checkout_form($form, &$form_state, $order, $checkout_page) {
global $user;
$form['#attached']['css'][] = drupal_get_path('module', 'commerce_checkout') .'/theme/commerce_checkout.css';
$form['#attached']['js'][] = drupal_get_path('module', 'commerce_checkout') . '/commerce_checkout.js';
$form_state['order'] = $order;
$form_state['checkout_page'] = $checkout_page;
$form_state['account'] = clone($user);
// Add any instructions for the checkout page.
$form['help'] = array(
'#markup' => '<div class="checkout-help">' . filter_xss($checkout_page['help']) . '</div>',
);
// Restore form errors.
if (!empty($form_state['storage']['errors'])) {
$form_errors = &drupal_static('form_set_error', array());
$form_errors = $form_state['storage']['errors'];
}
// Add any enabled checkout panes for this page to the form.
foreach (commerce_checkout_panes(array('enabled' => TRUE, 'page' => $checkout_page['page_id'])) as $pane_id => $checkout_pane) {
if ($callback = commerce_checkout_pane_callback($checkout_pane, 'checkout_form')) {
// Display error messages before the form.
if (!empty($form_state['storage']['messages'][$pane_id])) {
$form[$pane_id . '_errors'] = array(
'#markup' => $form_state['storage']['messages'][$pane_id],
);
}
// Create a fieldset for the pane and add the form data defined in the
// pane's form callback.
// $pane_values = isset($form_state['values'][$pane_id]) ? $form_state['values'][$pane_id] : array();
if ($pane_form = $callback($form, $form_state, $checkout_pane, $order)) {
$form[$pane_id] = $pane_form + array(
'#type' => 'fieldset',
'#title' => check_plain($checkout_pane['title']),
'#collapsible' => $checkout_pane['collapsible'],
'#collapsed' => $checkout_pane['collapsed'],
'#attributes' => array('class' => array($pane_id)),
'#tree' => TRUE,
);
}
}
}
// Only add buttons to the form if the checkout page hasn't disabled them.
if ($checkout_page['buttons']) {
$form['buttons'] = array(
'#type' => 'fieldset',
'#attributes' => array('class' => array('checkout-buttons')),
);
// Add the cancel or back button where appropriate. We define button level
// submit handlers because we're using hook_forms() to use this form builder
// function and to avoid issues if other modules implement button level submit
// handlers on these or custom checkout buttons.
if (!$checkout_page['prev_page'] && !empty($checkout_page['back_value'])) {
// Add an empty "Back" button value to avoid submission errors.
$form['buttons']['back'] = array(
'#type' => 'value',
'#value' => '',
);
// Store the cancel redirect in the form so modules can modify it easily.
$form_state['cancel_redirect'] = '<front>';
$form['buttons']['cancel'] = array(
'#type' => 'submit',
'#value' => t('Cancel'),
'#attributes' => array('class' => array('checkout-cancel')),
'#submit' => array('commerce_checkout_form_cancel_submit'),
'#limit_validation_errors' => array(),
);
}
elseif ($checkout_page['prev_page'] && !empty($checkout_page['back_value'])) {
$form['buttons']['back'] = array(
'#type' => 'submit',
'#value' => $checkout_page['back_value'],
'#attributes' => array('class' => array('checkout-back')),
'#submit' => array('commerce_checkout_form_back_submit'),
'#limit_validation_errors' => array(),
);
}
$form['buttons']['continue'] = array(
'#type' => 'submit',
'#value' => $checkout_page['submit_value'],
'#attributes' => array('class' => array('checkout-continue')),
'#submit' => array('commerce_checkout_form_submit'),
);
}
return $form;
}
/**
* Submit handler for the continue and back buttons of the checkout form.
*
* This function calls the validation function of each pane, followed by
* the submit function if the validation succeeded. As long as one pane
* fails validation, we then ask for the form to be rebuilt. Once all the panes
* are happy, we move on to the next page.
*/
function commerce_checkout_form_submit($form, &$form_state) {
global $user;
$checkout_page = $form_state['checkout_page'];
// Load a fresh copy of the order stored in the form.
$order = commerce_order_load($form_state['order']->order_id);
// Catch and clear already pushed messages.
$previous_messages = drupal_get_messages();
// Loop through the enabled checkout panes for the current page.
$form_validate = TRUE;
foreach (commerce_checkout_panes(array('enabled' => TRUE, 'page' => $checkout_page['page_id'])) as $pane_id => $checkout_pane) {
$validate = TRUE;
// If the pane has defined a checkout form validate handler...
if ($callback = commerce_checkout_pane_callback($checkout_pane, 'checkout_form_validate')) {
// Give it a chance to process the submitted data.
$validate = $callback($form, $form_state, $checkout_pane, $order);
}
// Catch and clear panes' messages.
$pane_messages = drupal_get_messages();
// Submit the pane if it validated.
if ($validate && $callback = commerce_checkout_pane_callback($checkout_pane, 'checkout_form_submit')) {
$callback($form, $form_state, $checkout_pane, $order);
}
// Generate status messages and theme them.
$_SESSION['messages'] = array_merge_recursive($pane_messages, drupal_get_messages());
$form_state['storage']['messages'][$pane_id] = theme('status_messages');
// A failed pane makes the form fail.
$form_validate &= $validate;
}
// Restore messages and form errors.
$_SESSION['messages'] = $previous_messages;
$form_errors = &drupal_static('form_set_error', array());
$form_state['storage']['errors'] = $form_errors;
$form_errors = array();
// Save the updated order object.
$order = commerce_order_save($order);
// If a pane failed validation or the form state has otherwise been altered to
// initiate a rebuild, return without moving to the next checkout page.
if (!$form_validate || $form_state['rebuild']) {
$form_state['rebuild'] = TRUE;
return;
}
// If the form was submitted via the continue button...
if ($form_state['values']['op'] == $form_state['values']['continue']) {
// If there is another checkout page...
if ($checkout_page['next_page']) {
// Update the order status to reflect the next checkout page.
$order = commerce_order_status_update($order, 'checkout_' . $checkout_page['next_page']);
// If it happens to be the complete page, process completion now.
if ($checkout_page['next_page'] == 'complete') {
commerce_checkout_complete($order);
}
// Redirect to the next checkout page.
$form_state['redirect'] = 'checkout/' . $order->order_id . '/' . $checkout_page['next_page'];
}
}
}
/**
* Special submit handler for the back button to avoid processing orders.
*/
function commerce_checkout_form_back_submit($form, &$form_state) {
// If there is a previous page...
if ($previous_page = commerce_checkout_page_load($form_state['checkout_page']['prev_page'])) {
$order = $form_state['order'];
// Move the form back to that page.
if ($previous_page['prev_page']) {
$form_state['redirect'] = 'checkout/' . $order->order_id . '/' . $previous_page['page_id'];
}
else {
$form_state['redirect'] = 'checkout/' . $order->order_id;
}
// Update the order status for the checkout step.
$form_state['order'] = commerce_order_status_update($order, 'checkout_' . $previous_page['page_id']);
}
}
/**
* Special submit handler for the cancel button to avoid processing orders.
*/
function commerce_checkout_form_cancel_submit($form, &$form_state) {
if ($form_state['order']) {
// TODO: Implement order comments to do this.
// commerce_order_comment_save($form_state['values']['order']->order_id, 0, t('Customer cancelled this order from the checkout form.'));
}
drupal_set_message(t('Checkout of your current order has been canceled and may be resumed when you are ready.'));
$form_state['redirect'] = $form_state['cancel_redirect'];
}
/**
* Themes the optional checkout review page data.
*/
function theme_commerce_checkout_review($variables) {
$form = $variables['form'];
// Turn the review data array into table rows.
$rows = array();
foreach ($form['#data'] as $pane_id => $data) {
// First add a row for the title.
$rows[] = array(
'data' => array(
array('data' => $data['title'], 'colspan' => 2),
),
'class' => array('pane-title', 'odd'),
);
// Next, add the data for this particular section.
if (is_array($data['data'])) {
// If it's an array, treat each key / value pair as a 2 column row.
foreach ($data['data'] as $key => $value) {
$rows[] = array(
'data' => array(
array('data' => $key .':', 'class' => array('pane-data-key')),
array('data' => $value, 'class' => array('pane-data-value')),
),
'class' => array('pane-data', 'even'),
);
}
}
else {
// Otherwise treat it as a block of text in its own row.
$rows[] = array(
'data' => array(
array('data' => $data['data'], 'colspan' => 2, 'class' => array('pane-data-full')),
),
'class' => array('pane-data', 'even'),
);
}
}
return theme('table', array('rows' => $rows, 'attributes' => array('class' => array('checkout-review'))));
}
<?php
// $Id$
/**
* @file
* Rules integration for the checkout process.
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_event_info().
*/
function commerce_checkout_rules_event_info() {
$events = array();
$events['commerce_checkout_complete'] = array(
'label' => t('Completing the checkout process'),
'group' => t('Commerce Checkout'),
'variables' => array(
'order' => array(
'type' => 'commerce_order',
'label' => t('Completed order', array(), array('context' => 'a drupal commerce order')),
),
),
'access callback' => 'commerce_order_rules_access',
);
return $events;
}
/**
* Implements hook_rules_action_info().
*/
function commerce_checkout_rules_action_info() {
$actions = array();
$actions['send_account_email'] = array(
'label' => t('Send account e-mail'),
'parameter' => array(
'account' => array(
'type' => 'user',
'label' => t('Account'),
),
'email_type' => array(
'type' => 'text',
'label' => t('E-mail type'),
'description' => t("Select the e-mail based on your site's account settings to send to the user."),
'options list' => 'commerce_checkout_account_email_options',
),
),
'group' => t('User'),
'base' => 'commerce_checkout_action_send_account_email',
'access callback' => 'rules_system_integration_access',
);
return $actions;
}
/**
* Returns the account e-mail types from the User module.
*
* @see _user_mail_notify()
*/
function commerce_checkout_account_email_options() {
return array(
'register_admin_created' => t('Welcome (new user created by administrator)'),
'register_no_approval_required' => t('Welcome (no approval required)'),
'register_pending_approval' => t('Welcome (awaiting approval)'),
'password_reset' => t('Password recovery'),
'status_activated' => t('Account activation'),
'status_blocked' => t('Account blocked'),
'cancel_confirm' => t('Account cancellation confirmation'),
'status_canceled' => t('Account canceled'),
);
}
/**
* Action callback: sends a selected account e-mail.
*/
function commerce_checkout_action_send_account_email($account, $email_type) {
// If we received an authenticated user account...
if (!empty($account)) {
$types = commerce_checkout_account_email_options();
// Attempt to send the account e-mail.
$result = _user_mail_notify($email_type, $account);
// Log the success or failure.
if ($result) {
watchdog('rules', '%type e-mail sent to %recipient.', array('%type' => $types[$email_type], '%recipient' => $account->mail));
}
else {
watchdog('rules', 'Failed to send %type e-mail to %recipient.', array('%type' => $types[$email_type], '%recipient' => $account->mail));
}
}
}
/**
* Implements hook_rules_condition_info().
*/
function commerce_checkout_rules_condition_info() {
$conditions = array();
module_load_include('inc', 'rules', 'data.rules');
$conditions['entity_exists'] = array(
'label' => t('Entity exists by property'),
'parameter' => array(
'type' => array(
'type' => 'text',
'label' => t('Entity type'),
'options list' => 'commerce_checkout_entity_type_options',
'description' => t('Specifies the type of the entity that should be fetched.'),
'restriction' => 'input',
),
'property' => array(
'type' => 'text',
'label' => t('Property'),
'description' => t('The property by which the entity is to be selected.'),
'restriction' => 'input',
),
'value' => array(
'type' => 'text',
'label' => t('Value'),
'description' => t('The property value of the entity to be fetched.'),
),
),
'group' => t('Entities'),
'base' => 'commerce_checkout_condition_entity_exists',
'callbacks' => array(
'form_alter' => 'rules_action_type_form_alter',
),
);
return $conditions;
}
/**
* Returns options containing entity types having the given key set in the info.
*/
function commerce_checkout_entity_type_options() {
$types = array();
foreach (entity_get_info() as $type => $entity_info) {
$types[$type] = $entity_info['label'];
}
return $types;
}
/**
* Condition callback: checks to see if an entity exists with the matching
* property value.
*/
function commerce_checkout_condition_entity_exists($type, $property, $value) {
// TODO: This can be replaced with a call to entity_metadata_table_query()
// when http://drupal.org/node/1041240 gets fixed.
$result = commerce_checkout_entity_metadata_table_query($type, $property, $value, 1);
return !empty($result);
}
/**
* @}
*/
<?php
// $Id$
/**
* @file
* Default rules configuration for Order.
*/
/**
* Implements hook_default_rules_configuration().
*/
function commerce_checkout_default_rules_configuration() {
// Store the customer profile entity info for use in default rules.
$customer_profile_entity_info = entity_get_info('commerce_customer_profile');
$rules = array();
// Add a reaction rule to update an order to the default status of the pending
// order status upon checkout completion.
$rule = rules_reaction_rule();
$rule->label = t('Update the order status on checkout completion');
$rule->active = TRUE;
$rule
->event('commerce_checkout_complete')
->action('commerce_order_update_state', array(
'order:select' => 'order',
'order_state' => 'pending',
));
$rules['commerce_checkout_order_status_update'] = $rule;
// Add a reaction rule to assign an oder to a pre-existing user account if an
// existing e-mail address is used in checkout.
$rule = rules_reaction_rule();
$rule->label = t('Assign an anonymous order to a pre-existing user');
$rule->active = TRUE;
$rule
->event('commerce_checkout_complete')
->condition('entity_exists', array(
'type' => 'user',
'property' => 'mail',
'value:select' => 'order:mail',
))
->condition('data_is', array(
'data:select' => 'order:type',
'op' => '==',
'value' => 'commerce_order',
))
->action('entity_query', array(
'type' => 'user',
'property' => 'mail',
'value:select' => 'order:mail',
'limit' => 1,
'entity_fetched:label' => t('Fetched account'),
'entity_fetched:var' => 'account_fetched',
));
// Build a loop that updates the order and customer profile uids with the uid
// from the fetched user account.
$loop = rules_loop(array(
'list:select' => 'account-fetched',
'item:var' => 'list_item',
'item:label' => t('Current list item'),
'item:type' => 'user',
))
->action('data_set', array(
'data:select' => 'order:uid',
'value:select' => 'list-item:uid',
));
// Accommodate any profile types referenced by the order.
foreach ($customer_profile_entity_info['bundles'] as $type => $data) {
$loop
->action('data_set', array(
'data:select' => 'order:' . strtr('commerce-customer-' . $type, '_', '-') . ':uid',
'value:select' => 'list-item:uid',
));
}
// Add the loop to the rule as an action.
$rule->action($loop);
// Adjust the weight so this rule executes after the order status has been
// updated.
$rule->weight = 1;
$rules['commerce_checkout_order_convert'] = $rule;
// Add a reaction rule that creates a new user account during checkout
// completion if the customer specified a non-existent e-mail address. The
// default functionality is to create an active user account with the e-mail
// for administrator created accounts and will always assume the need for
// e-mail verification for setting a password.
$rule = rules_reaction_rule();
$rule->label = t('Create a new account for an anonymous order');
// TODO: Default this to TRUE once http://drupal.org/node/1044342 is fixed.
$rule->active = FALSE;
$rule
->event('commerce_checkout_complete')
->condition(rules_condition('entity_exists', array(
'type' => 'user',
'property' => 'mail',
'value:select' => 'order:mail',
))->negate())
->condition('data_is', array(
'data:select' => 'order:type',
'op' => '==',
'value' => 'commerce_order',
))
->action('entity_create', array(
'type' => 'user',
'param_name:select' => 'order:mail',
'param_mail:select' => 'order:mail',
'entity_created:label' => t('Created account'),
'entity_created:var' => 'account_created',
))
->action('data_set', array(
'data:select' => 'account-created:status',
'value' => 1,
))
->action('entity_save', array(
'data:select' => 'account-created',
'immediate' => 1,
))
->action('entity_query', array(
'type' => 'user',
'property' => 'mail',
'value:select' => 'order:mail',
'limit' => 1,
'entity_fetched:label' => t('Fetched account'),
'entity_fetched:var' => 'account_fetched',
));
// Build a loop that send the account notification e-mail and updates the
// order and customer profile uids with the uid from the fetched user account.
$loop = rules_loop(array(
'list:select' => 'account-fetched',
'item:var' => 'list_item',
'item:label' => t('Current list item'),
'item:type' => 'user',
))
->action('send_account_email', array(
'account:select' => 'list-item',
'email_type' => 'register_admin_created',
))
->action('data_set', array(
'data:select' => 'order:uid',
'value:select' => 'list-item:uid',
));
// Accommodate any profile types referenced by the order.
foreach ($customer_profile_entity_info['bundles'] as $type => $data) {
$loop
->action('data_set', array(
'data:select' => 'order:' . strtr('commerce-customer-' . $type, '_', '-') . ':uid',
'value:select' => 'list-item:uid',
));
}
// Add the loop to the rule as an action.
$rule->action($loop);
// Adjust the weight so this rule executes after the one checking for a pre-
// existing user account.
$rule->weight = 2;
$rules['commerce_checkout_new_account'] = $rule;
// Add a reaction rule to send order e-mail upon checkout completion.
$rule = rules_reaction_rule();
$rule->label = t('Send an order notification e-mail');
$rule->active = TRUE;
$rule
->event('commerce_checkout_complete')
->action('mail', array(
'to:select' => 'order:mail',
'subject' => t('Order [order:order-number] at [site:name]'),
'message' => t("Thanks for your order [order:order-number] at [site:name].\n\nIf this is your first order with us, you will receive a separate e-mail with login instructions. You can view your order history with us at any time by logging into our website at:\n\n[site:login-url]\n\nYou can find the status of your current order at:\n\n[site:url]user/[order:uid]/orders/[order:order-id]\n\nPlease contact us if you have any questions about your order."),
'from' => '',
));
// Adjust the weight so this rule executes after the order has been updated to
// the proper user account.
$rule->weight = 4;
$rules['commerce_checkout_order_email'] = $rule;
return $rules;
}
<?php
// $Id$
/**
* @file
* Functional tests for the commerce checkout module.
*/
/**
* Test checkout process.
*/
class CommerceCheckoutTestProcess extends CommerceBaseTestCase {
/**
* Normal user (without admin or store permissions) for testing.
*/
protected $normal_user;
/**
* Checkout url.
*/
protected $cart_url;
public static function getInfo() {
return array(
'name' => 'Commerce checkout process functional tests',
'description' => 'Tests the checkout process.',
'group' => 'Drupal Commerce',
);
}
/**
* Implementation of setUp().
*/
function setUp() {
$modules = parent::setUpHelper('all');
parent::setUp($modules);
// User creation for different operations.
$this->site_admin = $this->createSiteAdmin();
$this->store_admin = $this->createStoreAdmin();
$this->normal_user = $this->drupalCreateUser(array('access checkout'));
// The rule that sends a mail after checkout completion should be disabled
// as it returns an error caused by how mail messages are stored.
$rules_config = rules_config_load('commerce_checkout_order_email');
$rules_config->active = FALSE;
$rules_config->save();
// Get the checkout url.
$links = commerce_line_item_summary_links();
$this->checkout_url = $links['checkout']['href'];
}
protected function generateAddressInformation() {
$address_info['name_line'] = $this->randomName();
$address_info['thoroughfare'] = $this->randomName();
$address_info['locality'] = $this->randomName();
$address_info['postal_code'] = rand(00000, 99999);
return $address_info;
}
/**
* Test changing the weight and page of a pane.
*/
public function testCommerceCheckoutPanesForm() {
// Log in as store admin
$this->drupalLogin($this->store_admin);
// Access to the config page for checkout forms
$this->drupalGet('admin/commerce/config/checkout/form');
$this->assertResponse(200);
// Modify weight of the panes
$this->drupalPost('admin/commerce/config/checkout/form', array('panes[cart_contents][weight]'=> 1), t('Save configuration'));
$this->assertOptionSelected('edit-panes-cart-contents-weight', 1, t('Pane weight changed'));
// Change one pane to other page
$this->drupalPost('admin/commerce/config/checkout/form', array('panes[checkout_review][page]'=> 'disabled'), t('Save configuration'));
$this->assertOptionSelected('edit-panes-checkout-review-page', 'disabled', t('Pane page changed'));
}
/**
* Test the checkout process using an authenticated user.
*/
public function testCommerceCheckoutProcessAuthenticatedUser() {
// Log in as normal user.
$this->drupalLogin($this->normal_user);
// Order creation, in cart status.
$this->order = $this->createDummyOrder($this->normal_user->uid);
// Access to checkout page.
$this->drupalGet($this->checkout_url);
// Check if the page resolves and if the default panes are present
$this->assertResponse(200);
$this->assertTitle(t('Checkout') . ' | Drupal', t('Title of the checkout phase checked'));
$this->assertText(t('Shopping cart contents'), t('Shopping cart contents pane is present'));
$this->assertText(t('Billing information'), t('Billing information pane is present'));
// We are testing with authenticated user, so no account information
// should appear
$this->assertNoText(t('Account information'), t('Account information pane is not present'));
// Generate random information, as city, postal code, etc.
$address_info = $this->generateAddressInformation();
// Fill in the billing address information
$billing_pane = $this->xpath("//select[starts-with(@name, 'customer_profile_billing[commerce_billing_address]')]");
$this->drupalPostAJAX(NULL, array((string) $billing_pane[0]['name'] => 'US'), (string) $billing_pane[0]['name']);
// Check if the country has been selected correctly, this uses XPath as the
// ajax call replaces the element and the id may change
$this->assertFieldByXPath("//select[starts-with(@id, 'edit-customer-profile-billing-commerce-billing-address')]//option[@selected='selected']", 'US', t('Country selected'));
// Fill in the required information for billing pane, with a random State.
$info = array(
'customer_profile_billing[commerce_billing_address][und][0][name_line]' => $address_info['name_line'],
'customer_profile_billing[commerce_billing_address][und][0][thoroughfare]' => $address_info['thoroughfare'],
'customer_profile_billing[commerce_billing_address][und][0][locality]' => $address_info['locality'],
'customer_profile_billing[commerce_billing_address][und][0][administrative_area]' => 'KY',
'customer_profile_billing[commerce_billing_address][und][0][postal_code]' => $address_info['postal_code'],
);
$this->drupalPost(NULL, $info, t('Continue'));
// Check for default panes and information in this checkout phase.
$this->assertTitle(t('Review order') . ' | Drupal', t('Title of the checkout phase checked'));
$this->assertText($address_info['name_line'], t('Check billing information'));
$this->assertText($address_info['thoroughfare'], t('Check billing information'));
$this->assertText($address_info['locality'], t('Check billing information'));
$this->assertText(trim($address_info['postal_code']), t('Check billing information'));
$this->assertText('United States', t('Check billing information'));
$this->assertText('Example payment', t('Check example payment method'));
// Load the order to check the status.
$order = commerce_order_load($this->order->order_id);
// At this point we should be in Checkout Review.
$this->assertEqual($order->status, 'checkout_review', t('Order status is Checkout Review in the review phase.'));
// Test the back & continue buttons.
$this->drupalPost(NULL, array(), t('Back'));
$this->assertTitle(t('Checkout') . ' | Drupal', t('Title of the checkout phase checked'));
$this->drupalPost(NULL, array(), t('Continue'));
$this->assertTitle(t('Review order') . ' | Drupal', t('Title of the checkout phase checked'));
// Finish checkout process
$this->drupalPost(NULL, array('commerce_payment[payment_details][name]' => 'Example payment method'), t('Continue'));
// Reload the order directly from db to update status.
$order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE);
// Order status should be pending when completing checkout process.
$this->assertEqual(reset($order)->status, 'pending', t('Order status is Pending after completing checkout.'));
// Check if the completion message has been displayed.
$this->assertTitle(t('Checkout complete') . ' | Drupal', t('Title of the checkout phase checked'));
$this->assertRaw('<div class="checkout-completion-message">' . t('Thank you for your order. Your order number is @order-number.', array('@order-number' => $this->order->order_number)) . '</div>', t('Completion message displayed'));
}
/**
* Test the checkout process with anonymous user.
*/
public function testCommerceCheckoutProcessAnonymousUser() {
// Login as admin user to grat permissions and do set up.
$this->drupalLogin($this->site_admin);
// Enable the helper module.
module_enable(array('commerce_checkout_test'));
menu_rebuild();
// Grant Anonymous user to use checkout.
$this->assertTrue(module_exists('commerce_checkout_test'), t('Test Commerce Checkout module enabled.'));
user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array(
'access checkout' => TRUE,
));
// Log out to continue the process as anonymous.
$this->drupalLogout();
// Order creation, in cart status.
$this->order = $this->createDummyOrder(0);
// Fill the session cart variables with the order id through the helper
// callback.
$this->drupalGet('commerce-checkout-test-cart-session-anonymous/'.$this->order->order_id);
// Access to checkout page.
$this->drupalGet($this->checkout_url);
// Check if the page resolves and if the default panes are present
$this->assertResponse(200);
$this->assertTitle(t('Checkout') . ' | Drupal', t('Title of the checkout phase checked'));
$this->assertText(t('Shopping cart contents'), t('Shopping cart contents pane is present'));
$this->assertText(t('Billing information'), t('Billing information pane is present'));
$this->assertText(t('Account information'), t('Account information pane is present'));
// Generate random information, as user name, city, etc.
$user_mail = $this->randomName() . '@example.com';
$address_info = $this->generateAddressInformation();
// Fill in the billing address information
$billing_pane = $this->xpath("//select[starts-with(@name, 'customer_profile_billing[commerce_billing_address]')]");
$this->drupalPostAJAX(NULL, array((string) $billing_pane[0]['name'] => 'US'), (string) $billing_pane[0]['name']);
// Check if the country has been selected correctly, this uses XPath as the
// ajax call replaces the element and the id may change
$this->assertFieldByXPath("//select[starts-with(@id, 'edit-customer-profile-billing-commerce-billing-address')]//option[@selected='selected']", 'US', t('Country selected'));
// Fill in the required information for billing pane, with a random State.
$info = array(
'customer_profile_billing[commerce_billing_address][und][0][name_line]' => $address_info['name_line'],
'customer_profile_billing[commerce_billing_address][und][0][thoroughfare]' => $address_info['thoroughfare'],
'customer_profile_billing[commerce_billing_address][und][0][locality]' => $address_info['locality'],
'customer_profile_billing[commerce_billing_address][und][0][administrative_area]' => 'KY',
'customer_profile_billing[commerce_billing_address][und][0][postal_code]' => $address_info['postal_code'],
);
// Also add the mail for the account pane.
$info+= array(
'account[login][mail]' => $user_mail,
);
// Go to the next checkout step with the required information.
$this->drupalPost(NULL, $info, t('Continue'));
// Check for default panes and information in this checkout phase.
$this->assertTitle(t('Review order') . ' | Drupal', t('Title of the checkout phase checked'));
$this->assertText($address_info['name_line'], t('Check billing information'));
$this->assertText($address_info['thoroughfare'], t('Check billing information'));
$this->assertText($address_info['locality'], t('Check billing information'));
$this->assertText(trim($address_info['postal_code']), t('Check billing information'));
$this->assertText('United States', t('Check billing information'));
$this->assertText($user_mail, t('Check account information'));
$this->assertText('Example payment', t('Check example payment method'));
// Load the order to check the status.
$order = commerce_order_load($this->order->order_id);
// At this point we should be in Checkout Review.
$this->assertEqual($order->status, 'checkout_review', t('Order status is Checkout Review in the review phase.'));
// Finish checkout process
$this->drupalPost(NULL, array('commerce_payment[payment_details][name]' => 'Example payment method'), t('Continue'));
// Reload the order directly from db to update status.
$order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE);
// Order status should be pending when completing checkout process.
$this->assertEqual(reset($order)->status, 'pending', t('Order status is Pending after completing checkout.'));
// Check if the completion message has been displayed.
$this->assertTitle(t('Checkout complete') . ' | Drupal', t('Title of the checkout phase checked'));
$this->assertRaw('<div class="checkout-completion-message">' . t('Thank you for your order. Your order number is @order-number.', array('@order-number' => $this->order->order_number)) . '</div>', t('Completion message displayed'));
}
}
/* $Id$ */
#panes td.page {
font-weight: bold;
}
#panes tr.page-header {
vertical-align: bottom;
font-weight: bold;
background-color: #FFF;
}
#panes tr.page-message {
font-weight: normal;
font-style: italic;
color: #999;
}
#panes tr.page-populated {
display: none;
}
// $Id$
(function ($) {
/**
* Add functionality to the checkout panes tabledrag enhanced table.
*
* This code is almost an exact copy of the code used for the block region and
* weight settings form.
*/
Drupal.behaviors.paneDrag = {
attach: function (context, settings) {
// tableDrag is required for this behavior.
if (typeof Drupal.tableDrag == 'undefined') {
return;
}
var table = $('table#panes');
var tableDrag = Drupal.tableDrag.panes; // Get the blocks tableDrag object.
// Add a handler for when a row is swapped, update empty regions.
tableDrag.row.prototype.onSwap = function(swappedRow) {
checkEmptyPages(table, this);
};
// A custom message for the panes page specifically.
Drupal.theme.tableDragChangedWarning = function () {
return '<div class="messages warning">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t("Changes to the checkout panes will not be saved until the <em>Save configuration</em> button is clicked.") + '</div>';
};
// Add a handler so when a row is dropped, update fields dropped into new regions.
tableDrag.onDrop = function() {
dragObject = this;
var pageRow = $(dragObject.rowObject.element).prev('tr').get(0);
var pageName = pageRow.className.replace(/([^ ]+[ ]+)*page-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
var pageField = $('select.checkout-pane-page', dragObject.rowObject.element);
if ($(dragObject.rowObject.element).prev('tr').is('.page-message')) {
var weightField = $('select.checkout-pane-weight', dragObject.rowObject.element);
var oldPageName = weightField[0].className.replace(/([^ ]+[ ]+)*checkout-pane-weight-([^ ]+)([ ]+[^ ]+)*/, '$2');
if (!pageField.is('.checkout-pane-page-'+ pageName)) {
pageField.removeClass('checkout-pane-page-' + oldPageName).addClass('checkout-pane-page-' + pageName);
weightField.removeClass('checkout-pane-weight-' + oldPageName).addClass('checkout-pane-weight-' + pageName);
pageField.val(pageName);
}
}
};
var checkEmptyPages = function(table, rowObject) {
$('tr.page-message', table).each(function() {
// If the dragged row is in this region, but above the message row, swap it down one space.
if ($(this).prev('tr').get(0) == rowObject.element) {
// Prevent a recursion problem when using the keyboard to move rows up.
if ((rowObject.method != 'keyboard' || rowObject.direction == 'down')) {
rowObject.swap('after', this);
}
}
// This region has become empty
if ($(this).next('tr').is(':not(.draggable)') || $(this).next('tr').size() == 0) {
$(this).removeClass('page-populated').addClass('page-empty');
}
// This region has become populated.
else if ($(this).is('.page-empty')) {
$(this).removeClass('page-empty').addClass('page-populated');
}
});
};
}
};
})(jQuery);
; $Id$
name = Commerce Checkout test
description = Provides test helper callbacks and functions for Commerce Checkout tests.
package = Testing
version = VERSION
core = 7.x
hidden = TRUE
<?php
// $Id$
/**
* @file
* Helper functions and callbacks for checkout tests.
*/
/**
* Implements hook_menu().
*/
function commerce_checkout_test_menu() {
$items = array();
$items['commerce-checkout-test-cart-session-anonymous/%'] = array(
'title' => 'Place session variables to anonymous cart',
'page callback' => 'commerce_checkout_test_cart_session_anonymous',
'page arguments' => array(1),
'access callback' => 'user_is_anonymous',
'type' => MENU_CALLBACK,
);
return $items;
}
/**
*
*/
function commerce_checkout_test_cart_session_anonymous($order_id) {
if (is_numeric($order_id)) {
$_SESSION['commerce_cart_orders'] = array($order_id);
}
return t('Commerce cart order session variable set.');
}
<?php
// $Id$
/**
* @file
* Hooks provided by the Customer module.
*/
/**
* Allows you to prepare customer profile data before it is saved.
*
* @param $profile
* The customer profile object to be saved.
*
* @see rules_invoke_all()
*/
function hook_commerce_customer_profile_presave(&$profile) {
// No example.
}
<?php
// $Id$
/**
* @file
* Checkout pane callback functions for the customer module.
*/
/**
* Checkout pane callback: returns the customer profile pane's settings form.
*/
function commerce_customer_profile_pane_settings_form($checkout_pane) {
$form = array();
// Extract the type of profile represented by this pane from its ID.
$type = substr($checkout_pane['pane_id'], 17);
// Build an options array of customer profile reference fields available for
// the data from this customer profile pane.
$options = array('' => t('- None -'));
foreach (commerce_info_fields('commerce_customer_profile_reference', 'commerce_order') as $name => $field) {
if ($type == $field['settings']['profile_type']) {
$instance = field_info_instance('commerce_order', $name, 'commerce_order');
$options[$name] = check_plain($instance['label']);
}
}
$form['commerce_' . $checkout_pane['pane_id'] . '_field'] = array(
'#type' => 'select',
'#title' => t('Related customer profile reference field'),
'#description' => t('Specify the customer profile reference field on the order to populate with profile data from this checkout pane.'),
'#options' => $options,
'#default_value' => variable_get('commerce_' . $checkout_pane['pane_id'] . '_field', ''),
);
return $form;
}
/**
* Checkout pane callback: returns a customer profile edit form.
*/
function commerce_customer_profile_pane_checkout_form($form, &$form_state, $checkout_pane, $order) {
$pane_form = array('#parents' => array($checkout_pane['pane_id']));
// Extract the type of profile represented by this pane from its ID.
$type = substr($checkout_pane['pane_id'], 17);
// Find the referenced profile using the related reference field...
$wrapper = entity_metadata_wrapper('commerce_order', $order);
$profile = NULL;
// If the associated order field has been set...
if ($field_name = variable_get('commerce_' . $checkout_pane['pane_id'] . '_field', '')) {
$profile = $wrapper->{$field_name}->value();
}
else {
// Or try the association stored in the order's data array if no field is set.
if (!empty($order->data['profiles'][$checkout_pane['pane_id']])) {
$profile = commerce_customer_profile_load($order->data['profiles'][$checkout_pane['pane_id']]);
}
}
// Create a new profile of the specified type if it hasn't already been made.
if (empty($profile)) {
$profile = commerce_customer_profile_new($type, $order->uid);
}
$pane_form['customer_profile'] = array(
'#type' => 'value',
'#value' => $profile,
);
// Add the field widgets for the profile.
field_attach_form('commerce_customer_profile', $profile, $pane_form, $form_state);
// Tweak the form to remove the fieldset from address fields.
$field_name = 'commerce_' . $type . '_address';
$language = $pane_form[$field_name]['#language'];
foreach (element_children($pane_form[$field_name][$language]) as $delta) {
$pane_form[$field_name][$language][$delta]['#type'] = 'container';
}
return $pane_form;
}
/**
* Checkout pane callback: validates a customer profile edit form.
*/
function commerce_customer_profile_pane_checkout_form_validate($form, &$form_state, $checkout_pane, $order) {
$profile = $form_state['values'][$checkout_pane['pane_id']]['customer_profile'];
// Notify field widgets to validate their data.
field_attach_form_validate('commerce_customer_profile', $profile, $form[$checkout_pane['pane_id']], $form_state);
return TRUE;
}
/**
* Checkout pane callback: submits a customer profile edit form.
*/
function commerce_customer_profile_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {
$profile = $form_state['values'][$checkout_pane['pane_id']]['customer_profile'];
// Ensure the profile is active.
$profile->status = TRUE;
// Set the profile's uid if it's being created at this time.
if (empty($profile->profile_id)) {
$profile->uid = $order->uid;
}
// Notify field widgets.
field_attach_submit('commerce_customer_profile', $profile, $form[$checkout_pane['pane_id']], $form_state);
// Save the profile.
commerce_customer_profile_save($profile);
// Store the profile ID for the related field as specified on the settings form.
$wrapper = entity_metadata_wrapper('commerce_order', $order);
if ($field_name = variable_get('commerce_' . $checkout_pane['pane_id'] . '_field', '')) {
$wrapper->{$field_name} = $profile;
}
else {
// Or make the association in the order's data array if no field was found.
$order->data['profiles'][$checkout_pane['pane_id']] = $profile->profile_id;
}
}
/**
* Checkout pane callback: returns the cart contents review data for the
* Review checkout pane.
*/
function commerce_customer_profile_pane_review($form, $form_state, $checkout_pane, $order) {
// Load the profile based on the related customer profile reference field...
if ($field_name = variable_get('commerce_' . $checkout_pane['pane_id'] . '_field', '')) {
$profile = entity_metadata_wrapper('commerce_order', $order)->{$field_name}->value();
}
else {
// Or use the association stored in the order's data array if no field is set.
$profile = commerce_customer_profile_load($order->data['profiles'][$checkout_pane['pane_id']]);
}
$content = commerce_customer_profile_build_content($profile, 'customer');
return drupal_render($content);
}
; $Id$
name = Customer
description = Defines the Customer entity with Address Field integration.
package = Commerce
dependencies[] = addressfield
dependencies[] = commerce
core = 7.x
; Module includes
files[] = includes/commerce_customer_profile.controller.inc
; Views handlers
files[] = includes/views/handlers/commerce_customer_handler_field_customer_profile.inc
files[] = includes/views/handlers/commerce_customer_handler_field_customer_profile_link.inc
files[] = includes/views/handlers/commerce_customer_handler_field_customer_profile_link_delete.inc
files[] = includes/views/handlers/commerce_customer_handler_field_customer_profile_link_edit.inc
files[] = includes/views/handlers/commerce_customer_handler_field_customer_profile_type.inc
files[] = includes/views/handlers/commerce_customer_handler_filter_customer_profile_type.inc
; Simple tests
; files[] = tests/commerce_customer.test
<?php
// $Id$
/**
* @file
* Provides metadata for the customer profile entity.
*/
/**
* Implements hook_entity_property_info().
*/
function commerce_customer_entity_property_info() {
$info = array();
// Add meta-data about the basic commerce_customer_profile properties.
$properties = &$info['commerce_customer_profile']['properties'];
$properties['profile_id'] = array(
'label' => t('Profile ID'),
'description' => t('The internal numeric ID of the profile.'),
'type' => 'integer',
);
$properties['type'] = array(
'label' => t('Type'),
'description' => t('The type of the profile.'),
'type' => 'token',
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer customer profiles',
'options list' => 'commerce_customer_profile_type_options_list',
'required' => TRUE,
'query callback' => 'entity_metadata_table_query',
);
$properties['uid'] = array(
'label' => t('User ID'),
'type' => 'integer',
'description' => t("The unique ID of the user the customer profile belongs to."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer customer profiles',
'clear' => array('user'),
'query callback' => 'entity_metadata_table_query',
);
$properties['user'] = array(
'label' => t('User'),
'type' => 'user',
'description' => t("The user the customer profile belongs to."),
'getter callback' => 'commerce_customer_profile_get_properties',
'setter callback' => 'commerce_customer_profile_set_properties',
'setter permission' => 'administer customer profiles',
'required' => TRUE,
'clear' => array('uid'),
);
$properties['status'] = array(
'label' => t('Status'),
'description' => t('Whether the profile is active.'),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer customer profiles',
'query callback' => 'entity_metadata_table_query',
'type' => 'boolean',
);
$properties['created'] = array(
'label' => t('Date created'),
'description' => t('The date the product was created.'),
'type' => 'date',
'setter callback' => 'entity_property_verbatim_set',
'query callback' => 'entity_metadata_table_query',
'setter permission' => 'administer customer profiles',
);
$properties['changed'] = array(
'label' => t('Date updated'),
'description' => t('The date the product was last updated.'),
'type' => 'date',
'setter callback' => 'entity_property_verbatim_set',
'query callback' => 'entity_metadata_table_query',
'setter permission' => 'administer customer profiles',
);
return $info;
}
<?php
// $Id$
/**
* Implements hook_schema().
*/
function commerce_customer_schema() {
$schema = array();
$schema['commerce_customer_profile'] = array(
'description' => 'The base table for customer profiles.',
'fields' => array(
'profile_id' => array(
'description' => 'The primary identifier for a customer profile.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'revision_id' => array(
'description' => 'The current {commerce_customer_profile_revision}.revision_id version identifier.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'type' => array(
'description' => 'The {commerce_customer_profile_type}.type of this profile.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'uid' => array(
'description' => 'The {users}.uid that this profile belongs to.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'status' => array(
'description' => 'Boolean indicating whether the profile is active or not.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'created' => array(
'description' => 'The Unix timestamp when the profile was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'changed' => array(
'description' => 'The Unix timestamp when the profile was most recently saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('profile_id'),
'unique keys' => array(
'revision_id' => array('revision_id'),
),
'foreign keys' => array(
'customer_profile_revision' => array(
'table' => 'commerce_customer_profile_revision',
'columns'=> array('revision_id' => 'revision_id'),
),
'owner' => array(
'table' => 'users',
'columns' => array('uid' => 'uid'),
),
),
);
$schema['commerce_customer_profile_revision'] = array(
'description' => 'Saves information about each saved revision of a {commerce_customer_profile}.',
'fields' => array(
'profile_id' => array(
'description' => 'The {commerce_customer_profile}.customer_id of the profile this revision belongs to.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'revision_id' => array(
'description' => 'The primary identifier for this revision.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'revision_uid' => array(
'description' => 'The {users}.uid that created this profile at this revision.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'status' => array(
'description' => 'Boolean indicating whether the profile is active or not.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'log' => array(
'description' => 'The log entry explaining the changes in this version.',
'type' => 'text',
'not null' => TRUE,
'size' => 'big',
),
'revision_timestamp' => array(
'description' => 'The Unix timestamp when this revision was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('revision_id'),
'foreign keys' => array(
'customer_profile' => array(
'table' => 'commerce_customer_profile',
'columns'=> array('profile_id' => 'profile_id'),
),
'creator' => array(
'table' => 'users',
'columns' => array('uid' => 'uid'),
),
),
);
return $schema;
}
/**
* Implements hook_field_schema().
*/
function commerce_customer_field_schema($field) {
if ($field['type'] == 'commerce_customer_profile_reference') {
return array(
'columns' => array(
'profile_id' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
),
),
'indexes' => array(
'profile_id' => array('profile_id'),
),
'foreign keys' => array(
'profile_id' => array(
'table' => 'commerce_customer',
'columns' => array('profile_id' => 'profile_id'),
),
),
);
}
}
/**
* Implements hook_uninstall().
*/
function commerce_customer_uninstall() {
// Remove the address field to customer profile bundles.
$entity_info = entity_get_info('commerce_customer_profile');
foreach ($entity_info['bundles'] as $type => $data) {
field_delete_field('commerce_' . $type . '_address');
}
}
<?php
// $Id$
/**
* @file
* Defines the customer profile entity and API functions to manage customers and
* interact with them.
*/
/**
* Implements hook_entity_info().
*/
function commerce_customer_entity_info() {
$return = array(
'commerce_customer_profile' => array(
'label' => t('Customer profile'),
'controller class' => 'CommerceCustomerProfileEntityController',
'base table' => 'commerce_customer_profile',
'revision table' => 'commerce_customer_profile_revision',
'fieldable' => TRUE,
'entity keys' => array(
'id' => 'profile_id',
'revision' => 'revision_id',
'bundle' => 'type',
'label' => 'profile_id', // TODO: Update to use a custom callback.
),
'bundle keys' => array(
'bundle' => 'type',
),
'bundles' => array(),
'load hook' => 'commerce_customer_profile_load',
'view modes' => array(
'administrator' => array(
'label' => t('Administrator'),
'custom settings' => FALSE,
),
'customer' => array(
'label' => t('Customer'),
'custom settings' => FALSE,
),
),
'creation callback' => '_commerce_customer_profile_create',
'save callback' => 'commerce_customer_profile_save',
'deletion callback' => 'commerce_customer_profile_delete',
'access callback' => 'commerce_customer_profile_access',
'token type' => 'customer',
),
);
foreach (commerce_customer_profile_type_get_name() as $type => $name) {
$return['commerce_customer_profile']['bundles'][$type] = array(
'label' => $name,
);
}
return $return;
}
/**
* Implements hook_enable().
*/
function commerce_customer_enable() {
// Add the address field to customer profile bundles.
$entity_info = entity_get_info('commerce_customer_profile');
foreach ($entity_info['bundles'] as $type => $data) {
commerce_customer_configure_customer_profile_type($type);
}
}
/**
* Implements hook_views_api().
*/
function commerce_customer_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'commerce_customer') . '/includes/views',
);
}
/**
* Implements hook_permission().
*/
function commerce_customer_permission() {
$permissions = array(
'administer customer profiles' => array(
'title' => t('Administer customer profiles'),
'description' => t('Allows users to perform any action on customer profiles of any type.'),
'restrict access' => TRUE,
),
'administer customer profile types' => array(
'title' => t('Administer customer profile types'),
'description' => t('Allows users to add customer profile types and configure their fields.'),
'restrict access' => TRUE,
),
'access customer profiles' => array(
'title' => t('Access customer profiles'),
'description' => t('Allows users to view lists of customer profiles in the Store admin and reference lists.'),
),
);
// Add profile type specific permissions. Note that users with administer
// customer profiles permission should have access to do anything the
// permissions below grant a user to do.
foreach (commerce_customer_profile_type_get_name() as $type => $name) {
$permissions['create ' . $type . ' customer profiles'] = array(
'title' => t('Create %type customer profiles', array('%type' => $name)),
);
$permissions['edit any ' . $type . ' customer profile'] = array(
'title' => t('Edit or delete any %type customer profile', array('%type' => $name)),
);
$permissions['edit own ' . $type . ' customer profiles'] = array(
'title' => t('Edit or delete own %type customer profiles', array('%type' => $name)),
);
}
return $permissions;
}
/**
* Implements hook_theme().
*/
function commerce_customer_theme() {
return array(
'commerce_customer_profile' => array(
'variables' => array('profile' => NULL, 'view_mode' => NULL),
),
);
}
/**
* Implements hook_commerce_customer_profile_type_info().
*/
function commerce_customer_commerce_customer_profile_type_info() {
$profile_types = array();
$profile_types['billing'] = array(
'type' => 'billing',
'name' => t('Billing information'),
'description' => t('The profile used to collect billing information on the checkout and order forms.'),
'help' => '',
);
return $profile_types;
}
/**
* Implements hook_commerce_checkout_pane_info().
*/
function commerce_customer_commerce_checkout_pane_info() {
$checkout_panes = array();
$weight = 5;
foreach (commerce_customer_profile_types() as $type => $profile_type) {
$checkout_panes['customer_profile_' . $type] = array(
'title' => $profile_type['name'],
'file' => 'includes/commerce_customer.checkout_pane.inc',
'base' => 'commerce_customer_profile_pane',
'page' => 'checkout',
'weight' => $weight++,
);
}
return $checkout_panes;
}
/**
* Returns an array of customer profile type arrays keyed by type.
*/
function commerce_customer_profile_types() {
// First check the static cache for a profile types array.
$profile_types = &drupal_static(__FUNCTION__);
// If it did not exist, fetch the types now.
if (!isset($profile_types)) {
// Find profile types defined by hook_commerce_customer_profile_type_info().
foreach (module_implements('commerce_customer_profile_type_info') as $module) {
foreach (module_invoke($module, 'commerce_customer_profile_type_info') as $type => $profile_type) {
// Set the module each profile type is defined by.
$profile_type['module'] = $module;
$profile_types[$type] = $profile_type;
}
}
// Last allow the info to be altered by other modules.
drupal_alter('commerce_customer_profile_type_info', $profile_types);
}
return $profile_types;
}
/**
* Loads a customer profile type.
*
* @param $type
* The machine-readable name of the customer profile type; accepts normal
* machine names and URL prepared machine names with underscores replaced by
* hyphens.
*/
function commerce_customer_profile_type_load($type) {
$type = strtr($type, array('-' => '_'));
$profile_types = commerce_customer_profile_types();
return !empty($profile_types[$type]) ? $profile_types[$type] : FALSE;
}
/**
* Returns the human readable name of any or all customer profile types.
*
* @param $type
* Optional parameter specifying the type whose name to return.
*
* @return
* Either an array of all profile type names keyed by the machine name or a
* string containing the human readable name for the specified type. If a
* type is specified that does not exist, this function returns FALSE.
*/
function commerce_customer_profile_type_get_name($type = NULL) {
$profile_types = commerce_customer_profile_types();
// Return a type name if specified and it exists.
if (!empty($type)) {
if (isset($profile_types[$type])) {
return $profile_types[$type]['name'];
}
else {
// Return FALSE if it does not exist.
return FALSE;
}
}
// Otherwise turn the array values into the type name only.
foreach ($profile_types as $key => $value) {
$profile_types[$key] = $value['name'];
}
return $profile_types;
}
/**
* Wraps commerce_customer_profile_type_get_name() for the Entity module.
*/
function commerce_customer_profile_type_options_list() {
return commerce_customer_profile_type_get_name();
}
/**
* Title callback: return the human-readable customer profile type name.
*/
function commerce_customer_profile_type_title($profile_type) {
return $profile_type['name'];
}
/**
* Returns a path argument from a customer profile type.
*/
function commerce_customer_profile_type_to_arg($type) {
return $type;
}
/**
* Returns an initialized customer profile object.
*
* @param $type
* The type of customer profile to create.
* @param $uid
* The uid of the user the customer profile is for.
*
* @return
* A customer profile object with all default fields initialized.
*/
function commerce_customer_profile_new($type = '', $uid = 0) {
return entity_get_controller('commerce_customer_profile')->create($type, $uid);
}
/**
* Creation callback for the Entity module.
*/
function _commerce_customer_profile_create($values = array()) {
// Create a new customer profile for the specified user.
$profile = commerce_customer_profile_new($values['type'], $values['uid']);
unset($values['type'], $values['uid']);
$wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile);
foreach ($values as $name => $value) {
$wrapper->$name->set($value);
}
return $wrapper->value();
}
/**
* Saves a customer profile.
*
* @param $profile
* The full customer profile object to save. If $profile->profile_id is empty,
* a new customer profile will be created.
*
* @return
* The saved customer profile object.
*/
function commerce_customer_profile_save($profile) {
return entity_get_controller('commerce_customer_profile')->save($profile);
}
/**
* Loads a customer profile by ID.
*/
function commerce_customer_profile_load($profile_id) {
$profiles = commerce_customer_profile_load_multiple(array($profile_id), array());
return $profiles ? reset($profiles) : FALSE;
}
/**
* Loads multiple customer profiles by ID or based on a set of conditions.
*
* @see entity_load()
*
* @param $profile_ids
* An array of customer profile IDs.
* @param $conditions
* An array of conditions on the {commerce_customer_profile} table in the form
* 'field' => $value.
* @param $reset
* Whether to reset the internal customer profile loading cache.
*
* @return
* An array of customer profile objects indexed by profile_id.
*/
function commerce_customer_profile_load_multiple($profile_ids = array(), $conditions = array(), $reset = FALSE) {
return entity_load('commerce_customer_profile', $profile_ids, $conditions, $reset);
}
/**
* Generate an array for rendering the given customer profile.
*
* @param $profile
* A fully loaded customer profile object.
* @param $view_mode
* The view mode for displaying the profile, 'administrator' or 'customer'.
*
* @return
* An array as expected by drupal_render().
*/
function commerce_customer_profile_build_content($profile, $view_mode = 'administrator') {
// Populate $profile->content with a render() array.
// Remove previously built content, if it exists.
$profile->content = array();
// Build fields content.
field_attach_prepare_view('commerce_customer_profile', array($profile->profile_id => $profile), $view_mode);
entity_prepare_view('commerce_customer_profile', array($profile->profile_id => $profile));
$profile->content += field_attach_view('commerce_customer_profile', $profile, $view_mode);
// Allow modules to make their own additions to the customer profile content.
module_invoke_all('commerce_customer_profile_view', $profile, $view_mode);
// Remove the content array from the profile to avoid duplicate rendering.
$build = $profile->content;
unset($profile->content);
$build += array(
'#theme' => 'commerce_customer_profile',
'#profile' => $profile,
'#view_mode' => $view_mode,
);
// Allow modules to modify the final build array.
drupal_alter('commerce_customer_profile_view', $build);
return $build;
}
/**
* Deletes a customer profile by ID.
*
* @param $profile_id
* The ID of the customer profile to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
function commerce_customer_profile_delete($profile_id) {
return commerce_customer_profile_delete_multiple(array($profile_id));
}
/**
* Deletes multiple customer profiles by ID.
*
* @param $profile_ids
* An array of customer profile IDs to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
function commerce_customer_profile_delete_multiple($profile_ids) {
return entity_get_controller('commerce_customer_profile')->delete($profile_ids);
}
/**
* Checks customer profile access for various operations.
*
* @param $op
* The operation being performed. One of 'view', 'update', 'create' or
* 'delete'.
* @param $profile
* Optionally a profile to check access for or for the create operation the
* profile type. If nothing is given access permissions for all profiles are returned.
* @param $account
* The user to check for. Leave it to NULL to check for the current user.
*/
function commerce_customer_profile_access($op, $profile = NULL, $account = NULL) {
global $user;
$account = isset($account) ? $account : $user;
if (user_access('administer customer profiles', $account)) {
return TRUE;
}
if ($op == 'view' && user_access('access customer profiles', $account)) {
return TRUE;
}
if (isset($profile) && is_string($profile) && $op == 'create' && user_access('create ' . $profile . ' customer profiles', $account)) {
return TRUE;
}
if (isset($profile) && ($op == 'update' || $op == 'delete')) {
if (user_access('edit any ' . $profile->type . ' customer profile', $account)) {
return TRUE;
}
// Others either don't have any access or must match the profile uid.
if ($account->uid && user_access('edit own ' . $profile->type . ' customer profiles', $account) && $profile->uid == $account->uid) {
return TRUE;
}
}
return FALSE;
}
/**
* Ensures the address field is present on the specified customer profile bundle.
*/
function commerce_customer_configure_customer_profile_type($type) {
// If a field type we know should exist isn't found, clear the Field cache.
if (!field_info_field_types('addressfield')) {
field_cache_clear();
}
// Look for or add an address field to the customer profile type.
$field_name = 'commerce_' . $type . '_address';
$field = field_info_field($field_name);
$instance = field_info_instance('commerce_customer_profile', $field_name, $type);
if (empty($field)) {
$field = array(
'field_name' => $field_name,
'type' => 'addressfield',
'cardinality' => 1,
'entity_types' => array('commerce_customer_profile'),
'translatable' => FALSE,
);
$field = field_create_field($field);
}
if (empty($instance)) {
$instance = array(
'field_name' => $field_name,
'entity_type' => 'commerce_customer_profile',
'bundle' => $type,
'label' => t('Address'),
'required' => TRUE,
'widget' => array(
'type' => 'addressfield_standard',
'weight' => -10,
),
'settings' => array(),
'display' => array(),
);
// Set the default display formatters for various view modes.
foreach (array('default', 'customer', 'administrator') as $view_mode) {
$instance['display'][$view_mode] = array(
'label' => 'hidden',
'type' => 'addressfield_default',
'weight' => -10,
);
}
field_create_instance($instance);
}
}
/**
* Implements hook_field_info().
*/
function commerce_customer_field_info() {
return array(
'commerce_customer_profile_reference' => array(
'label' => t('Customer profile reference'),
'description' => t('This field stores the ID of a related customer profile as an integer value.'),
'settings' => array('profile_type' => 'billing'),
'instance_settings' => array(),
'default_widget' => 'options_select',
'default_formatter' => 'commerce_customer_profile_reference_view',
'property_type' => 'commerce_customer_profile',
'property_callbacks' => array('commerce_customer_profile_property_info_callback'),
),
);
}
/**
* Implements hook_field_settings_form().
*/
function commerce_customer_field_settings_form($field, $instance, $has_data) {
$settings = $field['settings'];
$form = array();
if ($field['type'] == 'commerce_customer_profile_reference') {
$options = array();
// Build an options array of the customer profile types.
foreach (commerce_customer_profile_type_get_name() as $type => $name) {
$options[$type] = check_plain($name);
}
$form['profile_type'] = array(
'#type' => 'radios',
'#title' => t('Customer profile type that can be referenced'),
'#options' => $options,
'#default_value' => !empty($settings['profile_type']) ? $settings['profile_type'] : 'billing',
'#disabled' => $has_data,
);
}
return $form;
}
/**
* Implements hook_field_validate().
*
* Possible error codes:
* - 'invalid_profile_id': profile_id is not valid for the field (not a
* valid line item ID).
*/
function commerce_customer_field_validate($entity_type, $object, $field, $instance, $langcode, $items, &$errors) {
if ($field['type'] == 'commerce_customer_profile_reference') {
// Extract profile_ids to check.
$profile_ids = array();
// First check non-numeric profile_id's to avoid losing time with them.
foreach ($items as $delta => $item) {
if (is_array($item) && !empty($item['profile_id'])) {
if (is_numeric($item['profile_id'])) {
$profile_ids[] = $item['profile_id'];
}
else {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'invalid_profile_id',
'message' => t('%name: you have specified an invalid customer profile for this reference field.', array('%name' => $instance['label'])),
);
}
}
}
// Prevent performance hog if there are no ids to check.
if ($profile_ids) {
$profiles = commerce_customer_profile_load_multiple($profile_ids, array('type' => $field['settings']['profile_type']));
foreach ($items as $delta => $item) {
if (is_array($item)) {
// Check that the item specifies a profile_id and that a profile of
// the proper type exists with that ID.
if (!empty($item['profile_id']) && !isset($profiles[$item['profile_id']])) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'invalid_profile_id',
'message' => t('%name: you have specified an invalid customer profile for this reference field.', array('%name' => $instance['label'])),
);
}
}
}
}
}
}
/**
* Implements hook_field_is_empty().
*/
function commerce_customer_field_is_empty($item, $field) {
if ($field['type'] == 'commerce_customer_profile_reference') {
// profile_id = 0 รฎs empty too, which is exactly what we want.
return empty($item['profile_id']);
}
}
/**
* Implements hook_field_formatter_info().
*/
function commerce_customer_field_formatter_info() {
return array(
'commerce_customer_profile_reference_display' => array(
'label' => t('Customer profile display'),
'description' => t('Display the customer profile.'),
'field types' => array('commerce_customer_profile_reference'),
),
);
}
/**
* Implements hook_field_formatter_view().
*/
function commerce_customer_field_formatter_view($entity_type, $object, $field, $instance, $langcode, $items, $display) {
$result = array();
// Collect the list of customer profile IDs.
$profile_ids = array();
foreach ($items as $delta => $item) {
$profile_ids[] = $item['profile_id'];
}
switch ($display['type']) {
case 'commerce_customer_profile_reference_display':
foreach ($items as $delta => $item) {
$profile = commerce_customer_profile_load($item['profile_id']);
if ($profile) {
$result[$delta] = array(
'#markup' => drupal_render(commerce_customer_profile_build_content($profile, 'customer')),
);
}
}
break;
}
return $result;
}
/**
* Implements hook_field_widget_info().
*
* Defines widgets available for use with field types as specified in each
* widget's $info['field types'] array.
*/
function commerce_customer_field_widget_info() {
$widgets = array();
// Define the creation / reference widget for line items.
$widgets['commerce_customer_profile_manager'] = array(
'label' => t('Customer profile manager'),
'description' => t('Use a complex widget to edit the profile referenced by this object.'),
'field types' => array('commerce_customer_profile_reference'),
'settings' => array(),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
'default value' => FIELD_BEHAVIOR_NONE,
),
);
return $widgets;
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function commerce_customer_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
// Alter the field edit form so it's obvious that customer profile manager
// widgets do not support multiple values.
if (!empty($form['locked']) &&
!empty($form['instance']) &&
$form['instance']['widget']['type']['#value'] == 'commerce_customer_profile_manager') {
$form['field']['cardinality']['#options'] = array('1' => '1');
$form['field']['cardinality']['#description'] = t('The customer profile manager widget only supports single value editing and entry via its form.');
}
}
/**
* Implements hook_field_widget_info_alter().
*/
function commerce_customer_field_widget_info_alter(&$info) {
$info['options_select']['field types'][] = 'commerce_customer_profile_reference';
}
/**
* Implements hook_options_list().
*/
function commerce_customer_options_list($field) {
$options = array();
// Loop through all customer matches.
foreach (commerce_customer_match_customer_profiles($field) as $profile_id => $data) {
// Add them to the options list in optgroups by customer profile type.
$name = check_plain(commerce_customer_profile_type_get_name($data['type']));
$options[$name][$profile_id] = t('@profile: User @user', array('@profile' => $profile_id, '@user' => $data['uid']));
}
// Simplify the options list if only one optgroup exists.
if (count($options) == 1) {
$options = reset($options);
}
return $options;
}
/**
* Implements hook_field_widget_form().
*
* Used to define the form element for custom widgets.
*/
function commerce_customer_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
// Define the complex customer profile reference field widget.
if ($instance['widget']['type'] == 'commerce_customer_profile_manager') {
$profile_type = commerce_customer_profile_type_load($field['settings']['profile_type']);
// Build an array of customer profile IDs from this field's values.
$profile_ids = array();
foreach ($items as $item) {
$profile_ids[] = $item['profile_id'];
}
// Load the profiles for temporary storage in the form array.
$profiles = commerce_customer_profile_load_multiple($profile_ids);
if (empty($profiles)) {
$profiles[0] = commerce_customer_profile_new('billing');
}
// Update the base form element array to use the proper theme and validate
// functions and to include header information for the line item table.
$element += array(
'#element_validate' => array('commerce_customer_profile_manager_validate'),
'profiles' => array('#tree' => TRUE),
);
// Add a set of elements to the form for each referenced profile.
$key = 0;
foreach ($profiles as $profile) {
// Add a fieldset around the profile form.
$element['profiles'][$key] = array(
'#type' => 'fieldset',
'#title' => check_plain($profile_type['name']),
);
// Store the original customer profile for later comparison.
$element['profiles'][$key]['profile'] = array(
'#type' => 'value',
'#value' => $profile,
);
field_attach_form('commerce_customer_profile', $profile, $element['profiles'][$key], $form_state);
// Tweak the form to remove the fieldset from address fields.
$field_name = 'commerce_' . $profile_type['type'] . '_address';
$language = $element['profiles'][$key][$field_name]['#language'];
foreach (element_children($element['profiles'][$key][$field_name][$language]) as $delta) {
$element['profiles'][$key][$field_name][$language][$delta]['#type'] = 'container';
}
// Remove the default #parents array so the normal tree can do its thing.
unset($element['profiles'][$key]['#parents']);
// This checkbox will be overridden with a clickable delete image.
// TODO: Make this an #ajaxy submit button.
if ($profile->profile_id) {
$element['profiles'][$key]['remove'] = array(
'#type' => 'checkbox',
'#title' => t('Delete this profile'),
'#default_value' => FALSE,
);
}
$key += 1;
}
// If the reference field is not required, unrequire any elements in the
// profile edit form.
if (!$delta == 0 || !$instance['required']) {
commerce_unrequire_form_elements($element);
}
return $element;
}
}
/**
* Validation callback for a commerce_customer_profile_manager element.
*
* When the form is submitted, the profile reference field stores the profile
* IDs as derived from the $element['profiles'] array and updates any
* referenced profiles based on the extra form elements.
*/
function commerce_customer_profile_manager_validate($element, &$form_state, $form) {
$value = array();
// Loop through the profiles in the manager table.
foreach (element_children($element['profiles']) as $key) {
// Update the profile based on the values in the additional elements.
$profile = clone($element['profiles'][$key]['profile']['#value']);
// If the line item has been marked for deletion...
if ($profile->profile_id && $element['profiles'][$key]['remove']['#value']) {
// Delete the profile now and don't include it from the $value array.
commerce_customer_profile_delete($profile->profile_id);
}
else {
// Notify field widgets to validate their data.
field_attach_form_validate('commerce_customer_profile', $profile, $element['profiles'][$key], $form_state);
// TODO: Trap it on error, rebuild the form with error messages.
// Notify field widgets to save the field data.
field_attach_submit('commerce_customer_profile', $profile, $element['profiles'][$key], $form_state);
// Only save if values were actually changed.
if ($profile != $element['profiles'][$key]['profile']['#value']) {
commerce_customer_profile_save($profile);
}
// Add the profile ID to the current value of the reference field.
$value[] = array('profile_id' => $profile->profile_id);
}
}
form_set_value($element, $value, $form_state);
}
/**
* Implements hook_field_widget_error().
*/
function commerce_customer_field_widget_error($element, $error) {
form_error($element, $error['message']);
}
/**
* Callback to alter the property info of the reference field.
*
* @see commerce_customer_field_info().
*/
function commerce_customer_profile_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
$property['options list'] = 'entity_metadata_field_options_list';
unset($property['query callback']);
}
/**
* Fetches an array of all customer profiles matching the given parameters.
*
* This info is used in various places (allowed values, autocomplete results,
* input validation...). Some of them only need the profile_ids, others
* profile_id + titles, others yet profile_id + titles + rendered row (for
* display in widgets).
*
* The array we return contains all the potentially needed information,
* and lets calling functions use the parts they actually need.
*
* @param $field
* The field description.
* @param $ids
* Optional product ids to lookup.
* @param $limit
* If non-zero, limit the size of the result set.
*
* @return
* An array of valid profiles in the form:
* array(
* profile_id => array(
* 'uid' => The user ID,
* 'rendered' => The text to display in widgets (can be HTML)
* ),
* ...
* )
*/
function commerce_customer_match_customer_profiles($field, $ids = array(), $limit = NULL) {
$results = &drupal_static(__FUNCTION__, array());
// Create unique id for static cache.
$cid = implode(':', array(
$field['field_name'],
implode('-', $ids),
$limit,
));
if (!isset($results[$cid])) {
$matches = _commerce_customer_match_customer_profiles_standard($field, $ids, $limit);
// Store the results.
$results[$cid] = !empty($matches) ? $matches : array();
}
return $results[$cid];
}
/**
* Helper function for commerce_customer_match_customer_profiles().
*
* Returns an array of products matching the specific parameters.
*/
function _commerce_customer_match_customer_profiles_standard($field, $ids = array(), $limit = NULL) {
// Build the query object with the necessary fields.
$query = db_select('commerce_customer_profile', 'cp');
$profile_id_alias = $query->addField('cp', 'profile_id');
$profile_uid_alias = $query->addField('cp', 'uid');
$profile_type_alias = $query->addField('cp', 'type');
// Add a condition to the query to filter by matching profile types.
if (is_array($field['settings']['referenceable_types']) && !empty($field['settings']['referenceable_types'])) {
$types = array_diff(array_values($field['settings']['referenceable_types']), array(0, NULL));
// Only filter by type if some types have been specified.
if (!empty($types)) {
$query->condition('cp.type', $types, 'IN');
}
}
if ($ids) {
// Otherwise add a profile_id specific condition if specified.
$query->condition($product_id_alias, $ids, 'IN', $ids);
}
// Order the results by ID and then profile type.
$query
->orderBy($profile_id_alias)
->orderBy($profile_type_alias);
// Add a limit if specified.
if ($limit) {
$query->range(0, $limit);
}
// Execute the query and build the results array.
$result = $query->execute();
$matches = array();
foreach ($result->fetchAll() as $profile) {
$matches[$profile->profile_id] = array(
'uid' => $profile->uid,
'type' => $profile->type,
'rendered' => t('Profile @profile_id', array('@profile_id' => $profile->profile_id)),
);
}
return $matches;
}
/**
* Callback for getting customer profile properties.
*
* @see commerce_customer_entity_property_info()
*/
function commerce_customer_profile_get_properties($profile, array $options, $name) {
switch ($name) {
case 'user':
return $profile->uid;
}
}
/**
* Callback for setting customer profile properties.
*
* @see commerce_customer_entity_property_info()
*/
function commerce_customer_profile_set_properties($profile, $name, $value) {
if ($name == 'user') {
$profile->uid = $value;
}
}
<?php
// $Id$
/**
* @file
* Rules integration for customer profiles.
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_event_info().
*/
function commerce_customer_rules_event_info() {
$events = array();
$events['commerce_customer_profile_presave'] = array(
'label' => t('Before saving a customer profile'),
'group' => t('Commerce Customer'),
'variables' => commerce_customer_rules_event_variables(t('Customer profile'), TRUE, TRUE),
'access callback' => 'commerce_customer_rules_access',
);
$events['commerce_customer_profile_insert'] = array(
'label' => t('After saving a new customer profile'),
'group' => t('Commerce Customer'),
'variables' => commerce_customer_rules_event_variables(t('Created customer profile'), TRUE),
'access callback' => 'commerce_customer_rules_access',
);
$events['commerce_customer_profile_update'] = array(
'label' => t('After updating an existing customer profile'),
'group' => t('Commerce Customer'),
'variables' => commerce_customer_rules_event_variables(t('Updated customer profile'), TRUE),
'access callback' => 'commerce_customer_rules_access',
);
$events['commerce_customer_profile_delete'] = array(
'label' => t('After deleting a customer profile'),
'group' => t('Commerce Customer'),
'variables' => commerce_customer_rules_event_variables(t('Deleted customer profile')),
'access callback' => 'commerce_customer_rules_access',
);
return $events;
}
/**
* Returns a variables array for customer profile events.
*
* @param $label
* The label for the primary customer profile variable.
* @param $unchanged
* Boolean indicating whether or not to include the unchanged customer profile.
* @param $skip_save
* Boolean indicating whether or not the primary line item variable should
* skip saving after event execution.
*/
function commerce_customer_rules_event_variables($label, $unchanged = FALSE, $skip_save = FALSE) {
$variables = array(
'customer_profile' => array(
'type' => 'commerce_customer_profile',
'label' => $label,
'skip save' => $skip_save,
),
);
// Include the unchanged customer profile if specified.
if ($unchanged) {
$variables['customer_profile_unchanged'] = array(
'type' => 'commerce_customer_profile',
'label' => t('Unchanged customer profile'),
'skip save' => TRUE,
'handler' => 'rules_events_entity_unchanged',
);
}
return $variables;
}
/**
* Rules access callback: determines access to Customer events and conditions.
*/
function commerce_customer_rules_access($type, $name) {
if ($type == 'event' || $type == 'condition') {
// Currently there are no restrictions.
return TRUE;
}
}
/**
* @}
*/
<?php
// $Id$
/**
* @file
* Builds placeholder replacement tokens for customer related data.
*/
/**
* Implements hook_token_info().
*/
function commerce_customer_item_token_info() {
$type = array(
'name' => t('Customer profiles'),
'description' => t('Tokens related to customer profiles.'),
'needs-data' => 'customer-profile',
);
// Tokens for customer profiles.
$profile = array();
$profile['customer-profile-id'] = array(
'name' => t('Customer profile ID'),
'description' => t('The unique numeric ID of the customer profile.'),
);
$profile['revision-id'] = array(
'name' => t('Revision ID'),
'description' => t("The unique ID of the customer profile's latest revision."),
);
$profile['type'] = array(
'name' => t('Customer profile type'),
'description' => t('The type of the customer profile.'),
);
$profile['type-name'] = array(
'name' => t('Customer profile type name'),
'description' => t('The type name of the customer profile.'),
);
// Chained tokens for customer profiles.
$profile['owner'] = array(
'name' => t('Customer profile owner'),
'description' => t('The user the customer profile belongs to.'),
'type' => 'user',
);
$profile['created'] = array(
'name' => t('Date created'),
'description' => t('The date the customer profile was created.'),
'type' => 'date',
);
$profile['changed'] = array(
'name' => t('Date updated'),
'description' => t('The date the customer profile was last updated.'),
'type' => 'date',
);
return array(
'types' => array('customer-profile' => $type),
'tokens' => array('customer-profile' => $profile),
);
}
/**
* Implements hook_tokens().
*/
function commerce_customer_tokens($type, $tokens, array $data = array(), array $options = array()) {
$url_options = array('absolute' => TRUE);
if (isset($options['language'])) {
$url_options['language'] = $options['language'];
$language_code = $options['language']->language;
}
else {
$language_code = NULL;
}
$sanitize = !empty($options['sanitize']);
$replacements = array();
if ($type == 'customer-profile' && !empty($data['customer-profile'])) {
$profile = $data['customer-profile'];
foreach ($tokens as $name => $original) {
switch ($name) {
// Simple key values on the customer profile.
case 'customer-profile-id':
$replacements[$original] = $profile->profile_id;
break;
case 'revision-id':
$replacements[$original] = $profile->revision_id;
break;
case 'type':
$replacements[$original] = $sanitize ? check_plain($profile->type) : $profile->type;
break;
case 'type-name':
$replacements[$original] = commerce_customer_profile_type_get_name($profile->type);
break;
// Default values for the chained tokens handled below.
case 'owner':
if ($profile->uid == 0) {
$name = variable_get('anonymous', t('Anonymous'));
}
else {
$account = user_load($profile->uid);
$name = $account->name;
}
$replacements[$original] = $sanitize ? filter_xss($name) : $name;
break;
case 'created':
$replacements[$original] = format_date($profile->created, 'medium', '', NULL, $language_code);
break;
case 'changed':
$replacements[$original] = format_date($profile->changed, 'medium', '', NULL, $language_code);
break;
}
}
if ($owner_tokens = token_find_with_prefix($tokens, 'owner')) {
$owner = user_load($profile->uid);
$replacements += token_generate('user', $owner_tokens, array('user' => $owner), $options);
}
foreach (array('created', 'changed') as $date) {
if ($created_tokens = token_find_with_prefix($tokens, $date)) {
$replacements += token_generate('date', $created_tokens, array('date' => $profile->{$date}), $options);
}
}
}
return $replacements;
}
<?php
// $Id$
/**
* Export Drupal Commerce customer profiles to Views.
*/
/**
* Implements hook_views_data()
*/
function commerce_customer_views_data() {
$data = array();
$data['commerce_customer_profile']['table']['group'] = t('Commerce Customer Profile');
$data['commerce_customer_profile']['table']['base'] = array(
'field' => 'profile_id',
'title' => t('Commerce Customer Profile'),
'help' => t('Customer profiles containing addresses and other customer information.'),
);
// Expose the profile ID.
$data['commerce_customer_profile']['profile_id'] = array(
'title' => t('Profile ID'),
'help' => t('The unique internal identifier of the profile.'),
'field' => array(
'handler' => 'commerce_customer_handler_field_customer_profile',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
),
);
// Expose the profile type.
$data['commerce_customer_profile']['type'] = array(
'title' => t('Type'),
'help' => t('The human-readable name of the type of the customer profile.'),
'field' => array(
'handler' => 'commerce_customer_handler_field_customer_profile_type',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'commerce_customer_handler_filter_customer_profile_type',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Expose the owner uid.
$data['commerce_customer_profile']['uid'] = array(
'title' => t('Owner'),
'help' => t('Relate a profile to the user it belongs to.'),
'relationship' => array(
'handler' => 'views_handler_relationship',
'base' => 'users',
'field' => 'uid',
'label' => t('Profile owner'),
),
);
// Expose the profile status.
$data['commerce_customer_profile']['status'] = array(
'title' => t('Status'),
'help' => t('Whether or not the profile is active.'),
'field' => array(
'handler' => 'views_handler_field_boolean',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_boolean_operator',
'label' => t('Active'),
'type' => 'yes-no',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
);
// Expose the created and changed timestamps.
$data['commerce_customer_profile']['created'] = array(
'title' => t('Created date'),
'help' => t('The date the profile was created.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'filter' => array(
'handler' => 'views_handler_filter_date',
),
);
$data['commerce_customer_profile']['changed'] = array(
'title' => t('Updated date'),
'help' => t('The date the profile was last updated.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'filter' => array(
'handler' => 'views_handler_filter_date',
),
);
// Expose links to operate on the profile.
$data['commerce_customer_profile']['view_customer_profile'] = array(
'field' => array(
'title' => t('Link'),
'help' => t('Provide a simple link to the administrator view of the profile.'),
'handler' => 'commerce_customer_handler_field_customer_profile_link',
),
);
$data['commerce_customer_profile']['edit_customer_profile'] = array(
'field' => array(
'title' => t('Edit link'),
'help' => t('Provide a simple link to edit the profile.'),
'handler' => 'commerce_customer_handler_field_customer_profile_link_edit',
),
);
$data['commerce_customer_profile']['delete_customer_profile'] = array(
'field' => array(
'title' => t('Delete link'),
'help' => t('Provide a simple link to delete the profile.'),
'handler' => 'commerce_customer_handler_field_customer_profile_link_delete',
),
);
return $data;
}
<?php
// $Id$
/**
* @file
* Contains the basic customer profile field handler.
*/
/**
* Field handler to provide simple renderer that allows linking to a profile.
*/
class commerce_customer_handler_field_customer_profile extends views_handler_field {
function init(&$view, &$options) {
parent::init($view, $options);
if (!empty($this->options['link_to_profile'])) {
$this->additional_fields['profile_id'] = 'profile_id';
}
}
function option_definition() {
$options = parent::option_definition();
$options['link_to_profile'] = array('default' => FALSE);
return $options;
}
/**
* Provide the link to profile option.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['link_to_profile'] = array(
'#title' => t("Link this field to the profile's administrative view page"),
'#description' => t('This will override any other link you have set.'),
'#type' => 'checkbox',
'#default_value' => !empty($this->options['link_to_profile']),
);
}
/**
* Render whatever the data is as a link to the customer profile.
*
* Data should be made XSS safe prior to calling this function.
*/
function render_link($data, $values) {
if (!empty($this->options['link_to_profile']) && $data !== NULL && $data !== '') {
$this->options['alter']['make_link'] = TRUE;
$this->options['alter']['path'] = 'admin/commerce/customer-profiles/' . $values->{$this->aliases['profile_id']};
if (isset($this->aliases['language'])) {
$languages = language_list();
if (isset($languages[$values->{$this->aliases['language']}])) {
$this->options['alter']['language'] = $languages[$values->{$this->aliases['language']}];
}
}
}
return $data;
}
function render($values) {
return $this->render_link(check_plain($values->{$this->field_alias}), $values);
}
}
<?php
// $Id$
/**
* Field handler to translate a customer profile type into its readable form.
*/
class commerce_customer_handler_field_customer_profile_type extends commerce_customer_handler_field_customer_profile {
function render($values) {
$value = commerce_customer_profile_type_get_name($values->{$this->field_alias});
return $this->render_link((check_plain($value)), $values);
}
}
<?php
// $Id$
/**
* Filter by customer profile type.
*/
class commerce_customer_handler_filter_customer_profile_type extends views_handler_filter_in_operator {
function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Customer profile type');
$types = commerce_customer_profile_types();
foreach ($types as $type => $info) {
$options[$type] = t($info->name);
}
$this->value_options = $options;
}
}
}
<?php
// $Id$
/**
* @file
* The controller for the customer profile entity containing the CRUD operations.
*/
/**
* The controller class for customer profiles contains methods for the profile
* CRUD operations. The load method is inherited from the default controller.
*/
class CommerceCustomerProfileEntityController extends DrupalDefaultEntityController {
/**
* Create a default customer profile.
*
* @param $type
* The machine-readable type of the customer profile.
* @param $uid
* The uid of the user the customer profile is for.
*
* @return
* A customer profile object with all default fields initialized.
*/
public function create($type = '', $uid = 0) {
return (object) array(
'profile_id' => '',
'revision_id' => '',
'type' => $type,
'uid' => $uid,
'status' => 1,
'created' => '',
'changed' => '',
);
}
/**
* Saves a customer profile.
*
* When saving a profile without an ID, this function will create a new
* profile at that time. Subsequent profiles that should be saved as new
* revisions should set $profile->revision to TRUE and include a log string in
* $profile->log.
*
* @param $profile
* The full customer profile object to save.
*
* @return
* The saved customer profile object.
*/
public function save($profile) {
$transaction = db_transaction();
try {
global $user;
// Determine if we will be inserting a new profile.
$profile->is_new = empty($profile->profile_id);
// Set the timestamp fields.
if (empty($profile->created)) {
$profile->created = REQUEST_TIME;
}
$profile->changed = REQUEST_TIME;
$profile->revision_timestamp = REQUEST_TIME;
$update_profile = TRUE;
// Give modules the opportunity to prepare field data for saving.
rules_invoke_all('commerce_customer_profile_presave', $profile);
field_attach_presave('commerce_customer_profile', $profile);
if ($profile->is_new || !empty($profile->revision)) {
// When inserting either a new profile or revision, $profile->log must
// be set because {commerce_customer_profile_revision}.log is a text
// column and therefore cannot have a default value. However, it might
// not be set at this point, so we ensure that it is at least an empty
// string in that case.
if (!isset($profile->log)) {
$profile->log = '';
}
}
elseif (empty($profile->log)) {
// If we are updating an existing profile without adding a new revision,
// we need to make sure $profile->log is unset whenever it is empty. As
// long as $profile->log is unset, drupal_write_record() will not attempt
// to update the existing database column when re-saving the revision.
unset($profile->log);
}
// When saving a new profile revision, unset any existing $profile->revision_id
// to ensure a new revision will actually be created and store the old
// revision ID in a separate property for profile hook implementations.
if (!$profile->is_new && !empty($profile->revision) && $profile->revision_id) {
$profile->old_revision_id = $profile->revision_id;
unset($profile->revision_id);
}
// If this is a new profile...
if ($profile->is_new) {
// Save the new profile and fields.
drupal_write_record('commerce_customer_profile', $profile);
// Save the initial revision.
$this->saveRevision($profile, $user->uid);
$op = 'insert';
}
else {
// Save the updated profile and fields.
drupal_write_record('commerce_customer_profile', $profile, 'profile_id');
// If a new profile revision was requested, save a new record for that;
// otherwise, update the profile revision record that matches the value
// of $profile->revision_id.
if (!empty($profile->revision)) {
$this->saveRevision($profile, $user->uid);
}
else {
$this->saveRevision($profile, $user->uid, TRUE);
$update_profile = FALSE;
}
$op = 'update';
}
// If the revision ID is new or updated, save it to the profile.
if ($update_profile) {
db_update('commerce_customer_profile')
->fields(array('revision_id' => $profile->revision_id))
->condition('profile_id', $profile->profile_id)
->execute();
}
// Save fields.
$function = 'field_attach_' . $op;
$function('commerce_customer_profile', $profile);
module_invoke_all('commerce_customer_profile_' . $op, $profile);
module_invoke_all('entity_' . $op, $profile, 'commerce_customer_profile');
rules_invoke_event('commerce_customer_profile_' . $op, $profile);
// Clear internal properties.
unset($profile->is_new);
$this->resetCache();
// Ignore slave server temporarily to give time for the
// saved profile to be propagated to the slave.
db_ignore_slave();
return $profile;
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('commerce_customer', $e);
throw $e;
}
}
/**
* Saves a customer profile revision.
*
* @param $profile
* The fully loaded customer profile object.
* @param $uid
* The user's uid for the current revision.
* @param $update
* TRUE or FALSE indicating whether or not the existing revision should be
* updated instead of a new one created.
*/
function saveRevision($profile, $uid, $update = FALSE) {
// Hold on to the profile's original uid but swap in the revision's uid for
// the momentary write.
$temp_uid = $profile->uid;
$profile->uid = $uid;
// Update the existing revision if specified.
if ($update) {
drupal_write_record('commerce_customer_profile_revision', $profile, 'revision_id');
}
else {
// Otherwise insert a new revision. This will automatically update $profile
// to include the revision_id.
drupal_write_record('commerce_customer_profile_revision', $profile);
}
// Reset the profile's uid to the original value.
$profile->uid = $temp_uid;
}
/**
* Deletes multiple customer profiles by ID.
*
* @param $profile_ids
* An array of profile IDs to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
public function delete($profile_ids) {
if (!empty($profile_ids)) {
$profiles = $this->load($profile_ids, array());
// TODO: We'll need to ensure profiles on orders cannot be deleted when we
// have orders implemented.
$transaction = db_transaction();
try {
db_delete('commerce_customer_profile')
->condition('profile_id', $profile_ids, 'IN')
->execute();
foreach ($profiles as $profile_id => $profile) {
module_invoke_all('commerce_customer_profile_delete', $profile);
field_attach_delete('commerce_customer_profile', $profile);
rules_invoke_event('commerce_customer_profile_delete', $profile);
}
// Ignore slave server temporarily to give time for the
// saved profile to be propagated to the slave.
db_ignore_slave();
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('commerce_customer', $e);
throw $e;
}
// Clear the page and block and customer_profile_load_multiple caches.
cache_clear_all();
$this->resetCache();
}
return TRUE;
}
}
<?php
// $Id$
/**
* @file
* Forms for creating / editing and deleting customer profiles.
*/
/**
* Form callback: create or edit a customer profile.
*
* @param $profile
* The profile object to edit or for a create form an empty profile object
* with only a profile type defined.
*/
function commerce_customer_customer_profile_form($form, &$form_state, $profile) {
// Ensure this include file is loaded when the form is rebuilt from the cache.
$form_state['build_info']['files']['form'] = drupal_get_path('module', 'commerce_customer') . '/includes/commerce_customer_profile.forms.inc';
// Add the field related form elements.
$form_state['customer_profile'] = $profile;
field_attach_form('commerce_customer_profile', $profile, $form, $form_state);
$form['status'] = array(
'#type' => 'radios',
'#title' => t('Status'),
'#description' => t('Disabled profiles will not be visible to customers.'),
'#options' => array(
'1' => t('Active'),
'0' => t('Disabled'),
),
'#default_value' => $profile->status,
'#required' => TRUE,
'#weight' => 35,
);
$form['actions'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('form-actions')),
'#weight' => 40,
);
// We add the form's #submit array to this button along with the actual submit
// handler to preserve any submit handlers added by a form callback_wrapper.
$submit = array();
if (!empty($form['#submit'])) {
$submit += $form['#submit'];
}
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save profile'),
'#submit' => $submit + array('commerce_customer_customer_profile_form_submit'),
);
// We append the validate handler to #validate in case a form callback_wrapper
// is used to add validate handlers earlier.
$form['#validate'][] = 'commerce_customer_customer_profile_form_validate';
return $form;
}
/**
* Validation callback for commerce_customer_profile_form().
*/
function commerce_customer_customer_profile_form_validate($form, &$form_state) {
$profile = $form_state['customer_profile'];
// Notify field widgets to validate their data.
field_attach_form_validate('commerce_customer_profile', $profile, $form, $form_state);
}
/**
* Submit callback for commerce_customer_profile_form().
*/
function commerce_customer_customer_profile_form_submit($form, &$form_state) {
global $user;
$profile = &$form_state['customer_profile'];
// Save default parameters back into the $profile object.
$profile->status = $form_state['values']['status'];
// Set the profile's uid if it's being created at this time.
if (empty($profile->profile_id)) {
$profile->uid = $user->uid;
}
// Notify field widgets.
field_attach_submit('commerce_customer_profile', $profile, $form, $form_state);
// Save the profile.
commerce_customer_profile_save($profile);
// Redirect based on the button clicked.
drupal_set_message(t('Profile saved.'));
}
/**
* Form callback: confirmation form for deleting a profile.
*
* @param $profile
* The profile object to be deleted.
*
* @see confirm_form()
*/
function commerce_customer_customer_profile_delete_form($form, &$form_state, $profile) {
$form_state['customer_profile'] = $profile;
// Ensure this include file is loaded when the form is rebuilt from the cache.
$form_state['build_info']['files']['form'] = drupal_get_path('module', 'commerce_customer') . '/includes/commerce_customer_profile.forms.inc';
$form['#submit'][] = 'commerce_customer_profile_delete_form_submit';
$form = confirm_form($form,
t('Are you sure you want to delete this profile?'),
'',
'<p>' . t('Deleting this profile cannot be undone.') . '</p>',
t('Delete'),
t('Cancel'),
'confirm'
);
return $form;
}
/**
* Submit callback for commerce_customer_profile_delete_form().
*/
function commerce_customer_customer_profile_delete_form_submit($form, &$form_state) {
$profile = $form_state['customer_profile'];
if (commerce_customer_profile_delete($profile->profile_id)) {
drupal_set_message(t('The profile has been deleted.'));
watchdog('commerce_customer_profile', 'Deleted customer profile @profile_id.', array('@profile_id' => $profile->profile_id), WATCHDOG_NOTICE);
}
else {
drupal_set_message(t('The profile could not be deleted.'), 'error');
}
}
; $Id$
name = Customer UI
description = Exposes a default UI for Customers through profile edit forms and default Views.
package = Commerce
core = 7.x
dependencies[] = contextual
dependencies[] = field_ui
dependencies[] = commerce_ui
dependencies[] = commerce_customer
dependencies[] = views
; Base module files
files[] = commerce_customer_ui.module
; Module includes
files[] = includes/commerce_customer_ui.profiles.inc
files[] = includes/commerce_customer_ui.profile_types.inc
; Views includes
files[] = includes/views/commerce_customer_ui.views_default.inc
<?php
// $Id$
/**
* @file
*/
/**
* Implements hook_menu().
*/
function commerce_customer_ui_menu() {
$items = array();
// Note: admin/commerce/customer-profiles is defined by a default View.
// Add a customer profile.
$items['admin/commerce/customer-profiles/add'] = array(
'title' => 'Add a customer profile',
'description' => 'Add a new customer profile.',
'page callback' => 'commerce_customer_ui_customer_profile_add_page',
'access callback' => 'commerce_customer_ui_customer_profile_add_any_access',
'weight' => 10,
'file' => 'includes/commerce_customer_ui.profiles.inc',
);
foreach (commerce_customer_profile_types() as $type => $profile_type) {
$items['admin/commerce/customer-profiles/add/' . strtr($type, array('_' => '-'))] = array(
'title' => 'Create @name',
'title arguments' => array('@name' => $profile_type['name']),
'description' => $profile_type['description'],
'page callback' => 'commerce_customer_ui_customer_profile_form_wrapper',
'page arguments' => array(commerce_customer_profile_new($type)),
'access callback' => 'commerce_customer_profile_access',
'access arguments' => array('create', $type),
'file' => 'includes/commerce_customer_ui.profiles.inc',
);
}
$items['admin/commerce/customer-profiles/%commerce_customer_profile'] = array(
'title callback' => 'commerce_customer_ui_customer_profile_title',
'title arguments' => array(3),
'page callback' => 'commerce_customer_ui_customer_profile_form_wrapper',
'page arguments' => array(3),
'access callback' => 'commerce_customer_profile_access',
'access arguments' => array('update', 3),
'weight' => 0,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
'file' => 'includes/commerce_customer_ui.profiles.inc',
);
$items['admin/commerce/customer-profiles/%commerce_customer_profile/edit'] = array(
'title' => 'Edit',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
);
$items['admin/commerce/customer-profiles/%commerce_customer_profile/delete'] = array(
'title' => 'Delete a customer profile',
'page callback' => 'commerce_customer_ui_customer_profile_delete_form_wrapper',
'page arguments' => array(3),
'access callback' => 'commerce_customer_profile_access',
'access arguments' => array('update', 3),
'file' => 'includes/commerce_customer_ui.profiles.inc',
);
$items['admin/commerce/customer-profiles/types'] = array(
'title' => 'Profile types',
'description' => 'Manage customer profile types for your store.',
'page callback' => 'commerce_customer_ui_customer_profile_types_overview',
'access arguments' => array('administer customer profile types'),
'type' => MENU_LOCAL_TASK,
'weight' => 0,
'file' => 'includes/commerce_customer_ui.profile_types.inc',
);
$items['admin/commerce/customer-profiles/types/%'] = array(
'title callback' => 'Profile types',
'page callback' => 'commerce_customer_ui_profile_type_redirect',
'page arguments' => array(4),
'access arguments' => array('administer customer profile types'),
);
return $items;
}
/**
* Menu item title callback: returns the ID of a customer profile for its pages.
*
* @param $profile
* The customer profile object as loaded via the URL wildcard.
* @return
* A page title of the format "Profile [profile-id]".
*/
function commerce_customer_ui_customer_profile_title($profile) {
return t('Customer profile @profile_id', array('@profile_id' => $profile->profile_id));
}
/**
* Access callback: determine if the user can create any type of profile.
*/
function commerce_customer_ui_customer_profile_add_any_access() {
// Grant automatic access to users with administer customer profiles permission.
if (user_access('administer customer profiles')) {
return TRUE;
}
// Check the user's access on a profile type basis.
foreach (commerce_customer_profile_types() as $type => $profile_type) {
if (commerce_customer_profile_access('create', $type)) {
return TRUE;
}
}
return FALSE;
}
/**
* Redirects a customer profile type URL to its fields management page.
*/
function commerce_customer_ui_profile_type_redirect($type) {
drupal_goto('admin/commerce/customer-profiles/types/' . strtr($type, array('_' => '-')) . '/fields');
}
/**
* Implements hook_menu_alter().
*/
function commerce_customer_ui_menu_alter(&$items) {
// Transform the field UI tabs into contextual links.
$items['admin/commerce/customer-profiles/types/%/fields']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
$items['admin/commerce/customer-profiles/types/%/display']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
}
/**
* Implements hook_menu_local_tasks_alter().
*/
function commerce_customer_ui_menu_local_tasks_alter(&$data, $router_item, $root_path) {
// Add action link 'admin/commerce/customer-profiles/add' on
// 'admin/commerce/customer-profiles'.
if ($root_path == 'admin/commerce/customer-profiles') {
$item = menu_get_item('admin/commerce/customer-profiles/add');
if ($item['access']) {
$data['actions']['output'][] = array(
'#theme' => 'menu_local_action',
'#link' => $item,
);
}
}
}
/**
* Implements hook_admin_menu_map().
*/
function commerce_customer_ui_admin_menu_map() {
// Add awareness to the administration menu of the various profile types so
// they are included in the dropdown menu.
$map['admin/commerce/customer-profiles/types/%'] = array(
'parent' => 'admin/commerce/customer-profiles/types',
'arguments' => array(
array('%' => array_keys(commerce_customer_profile_types())),
),
);
return $map;
}
/**
* Implements hook_help().
*/
function commerce_customer_ui_help($path, $arg) {
switch ($path) {
case 'admin/commerce/customer-profiles/types/add':
return '<p>' . t('Individual customer profile types can have different fields assigned to them.') . '</p>';
}
// Return the user defined help text per customer profile type when adding profiles.
if ($arg[1] == 'commerce' && $arg[2] == 'customer-profiles' && $arg[3] == 'add' && $arg[4]) {
$profile_type = commerce_customer_profile_type_load($arg[5]);
return (!empty($profile_type['help']) ? '<p>' . filter_xss_admin($profile_type['help']) . '</p>' : '');
}
}
/**
* Implements hook_theme().
*/
function commerce_customer_ui_theme() {
return array(
'customer_profile_add_list' => array(
'variables' => array('content' => array()),
'file' => 'includes/commerce_customer_ui.profiles.inc',
),
'customer_profile_type_admin_overview' => array(
'variables' => array('type' => NULL),
'file' => 'includes/commerce_customer_ui.profile_types.inc',
),
'commerce_customer_profile_status' => array(
'variables' => array('status' => NULL, 'label' => NULL, 'profile' => NULL),
'path' => drupal_get_path('module', 'commerce_customer_ui') . '/theme',
'template' => 'commerce-customer-profile-status',
),
);
}
/**
* Implements hook_entity_info_alter().
*/
function commerce_customer_ui_entity_info_alter(&$entity_info) {
// Add a URI callback to the profile entity.
$entity_info['commerce_customer_profile']['uri callback'] = 'commerce_customer_ui_customer_profile_uri';
// Expose the admin UI for profile fields.
foreach ($entity_info['commerce_customer_profile']['bundles'] as $type => &$bundle) {
$bundle['admin'] = array(
'path' => 'admin/commerce/customer-profiles/types/%',
'real path' => 'admin/commerce/customer-profiles/types/' . strtr($type, array('_' => '-')),
'access arguments' => array('administer customer profile types'),
);
}
}
/**
* Entity uri callback: points to the edit form of the given profile.
*/
function commerce_customer_ui_customer_profile_uri($profile) {
// Only return a value if the user has permission to view the profile.
if (commerce_customer_profile_access('view', $profile)) {
return array(
'path' => 'admin/commerce/customer-profiles/' . $profile->profile_id,
);
}
return NULL;
}
/**
* Implements hook_forms().
*/
function commerce_customer_ui_forms($form_id, $args) {
$forms = array();
// Define a wrapper ID for the customer profile add / edit form.
$forms['commerce_customer_ui_customer_profile_form'] = array(
'callback' => 'commerce_customer_customer_profile_form',
);
// Define a wrapper ID for the customer profile delete confirmation form.
$forms['commerce_customer_ui_customer_profile_delete_form'] = array(
'callback' => 'commerce_customer_customer_profile_delete_form',
);
return $forms;
}
/**
* Implements hook_form_alter().
*/
function commerce_customer_ui_form_alter(&$form, &$form_state, $form_id) {
// On field administration forms for customer profile types add a breadcrumb.
if (in_array($form_id, array('field_ui_field_overview_form', 'field_ui_display_overview_form'))) {
if ($form['#entity_type'] == 'commerce_customer_profile') {
// Load the customer profile type being modified for this form.
$profile_type = commerce_customer_profile_type_load($form['#bundle']);
drupal_set_title(check_plain($profile_type['name']));
commerce_customer_ui_set_breadcrumb(TRUE);
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* The Customer UI module instantiates the Profile add/edit form at particular
* paths in the Commerce IA. It uses its own form ID to do so and alters the
* form here to add in appropriate redirection and an additional button.
*
* @see commerce_customer_ui_customer_profile_form()
*/
function commerce_customer_ui_form_commerce_customer_ui_customer_profile_form_alter(&$form, &$form_state) {
// Add a submit handler to the save button to add a redirect.
$form['actions']['submit']['#submit'][] = 'commerce_customer_ui_customer_profile_form_submit';
// Add the save and continue button for new profiles.
if (empty($form_state['customer_profile']->profile_id)) {
$form['actions']['save_continue'] = array(
'#type' => 'submit',
'#value' => t('Save and add another'),
'#submit' => $form['actions']['submit']['#submit'],
'#suffix' => l('Cancel', 'admin/commerce/customer-profiles'),
'#weight' => 45,
);
}
else {
$form['actions']['submit']['#suffix'] = l('Cancel', 'admin/commerce/customer-profiles');
}
}
/**
* Submit callback for commerce_customer_ui_customer_profile_form().
*
* @see commerce_customer_ui_form_commerce_customer_ui_customer_profile_form_alter()
*/
function commerce_customer_ui_customer_profile_form_submit($form, &$form_state) {
// Set the redirect based on the button clicked.
if ($form_state['clicked_button']['#parents'][0] == 'save_continue') {
$form_state['redirect'] = 'admin/commerce/customer-profiles/add/' . strtr($form_state['customer_profile']->type, array('_' => '-'));
}
elseif (arg(2) == 'customer-profiles' && arg(3) == 'add') {
$form_state['redirect'] = 'admin/commerce/customer-profiles';
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* The Customer UI module instantiates the Profile delete form at a particular
* path in the Commerce IA. It uses its own form ID to do so and alters the
* form here to add in appropriate redirection.
*
* @see commerce_customer_ui_customer_profile_delete_form()
*/
function commerce_customer_ui_form_commerce_customer_ui_customer_profile_delete_form_alter(&$form, &$form_state) {
$form['actions']['cancel']['#markup'] = l(t('Cancel'), 'admin/commerce/customer-profiles');
$form['#submit'][] = 'commerce_customer_ui_customer_profile_delete_form_submit';
}
/**
* Submit callback for commerce_customer_ui_customer_profile_delete_form().
*
* @see commerce_customer_ui_form_commerce_customer_ui_customer_profile_delete_form_alter()
*/
function commerce_customer_ui_customer_profile_delete_form_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/commerce/customer-profiles';
}
/**
* Implements hook_views_api().
*/
function commerce_customer_ui_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'commerce_customer_ui') . '/includes/views',
);
}
/**
* Sets the breadcrumb for administrative customer pages.
*
* @param $profiles
* TRUE or FALSE indicating whether or not the breadcrumb should include the
* profiles overview page.
* @param $profile_types
* TRUE or FALSE indicating whether or not the breadcrumb should include the
* profile types administrative page.
*/
function commerce_customer_ui_set_breadcrumb($profile_types = FALSE) {
$breadcrumb = array(
l(t('Home'), '<front>'),
l(t('Administration'), 'admin'),
l(t('Store'), 'admin/commerce'),
l(t('Customer profiles'), 'admin/commerce/customer-profiles'),
);
if ($profile_types) {
$breadcrumb[] = l(t('Profile types'), 'admin/commerce/customer-profiles/types');
}
drupal_set_breadcrumb($breadcrumb);
}
/* $Id$ */
.links.operations {
text-transform: lowercase;
}
<?php
// $Id$
/**
* @file
*/
/**
* Menu callback: display an overview of available types.
*/
function commerce_customer_ui_customer_profile_types_overview() {
drupal_add_css(drupal_get_path('module', 'commerce_customer_ui') . '/theme/commerce_customer_ui.profile_types.css');
$header = array(
t('Name'),
t('Operations'),
);
$rows = array();
// Loop through all defined customer profile types.
foreach (commerce_customer_profile_types() as $type => $profile_type) {
// Build the operation links for the current profile type.
$links = menu_contextual_links('commerce-customer-profile-type', 'admin/commerce/customer-profiles/types', array(strtr($type, array('_' => '-'))));
// Add the profile type's row to the table's rows array.
$rows[] = array(
theme('customer_profile_type_admin_overview', array('profile_type' => $profile_type)),
theme('links', array('links' => $links, 'attributes' => array('class' => 'links inline operations'))),
);
}
// If no profile types are defined...
if (empty($rows)) {
// Add a standard empty row with a link to add a new profile type.
$rows[] = array(
array(
'data' => t('There are no customer profile types yet. <a href="@link">Add profile type</a>.', array('@link' => url('admin/commerce/customers/profiles/types/add'))),
'colspan' => 2,
)
);
}
return theme('table', array('header' => $header, 'rows' => $rows));
}
/**
* Builds an overview of a customer profile type for display to an administrator.
*
* @param $variables
* An array of variables used to generate the display; by default includes the
* type key with a value of the profile type object.
*
* @ingroup themeable
*/
function theme_customer_profile_type_admin_overview($variables) {
$profile_type = $variables['profile_type'];
$output = check_plain($profile_type['name']);
$output .= ' <small> (Machine name: ' . check_plain($profile_type['type']) . ')</small>';
$output .= '<div class="description">' . filter_xss_admin($profile_type['description']) . '</div>';
return $output;
}
<?php
// $Id$
/**
* @file
* Page callbacks and form builder functions for administering customer profiles.
*/
/**
* Menu callback: display a list of customer profile types that the user can create.
*/
function commerce_customer_ui_customer_profile_add_page() {
$item = menu_get_item();
$content = system_admin_menu_block($item);
// Bypass the admin/commerce/customers/profiles/add listing if only one
// customer profile available.
if (count($content) == 1) {
$item = array_shift($content);
drupal_goto($item['href']);
}
return theme('customer_profile_add_list', array('content' => $content));
}
/**
* Displays the list of available customer profile types for profile creation.
*
* @ingroup themeable
*/
function theme_customer_profile_add_list($variables) {
$content = $variables['content'];
$output = '';
if ($content) {
$output = '<dl class="commerce-customer-profile-type-list">';
foreach ($content as $item) {
$output .= '<dt>' . l($item['title'], $item['href'], $item['localized_options']) . '</dt>';
$output .= '<dd>' . filter_xss_admin($item['description']) . '</dd>';
}
$output .= '</dl>';
}
else {
if (user_access('administer customer profile types')) {
$output = '<p>' . t('You have not enabled modules defining any customer profile types yet.') . '</p>';
}
else {
$output = '<p>' . t('No customer profile types have been enabled yet for you to use.') . '</p>';
}
}
return $output;
}
/**
* Form callback wrapper: create or edit a customer profile.
*
* @param $profile
* The customer profile object being edited by this form.
*
* @see commerce_customer_customer_profile_form()
*/
function commerce_customer_ui_customer_profile_form_wrapper($profile) {
// Add the breadcrumb for the form's location.
commerce_customer_ui_set_breadcrumb();
// Include the forms file from the Customer module.
module_load_include('inc', 'commerce_customer', 'includes/commerce_customer_profile.forms');
return drupal_get_form('commerce_customer_ui_customer_profile_form', $profile);
}
/**
* Form callback wrapper: confirmation form for deleting a customer profile.
*
* @param $profile
* The customer profile object being deleted by this form.
*
* @see commerce_customer_customer_profile_delete_form()
*/
function commerce_profile_ui_customer_profile_delete_form_wrapper($profile) {
// Add the breadcrumb for the form's location.
commerce_profile_ui_set_breadcrumb(TRUE);
// Include the forms file from the Customer module.
module_load_include('inc', 'commerce_customer', 'includes/commerce_customer_profile.forms');
return drupal_get_form('commerce_customer_ui_customer_profile_delete_form', $profile);
}
<?php
// $Id$
/**
* Views for the default customer UI.
*/
/**
* Implements hook_views_default_views().
*/
function commerce_customer_ui_views_default_views() {
$views = array();
// Customer profile admin list at admin/commerce/customer-profiles.
$view = new view;
$view->name = 'commerce_customer_profiles';
$view->description = 'A list of customer profiles of all types.';
$view->tag = 'commerce';
$view->view_php = '';
$view->base_table = 'commerce_customer_profile';
$view->is_cacheable = FALSE;
$view->api_version = '3.0-alpha1';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
/* Display: Defaults */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->display->display_options['title'] = 'Customer profiles';
$handler->display->display_options['access']['type'] = 'perm';
$handler->display->display_options['access']['perm'] = 'administer customer profiles';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'full';
$handler->display->display_options['pager']['options']['items_per_page'] = '50';
$handler->display->display_options['pager']['options']['offset'] = '0';
$handler->display->display_options['pager']['options']['id'] = '0';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'profile_id' => 'profile_id',
'name' => 'name',
'type' => 'type',
'edit_customer_profile' => 'edit_customer_profile',
);
$handler->display->display_options['style_options']['default'] = 'profile_id';
$handler->display->display_options['style_options']['info'] = array(
'profile_id' => array(
'sortable' => 1,
'align' => '',
'separator' => '',
),
'name' => array(
'sortable' => 1,
'align' => '',
'separator' => '',
),
'type' => array(
'sortable' => 1,
'align' => '',
'separator' => '',
),
'edit_customer_profile' => array(
'align' => '',
'separator' => '',
),
);
$handler->display->display_options['style_options']['override'] = 1;
$handler->display->display_options['style_options']['sticky'] = 0;
/* Relationship: Commerce Customer Profile: Owner */
$handler->display->display_options['relationships']['uid']['id'] = 'uid';
$handler->display->display_options['relationships']['uid']['table'] = 'commerce_customer_profile';
$handler->display->display_options['relationships']['uid']['field'] = 'uid';
$handler->display->display_options['relationships']['uid']['required'] = 0;
/* Field: Commerce Customer Profile: Profile ID */
$handler->display->display_options['fields']['profile_id']['id'] = 'profile_id';
$handler->display->display_options['fields']['profile_id']['table'] = 'commerce_customer_profile';
$handler->display->display_options['fields']['profile_id']['field'] = 'profile_id';
$handler->display->display_options['fields']['profile_id']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['profile_id']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['profile_id']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['profile_id']['alter']['trim'] = 0;
$handler->display->display_options['fields']['profile_id']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['profile_id']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['profile_id']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['profile_id']['alter']['html'] = 0;
$handler->display->display_options['fields']['profile_id']['hide_empty'] = 0;
$handler->display->display_options['fields']['profile_id']['empty_zero'] = 0;
$handler->display->display_options['fields']['profile_id']['link_to_profile'] = 1;
/* Field: User: Name */
$handler->display->display_options['fields']['name']['id'] = 'name';
$handler->display->display_options['fields']['name']['table'] = 'users';
$handler->display->display_options['fields']['name']['field'] = 'name';
$handler->display->display_options['fields']['name']['relationship'] = 'uid';
$handler->display->display_options['fields']['name']['label'] = 'User';
$handler->display->display_options['fields']['name']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['name']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['name']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['name']['alter']['trim'] = 0;
$handler->display->display_options['fields']['name']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['name']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['name']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['name']['alter']['html'] = 0;
$handler->display->display_options['fields']['name']['hide_empty'] = 0;
$handler->display->display_options['fields']['name']['empty_zero'] = 0;
$handler->display->display_options['fields']['name']['link_to_user'] = 1;
$handler->display->display_options['fields']['name']['overwrite_anonymous'] = 0;
/* Field: Commerce Customer Profile: Type */
$handler->display->display_options['fields']['type']['id'] = 'type';
$handler->display->display_options['fields']['type']['table'] = 'commerce_customer_profile';
$handler->display->display_options['fields']['type']['field'] = 'type';
$handler->display->display_options['fields']['type']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['type']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['type']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['type']['alter']['trim'] = 0;
$handler->display->display_options['fields']['type']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['type']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['type']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['type']['alter']['html'] = 0;
$handler->display->display_options['fields']['type']['hide_empty'] = 0;
$handler->display->display_options['fields']['type']['empty_zero'] = 0;
$handler->display->display_options['fields']['type']['link_to_profile'] = 0;
/* Field: Commerce Customer Profile: Edit link */
$handler->display->display_options['fields']['edit_customer_profile']['id'] = 'edit_customer_profile';
$handler->display->display_options['fields']['edit_customer_profile']['table'] = 'commerce_customer_profile';
$handler->display->display_options['fields']['edit_customer_profile']['field'] = 'edit_customer_profile';
$handler->display->display_options['fields']['edit_customer_profile']['label'] = 'Operations';
$handler->display->display_options['fields']['edit_customer_profile']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['edit_customer_profile']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['edit_customer_profile']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['edit_customer_profile']['alter']['trim'] = 0;
$handler->display->display_options['fields']['edit_customer_profile']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['edit_customer_profile']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['edit_customer_profile']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['edit_customer_profile']['alter']['html'] = 0;
$handler->display->display_options['fields']['edit_customer_profile']['hide_empty'] = 0;
$handler->display->display_options['fields']['edit_customer_profile']['empty_zero'] = 0;
/* Display: Admin page */
$handler = $view->new_display('page', 'Admin page', 'page_1');
$handler->display->display_options['path'] = 'admin/commerce/customer-profiles/list';
$handler->display->display_options['menu']['type'] = 'default tab';
$handler->display->display_options['menu']['title'] = 'List';
$handler->display->display_options['menu']['weight'] = '-10';
$handler->display->display_options['tab_options']['type'] = 'normal';
$handler->display->display_options['tab_options']['title'] = 'Customer profiles';
$handler->display->display_options['tab_options']['description'] = 'Manage customer profiles and profile types in the store.';
$handler->display->display_options['tab_options']['weight'] = '0';
$handler->display->display_options['tab_options']['name'] = 'management';
$views[$view->name] = $view;
return $views;
}
<?php
// $Id$
/**
* @file
* Hooks provided by the Line Item module.
*/
/**
* Defines links for use in line item summary area handlers on Views.
*
* The line item summary area handler totals the value of the various line items
* in a View and optionally includes a set of links. These are used in the core
* shopping cart block View to let the user browse straight to the shopping cart
* form or the checkout form. The format of the return value is a links array as
* required by theme_links() with the addition of a weight parameter used to
* sort the links prior to display.
*
* @return
* An associative array of link arrays keyed by link names, with the names
* being appended to the class of each link's list item when rendered by
* theme_links(). Link arrays should include the following key / value
* properties expected by theme_links():
* - title: the link text
* - href: the link URL; if ommitted, the link is rendered as plain text
* - html: boolean indicating whether or not the link text should be rendered
* as HTML or escaped; defaults to FALSE
* - weight: custom to this hook, the weight property is an integer value used
* to sort links prior to rendering; defaults to 0
* - access: custom to this hook, a boolean value indicating whether or not
* the current user has access to the link; defaults to TRUE
* The full link array will be passed to theme_link(), meaning any additional
* properties can be included as desired (such as the attributes array as
* demonstrated below).
*
* @see commerce_line_item_summary_links()
* @see commerce_cart_commerce_line_item_summary_link_info()
* @see theme_links()
*/
function hook_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,
'access' => user_access('access checkout'),
),
);
}
/**
* Allows you to alter line item summary links.
*
* @param $links
* Array of line item summary links keyed by name exposed by
* hook_commerce_line_item_summary_link_info() implementations.
*
* @see hook_commerce_line_item_summary_link_info()
*/
function hook_commerce_line_item_summary_link_info_alter(&$links) {
// Alter the weight of the checkout link to display before the view cart link.
if (!empty($links['checkout'])) {
$links['checkout']['weight'] = -5;
}
}
/**
* Allows you to prepare line item data before it is saved for the first time,
* i.e. on insert.
*
* @param $line_item
* The line item object to be saved.
*
* @see rules_invoke_all()
*/
function hook_commerce_line_item_presave_insert(&$line_item) {
// No example.
}
/**
* Allows you to prepare line item data before subsequent saves, i.e. on update.
*
* @param $line_item
* The line item object to be saved.
*
* @see rules_invoke_all()
*/
function hook_commerce_line_item_presave_update(&$line_item) {
// No example.
}
<?php
// $Id$
/**
* @file
* The controller for the line item entity containing the CRUD operations.
*/
/**
* The controller class for line items contains methods for the line item CRUD
* operations. The load method is inherited from the default controller.
*/
class CommerceLineItemEntityController extends DrupalDefaultEntityController {
/**
* Create a default line item.
*
* @param $type
* The machine-readable type of the line item.
* @param $order_id
* The ID of the order the line item belongs to (if available).
*
* @return
* A line item object with all default fields initialized.
*/
public function create($type = '', $order_id = 0) {
return (object) array(
'line_item_id' => '',
'order_id' => $order_id,
'type' => $type,
'line_item_label' => '',
'quantity' => 1,
'created' => '',
'changed' => '',
);
}
/**
* Saves a line item.
*
* @param $line_item
* The full line item object to save.
*
* @return
* The saved line item object.
*/
public function save($line_item) {
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$transaction = db_transaction();
try {
$line_item->changed = REQUEST_TIME;
// Update the total of the line item based on the quantity and unit price.
$wrapper->commerce_total->amount = $line_item->quantity * $wrapper->commerce_unit_price->amount->value();
$wrapper->commerce_total->currency_code = $wrapper->commerce_unit_price->currency_code->value();
// Give modules the opportunity to prepare field data for saving.
rules_invoke_all('commerce_line_item_presave', $line_item);
field_attach_presave('commerce_line_item', $line_item);
// If this is a new line item...
if (empty($line_item->line_item_id)) {
// Set the creation timestamp.
$line_item->created = REQUEST_TIME;
// Save the new line item and fields.
drupal_write_record('commerce_line_item', $line_item);
field_attach_insert('commerce_line_item', $line_item);
$op = 'insert';
}
else {
// Save the updated line item and fields.
drupal_write_record('commerce_line_item', $line_item, 'line_item_id');
field_attach_update('commerce_line_item', $line_item);
$op = 'update';
}
module_invoke_all('commerce_line_item_' . $op, $line_item);
module_invoke_all('entity_' . $op, $line_item, 'commerce_line_item');
rules_invoke_event('commerce_line_item_' . $op, $line_item);
// Ignore slave server temporarily to give time for the saved line item to
// be propagated to the slave.
db_ignore_slave();
return $line_item;
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('commerce_line_item', $e);
throw $e;
}
}
/**
* Deletes multiple line items by ID.
*
* @param $line_item_ids
* An array of line item IDs to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
public function delete($line_item_ids) {
if (!empty($line_item_ids)) {
$line_items = $this->load($line_item_ids, array());
$transaction = db_transaction();
try {
db_delete('commerce_line_item')
->condition('line_item_id', $line_item_ids, 'IN')
->execute();
foreach ($line_items as $line_item_id => $line_item) {
module_invoke_all('commerce_line_item_delete', $line_item);
field_attach_delete('commerce_line_item', $line_item);
rules_invoke_event('commerce_line_item_delete', $line_item);
}
// Ignore slave server temporarily to give time for the
// saved line item to be propagated to the slave.
db_ignore_slave();
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('commerce_line_item', $e);
throw $e;
}
// Clear the page and block and line_item_load_multiple caches.
cache_clear_all();
$this->resetCache();
}
return TRUE;
}
}
<?php
// $Id$
/**
* @file
* Functions for generating line item editing form to be used in line item views.
*/
/**
* Generates a Views based line item edit form for line items on a single order.
*
* This form currently only supports line items from a single order, and it
* determines which order the line items are for based on a Views argument.
* Therefore, the View must include an Order ID argument or an empty form array
* will be returned.
*/
function commerce_line_item_views_form($form, &$form_state, $view, $output) {
// Require the existence of an order_id argument.
if (empty($view->argument['order_id'])) {
return array();
}
$form['#attached']['css'][] = drupal_get_path('module', 'commerce_line_item') . '/theme/commerce_line_item_views_form.css';
// Wrap the form in a div so we can add CSS and javascript behaviors to it.
$form['#prefix'] = '<div class="commerce-line-item-views-form">';
$form['#suffix'] = '</div>';
// Load the order from the Views argument.
$order = commerce_order_load($view->argument['order_id']->value[0]);
$form_state['order'] = $order;
// Add the form arguments to the form array so they are included when the form
// array is passed to the theme function.
$form['view'] = array(
'#type' => 'value',
'#value' => $view,
);
$form['output'] = array(
'#type' => 'value',
'#value' => $output,
);
// Loop over each field on the View...
foreach ($view->field as $field_name => $field) {
// If it extends the line item field edit handler...
if(is_a($field, 'commerce_line_item_handler_field_edit')) {
// Retrieve the form array for this item from the handler.
$field_edit_form = $field->get_edit_form();
// Add it to the current form if data was returned.
if (!empty($field_edit_form)) {
$form[$field_name] = $field_edit_form + array('#tree' => TRUE);
}
}
}
// Add the handlers manually since we're using hook_forms() to associate this
// form with form IDs based on the View used to display it.
$form['#validate'][] = 'commerce_line_item_views_form_validate';
$form['#submit'][] = 'commerce_line_item_views_form_submit';
// Now add the submit buttons after setting the handlers, as it's likely that
// buttons will need to add button specific handlers based on the form level
// validate and submit handlers.
$form['actions'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('form-actions')),
'#weight' => 100,
);
$form['actions']['update'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#submit' => array_merge($form['#submit'], array('commerce_line_item_line_item_views_form_submit')),
);
return $form;
}
/**
* Validate handler for the line item views form.
*/
function commerce_line_item_views_form_validate($form, &$form_state) {
$view = $form['view']['#value'];
// Call the validation method on every field handler that extends
// commerce_line_item_handler_field_edit.
foreach ($view->field as $field_name => $field) {
if(is_a($field, 'commerce_line_item_handler_field_edit')) {
$field->edit_form_validate($form, $form_state);
}
}
}
/**
* Submit handler for the line item views form.
*/
function commerce_line_item_views_form_submit($form, &$form_state) {
$view = $form['view']['#value'];
// Call the submit method on every field handler that extends
// commerce_line_item_handler_field_edit.
foreach ($view->field as $field_name => $field) {
if(is_a($field, 'commerce_line_item_handler_field_edit')) {
$field->edit_form_submit($form, $form_state);
}
}
}
/**
* Themes the line item Views form.
*/
function theme_commerce_line_item_views_form($variables) {
$form = $variables['form'];
// Extract the form arguments from the form array.
$view = $form['view']['#value'];
$output = $form['output']['#value'];
// For each field on the View that extends the edit field handler, prepare its
// placeholder in the output to be replaced with the rendered form element
// that should go there.
$search = array();
$replace = array();
foreach ($view->field as $field_name => $field) {
if(is_a($field, 'commerce_line_item_handler_field_edit')) {
// Form elements have been prepared keyed by ID in association with the
// Views field name, so loop over them and render the form element.
foreach (element_children($form[$field_name]) as $line_item_id) {
$search[] = '<!--post-commerce-line-item-' . $field_name . '-' . $line_item_id . '-->';
$replace[] = drupal_render($form[$field_name][$line_item_id]);
}
}
}
// Add line item action submit buttons.
$search[] = '<!--post-commerce-line-item-buttons-->';
$replace[] = drupal_render($form['actions']);
// Apply replacements to the rendered output.
$output = str_replace($search, $replace, $output);
// Render and add remaining form fields.
$output .= drupal_render_children($form);
return $output;
}
; $Id$
name = Line Item
description = Defines the Line Item entity and associated features.
package = Commerce
dependencies[] = rules
dependencies[] = commerce_price
core = 7.x
; Module includes
files[] = includes/commerce_line_item.controller.inc
; Views handlers
files[] = includes/views/handlers/commerce_line_item_handler_area_line_item_summary.inc
files[] = includes/views/handlers/commerce_line_item_handler_area_line_item_actions.inc
files[] = includes/views/handlers/commerce_line_item_handler_argument_line_item_line_item_id.inc
files[] = includes/views/handlers/commerce_line_item_handler_field_line_item_type.inc
files[] = includes/views/handlers/commerce_line_item_handler_filter_line_item_type.inc
files[] = includes/views/handlers/commerce_line_item_handler_field_edit.inc
files[] = includes/views/handlers/commerce_line_item_handler_field_edit_quantity.inc
files[] = includes/views/handlers/commerce_line_item_handler_field_edit_delete.inc
; Simple tests
; files[] = tests/commerce_line_item.test
<?php
// $Id$
/**
* @file
* Provides metadata for the line item entity.
*/
/**
* Implements hook_entity_property_info().
*/
function commerce_line_item_entity_property_info() {
$info = array();
// Add meta-data about the basic commerce_line_item properties.
$properties = &$info['commerce_line_item']['properties'];
$properties['line_item_id'] = array(
'label' => t('Line item ID'),
'description' => t('The internal numeric ID of the line item.'),
'type' => 'integer',
);
$properties['order_id'] = array(
'label' => t('Order ID', array(), array('context' => 'a drupal commerce order')),
'type' => 'integer',
'description' => t('The unique ID of the order the line item belongs to.'),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer line items',
'clear' => array('order'),
'query callback' => 'entity_metadata_table_query',
);
$properties['order'] = array(
'label' => t('Order', array(), array('context' => 'a drupal commerce order')),
'type' => 'commerce_order',
'description' => t('The order the line item belongs to.'),
'getter callback' => 'commerce_line_item_get_properties',
'setter callback' => 'commerce_line_item_set_properties',
'setter permission' => 'administer line items',
'required' => TRUE,
'clear' => array('order_id'),
);
$properties['type'] = array(
'label' => t('Type'),
'description' => t('The human readable name of the line item type.'),
'type' => 'token',
'setter callback' => 'entity_property_verbatim_set',
'options list' => 'commerce_line_item_type_options_list',
'query callback' => 'entity_metadata_table_query',
'required' => TRUE,
);
$properties['line_item_label'] = array(
'label' => t('Line item label'),
'description' => t('The label displayed with the line item.'),
'type' => 'text',
'setter callback' => 'entity_property_verbatim_set',
'query callback' => 'entity_metadata_table_query',
'required' => TRUE,
);
$properties['quantity'] = array(
'label' => t('Quantity'),
'description' => t('Quantity associated with this line item'),
'type' => 'decimal',
'getter callback' => 'entity_property_verbatim_get',
'setter callback' => 'entity_property_verbatim_set',
'required' => TRUE,
);
$properties['created'] = array(
'label' => t('Date created'),
'description' => t('The date the line item was created.'),
'type' => 'date',
'setter callback' => 'entity_metadata_verbatim_set',
'setter permission' => 'administer line items',
'query callback' => 'entity_metadata_table_query',
);
$properties['changed'] = array(
'label' => t('Date changed'),
'description' => t('The date the line item was most recently updated.'),
'type' => 'date',
'query callback' => 'entity_metadata_table_query',
);
return $info;
}
/**
* Implements hook_entity_property_info_alter() on top of the Line Item module.
*/
function commerce_line_item_entity_property_info_alter(&$info) {
// Move the price properties to the line item by default; as they are required
// default fields, this makes dealing with them more convenient.
$properties = array();
foreach ($info['commerce_line_item']['bundles'] as $bundle => $bundle_info) {
$bundle_info += array('properties' => array());
$properties += $bundle_info['properties'];
}
$info['commerce_line_item']['properties']['commerce_unit_price'] = $properties['commerce_unit_price'];
$info['commerce_line_item']['properties']['commerce_total'] = $properties['commerce_total'];
}
<?php
// $Id$
/**
* Implements hook_schema().
*/
function commerce_line_item_schema() {
$schema = array();
$schema['commerce_line_item'] = array(
'description' => 'The base table for line items.',
'fields' => array(
'line_item_id' => array(
'description' => 'The primary identifier for a line item.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'order_id' => array(
'description' => 'The unique ID of the order the line item belongs to.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'type' => array(
'description' => 'The module defined type of this line item.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'line_item_label' => array(
'description' => 'The merchant defined label for a line item.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'quantity' => array(
'type' => 'numeric',
'size' => 'normal',
'not null' => TRUE,
'default' => 0,
'precision' => 10,
'scale' => 2,
),
'created' => array(
'description' => 'The Unix timestamp when the line item was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'changed' => array(
'description' => 'The Unix timestamp when the line item was most recently saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('line_item_id'),
);
return $schema;
}
/**
* Implements hook_field_schema().
*/
function commerce_line_item_field_schema($field) {
if ($field['type'] == 'commerce_line_item_reference') {
return array(
'columns' => array(
'line_item_id' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
),
),
'indexes' => array(
'line_item_id' => array('line_item_id'),
),
'foreign keys' => array(
'line_item_id' => array(
'table' => 'commerce_line_item',
'columns' => array('line_item_id' => 'line_item_id'),
),
),
);
}
}
/**
* Implements hook_uninstall().
*/
function commerce_line_item_uninstall() {
field_delete_field('commerce_unit_price');
field_delete_field('commerce_total');
}
/* $Id$ */
#line-item-manager caption {
font-size: 100%;
font-weight: bold;
padding-bottom: 5px;
text-align: left;
text-transform: uppercase;
}
.add-line-item .form-type-select {
float: left;
padding: 3px 10px 0 0;
}
<?php
// $Id$
/**
* @file
* Defines the core Commerce line item entity and API functions interact with
* line items on orders.
*/
/**
* Implements hook_entity_info().
*/
function commerce_line_item_entity_info() {
$return = array(
'commerce_line_item' => array(
'label' => t('Line item'),
'controller class' => 'CommerceLineItemEntityController',
'base table' => 'commerce_line_item',
'fieldable' => TRUE,
'entity keys' => array(
'id' => 'line_item_id',
'bundle' => 'type',
'label' => 'line_item_id', // TODO: Update to use a custom callback.
),
'bundle keys' => array(
'bundle' => 'type',
),
'bundles' => array(),
'load hook' => 'commerce_line_item_load',
'view modes' => array(
'display' => array(
'label' => t('Display'),
'custom settings' => FALSE,
),
),
'creation callback' => '_commerce_line_item_create',
'save callback' => 'commerce_line_item_save',
'deletion callback' => 'commerce_line_item_delete',
'access callback' => 'commerce_line_item_access',
'token type' => 'line_item',
),
);
foreach (commerce_line_item_type_get_name() as $type => $name) {
$return['commerce_line_item']['bundles'][$type] = array(
'label' => $name,
);
}
return $return;
}
/**
* Implements hook_field_extra_fields().
*/
function commerce_line_item_field_extra_fields() {
$extra = array();
foreach (commerce_line_item_types() as $type => $line_item_type) {
$extra['commerce_line_item'][$type] = array(
'form' => array(
'label' => array(
'label' => t('Line item label'),
'description' => t('Line item module label form element'),
'weight' => -10,
),
'quantity' => array(
'label' => t('Quantity'),
'description' => t('Line item module quantity form element'),
'weight' => -5,
),
),
'display' => array(
'label' => array(
'label' => t('Line item label'),
'description' => t('Short descriptive label for the line item'),
'weight' => -10,
),
'quantity' => array(
'label' => t('Quantity'),
'description' => t('Quantity associated with this line item'),
'weight' => -5,
),
),
);
}
return $extra;
}
/**
* Implements hook_theme().
*/
function commerce_line_item_theme() {
return array(
'commerce_line_item_manager' => array(
'render element' => 'form',
),
'commerce_line_item_summary' => array(
'variables' => array('quantity_raw' => NULL, 'quantity_label' => NULL, 'quantity' => NULL, 'total_raw' => NULL, 'total' => NULL, 'links' => NULL, 'view' => NULL),
'path' => drupal_get_path('module', 'commerce_line_item') . '/theme',
'template' => 'commerce-line-item-summary',
),
'commerce_line_item_views_form' => array(
'render element' => 'form',
),
'commerce_line_item_actions' => array(
'variables' => array('buttons' => NULL),
),
);
}
/**
* Implements hook_forms().
*
* To provide distinct form IDs for line item Views forms, the View name and
* specific display name are appended to the base ID,
* commerce_line_item_views_form. When such a form is built or submitted, this
* function will return the proper callback function to use for the given form.
*/
function commerce_line_item_forms($form_id, $args) {
if (strpos($form_id, 'commerce_line_item_views_form_') === 0) {
return array(
$form_id => array(
'callback' => 'commerce_line_item_views_form',
),
);
}
}
/**
* Returns a form ID for a line item Views form using the name and display of
* the View used to build the form.
*/
function commerce_line_item_views_form_id($view) {
$parts = array(
'commerce_line_item_views_form',
$view->name,
$view->current_display,
);
return implode('_', $parts);
}
/**
* Submit handler used when clicking the update button.
*/
function commerce_line_item_line_item_views_form_submit($form, &$form_state) {
drupal_set_message(t('Line items saved.'));
}
/**
* Adds the necessary CSS for the line item summary template.
*/
function template_preprocess_commerce_line_item_summary(&$variables) {
drupal_add_css(drupal_get_path('module', 'commerce_line_item') . '/theme/commerce_line_item_summary.css');
}
/**
* Implements hook_views_api().
*/
function commerce_line_item_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'commerce_line_item') . '/includes/views',
);
}
/**
* Implements hook_enable().
*/
function commerce_line_item_enable() {
// Loop through and configure all the currently defined line item types.
foreach (commerce_line_item_types() as $line_item_type) {
commerce_line_item_configure_line_item_type($line_item_type);
}
}
/**
* Implements hook_modules_enabled().
*/
function commerce_line_item_modules_enabled($modules) {
// Loop through all the enabled modules.
foreach ($modules as $module) {
// If the module implements hook_commerce_line_item_type_info()...
if (module_hook($module, 'commerce_line_item_type_info')) {
$line_item_types = module_invoke($module, 'commerce_line_item_type_info');
// Loop through and configure the line item types defined by the module.
foreach ($line_item_types as $line_item_type) {
commerce_line_item_configure_line_item_type($line_item_type);
}
}
}
}
/**
* Implements hook_permission().
*/
function commerce_line_item_permission() {
$permissions = array(
'administer line item types' => array(
'title' => t('Administer line item types'),
'description' => t('View and configure fields attached to module defined line item types.'),
'restrict access' => TRUE,
),
'administer line items' => array(
'title' => t('Administer line items'),
'description' => t('Update and delete any line item on the site.'),
'restrict access' => TRUE,
),
);
return $permissions;
}
/**
* Implements hook_commerce_line_item_delete().
*
* When a line item is deleted, any object referencing it should have its field
* data updated so the reference is wiped.
*/
function commerce_line_item_commerce_line_item_delete($line_item) {
// Check the data in every line item reference field.
foreach (commerce_info_fields('commerce_line_item_reference') as $field_name => $field) {
// Query for any entity referencing the deleted line item in this field.
$query = new EntityFieldQuery();
$query->fieldCondition($field_name, 'line_item_id', $line_item->line_item_id, '=');
$result = $query->execute();
// If results were returned...
if (!empty($result)) {
// Loop over results for each type of entity returned.
foreach ($result as $entity_type => $data) {
// Load the entities of the current type.
$entities = entity_load($entity_type, array_keys($data));
// Loop over each entity and remove the reference to the deleted line item.
foreach ($entities as $entity_id => $entity) {
$order = commerce_line_item_reference_delete($entity_type, $entity, $field_name, $line_item->line_item_id);
commerce_order_save($order);
}
}
}
}
}
/**
* Implements hook_field_attach_delete().
*
* When an entity is deleted, this hook is invoked so any attached fields can
* do necessary clean-up. Because line items can't exist apart from a line item
* reference field, this function checks the entity being deleted for any
* referenced line items that are orphaned and deletes them.
*/
function commerce_line_item_field_attach_delete($entity_type, $entity) {
$wrapper = entity_metadata_wrapper($entity_type, $entity);
$entity_info = entity_get_info($entity_type);
// If the entity being deleted has a bundle...
if (!empty($entity_info['entity keys']['bundle'])) {
// Extract the bundle name using the specified property.
$property = $entity_info['entity keys']['bundle'];
$bundle = $entity->{$property};
}
else {
// Otherwise use the entity type as the bundle name.
$bundle = $entity_type;
}
// Find any line item reference fields on this entity and delete any orphan
// referenced line items.
$line_item_ids = array();
foreach (field_info_instances($entity_type, $bundle) as $field_name => $field) {
// Only examine line item reference fields using the manager widget.
if ($field['widget']['type'] == 'commerce_line_item_manager' && $wrapper->{$field_name}->count() > 0) {
// Loop over each line item referenced on this field...
foreach ($wrapper->{$field_name} as $delta => $line_item_wrapper) {
// To determine if it's still an orphan (i.e. no other entities reference
// this line item through any line item reference field).
$orphan = TRUE;
// Loop over each line item reference field to look for references.
foreach (commerce_info_fields('commerce_line_item_reference') as $key => $value) {
$query = new EntityFieldQuery();
$query->fieldCondition($key, 'line_item_id', $line_item_wrapper->line_item_id->value(), '=')->count();
// If some entity still references this line item through this field...
if ($query->execute() > 0) {
// It is not an orphan.
$orphan = FALSE;
}
}
// If this line item is an orphan, add it to the array of line items to
// be deleted.
if ($orphan) {
$line_item_ids[] = $line_item_wrapper->line_item_id->value();
}
}
}
}
// Delete the line items if any were found.
if (!empty($line_item_ids)) {
commerce_line_item_delete_multiple($line_item_ids);
}
}
/**
* Theme line item edit form actions.
*/
function theme_commerce_line_item_actions($variables) {
return '<div class="commerce-line-item-actions">' . $variables['buttons'] . '</div>';
}
/**
* Returns an array of line item type arrays keyed by type.
*/
function commerce_line_item_types() {
// First check the static cache for a line item types array.
$line_item_types = &drupal_static(__FUNCTION__);
// If it did not exist, fetch the types now.
if (!isset($line_item_types)) {
$line_item_types = module_invoke_all('commerce_line_item_type_info');
drupal_alter('commerce_line_item_type_info', $line_item_types);
foreach ($line_item_types as $type => &$line_item_type) {
$defaults = array(
'base' => $type,
'callbacks' => array(),
);
$line_item_type += $defaults;
// Merge in default callbacks.
foreach (array('configuration', 'title', 'add_form', 'add_form_submit') as $callback) {
if (!isset($line_item_type['callbacks'][$callback])) {
$line_item_type['callbacks'][$callback] = $line_item_type['base'] . '_' . $callback;
}
}
}
}
return $line_item_types;
}
/**
* Returns a single line item type array.
*
* @param $type
* The machine-readable name of the line item type.
*
* @return
* The specified line item type array or FALSE if it does not exist.
*/
function commerce_line_item_type_load($type) {
$line_item_types = commerce_line_item_types();
return isset($line_item_types[$type]) ? $line_item_types[$type] : FALSE;
}
/**
* Resets the cached list of line item types.
*/
function commerce_line_item_types_reset() {
$line_item_types = &drupal_static('commerce_line_item_types');
$line_item_types = NULL;
}
/**
* Returns the human readable name of any or all line item types.
*
* @param $type
* Optional parameter specifying the type whose name to return.
*
* @return
* Either an array of all line item type names keyed by the machine name or a
* string containing the human readable name for the specified type. If a
* type is specified that does not exist, this function returns FALSE.
*/
function commerce_line_item_type_get_name($type = NULL) {
$line_item_types = commerce_line_item_types();
// Return a type name if specified and it exists.
if (!empty($type)) {
if (isset($line_item_types[$type])) {
return $line_item_types[$type]['name'];
}
else {
// Return FALSE if it does not exist.
return FALSE;
}
}
// Otherwise turn the array values into the type name only.
$line_item_type_names = array();
foreach ((array) $line_item_types as $key => $value) {
$line_item_type_names[$key] = $value['name'];
}
return $line_item_type_names;
}
/**
* Wraps commerce_line_item_type_get_name() for the Entity module.
*/
function commerce_line_item_type_options_list() {
return commerce_line_item_type_get_name();
}
/**
* Configures a line item type by calling its configuration callback.
*
* @param $line_item_type
* The fully loaded line item type array to configure.
*/
function commerce_line_item_configure_line_item_type($line_item_type) {
// Add the default price fields to the line item type.
$weight = 0;
foreach (array('commerce_unit_price' => t('Unit price'), 'commerce_total' => t('Total')) as $field_name => $label) {
commerce_price_create_instance($field_name, 'commerce_line_item', $line_item_type['type'], $label, $weight++);
}
// If this line item type specifies a configuration callback...
if ($callback = commerce_line_item_type_callback($line_item_type, 'configuration')) {
// Invoke it now.
$callback();
}
}
/**
* Title callback: return the human-readable line item type name.
*/
function commerce_line_item_type_title($line_item_type) {
return $line_item_type['name'];
}
/**
* Returns a path argument from a line item type.
*/
function commerce_line_item_type_to_arg($type) {
return $type;
}
/**
* Returns the specified callback for the given line item type if one exists.
*
* @param $line_item_type
* The line item type array.
* @param $callback
* The callback function to return, one of:
* - configuration
* - title
* - add_form
* - add_form_validate
* - add_form_submit
*
* @return
* A string containing the name of the callback function or FALSE if it could
* not be found.
*/
function commerce_line_item_type_callback($line_item_type, $callback) {
// If the specified callback function exists, return it.
if (!empty($line_item_type['callbacks'][$callback]) &&
function_exists($line_item_type['callbacks'][$callback])) {
return $line_item_type['callbacks'][$callback];
}
// Otherwise return FALSE.
return FALSE;
}
/**
* Returns an initialized line item object.
*
* @param $type
* The machine-readable type of the line item.
* @param $order_id
* The ID of the order the line item belongs to (if available).
*
* @return
* A line item object with all default fields initialized.
*/
function commerce_line_item_new($type = '', $order_id = 0) {
return entity_get_controller('commerce_line_item')->create($type, $order_id);
}
/**
* Creation callback for the Entity module.
*/
function _commerce_line_item_create($values = array()) {
// Create a new line item of the specified type.
$line_item = commerce_line_item_new($values['type']);
unset($values['type']);
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
foreach ($values as $name => $value) {
$wrapper->$name->set($value);
}
return $wrapper->value();
}
/**
* Saves a line item.
*
* @param $line_item
* The full line item object to save.
*
* @return
* The saved line item object.
*/
function commerce_line_item_save($line_item) {
return entity_get_controller('commerce_line_item')->save($line_item);
}
/**
* Loads a line item by ID.
*/
function commerce_line_item_load($line_item_id) {
$line_items = commerce_line_item_load_multiple(array($line_item_id), array());
return $line_items ? reset($line_items) : FALSE;
}
/**
* Loads multiple line items by ID or based on a set of matching conditions.
*
* @see entity_load()
*
* @param $line_item_ids
* An array of line item IDs.
* @param $conditions
* An array of conditions on the {commerce_line_item} table in the form
* 'field' => $value.
* @param $reset
* Whether to reset the internal line item loading cache.
*
* @return
* An array of line item objects indexed by line_item_id.
*/
function commerce_line_item_load_multiple($line_item_ids = array(), $conditions = array(), $reset = FALSE) {
return entity_load('commerce_line_item', $line_item_ids, $conditions, $reset);
}
/**
* Deletes a line item by ID.
*
* @param $line_item_id
* The ID of the line item to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
function commerce_line_item_delete($line_item_id) {
return commerce_line_item_delete_multiple(array($line_item_id));
}
/**
* Deletes multiple line items by ID.
*
* @param $line_item_ids
* An array of line item IDs to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
function commerce_line_item_delete_multiple($line_item_ids) {
return entity_get_controller('commerce_line_item')->delete($line_item_ids);
}
/**
* Determines access to perform an operation on a particular line item.
*
* @param $op
* The operation to perform on the line item.
* @param $line_item
* The line item object in question.
*
* @return
* TRUE or FALSE indicating whether or not access should be granted.
*/
function commerce_line_item_access($op, $line_item, $account = NULL) {
if (empty($account)) {
global $user;
$account = clone($user);
}
// TODO: Implement actual access control for line items.
return TRUE;
}
/**
* Implements hook_field_info().
*/
function commerce_line_item_field_info() {
return array(
'commerce_line_item_reference' => array(
'label' => t('Line item reference'),
'description' => t('This field stores the ID of a related line item as an integer value.'),
'settings' => array(),
'instance_settings' => array(),
'default_widget' => 'commerce_line_item_manager',
'default_formatter' => 'commerce_line_item_reference_view',
'property_type' => 'commerce_line_item',
'property_callbacks' => array('commerce_line_item_property_info_callback'),
),
);
}
/**
* Implements hook_field_validate().
*
* Possible error codes:
* - 'invalid_line_item_id': line_item_id is not valid for the field (not a
* valid line item ID).
*/
function commerce_line_item_field_validate($entity_type, $object, $field, $instance, $langcode, $items, &$errors) {
// Extract line_item_ids to check.
$line_item_ids = array();
// First check non-numeric line_item_id's to avoid losing time with them.
foreach ($items as $delta => $item) {
if (is_array($item) && !empty($item['line_item_id'])) {
if (is_numeric($item['line_item_id'])) {
$line_item_ids[] = $item['line_item_id'];
}
else {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'invalid_line_item_id',
'message' => t('%name: you have specified an invalid line item for this field.', array('%name' => $instance['label'])),
);
}
}
}
// Prevent performance hog if there are no ids to check.
if ($line_item_ids) {
$line_items = commerce_line_item_load_multiple($line_item_ids);
foreach ($items as $delta => $item) {
if (is_array($item)) {
if (!empty($item['line_item_id']) && !isset($line_items[$item['line_item_id']])) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'invalid_line_item_id',
'message' => t('%name: you have specified an invalid line item for this reference field.', array('%name' => $instance['label'])),
);
}
}
}
}
}
/**
* Implements hook_field_is_empty().
*/
function commerce_line_item_field_is_empty($item, $field) {
// line_item_id = 0 รฎs empty too, which is exactly what we want.
return empty($item['line_item_id']);
}
/**
* Implements hook_field_formatter_info().
*/
function commerce_line_item_field_formatter_info() {
return array(
'commerce_line_item_reference_view' => array(
'label' => t('Line item View'),
'description' => t('Display the line items via a default View.'),
'field types' => array('commerce_line_item_reference'),
'settings' => array(
'view' => 'commerce_line_item_table|default',
),
),
);
}
/**
* Implements hook_field_formatter_settings_form().
*/
function commerce_line_item_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$element = array();
if ($display['type'] == 'commerce_line_item_reference_view') {
// Build an options array of Views available for the order contents pane.
$options = array();
// Generate an option list from all user defined and module defined views.
foreach (views_get_all_views() as $name => $view) {
// Only include line item Views.
if ($view->base_table == 'commerce_line_item') {
foreach ($view->display as $display_name => $display) {
$options[check_plain($name)][$name .'|'. $display_name] = check_plain($display->display_title);
}
}
}
$element['view'] = array(
'#type' => 'select',
'#title' => t('Order contents View'),
'#description' => t('Specify the View to use in the order contents pane.'),
'#options' => $options,
'#default_value' => $settings['view'],
);
}
return $element;
}
/**
* Implements hook_field_formatter_settings_summary().
*/
function commerce_line_item_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$summary = array();
if ($display['type'] == 'commerce_line_item_reference_view') {
// Load the View and display its information in the summary.
list($name, $display_name) = explode('|', $display['settings']['view']);
$view = views_get_view($name);
$summary = t('View: @name - @display', array('@name' => $view->name, '@display' => $view->display[$display_name]->display_title));
}
return $summary;
}
/**
* Implements hook_field_formatter_view().
*/
function commerce_line_item_field_formatter_view($entity_type, $object, $field, $instance, $langcode, $items, $display) {
$result = array();
// Collect the list of line item IDs.
$line_item_ids = array();
foreach ($items as $delta => $item) {
$line_item_ids[] = $item['line_item_id'];
}
switch ($display['type']) {
case 'commerce_line_item_reference_view':
// Extract the View and display ID from the setting.
list($view_id, $display_id) = explode('|', $display['settings']['view']);
$result[0] = array(
'#markup' => commerce_embed_view($view_id, $display_id, array(implode(',', $line_item_ids))),
);
break;
}
return $result;
}
/**
* Implements hook_field_widget_info().
*
* Defines widgets available for use with field types as specified in each
* widget's $info['field types'] array.
*/
function commerce_line_item_field_widget_info() {
$widgets = array();
// Define the creation / reference widget for line items.
$widgets['commerce_line_item_manager'] = array(
'label' => t('Line item manager'),
'description' => t('Use a complex widget to manager the line items referenced by this object.'),
'field types' => array('commerce_line_item_reference'),
'settings' => array(),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
'default value' => FIELD_BEHAVIOR_NONE,
),
);
return $widgets;
}
/**
* Implements hook_field_widget_form().
*
* Used to define the form element for custom widgets.
*/
function commerce_line_item_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
// Define the complex line item reference field widget.
if ($instance['widget']['type'] == 'commerce_line_item_manager') {
$line_item_ids = array();
// Build an array of line item IDs from this field's values.
foreach ($items as $item) {
$line_item_ids[] = $item['line_item_id'];
}
// Load the line items for temporary storage in the form array.
$line_items = commerce_line_item_load_multiple($line_item_ids);
// Update the base form element array to use the proper theme and validate
// functions and to include header information for the line item table.
$element += array(
'#theme' => 'commerce_line_item_manager',
'#element_validate' => array('commerce_line_item_manager_validate'),
'#header' => array(t('Remove'), t('Title'), t('SKU'), t('Qty'), t('Unit Price'), t('Total')),
'#empty' => t('No line items found.'),
'line_items' => array(),
);
// Add a set of elements to the form for each referenced line item.
foreach ($line_items as $line_item_id => $line_item) {
// Store the original line item for later comparison.
$element['line_items'][$line_item_id]['line_item'] = array(
'#type' => 'value',
'#value' => $line_item,
);
// This checkbox will be overridden with a clickable delete image.
$element['line_items'][$line_item_id]['remove'] = array(
'#type' => 'checkbox',
'#default_value' => FALSE,
);
// The display title should come from the line item type.
$line_item_type = commerce_line_item_type_load($line_item->type);
$title_callback = commerce_line_item_type_callback($line_item_type, 'title');
$element['line_items'][$line_item_id]['title'] = array(
'#markup' => $title_callback ? $title_callback($line_item) : '',
);
$element['line_items'][$line_item_id]['line_item_label'] = array(
'#markup' => check_plain($line_item->line_item_label),
);
$element['line_items'][$line_item_id]['quantity'] = array(
'#type' => 'textfield',
'#datatype' => 'integer',
'#default_value' => round($line_item->quantity),
'#size' => 4,
'#maxlength' => 8,
);
// Retrieve the widget form for just the unit price.
$widget_form = _field_invoke_default('form', 'commerce_line_item', $line_item, $form, $form_state, array('field_name' => 'commerce_unit_price'));
// Unset the title and description and add it to the line item form.
$language = $widget_form['commerce_unit_price']['#language'];
unset($widget_form['commerce_unit_price'][$language][0]['amount']['#title']);
unset($widget_form['commerce_unit_price'][$language][0]['amount']['#description']);
$element['line_items'][$line_item_id]['commerce_unit_price'] = $widget_form['commerce_unit_price'];
// Wrap the line item and add its formatted total to the form.
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$element['line_items'][$line_item_id]['commerce_total'] = array(
'#markup' => commerce_currency_format($wrapper->commerce_total->amount->value(), $wrapper->commerce_total->currency_code->value(), $line_item),
);
}
// If the the form has been instructed to add a line item...
if (!empty($form_state['line_item_add'])) {
// Load the info object for the selected line item type.
$line_item_type = commerce_line_item_type_load($form_state['line_item_add']);
// Store the line item info object in the form array.
$element['actions']['line_item_type'] = array(
'#type' => 'value',
'#value' => $line_item_type,
);
// TODO: What would it look like to use the line item type's add form
// instead of this custom callback mechanism? Probably better...
// If this type specifies a valid add form callback function...
if ($callback = commerce_line_item_type_callback($line_item_type, 'add_form')) {
// Load in the appropriate form elements to the actions array.
$element['actions'] += $callback();
}
// Add a default save button.
$element['actions']['save_line_item'] = array(
'#type' => 'submit',
'#value' => !empty($line_item_type['add_form_submit_value']) ? $line_item_type['add_form_submit_value'] : t('Save'),
'#ajax' => array(
'callback' => 'commerce_line_item_manager_refresh',
'wrapper' => 'line-item-manager',
),
);
$element['actions']['cancel'] = array(
'#type' => 'submit',
'#value' => t('Cancel'),
'#ajax' => array(
'callback' => 'commerce_line_item_manager_refresh',
'wrapper' => 'line-item-manager',
),
);
}
else {
// Otherwise display the select list to add a new line item.
$options = commerce_line_item_type_get_name();
// Only display the line item selector if line item types exist.
if (!empty($options)) {
$element['actions']['line_item_type'] = array(
'#type' => 'select',
'#options' => commerce_line_item_type_get_name(),
'#prefix' => '<div class="add-line-item">',
);
$element['actions']['line_item_add'] = array(
'#type' => 'submit',
'#value' => t('Add line item'),
'#ajax' => array(
'callback' => 'commerce_line_item_manager_refresh',
'wrapper' => 'line-item-manager',
),
'#suffix' => '</div>',
);
}
}
return $element;
}
}
/**
* Returns the line item manager element for display via AJAX.
*/
function commerce_line_item_manager_refresh($form, $form_state) {
return $form['commerce_line_items'][$form['commerce_line_items']['#language']];
}
/**
* Themes the line item manager widget form element.
*/
function theme_commerce_line_item_manager($variables) {
drupal_add_css(drupal_get_path('module', 'commerce_line_item') . '/theme/commerce_line_item.manager.css');
$form = $variables['form'];
$rows = array();
// Add each line item to the table.
foreach (element_children($form['line_items']) as $line_item_id) {
$row = array(
drupal_render($form['line_items'][$line_item_id]['remove']),
drupal_render($form['line_items'][$line_item_id]['title']),
drupal_render($form['line_items'][$line_item_id]['line_item_label']),
drupal_render($form['line_items'][$line_item_id]['quantity']),
drupal_render($form['line_items'][$line_item_id]['commerce_unit_price']),
drupal_render($form['line_items'][$line_item_id]['commerce_total']),
);
$rows[] = $row;
}
// Setup the table's variables array and build the output.
$table_variables = array(
'caption' => check_plain($form['#title']),
'header' => $form['#header'],
'rows' => $rows,
'empty' => $form['#empty'],
);
$output = theme('table', $table_variables) . drupal_render($form['actions']);
return '<div id="line-item-manager">' . $output . '</div>';
}
/**
* Validation callback for a commerce_line_item_manager element.
*
* When the form is submitted, the line item reference field stores the line
* item IDs as derived from the $element['line_items'] array and updates any
* referenced line items based on the extra form elements.
*/
function commerce_line_item_manager_validate($element, &$form_state, $form) {
$value = array();
// Loop through the line items in the manager table.
foreach (element_children($element['line_items']) as $line_item_id) {
// If the line item has been marked for deletion...
if ($element['line_items'][$line_item_id]['remove']['#value']) {
// Delete the line item now and don't include it from the $value array.
commerce_line_item_delete($line_item_id);
}
else {
// Add the line item ID to the current value of the reference field.
$value[] = array('line_item_id' => $line_item_id);
// Update the line item based on the values in the additional elements.
$line_item = clone($element['line_items'][$line_item_id]['line_item']['#value']);
// Validate the quantity of each line item.
$element_name = implode('][', $element['line_items'][$line_item_id]['quantity']['#parents']);
$quantity = $element['line_items'][$line_item_id]['quantity']['#value'];
if (!is_numeric($quantity) || $quantity <= 0) {
form_set_error($element_name, t('You must specify a positive number for the quantity'));
}
elseif ($element['line_items'][$line_item_id]['quantity']['#datatype'] == 'integer' &&
(int) $quantity != $quantity) {
form_set_error($element_name, t('You must specify a whole number for the quantity.'));
}
else {
$line_item->quantity = $quantity;
}
// Manually validate the unit price of each line item.
$unit_price = $form_state['values']['commerce_line_items'][$element['#language']]['line_items'][$line_item_id]['commerce_unit_price'];
if (is_numeric($unit_price[$element['#language']][0]['amount'])) {
$line_item->commerce_unit_price = $unit_price;
}
else {
$name = implode('][', array_merge($element['line_items'][$line_item_id]['commerce_unit_price']['#parents'], array($element['#language'], 0, 'amount')));
form_set_error($name, 'You must enter a numeric value for the unit price.');
}
// Only save if values were actually changed.
if ($line_item != $element['line_items'][$line_item_id]['line_item']['#value']) {
commerce_line_item_save($line_item);
}
}
}
// If the "Add line item" button was clicked, store the line item type in the
// $form_state for the rebuild of the $form array.
if (!empty($form_state['clicked_button'])) {
if ($form_state['clicked_button']['#value'] == t('Add line item')) {
$form_state['line_item_add'] = $element['actions']['line_item_type']['#value'];
$form_state['rebuild'] = TRUE;
}
else {
unset($form_state['line_item_add']);
$parent = array_pop($form_state['clicked_button']['#parents']);
// If the save button was clicked from the line item type action form...
if ($parent == 'save_line_item') {
$line_item_type = $element['actions']['line_item_type']['#value'];
// Extract an order ID from the form if present.
$order_id = 0;
if (!empty($form_state['commerce_order'])) {
$order_id = $form_state['commerce_order']->order_id;
}
// Create the new line item.
$line_item = commerce_line_item_new($line_item_type['type'], $order_id);
// If this type specifies a valid add form callback function...
if ($callback = commerce_line_item_type_callback($line_item_type, 'add_form_submit')) {
// Allow the callback to alter data onto the line item to be saved and
// to return an error message if something goes wrong.
$error = $callback($line_item, $element, $form_state, $form);
}
else {
// Assume no error if the callback isn't specified.
$error = FALSE;
}
// If we didn't end up with any errors...
if (empty($error)) {
// Save it and add it to the line item reference field's values array.
$line_item = commerce_line_item_save($line_item);
$value[] = array('line_item_id' => $line_item->line_item_id);
}
else {
// Otherwise display the error message; note this is not using
// form_set_error() on purpose, because a failed addition of a line item
// doesn't affect the rest of the form submission process.
drupal_set_message($error, 'error');
}
$form_state['rebuild'] = TRUE;
}
elseif ($parent == 'cancel') {
// If the cancel button was clicked refresh without action.
$form_state['rebuild'] = TRUE;
}
}
}
form_set_value($element, $value, $form_state);
}
/**
* Implements hook_field_widget_error().
*/
function commerce_line_item_field_widget_error($element, $error) {
form_error($element, $error['message']);
}
/**
* Callback for getting line item properties.
*
* @see commerce_line_item_entity_property_info()
*/
function commerce_line_item_get_properties($line_item, array $options, $name) {
switch ($name) {
case 'order':
return $line_item->order_id;
}
}
/**
* Callback for setting line item properties.
*
* @see commerce_line_item_entity_property_info()
*/
function commerce_line_item_set_properties($line_item, $name, $value) {
switch ($name) {
case 'order':
$line_item->order_id = $value;
break;
}
}
/**
* Callback to alter the property info of the reference field.
*
* @see commerce_line_item_field_info().
*/
function commerce_line_item_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
$property['options list'] = 'entity_metadata_field_options_list';
unset($property['query callback']);
}
/**
* Deletes the reference to a specific line item from the line item reference
* field on the given entity.
*/
function commerce_line_item_reference_delete($entity_type, $entity, $field_name, $line_item_id) {
$wrapper = entity_metadata_wrapper($entity_type, $entity);
// Loop over every line item referenced by the specified field.
foreach ($wrapper->{$field_name} as $delta => $line_item_wrapper) {
// If its ID matches the specified line item...
if ($line_item_wrapper->line_item_id->value() == $line_item_id) {
// Unset it and return the updated order object.
$wrapper->{$field_name}->offsetUnset($delta);
return $wrapper->value();
}
}
}
/**
* Returns the total quantity of an array of line items.
*
* @param $line_items
* The array of line items whose quantities you want to count; also accepts an
* EntityListWrapper of a line item reference field.
* @param $type
* Optional type string to filter line items by type before counting.
*
* @return
* The total quantity of all the matching line items.
*/
function commerce_line_items_quantity($line_items, $type = '') {
// Sum up the quantity of all matching line items.
$quantity = 0;
foreach ($line_items as $line_item) {
if (!is_a($line_item, 'EntityMetadataWrapper')) {
$line_item = entity_metadata_wrapper('commerce_line_item', $line_item);
}
if (empty($type) || $line_item->type->value() == $type) {
$quantity += $line_item->quantity->value();
}
}
return $quantity;
}
/**
* Returns the total price and currency of an array of line items.
*
* @param $line_items
* The array of line items whose quantities you want to count; also accepts an
* EntityListWrapper of a line item reference field.
* @param $type
* Optional type string to filter line items by type before counting.
*
* @return
* An associative array of containing the total 'amount' and 'currency_code'
* the line items.
*/
function commerce_line_items_total($line_items, $type = '') {
// Sum up the total price of all matching line items.
$total = 0;
$currency_code = '';
foreach ($line_items as $line_item) {
if (!is_a($line_item, 'EntityMetadataWrapper')) {
$line_item = entity_metadata_wrapper('commerce_line_item', $line_item);
}
if (empty($type) || $line_item->type == $type) {
// Set the initial currency code.
if (empty($currency_code)) {
$currency_code = $line_item->commerce_total->currency_code->value();
}
else {
// Check to see if the line items are of a mixed currency.
if ($currency_code != $line_item->commerce_total->currency_code->value()) {
// TODO: Figure out what to do then!
}
}
$total += $line_item->commerce_total->amount->value();
}
}
return array('amount' => $total, 'currency_code' => $currency_code);
}
/**
* Returns a sorted array of line item summary links.
*
* @see hook_commerce_line_item_summary_link_info()
*/
function commerce_line_item_summary_links() {
// Retrieve links defined by the hook and allow other modules to alter them.
$links = module_invoke_all('commerce_line_item_summary_link_info');
// Merge in default values for our custom properties.
foreach ($links as $key => &$link) {
$link += array(
'weight' => 0,
'access' => TRUE,
);
}
drupal_alter('commerce_line_item_summary_link_info', $links);
// Sort the links by weight and return the array.
uasort($links, 'drupal_sort_weight');
return $links;
}
/**
* Implements hook_field_views_data().
*/
function commerce_line_item_field_views_data($field) {
$data = field_views_field_default_views_data($field);
// Build an array of bundles the field appears on.
$bundles = array();
foreach ($field['bundles'] as $entity => $bundles) {
$bundles[] = $entity . ' (' . implode(', ', $bundles) . ')';
}
foreach ($data as $table_name => $table_data) {
foreach ($table_data as $field_name => $field_data) {
if (!in_array($field_name, array('table', 'entity_id', 'revision_id'))) {
$data[$table_name][$field_name]['relationship'] = array(
'title' => t('Referenced line item'),
'help' => t('The line item referenced through this field.'),
'base' => 'commerce_line_item',
'base field' => 'line_item_id',
'handler' => 'views_handler_relationship',
'label' => t('Line Item'),
);
}
}
}
return $data;
}
<?php
// $Id$
/**
* @file
* Rules integration for line items.
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_event_info().
*/
function commerce_line_item_rules_event_info() {
$events = array();
$events['commerce_line_item_presave'] = array(
'label' => t('Before saving a line item'),
'group' => t('Commerce Line Item'),
'variables' => commerce_line_item_rules_event_variables(t('Line item'), TRUE, TRUE),
'access callback' => 'commerce_line_item_rules_access',
);
$events['commerce_line_item_insert'] = array(
'label' => t('After saving a new line item'),
'group' => t('Commerce Line Item'),
'variables' => commerce_line_item_rules_event_variables(t('Created line item'), TRUE),
'access callback' => 'commerce_line_item_rules_access',
);
$events['commerce_line_item_update'] = array(
'label' => t('After updating an existing line item'),
'group' => t('Commerce Line Item'),
'variables' => commerce_line_item_rules_event_variables(t('Updated line item'), TRUE),
'access callback' => 'commerce_line_item_rules_access',
);
$events['commerce_line_item_delete'] = array(
'label' => t('After deleting a line item'),
'group' => t('Commerce Line Item'),
'variables' => commerce_line_item_rules_event_variables(t('Deleted line item')),
'access callback' => 'commerce_line_item_rules_access',
);
return $events;
}
/**
* Returns a variables array for line item events.
*
* @param $label
* The label for the primary line item variable.
* @param $unchanged
* Boolean indicating whether or not to include the unchanged line item.
* @param $skip_save
* Boolean indicating whether or not the primary line item variable should
* skip saving after event execution.
*/
function commerce_line_item_rules_event_variables($label, $unchanged = FALSE, $skip_save = FALSE) {
$variables = array(
'line_item' => array(
'type' => 'commerce_line_item',
'label' => $label,
'skip save' => $skip_save,
),
);
// Include the unchanged line item if specified.
if ($unchanged) {
$variables['line_item_unchanged'] = array(
'type' => 'commerce_line_item',
'label' => t('Unchanged line item'),
'skip save' => TRUE,
'handler' => 'rules_events_entity_unchanged',
);
}
return $variables;
}
/**
* Rules integration access callback.
*/
function commerce_line_item_rules_access($type, $name) {
if ($type == 'event' || $type == 'condition') {
return TRUE;
}
}
/**
* Implements hook_rules_action_info().
*/
function commerce_line_item_rules_action_info() {
$actions = array();
// Prepare an array of arithmetical actions for altering prices.
$action_types = array(
'commerce_line_item_unit_price_add' => array(
'label' => t('Add an amount to the unit price'),
'amount description' => t('Specify a numeric amount to add to the unit price.'),
),
'commerce_line_item_unit_price_subtract' => array(
'label' => t('Subtract an amount from the unit price'),
'amount description' => t('Specify a numeric amount to subtract from the price.'),
),
'commerce_line_item_unit_price_multiply' => array(
'label' => t('Multiply the unit price by some amount'),
'amount description' => t('Specify the numeric amount by which to multiply the price.'),
),
'commerce_line_item_unit_price_divide' => array(
'label' => t('Divide the unit price by some amount'),
'amount description' => t('Specify a numeric amount by which to divide the price.'),
),
'commerce_line_item_unit_price_amount' => array(
'label' => t('Set the unit price to a specific amount'),
'amount description' => t('Specify the numeric amount to set the price to.'),
),
);
// Define the action using a common set of parameters.
foreach ($action_types as $key => $value) {
$actions[$key] = array(
'label' => $value['label'],
'parameter' => array(
'line_item' => array(
'type' => 'commerce_line_item',
'label' => t('Line item'),
),
'amount' => array(
'type' => 'decimal',
'label' => t('Amount'),
'description' => $value['amount description'],
),
),
'group' => t('Commerce Line Item'),
);
}
$actions['commerce_line_item_unit_price_currency_code'] = array(
'label' => t("Set the unit price's currency code"),
'parameter' => array(
'line_item' => array(
'type' => 'commerce_line_item',
'label' => t('Line item'),
),
'currency_code' => array(
'type' => 'text',
'label' => t('Currency'),
'options list' => 'commerce_currency_get_code',
),
),
'group' => t('Commerce Line Item'),
);
$actions['commerce_line_item_unit_price_currency_convert'] = array(
'label' => t("Convert the unit price to a different currency"),
'parameter' => array(
'line_item' => array(
'type' => 'commerce_line_item',
'label' => t('Line item'),
),
'currency_code' => array(
'type' => 'text',
'label' => t('Currency'),
'options list' => 'commerce_currency_get_code',
),
),
'group' => t('Commerce Line Item'),
);
return $actions;
}
/**
* Rules action: add an amount to the unit price.
*/
function commerce_line_item_unit_price_add($line_item, $amount) {
if (is_numeric($amount)) {
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$wrapper->commerce_unit_price->amount = $wrapper->commerce_unit_price->amount->value() + $amount;
}
}
/**
* Rules action: subtract an amount from the unit price.
*/
function commerce_line_item_unit_price_subtract($line_item, $amount) {
if (is_numeric($amount)) {
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$wrapper->commerce_unit_price->amount = $wrapper->commerce_unit_price->amount->value() - $amount;
}
}
/**
* Rules action: multiply the unit price by some amount.
*/
function commerce_line_item_unit_price_multiply($line_item, $amount) {
if (is_numeric($amount)) {
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$wrapper->commerce_unit_price->amount = $wrapper->commerce_unit_price->amount->value() * $amount;
}
}
/**
* Rules action: divide the unit price by some amount.
*/
function commerce_line_item_unit_price_divide($line_item, $amount) {
if (is_numeric($amount)) {
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$wrapper->commerce_unit_price->amount = $wrapper->commerce_unit_price->amount->value() / $amount;
}
}
/**
* Rules action: set the unit price to a specific amount.
*/
function commerce_line_item_unit_price_amount($line_item, $amount) {
if (is_numeric($amount)) {
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$wrapper->commerce_unit_price->amount = $amount;
}
}
/**
* Rules action: set the unit price's currency code.
*/
function commerce_line_item_unit_price_currency_code($line_item, $currency_code) {
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$wrapper->commerce_unit_price->currency_code = $currency_code;
}
/**
* Rules action: convert the unit price to a different currency.
*/
function commerce_line_item_unit_price_currency_convert($line_item, $currency_code) {
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$wrapper->commerce_unit_price->amount = commerce_currency_convert($wrapper->commerce_unit_price->amount->value(), $wrapper->commerce_unit_price->currency_code->value(), $currency_code);
$wrapper->commerce_unit_price->currency_code = $currency_code;
}
/**
* @}
*/
<?php
// $Id$
/**
* @file
* Builds placeholder replacement tokens for line item related data.
*/
/**
* Implements hook_token_info().
*/
function commerce_line_item_token_info() {
$type = array(
'name' => t('Line items'),
'description' => t('Tokens related to individual line items.'),
'needs-data' => 'line_item',
);
// Tokens for line items.
$line_item = array();
$line_item['line-item-id'] = array(
'name' => t('Line item ID'),
'description' => t('The unique numeric ID of the line item.'),
);
$line_item['type'] = array(
'name' => t('Line item type'),
'description' => t('The type of the line item.'),
);
$line_item['type-name'] = array(
'name' => t('Line item type name'),
'description' => t('The human-readable name of the line item type.'),
);
$line_item['line-item-label'] = array(
'name' => t('Line item label'),
'description' => t('The label displayed with the line item.'),
);
$line_item['quantity'] = array(
'name' => t('Quantity'),
'description' => t('The quantity of the line item.'),
);
// Chained tokens for products.
$line_item['order'] = array(
'name' => t('Order'),
'description' => t('Order associated with the line item'),
'type' => 'order',
);
$line_item['created'] = array(
'name' => t('Date created'),
'description' => t('The date the line item was created.'),
'type' => 'date',
);
$line_item['changed'] = array(
'name' => t('Date updated'),
'description' => t('The date the line item was last updated.'),
'type' => 'date',
);
return array(
'types' => array('line_item' => $type),
'tokens' => array('line_item' => $line_item),
);
}
/**
* Implements hook_tokens().
*/
function commerce_line_item_tokens($type, $tokens, array $data = array(), array $options = array()) {
$url_options = array('absolute' => TRUE);
if (isset($options['language'])) {
$url_options['language'] = $options['language'];
$language_code = $options['language']->language;
}
else {
$language_code = NULL;
}
$sanitize = !empty($options['sanitize']);
$replacements = array();
if ($type == 'line_item' && !empty($data['line_item'])) {
$line_item = $data['line_item'];
foreach ($tokens as $name => $original) {
switch ($name) {
// Simple key values on the line item.
case 'line-item-id':
$replacements[$original] = $line_item->line_item_id;
break;
case 'type':
$replacements[$original] = $sanitize ? check_plain($line_item->type) : $line_item->type;
break;
case 'type-name':
$replacements[$original] = $sanitize ? check_plain(commerce_line_item_type_get_name($line_item->type)) : commerce_line_item_type_get_name($line_item->type);
break;
case 'line-item-label':
$replacements[$original] = $sanitize ? check_plain($line_item->line_item_label) : $line_item->line_item_label;
break;
case 'quantity':
$replacements[$original] = $sanitize ? check_plain($line_item->quantity) : $line_item->quantity;
break;
// Default values for the chained tokens handled below.
case 'order':
if ($line_item->order_id) {
$order = commerce_order_load($line_item->order_id);
$replacements[$original] = $sanitize ? check_plain($order->order_number) : $order->order_number;
}
break;
case 'created':
$replacements[$original] = format_date($line_item->created, 'medium', '', NULL, $language_code);
break;
case 'changed':
$replacements[$original] = format_date($line_item->changed, 'medium', '', NULL, $language_code);
break;
}
}
if ($order_tokens = token_find_with_prefix($tokens, 'order')) {
$order = commerce_order_load($line_item->order_id);
$replacements += token_generate('order', $order_tokens, array('order' => $order), $options);
}
foreach (array('created', 'changed') as $date) {
if ($created_tokens = token_find_with_prefix($tokens, $date)) {
$replacements += token_generate('date', $created_tokens, array('date' => $order->{$date}), $options);
}
}
}
return $replacements;
}
<?php
// $Id$
/**
* Export Drupal Commerce line items to Views.
*/
/**
* Implements hook_views_data()
*/
function commerce_line_item_views_data() {
$data = array();
$data['commerce_line_item']['table']['group'] = t('Commerce Line Item');
$data['commerce_line_item']['table']['base'] = array(
'field' => 'line_item_id',
'title' => t('Commerce Line Item'),
'help' => t('A line item referenced by another entity.'),
);
// Expose the line item ID.
$data['commerce_line_item']['line_item_id'] = array(
'title' => t('Line item ID'),
'help' => t('The unique internal identifier of the line item.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'commerce_line_item_handler_argument_line_item_line_item_id',
'name field' => 'line_item_label',
'numeric' => TRUE,
'validate type' => 'line_item_id',
),
);
// Expose the product type.
$data['commerce_line_item']['type'] = array(
'title' => t('Type'),
'help' => t('The human-readable name of the type of the line item.'),
'field' => array(
'handler' => 'commerce_line_item_handler_field_line_item_type',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'commerce_line_item_handler_filter_line_item_type',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// TODO: Expose the display view build mode.
// Expose the line item label.
$data['commerce_line_item']['line_item_label'] = array(
'title' => t('Line item label'),
'help' => t('The label of the line item.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Expose the line item quantity.
$data['commerce_line_item']['quantity'] = array(
'title' => t('Quantity'),
'help' => t('The quantity of the line item.'),
'field' => array(
'handler' => 'views_handler_field_numeric',
'click sortable' => TRUE,
'float' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
),
);
// Adds a textfield to edit line item quantity on the view.
$data['commerce_line_item']['edit_quantity'] = array(
'field' => array(
'title' => t('Quantity text field'),
'help' => t('Adds a text field to edit the line item quantity in the View.'),
'handler' => 'commerce_line_item_handler_field_edit_quantity',
),
);
// Adds a button to delete a line item.
$data['commerce_line_item']['edit_delete'] = array(
'field' => array(
'title' => t('Delete button'),
'help' => t('Adds a button to delete a line item.'),
'handler' => 'commerce_line_item_handler_field_edit_delete',
),
);
// Expose the order ID.
$data['commerce_line_item']['order_id'] = array(
'title' => t('Order ID', array(), array('context' => 'a drupal commerce order')),
'help' => t('The unique internal identifier of the associated order.'),
'field' => array(
'handler' => 'commerce_order_handler_field_order',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'commerce_order_handler_argument_order_order_id',
'name field' => 'order_number',
'numeric' => TRUE,
'validate type' => 'order_id',
),
'relationship' => array(
'handler' => 'views_handler_relationship',
'base' => 'commerce_order',
'field' => 'order_id',
'label' => t('Order', array(), array('context' => 'a drupal commerce order')),
),
);
// Expose the created and changed timestamps.
$data['commerce_line_item']['created'] = array(
'title' => t('Created date'),
'help' => t('The date the line item was created.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'filter' => array(
'handler' => 'views_handler_filter_date',
),
);
$data['commerce_line_item']['changed'] = array(
'title' => t('Updated date'),
'help' => t('The date the line item was last updated.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'filter' => array(
'handler' => 'views_handler_filter_date',
),
);
// Define a handler for an area used to summarize a set of line items.
$data['commerce_line_item']['line_item_summary'] = array(
'title' => t('Line item summary'),
'help' => t('Summarize the line items in a View with an optional link to checkout.'),
'area' => array(
'handler' => 'commerce_line_item_handler_area_line_item_summary',
),
);
// Define an area handler for displaying line item action buttons.
$data['commerce_line_item']['line_item_actions'] = array(
'title' => t('Line item actions'),
'help' => t('Display buttons to act on line items.'),
'area' => array(
'handler' => 'commerce_line_item_handler_area_line_item_actions',
),
);
return $data;
}
/**
* Implements hook_views_post_render().
*/
function commerce_line_item_views_post_render(&$view, &$output, &$cache) {
if ($view->base_table == 'commerce_order') {
// Check the fields on the View to see if any adding to an edit form by
// extending the field edit handler.
$has_form_fields = FALSE;
foreach ($view->field as $field_name => $field) {
if(is_a($field, 'commerce_line_item_handler_field_edit')) {
$has_form_fields = TRUE;
}
}
// If we did find form fields in the View, format the output of the View as
// a form using the line item Views form.
if ($has_form_fields) {
module_load_include('inc', 'commerce_line_item', 'includes/commerce_line_item.forms');
$form = drupal_get_form(commerce_line_item_views_form_id($view), $view, $output);
$output = drupal_render($form);
}
}
}
<?php
// $Id$
/**
* Views for line item reference displays.
*/
/**
* Implements hook_views_default_views().
*/
function commerce_line_item_views_default_views() {
$views = array();
// Line item view for the order view page.
$view = new view;
$view->name = 'commerce_line_item_table';
$view->description = 'Display a set of line items in a table.';
$view->tag = 'commerce';
$view->view_php = '';
$view->base_table = 'commerce_line_item';
$view->is_cacheable = FALSE;
$view->api_version = '3.0-alpha1';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
/* Display: Defaults */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->display->display_options['access']['type'] = 'none';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'none';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'line_item_id' => 'line_item_id',
'line_item_label' => 'line_item_label',
'quantity' => 'quantity',
'entity_id_1' => 'entity_id_1',
'entity_id' => 'entity_id',
);
$handler->display->display_options['style_options']['default'] = '-1';
$handler->display->display_options['style_options']['info'] = array(
'line_item_id' => array(
'sortable' => 0,
'align' => '',
'separator' => '',
),
'line_item_label' => array(
'sortable' => 0,
'align' => '',
'separator' => '',
),
'quantity' => array(
'sortable' => 0,
'align' => '',
'separator' => '',
),
'entity_id_1' => array(
'sortable' => 0,
'align' => 'views-align-right',
'separator' => '',
),
'entity_id' => array(
'sortable' => 0,
'align' => 'views-align-right',
'separator' => '',
),
);
$handler->display->display_options['style_options']['override'] = 1;
$handler->display->display_options['style_options']['sticky'] = 0;
/* Empty text: Global: Text area */
$handler->display->display_options['empty']['area']['id'] = 'area';
$handler->display->display_options['empty']['area']['table'] = 'views';
$handler->display->display_options['empty']['area']['field'] = 'area';
$handler->display->display_options['empty']['area']['label'] = 'Empty line item text.';
$handler->display->display_options['empty']['area']['empty'] = FALSE;
$handler->display->display_options['empty']['area']['content'] = 'No line items found.';
$handler->display->display_options['empty']['area']['format'] = 'filtered_html';
/* Field: Commerce Line Item: Line item ID */
$handler->display->display_options['fields']['line_item_id']['id'] = 'line_item_id';
$handler->display->display_options['fields']['line_item_id']['table'] = 'commerce_line_item';
$handler->display->display_options['fields']['line_item_id']['field'] = 'line_item_id';
$handler->display->display_options['fields']['line_item_id']['label'] = 'ID';
$handler->display->display_options['fields']['line_item_id']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['line_item_id']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['line_item_id']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['line_item_id']['alter']['trim'] = 0;
$handler->display->display_options['fields']['line_item_id']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['line_item_id']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['line_item_id']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['line_item_id']['alter']['html'] = 0;
$handler->display->display_options['fields']['line_item_id']['hide_empty'] = 0;
$handler->display->display_options['fields']['line_item_id']['empty_zero'] = 0;
/* Field: Commerce Line Item: Type */
$handler->display->display_options['fields']['type']['id'] = 'type';
$handler->display->display_options['fields']['type']['table'] = 'commerce_line_item';
$handler->display->display_options['fields']['type']['field'] = 'type';
$handler->display->display_options['fields']['type']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['type']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['type']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['type']['alter']['trim'] = 0;
$handler->display->display_options['fields']['type']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['type']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['type']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['type']['alter']['html'] = 0;
$handler->display->display_options['fields']['type']['hide_empty'] = 0;
$handler->display->display_options['fields']['type']['empty_zero'] = 0;
/* Field: Commerce Line Item: Line item label */
$handler->display->display_options['fields']['line_item_label']['id'] = 'line_item_label';
$handler->display->display_options['fields']['line_item_label']['table'] = 'commerce_line_item';
$handler->display->display_options['fields']['line_item_label']['field'] = 'line_item_label';
$handler->display->display_options['fields']['line_item_label']['label'] = 'Label';
$handler->display->display_options['fields']['line_item_label']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['trim'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['line_item_label']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['line_item_label']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['line_item_label']['alter']['html'] = 0;
$handler->display->display_options['fields']['line_item_label']['hide_empty'] = 0;
$handler->display->display_options['fields']['line_item_label']['empty_zero'] = 0;
/* Field: Commerce Line Item: Quantity */
$handler->display->display_options['fields']['quantity']['id'] = 'quantity';
$handler->display->display_options['fields']['quantity']['table'] = 'commerce_line_item';
$handler->display->display_options['fields']['quantity']['field'] = 'quantity';
$handler->display->display_options['fields']['quantity']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['trim'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['quantity']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['quantity']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['quantity']['alter']['html'] = 0;
$handler->display->display_options['fields']['quantity']['hide_empty'] = 0;
$handler->display->display_options['fields']['quantity']['empty_zero'] = 0;
$handler->display->display_options['fields']['quantity']['set_precision'] = 0;
$handler->display->display_options['fields']['quantity']['precision'] = '0';
$handler->display->display_options['fields']['quantity']['format_plural'] = 0;
/* Field: Fields: commerce_unit_price */
$handler->display->display_options['fields']['entity_id_1']['id'] = 'entity_id_1';
$handler->display->display_options['fields']['entity_id_1']['table'] = 'field_data_commerce_unit_price';
$handler->display->display_options['fields']['entity_id_1']['field'] = 'entity_id';
$handler->display->display_options['fields']['entity_id_1']['label'] = 'Unit price';
$handler->display->display_options['fields']['entity_id_1']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['trim'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['entity_id_1']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['entity_id_1']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['entity_id_1']['alter']['html'] = 0;
$handler->display->display_options['fields']['entity_id_1']['hide_empty'] = 0;
$handler->display->display_options['fields']['entity_id_1']['empty_zero'] = 0;
$handler->display->display_options['fields']['entity_id_1']['type'] = 'commerce_price_formatted_amount';
/* Field: Fields: commerce_total */
$handler->display->display_options['fields']['entity_id']['id'] = 'entity_id';
$handler->display->display_options['fields']['entity_id']['table'] = 'field_data_commerce_total';
$handler->display->display_options['fields']['entity_id']['field'] = 'entity_id';
$handler->display->display_options['fields']['entity_id']['label'] = 'Total';
$handler->display->display_options['fields']['entity_id']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['trim'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['entity_id']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['entity_id']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['entity_id']['alter']['html'] = 0;
$handler->display->display_options['fields']['entity_id']['hide_empty'] = 0;
$handler->display->display_options['fields']['entity_id']['empty_zero'] = 0;
$handler->display->display_options['fields']['entity_id']['type'] = 'commerce_price_formatted_amount';
/* Sort criterion: Commerce Line Item: Line item ID */
$handler->display->display_options['sorts']['line_item_id']['id'] = 'line_item_id';
$handler->display->display_options['sorts']['line_item_id']['table'] = 'commerce_line_item';
$handler->display->display_options['sorts']['line_item_id']['field'] = 'line_item_id';
/* Argument: Commerce Line Item: Line item ID */
$handler->display->display_options['arguments']['line_item_id']['id'] = 'line_item_id';
$handler->display->display_options['arguments']['line_item_id']['table'] = 'commerce_line_item';
$handler->display->display_options['arguments']['line_item_id']['field'] = 'line_item_id';
$handler->display->display_options['arguments']['line_item_id']['default_action'] = 'empty';
$handler->display->display_options['arguments']['line_item_id']['style_plugin'] = 'default_summary';
$handler->display->display_options['arguments']['line_item_id']['default_argument_type'] = 'fixed';
$handler->display->display_options['arguments']['line_item_id']['break_phrase'] = 1;
$handler->display->display_options['arguments']['line_item_id']['not'] = 0;
$views[$view->name] = $view;
return $views;
}
<?php
// $Id$
/**
* Defines a line item actions area handler so the action buttons can be plugged
* into the View itself.
*/
class commerce_line_item_handler_area_line_item_actions extends views_handler_area {
function option_definition() {
$options = parent::option_definition();
// Undefine the empty option.
unset($options['empty']);
return $options;
}
function render($empty = FALSE) {
if (!$empty) {
$variables = array(
'buttons' => '<!--post-commerce-line-item-buttons-->',
);
return theme('commerce_line_item_actions', $variables);
}
}
}
<?php
// $Id$
/**
* Defines a line item summary area handler so the summary can be plugged into
* the View itself.
*/
class commerce_line_item_handler_area_line_item_summary extends views_handler_area {
function option_definition() {
$options = parent::option_definition();
// Undefine the empty option.
unset($options['empty']);
// Define an option to control the links displayed in the summary.
$options['links'] = array('default' => array());
foreach (commerce_line_item_summary_links() as $name => $link) {
$options['links']['default'][$name] = 0;
}
// Define an option to control the info displayed in the summary.
$options['info'] = array('default' => array(
'quantity' => 1,
'total' => 1,
));
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
// Don't display a form element for the undefined empty option.
unset($form['empty']);
// Add checkboxes for the summary links if any are available.
$options = array();
foreach (commerce_line_item_summary_links() as $name => $link) {
$options[$name] = $link['title'];
}
if (!empty($options)) {
$form['links'] = array(
'#type' => 'checkboxes',
'#title' => t('Links'),
'#description' => t('Select the links you want to appear beneath the summary.'),
'#options' => $options,
'#default_value' => $this->options['links'],
);
}
$form['info'] = array(
'#type' => 'checkboxes',
'#title' => t('Info'),
'#description' => t('Select what info you want displayed in the summary.'),
'#options' => array('quantity' => t('Item quantity'), 'total' => t('Total')),
'#default_value' => $this->options['info'],
);
}
function render($empty = FALSE) {
if (!$empty) {
// Build an array of line item IDs from the View results that we will load
// and use for calculating totals.
$line_item_ids = array();
// Find the alias for the line_item_id field.
$field_alias = '';
foreach ($this->view->query->fields as $key => $value) {
if ($value['field'] == 'line_item_id') {
$field_alias = $value['alias'];
}
}
foreach ($this->view->result as $result) {
if (!empty($result->{$field_alias})) {
$line_item_ids[] = $result->{$field_alias};
}
}
$line_items = commerce_line_item_load_multiple($line_item_ids);
// Add total information and the line item summary links.
$quantity = commerce_line_items_quantity($line_items);
$total = commerce_line_items_total($line_items);
$currency = commerce_currency_load($total['currency_code']);
$links = array();
foreach (commerce_line_item_summary_links() as $name => $link) {
if ($this->options['links'][$name] === $name && $link['access']) {
$links[str_replace('_', '-', 'line-item-summary-' . $name)] = $link;
}
}
// Build the variables array to send to the template.
$variables = array(
'view' => $this->view,
'links' => theme('links', array('links' => $links, 'attributes' => array('class' => array('links', 'inline')))),
);
if ($this->options['info']['quantity']) {
$variables = array(
'quantity_raw' => $quantity,
'quantity_label' => format_plural($quantity, 'item', 'items'),
'quantity' => format_plural($quantity, '1 item', '@count items'),
) + $variables;
}
if ($this->options['info']['total']) {
$variables = array(
'total_raw' => number_format(commerce_currency_round($total['amount'], $currency), $currency['decimals']),
'total_label' => t('Total:'),
'total' => commerce_currency_format($total['amount'], $total['currency_code'], $this->view),
) + $variables;
}
return theme('commerce_line_item_summary', $variables);
}
return '';
}
}
<?php
// $Id$
/**
* @file
* Provide line item id argument handler.
*/
/**
* Argument handler to accept a line item id.
*/
class commerce_line_item_handler_argument_line_item_line_item_id extends views_handler_argument_numeric {
/**
* Override the behavior of title(). Get the title of the node.
*/
function title_query() {
$titles = array();
$result = db_query("SELECT li.line_item_label FROM {commerce_line_item} li WHERE li.line_item_id IN (:line_item_ids)", array(':line_item_ids' => $this->value));
foreach ($result as $line_item) {
$titles[] = check_plain($line_item->line_item_label);
}
return $titles;
}
}
<?php
// $Id$
/**
* @file
* Base field handler to present a form field to edit a line item field.
*/
/**
* Field handler to present a field to change quantity of a line item.
*/
class commerce_line_item_handler_field_edit extends views_handler_field {
var $form;
function construct() {
parent::construct();
$this->additional_fields['line_item_id'] = 'line_item_id';
// Set real_field in order to make it generate a field_alias.
$this->real_field = 'line_item_id';
}
/**
* Returns a form field array used to edit the value of this field.
*/
function get_edit_form() {
return $this->form;
}
/**
* Validates the new value set to the field.
*/
function edit_form_validate($form, &$form_state) {}
/**
* Process the new field value.
*/
function edit_form_submit($form, &$form_state) {}
}
<?php
// $Id$
/**
* @file
* Field handler to present a button to remove a line item. It's a dummy
* handler, most part of the implementation is done via pre and post render
* hooks.
*/
/**
* Field handler to present a button to delete a line item.
*/
class commerce_line_item_handler_field_edit_delete extends commerce_line_item_handler_field_edit {
function render($values) {
$line_item_id = $values->{$this->aliases['line_item_id']};
// Add form that will be used for replacing the placeholder generated bellow
// at form rendering phase in commerce_line_item_views_post_render().
$this->form[$line_item_id] = array(
'#type' => 'submit',
'#value' => t('Delete'),
'#name' => 'delete-line-item-' . $line_item_id,
'#attributes' => array('class' => array('delete-line-item')),
);
// Return the placeholder that will be used as token for replacing itself
// with the rendered form field created above.
return '<!--post-commerce-line-item-edit_delete-' . $line_item_id . '-->';
}
function edit_form_submit($form, &$form_state) {
$order = commerce_order_load($form_state['order']->order_id);
foreach (element_children($form['edit_delete']) as $line_item_id) {
// Check for the removal of an item.
if ($form_state['clicked_button']['#name'] == 'delete-line-item-' . $line_item_id) {
commerce_cart_order_product_line_item_delete($order, $line_item_id);
}
}
}
}
<?php
// $Id$
/**
* @file
* Field handler to present a form field to change quantity of a line item. It's
* a dummy handler, most part of the implementation is done via post render
* hook.
*/
/**
* Field handler to present a field to change quantity of a line item.
*/
class commerce_line_item_handler_field_edit_quantity extends commerce_line_item_handler_field_edit {
function construct() {
parent::construct();
$this->additional_fields['quantity'] = 'quantity';
// Set real_field in order to make it generate a field_alias.
$this->real_field = 'quantity';
}
function render($values) {
$line_item_id = $values->{$this->aliases['line_item_id']};
$quantity = $values->{$this->aliases['quantity']};
// Add form that will be used for replacing the placeholder generated bellow
// at form rendering phase in commerce_line_item_views_post_render().
$this->form[$line_item_id] = array(
'#type' => 'textfield',
'#datatype' => 'integer',
'#default_value' => round($quantity),
'#size' => 4,
'#maxlength' => 8,
);
// Return the placeholder that will be used as token for replacing itself
// with the rendered form field created above.
return '<!--post-commerce-line-item-edit_quantity-' . $line_item_id . '-->';
}
function edit_form_validate($form, &$form_state) {
foreach (element_children($form['edit_quantity']) as $line_item_id) {
// Ensure the quantity is actually a numeric value.
if (!is_numeric($form_state['values']['edit_quantity'][$line_item_id]) || $form_state['values']['edit_quantity'][$line_item_id] <= 0) {
form_set_error('edit_quantity][' . $line_item_id, t('You must specify a positive number for the quantity'));
}
// If the custom data type attribute of the quantity element is integer,
// ensure we only accept whole number values.
if ($form['edit_quantity'][$line_item_id]['#datatype'] == 'integer' &&
(int) $form_state['values']['edit_quantity'][$line_item_id] != $form_state['values']['edit_quantity'][$line_item_id]) {
form_set_error('edit_quantity][' . $line_item_id, t('You must specify a whole number for the quantity.'));
}
}
}
function edit_form_submit($form, &$form_state) {
foreach (element_children($form['edit_quantity']) as $line_item_id) {
// If the line item hasn't been deleted...
if ($line_item = commerce_line_item_load($line_item_id)) {
// And the quantity on the form is different...
if ($form_state['values']['edit_quantity'][$line_item_id] != $line_item->quantity) {
// Change the quantity and save the line item.
$line_item->quantity = $form_state['values']['edit_quantity'][$line_item_id];
commerce_line_item_save($line_item);
}
}
}
}
}
<?php
// $Id$
/**
* Field handler to translate a line item type into its readable form.
*/
class commerce_line_item_handler_field_line_item_type extends views_handler_field {
function render($values) {
$value = commerce_line_item_type_get_name($values->{$this->field_alias});
return check_plain($value);
}
}
<?php
// $Id$
/**
* Filter by line item type
*/
class commerce_line_item_handler_filter_line_item_type extends views_handler_filter_in_operator {
function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Line item type');
foreach (commerce_line_item_type_get_name() as $type => $name) {
$options[$type] = $name;
}
$this->value_options = $options;
}
}
}
/* $Id$ */
.line-item-summary {
text-align: right;
margin-bottom: 1em;
}
.line-item-summary .line-item-quantity {
float: left;
}
.line-item-summary .line-item-total-label {
font-weight: bold;
}
.line-item-summary .links {
margin-top: .5em;
clear: left;
}
.line-item-summary .links li.last {
padding-right: 0;
}
; $Id$
name = Line Item UI
description = Exposes a default UI for Line Items through line item type forms and default Views.
package = Commerce
core = 7.x
dependencies[] = contextual
dependencies[] = field_ui
dependencies[] = commerce_ui
dependencies[] = commerce_line_item
dependencies[] = views
<?php
// $Id$
/**
* @file
* Provides the user interface for managing Line Item types.
*/
/**
* Implements hook_menu().
*/
function commerce_line_item_ui_menu() {
$items = array();
$items['admin/commerce/config/line-items'] = array(
'title' => 'Line item types',
'description' => 'Manage line item types for your store.',
'page callback' => 'commerce_line_item_ui_types_overview',
'access arguments' => array('administer line item types'),
'file' => 'includes/commerce_line_item_ui.types.inc',
);
$items['admin/commerce/config/line-items/%'] = array(
'title' => 'Line item types',
'page callback' => 'commerce_line_item_ui_line_item_type_redirect',
'page arguments' => array(4),
'access arguments' => array('administer line item types'),
);
return $items;
}
/**
* Redirects a line item type URL to its fields management page.
*/
function commerce_line_item_ui_line_item_type_redirect($type) {
drupal_goto('admin/commerce/config/line-items/' . strtr($type, array('_' => '-')) . '/fields');
}
/**
* Implements hook_menu_alter().
*/
function commerce_line_item_ui_menu_alter(&$items) {
// Transform the field UI tabs into contextual links.
$items['admin/commerce/config/line-items/%/fields']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
$items['admin/commerce/config/line-items/%/display']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
}
/**
* Implements hook_theme().
*/
function commerce_line_item_ui_theme() {
return array(
'line_item_type_admin_overview' => array(
'variables' => array('type' => NULL),
'file' => 'includes/commerce_line_item_ui.types.inc',
),
);
}
/**
* Implements hook_entity_info_alter().
*
* Expose the admin UI for line item fields.
*/
function commerce_line_item_ui_entity_info_alter(&$entity_info) {
foreach ($entity_info['commerce_line_item']['bundles'] as $type => &$bundle) {
$bundle['admin'] = array(
'path' => 'admin/commerce/config/line-items/%',
'real path' => 'admin/commerce/config/line-items/' . $type,
'access arguments' => array('administer line item types'),
);
}
}
/**
* Implements hook_form_alter().
*/
function commerce_line_item_ui_form_alter(&$form, &$form_state, $form_id) {
// On field administration forms for line item types set the title.
if (in_array($form_id, array('field_ui_field_overview_form', 'field_ui_display_overview_form'))) {
if ($form['#entity_type'] == 'commerce_line_item') {
// Load the line item type being modified for this form.
$line_item_type = commerce_line_item_type_load($form['#bundle']);
drupal_set_title(check_plain($line_item_type['name']));
}
}
}
/**
* Implements hook_views_api().
*
function commerce_line_item_ui_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'commerce_line_item_ui') . '/includes/views',
);
}*/
/* $Id$ */
.links.operations {
text-transform: lowercase;
}
<?php
// $Id$
/**
* Menu callback: display an overview of available types.
*/
function commerce_line_item_ui_types_overview() {
drupal_add_css(drupal_get_path('module', 'commerce_line_item_ui') .'/theme/commerce_line_item_ui.types.css');
$header = array(
t('Name'),
t('Operations'),
);
$rows = array();
// Loop through all defined line item types.
foreach (commerce_line_item_types() as $type => $line_item_type) {
// Build the operation links for the current line item type.
$links = menu_contextual_links('commerce-line-item-type', 'admin/commerce/config/line-items', array($type));
// Add the line item type's row to the table's rows array.
$rows[] = array(
theme('line_item_type_admin_overview', array('line_item_type' => $line_item_type)),
theme('links', array('links' => $links, 'attributes' => array('class' => 'links inline operations'))),
);
}
// If no line item types are defined...
if (empty($rows)) {
// Add a standard empty row with a link to add a new line item type.
$rows[] = array(
array(
'data' => t('There are no line item types defined by enabled modules.'),
'colspan' => 2,
)
);
}
return theme('table', array('header' => $header, 'rows' => $rows));
}
/**
* Builds an overview of a line item type for display to an administrator.
*
* @param $variables
* An array of variables used to generate the display; by default includes the
* type key with a value of the line item type object.
*
* @ingroup themeable
*/
function theme_line_item_type_admin_overview($variables) {
$line_item_type = $variables['line_item_type'];
$output = check_plain($line_item_type['name']);
$output .= ' <small> (Machine name: ' . check_plain($line_item_type['type']) . ')</small>';
$output .= '<div class="description">' . filter_xss_admin($line_item_type['description']) . '</div>';
return $output;
}
/* $Id$ */
.commerce-line-item-views-form input.delete-line-item {
font-size: 0.8em;
padding: 1px 6px;
}
.commerce-line-item-views-form .cart-subtotal {
text-align: right;
font-size: 1.5em;
}
.commerce-line-item-views-form .commerce-line-item-actions {
text-align: right;
}
<?php
// $Id$
/**
* @file
* Hooks provided by the Order module.
*/
/**
* Allows you to prepare order data before it is saved.
*
* @param $order
* The order object to be saved.
*
* @see rules_invoke_all()
*/
function hook_commerce_order_presave(&$order) {
// No example.
}
<?php
// $Id$
/**
* @file
* Checkout pane callback functions for the Order module.
*/
/**
* Account pane: settings form callback.
*/
function commerce_order_account_pane_settings_form($checkout_pane) {
$form = array();
$form['commerce_order_account_pane_auth_display'] = array(
'#type' => 'checkbox',
'#title' => t('Display the account information pane for authenticated users.'),
'#description' => t('If checked, the pane will show account information to authenticated users but not allow changes.'),
'#default_value' => variable_get('commerce_order_account_pane_auth_display', FALSE)
);
return $form;
}
/**
* Account pane: form callback.
*/
function commerce_order_account_pane_checkout_form($form, &$form_state, $checkout_pane, $order) {
global $user;
$pane_form = array();
// If the user is logged in...
if ($user->uid > 0) {
// And the pane has been configured to display account information...
if (variable_get('commerce_order_account_pane_auth_display', FALSE)) {
// Note we're not using theme_username() to avoid linking out of checkout.
$pane_form['username'] = array(
'#type' => 'item',
'#title' => t('Username'),
'#markup' => check_plain($user->name),
);
$pane_form['mail'] = array(
'#type' => 'item',
'#title' => t('E-mail address'),
'#markup' => check_plain($order->mail),
);
}
}
else {
// Otherwise add an order e-mail address field to the form.
$pane_form['login'] = array(
'#type' => 'container',
'#prefix' => '<div id="account-login-container">',
'#suffix' => '</div>',
);
$pane_form['login']['mail'] = array(
'#type' => 'textfield',
'#title' => t('E-mail address'),
'#default_value' => $order->mail,
'#required' => TRUE,
);
}
return $pane_form;
}
/**
* Account pane: validatation callback.
*/
function commerce_order_account_pane_checkout_form_validate($form, &$form_state, $checkout_pane, $order) {
if (!empty($form_state['values'][$checkout_pane['pane_id']])) {
$pane_values = $form_state['values'][$checkout_pane['pane_id']];
// If the e-mail address field was present on the form...
if (!empty($pane_values['login']['mail'])) {
// Display an error if an invalid e-mail address was given.
if ($error = user_validate_mail($pane_values['login']['mail'])) {
form_set_error($checkout_pane['pane_id'] . '][login][mail', $error);
return FALSE;
}
}
}
return TRUE;
}
/**
* Account pane: checkout form submission callback.
*/
function commerce_order_account_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {
if (!empty($form_state['values'][$checkout_pane['pane_id']])) {
$pane_values = $form_state['values'][$checkout_pane['pane_id']];
if (!empty($pane_values['login']['mail'])) {
$order->mail = $pane_values['login']['mail'];
}
}
}
/**
* Account pane: returns the username and e-mail for the Review checkout pane.
*/
function commerce_order_account_pane_review($form, $form_state, $checkout_pane, $order) {
$content = array();
// Display the username if it's different from the e-mail address.
if ($order->uid > 0) {
$account = user_load($order->uid);
if ($account->name != $order->mail) {
// Note we're not using theme_username() to avoid linking out of checkout.
$content[] = array(
'#type' => 'item',
'#title' => t('Username'),
'#markup' => check_plain($account->name),
);
}
}
$content[] = array(
'#type' => 'item',
'#title' => t('E-mail address'),
'#markup' => check_plain($order->mail),
);
return drupal_render($content);
}
<?php
// $Id$
/**
* @file
* The controller for the order entity containing the CRUD operations.
*/
/**
* The controller class for orders contains methods for the order CRUD
* operations. The load method is inherited from the default controller.
*/
class CommerceOrderEntityController extends DrupalDefaultEntityController {
/**
* Create a default order.
*
* @param $uid
* Optionally the uid of the customer for the order.
* @param $status
* Optionally the order status of the new order.
* @param $type
* The type of the order; defaults to the standard 'order' type.
*
* @return
* An order object with all default fields initialized.
*/
public function create($uid = 0, $status = '', $type = 'commerce_order') {
return (object) array(
'order_id' => '',
'order_number' => '',
'revision_id' => '',
'type' => $type,
'uid' => $uid,
'mail' => ( $uid > 0 && ($account = user_load($uid)) ) ? $account->mail : '',
'status' => $status,
'data' => array(),
'created' => '',
'changed' => '',
);
}
/**
* Saves an order.
*
* When saving an order without an order ID, this function will create a new
* order at that time. For new orders, it will also determine and save the
* order number and then save the initial revision of the order. Subsequent
* orders that should be saved as new revisions should set $order->revision to
* TRUE and include a log string in $order->log.
*
* @param $order
* The full order object to save.
*
* @return
* The saved order object.
*/
public function save($order) {
$transaction = db_transaction();
try {
global $user;
// Determine if we will be inserting a new order.
$order->is_new = empty($order->order_id);
// Set the timestamp fields.
if (empty($order->created)) {
$order->created = REQUEST_TIME;
$order->hostname = ip_address();
}
$order->changed = REQUEST_TIME;
$order->revision_timestamp = REQUEST_TIME;
$order->revision_hostname = ip_address();
$update_order = TRUE;
// Update the order total field's value.
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
// First set the currency to the site default if this is a new order.
if ($order->is_new || $order_wrapper->commerce_order_total->currency_code->value() == NULL) {
$order_wrapper->commerce_order_total->currency_code = commerce_default_currency();
}
// Then loop over each line item and add its total to the order total.
$total = 0;
foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {
// Convert the line item's total to the order's currency for totalling.
$total += commerce_currency_convert(
$line_item_wrapper->commerce_total->amount->value(),
$line_item_wrapper->commerce_total->currency_code->value(),
$order_wrapper->commerce_order_total->currency_code->value()
);
}
$order_wrapper->commerce_order_total->amount = $total;
// Give modules the opportunity to prepare field data for saving.
rules_invoke_all('commerce_order_presave', $order);
field_attach_presave('commerce_order', $order);
if ($order->is_new || !empty($order->revision)) {
// When inserting either a new order or revision, $order->log must be set
// because {commerce_order_revision}.log is a text column and therefore
// cannot have a default value. However, it might not be set at this
// point, so we ensure that it is at least an empty string in that case.
if (!isset($order->log)) {
$order->log = '';
}
}
elseif (empty($order->log)) {
// If we are updating an existing order without adding a new revision,
// we need to make sure $order->log is unset whenever it is empty. As
// long as $order->log is unset, drupal_write_record() will not attempt
// to update the existing database column when re-saving the revision.
unset($order->log);
}
// When saving a new order revision, unset any existing $order->revision_id
// to ensure a new revision will actually be created and store the old
// revision ID in a separate property for order hook implementations.
if (!$order->is_new && !empty($order->revision) && $order->revision_id) {
$order->old_revision_id = $order->revision_id;
unset($order->revision_id);
}
// If this is a new order...
if ($order->is_new) {
// Save the new order.
drupal_write_record('commerce_order', $order);
// Save the order number.
// TODO: Provide token support for order number patterns.
$order->order_number = $order->order_id;
db_update('commerce_order')
->fields(array('order_number' => $order->order_number))
->condition('order_id', $order->order_id)
->execute();
// Save the initial revision.
$this->saveRevision($order, $user->uid);
$op = 'insert';
}
else {
// Invoke presave to let modules react before the save.
rules_invoke_all('commerce_order_presave', $order);
// Save the updated order.
drupal_write_record('commerce_order', $order, 'order_id');
// If a new order revision was requested, save a new record for that;
// otherwise, update the order revision record that matches the value
// of $order->revision_id.
if (!empty($order->revision)) {
$this->saveRevision($order, $user->uid);
}
else {
$this->saveRevision($order, $user->uid, TRUE);
$update_order = FALSE;
}
$op = 'update';
}
// If the revision ID is new or updated, save it to the order.
if ($update_order) {
db_update('commerce_order')
->fields(array('revision_id' => $order->revision_id))
->condition('order_id', $order->order_id)
->execute();
}
// Save fields.
$function = 'field_attach_' . $op;
$function('commerce_order', $order);
module_invoke_all('commerce_order_' . $op, $order);
module_invoke_all('entity_' . $op, $order, 'commerce_order');
rules_invoke_event('commerce_order_' . $op, $order);
// Clear internal properties.
unset($order->is_new);
$this->resetCache();
// Ignore slave server temporarily to give time for the saved order to be
// propagated to the slave.
db_ignore_slave();
return $order;
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('commerce_order', $e);
throw $e;
}
}
/**
* Saves an order revision with the uid of the current user.
*
* @param $order
* The fully loaded order object.
* @param $uid
* The user's uid for the current revision.
* @param $update
* TRUE or FALSE indicating whether or not the existing revision should be
* updated instead of a new one created.
*/
function saveRevision($order, $uid, $update = FALSE) {
$order->revision_uid = $uid;
// Update the existing revision if specified.
if ($update) {
drupal_write_record('commerce_order_revision', $order, 'revision_id');
}
else {
// Otherwise insert a new revision. This will automatically update $order
// to include the revision_id.
drupal_write_record('commerce_order_revision', $order);
}
}
/**
* Unserializes the data property of loaded orders.
*/
public function attachLoad(&$queried_orders, $revision_id = FALSE) {
foreach ($queried_orders as $order_id => $order) {
$queried_orders[$order_id]->data = unserialize($order->data);
}
// Call the default attachLoad() method. This will add fields and call
// hook_user_load().
parent::attachLoad($queried_orders, $revision_id);
}
/**
* Deletes multiple orders by ID.
*
* @param $order_ids
* An array of order IDs to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
public function delete($order_ids) {
if (!empty($order_ids)) {
$orders = $this->load($order_ids, array());
$transaction = db_transaction();
try {
// Loop through the orders to delete.
foreach ($orders as $order_id => $order) {
// Alert other modules and systems that the order is being deleted.
module_invoke_all('commerce_order_delete', $order);
field_attach_delete('commerce_order', $order);
rules_invoke_event('commerce_order_delete', $order);
}
db_delete('commerce_order')
->condition('order_id', $order_ids, 'IN')
->execute();
db_delete('commerce_order_revision')
->condition('order_id', $order_ids, 'IN')
->execute();
// Ignore slave server temporarily to give time for the
// saved order to be propagated to the slave.
db_ignore_slave();
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('commerce_order', $e);
throw $e;
}
// Clear the page and block and order_load_multiple caches.
cache_clear_all();
$this->resetCache();
}
return TRUE;
}
}
<?php
// $Id$
/**
* @file
* Forms for creating / editing and deleting orders.
*/
/**
* Form callback: create or edit an order.
*
* @param $order
* The order object to edit through the form.
*/
function commerce_order_order_form($form, &$form_state, $order) {
// Ensure this include file is loaded when the form is rebuilt from the cache.
$form_state['build_info']['files']['form'] = drupal_get_path('module', 'commerce_order') . '/includes/commerce_order.forms.inc';
// Ensure the owner name is accessible if the uid is set.
if (!empty($order->uid) && $owner = user_load($order->uid)) {
$order->name = $owner->name;
if (empty($order->mail)) {
$order->mail = $owner->mail;
}
}
if (empty($order->created)) {
$order->date = format_date(REQUEST_TIME, 'custom', 'Y-m-d H:i:s O');
}
// Add the field related form elements.
$form_state['commerce_order'] = $order;
field_attach_form('commerce_order', $order, $form, $form_state);
// Hide the order total field from direct editing.
$form['commerce_order_total']['#access'] = FALSE;
$form['additional_settings'] = array(
'#type' => 'vertical_tabs',
'#weight' => 99,
);
// Build an array of order status options grouped by order state.
$options = array();
foreach (commerce_order_state_get_title() as $name => $title) {
foreach (commerce_order_statuses(array('state' => $name)) as $order_status) {
$options[check_plain($title)][$order_status['name']] = check_plain($order_status['title']);
}
}
// Add a section to update the status and leave a log message.
$form['order_status'] = array(
'#type' => 'fieldset',
'#title' => t('Order status'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#group' => 'additional_settings',
'#attached' => array(
'js' => array(
drupal_get_path('module', 'commerce_order') . '/commerce_order.js',
array(
'type' => 'setting',
'data' => array('status_titles' => commerce_order_status_get_title()),
),
),
),
'#weight' => 20,
);
$form['order_status']['status'] = array(
'#type' => 'select',
'#title' => t('Status'),
'#options' => $options,
'#default_value' => $order->status,
);
$form['order_status']['status_original'] = array(
'#type' => 'hidden',
'#value' => $order->status,
'#attributes' => array('id' => 'edit-status-original'),
);
$form['order_status']['log'] = array(
'#type' => 'textarea',
'#title' => t('Update log message'),
'#description' => t('Provide an explanation of the changes you are making. This will provide a meaningful audit trail for updates to this order.'),
'#rows' => 4,
);
// Add the user account and e-mail fields.
$form['user'] = array(
'#type' => 'fieldset',
'#title' => t('User information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#access' => user_access('administer orders'),
'#group' => 'additional_settings',
'#attached' => array(
'js' => array(
drupal_get_path('module', 'commerce_order') . '/commerce_order.js',
array(
'type' => 'setting',
'data' => array('anonymous' => variable_get('anonymous', t('Anonymous'))),
),
),
),
'#weight' => 30,
);
$form['user']['name'] = array(
'#type' => 'textfield',
'#title' => t('Owned by'),
'#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
'#maxlength' => 60,
'#autocomplete_path' => 'user/autocomplete',
'#default_value' => !empty($order->name) ? $order->name : '',
'#weight' => -1,
);
$form['user']['mail'] = array(
'#type' => 'textfield',
'#title' => t('Order contact e-mail'),
'#description' => t('Defaults to the owner account e-mail address if left blank. Used for order communication.'),
'#default_value' => $order->mail,
);
// Add a log checkbox and timestamp field to a history tab.
$form['order_history'] = array(
'#type' => 'fieldset',
'#title' => t('Order history', array(), array('context' => 'a drupal commerce order')),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#group' => 'additional_settings',
'#attached' => array(
'js' => array(drupal_get_path('module', 'commerce_order') . '/commerce_order.js'),
),
'#weight' => 40,
);
$form['order_history']['revision'] = array(
'#type' => 'checkbox',
'#title' => t('Create new revision on update'),
'#description' => t('If an update log message is entered, a revision will be created even if this is unchecked.'),
'#default_value' => TRUE,
'#access' => user_access('administer orders'),
);
$form['order_history']['date'] = array(
'#type' => 'textfield',
'#title' => t('Created on'),
'#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => !empty($order->date) ? date_format(date_create($order->date), 'Y-m-d H:i:s O') : format_date($order->created, 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($order->date) ? date_format(date_create($order->date), 'O') : format_date($order->created, 'custom', 'O'))),
'#maxlength' => 25,
'#default_value' => !empty($order->created) ? format_date($order->created, 'custom', 'Y-m-d H:i:s O') : '',
);
$form['order_history']['created'] = array(
'#type' => 'hidden',
'#value' => !empty($order->created) ? format_date($order->created, 'short') : '',
'#attributes' => array('id' => 'edit-created'),
);
$form['order_history']['changed'] = array(
'#type' => 'hidden',
'#value' => !empty($order->changed) ? format_date($order->changed, 'short') : '',
'#attributes' => array('id' => 'edit-changed'),
);
$form['actions'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('form-actions')),
'#weight' => 100,
);
// We add the form's #submit array to this button along with the actual submit
// handler to preserve any submit handlers added by a form callback_wrapper.
$submit = array();
if (!empty($form['#submit'])) {
$submit += $form['#submit'];
}
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save order', array(), array('context' => 'a drupal commerce order')),
'#submit' => $submit + array('commerce_order_order_form_submit'),
'#weight' => 40,
);
// We append the validate handler to #validate in case a form callback_wrapper
// is used to add validate handlers earlier.
$form['#validate'][] = 'commerce_order_order_form_validate';
return $form;
}
/**
* Validation callback for commerce_order_order_form().
*/
function commerce_order_order_form_validate($form, &$form_state) {
$order = $form_state['commerce_order'];
// Validate the "owned by" field.
if (!empty($form_state['values']['name']) && !($account = user_load_by_name($form_state['values']['name']))) {
// The use of empty() is mandatory in the context of usernames as the empty
// string denotes an anonymous user.
form_set_error('name', t('The username %name does not exist.', array('%name' => $form_state['values']['name'])));
}
// Validate the "created on" field.
if (!empty($form_state['values']['date']) && strtotime($form_state['values']['date']) === FALSE) {
form_set_error('date', t('You have to specify a valid date.'));
}
// TODO: Pending token patterns for order numbers, validate the characters and
// the final string for uniqueness.
// Notify field widgets to validate their data.
field_attach_form_validate('commerce_order', $order, $form, $form_state);
}
/**
* Submit callback for commerce_order_order_form().
*/
function commerce_order_order_form_submit($form, &$form_state) {
global $user;
// If the user is editing an order, load a fresh copy to merge changes to.
if ($form_state['commerce_order']->order_id) {
$form_state['commerce_order'] = commerce_order_load($form_state['commerce_order']->order_id);
}
// Merge changes into the order object in the form state so it is accessible
// by field handlers.
$order = $form_state['commerce_order'];
if ($form_state['values']['revision'] || !empty($form_state['values']['log'])) {
$order->revision = TRUE;
$order->log = $form_state['values']['log'];
}
// Set the order's owner uid based on the supplied name.
if (!empty($form_state['values']['name']) && $account = user_load_by_name($form_state['values']['name'])) {
$order->uid = $account->uid;
if (empty($form_state['values']['mail'])) {
$order->mail = $account->mail;
}
}
else {
$order->uid = 0;
}
if (!empty($form_state['values']['mail'])) {
$order->mail = $form_state['values']['mail'];
}
$order->created = !empty($form_state['values']['date']) ? strtotime($form_state['values']['date']) : REQUEST_TIME;
// Notify field widgets.
field_attach_submit('commerce_order', $order, $form, $form_state);
// Ensure the attached customer profiles are associated with the order owner
// if they do not have a uid yet and the order does.
if ($order->uid) {
$entity_info = entity_get_info('commerce_customer_profile');
$wrapper = entity_metadata_wrapper('commerce_order', $order);
foreach ($entity_info['bundles'] as $profile_type => $data) {
$field_name = 'commerce_customer_' . $profile_type;
if ($wrapper->{$field_name}->uid->value() == 0) {
$wrapper->{$field_name}->uid = $order->uid;
$wrapper->{$field_name}->save();
}
}
}
// Update the order status if specified.
if ($form_state['values']['status'] != $form_state['values']['status_original']) {
// We skip order saving in the update since we do it below once for the
// entire form submission.
commerce_order_status_update($order, $form_state['values']['status'], TRUE);
}
// Save the order.
$order = commerce_order_save($order);
}
/**
* Form callback: confirmation form for deleting an order.
*
* @param $order
* The order object to delete through the form.
*
* @return
* The form array to add or edit an order.
*
* @see confirm_form()
*/
function commerce_order_order_delete_form($form, &$form_state, $order) {
$form_state['order'] = $order;
// Ensure this include file is loaded when the form is rebuilt from the cache.
$form_state['build_info']['files']['form'] = drupal_get_path('module', 'commerce_order') . '/includes/commerce_order.forms.inc';
$form['#submit'][] = 'commerce_order_order_delete_form_submit';
$form = confirm_form($form,
t('Are you sure you want to delete order @number?', array('@number' => $order->order_number)),
'',
'<p>' . t('Deleting this order cannot be undone.') . '</p>',
t('Delete'),
t('Cancel'),
'confirm'
);
return $form;
}
/**
* Submit callback for commerce_order_order_delete_form().
*/
function commerce_order_order_delete_form_submit($form, &$form_state) {
$order = $form_state['order'];
if (commerce_order_delete($order->order_id)) {
drupal_set_message(t('Order @number has been deleted.', array('@number' => $order->order_number)));
watchdog('commerce_order', 'Deleted order @number.', array('@number' => $order->order_number), WATCHDOG_NOTICE);
}
else {
drupal_set_message(t('Order @number could not be deleted.', array('@number' => $order-->order_number)), 'error');
}
}
; $Id$
name = Order
description = Defines the Order entity and associated features.
package = Commerce
dependencies[] = rules
dependencies[] = commerce
dependencies[] = commerce_customer
dependencies[] = commerce_line_item
core = 7.x
; Module includes
files[] = includes/commerce_order.controller.inc
; Views includes
files[] = includes/views/commerce_order.views.inc
; Views handlers
files[] = includes/views/handlers/commerce_order_handler_argument_order_order_id.inc
files[] = includes/views/handlers/commerce_order_handler_field_order.inc
files[] = includes/views/handlers/commerce_order_handler_field_order_status.inc
files[] = includes/views/handlers/commerce_order_handler_field_order_state.inc
files[] = includes/views/handlers/commerce_order_handler_field_order_type.inc
files[] = includes/views/handlers/commerce_order_handler_field_order_link.inc
files[] = includes/views/handlers/commerce_order_handler_field_order_link_delete.inc
files[] = includes/views/handlers/commerce_order_handler_field_order_link_edit.inc
files[] = includes/views/handlers/commerce_order_handler_field_order_operations.inc
files[] = includes/views/handlers/commerce_order_handler_filter_order_status.inc
files[] = includes/views/handlers/commerce_order_handler_filter_order_state.inc
files[] = includes/views/handlers/commerce_order_handler_filter_order_type.inc
; Views plugins
files[] = includes/views/handlers/commerce_order_plugin_argument_validate_user.inc
; Simple tests
; files[] = tests/commerce_order.test
<?php
// $Id$
/**
* @file
* Provides metadata for the order entity.
*/
/**
* Implements hook_entity_property_info().
*/
function commerce_order_entity_property_info() {
$info = array();
// Add meta-data about the basic commerce_order properties.
$properties = &$info['commerce_order']['properties'];
$properties['order_id'] = array(
'label' => t('Order ID', array(), array('context' => 'a drupal commerce order')),
'description' => t('The internal numeric ID of the order.'),
'type' => 'integer',
);
$properties['order_number'] = array(
'label' => t('Order number', array(), array('context' => 'a drupal commerce order')),
'description' => t('The order number displayed to the customer.'),
'type' => 'text',
'setter callback' => 'entity_property_verbatim_set',
'required' => TRUE,
'query callback' => 'entity_metadata_table_query',
);
$properties['view_url'] = array(
'label' => t('View URL'),
'description' => t('The URL a customer can visit to view the order.'),
'getter callback' => 'commerce_order_get_properties',
'type' => 'uri',
);
$properties['admin_url'] = array(
'label' => t('Admin URL'),
'description' => t("The URL of the order's administrative view page."),
'getter callback' => 'commerce_order_get_properties',
'type' => 'uri',
);
$properties['edit_url'] = array(
'label' => t('Edit URL'),
'description' => t("The URL of the order's edit page."),
'getter callback' => 'commerce_order_get_properties',
'type' => 'uri',
);
$properties['status'] = array(
'label' => t('Status'),
'description' => t('The current status of the order.'),
'type' => 'text',
'setter callback' => 'entity_property_verbatim_set',
'options list' => 'commerce_order_status_options_list',
'query callback' => 'entity_metadata_table_query',
'required' => TRUE,
);
$properties['state'] = array(
'label' => t('State'),
'description' => t('The state of the order derived from its status.'),
'type' => 'token',
'getter callback' => 'commerce_order_get_properties',
'options list' => 'commerce_order_state_options_list',
);
$properties['created'] = array(
'label' => t('Date created'),
'description' => t('The date the order was created.'),
'type' => 'date',
'setter callback' => 'entity_property_verbatim_set',
'query callback' => 'entity_metadata_table_query',
'setter permission' => 'administer orders',
);
$properties['changed'] = array(
'label' => t('Date changed'),
'description' => t('The date the order was most recently updated.'),
'type' => 'date',
'setter callback' => 'entity_property_verbatim_set',
'query callback' => 'entity_metadata_table_query',
'setter permission' => 'administer orders',
);
$properties['type'] = array(
'label' => t('Type'),
'description' => t('The human readable name of the order type.'),
'type' => 'text',
'options list' => 'commerce_order_type_options_list',
'required' => TRUE,
'query callback' => 'entity_metadata_table_query',
);
$properties['uid'] = array(
'label' => t("Owner ID"),
'type' => 'integer',
'description' => t("The unique ID of the order owner."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer orders',
'clear' => array('owner'),
'query callback' => 'entity_metadata_table_query',
);
$properties['owner'] = array(
'label' => t("Owner"),
'type' => 'user',
'description' => t("The owner of the order."),
'getter callback' => 'commerce_order_get_properties',
'setter callback' => 'commerce_order_set_properties',
'setter permission' => 'administer orders',
'required' => TRUE,
'clear' => array('uid'),
);
$properties['mail'] = array(
'label' => t('Order e-mail'),
'description' => t('The e-mail address associated with this order.'),
'setter callback' => 'entity_property_verbatim_set',
'validation callback' => 'valid_email_address',
'query callback' => 'entity_metadata_table_query',
);
return $info;
}
/**
* Implements hook_entity_property_info_alter() on top of the Order module.
*/
function commerce_order_entity_property_info_alter(&$info) {
// Move the line items and order total properties to the order by default; as
// they are required default fields, this makes dealing with them more convenient.
$properties = array();
foreach ($info['commerce_order']['bundles'] as $bundle => $bundle_info) {
$bundle_info += array('properties' => array());
$properties += $bundle_info['properties'];
}
$info['commerce_order']['properties']['commerce_line_items'] = $properties['commerce_line_items'];
$info['commerce_order']['properties']['commerce_order_total'] = $properties['commerce_order_total'];
}
<?php
// $Id$
/**
* Implements hook_schema().
*/
function commerce_order_schema() {
$schema = array();
$schema['commerce_order'] = array(
'description' => 'The base table for orders.',
'fields' => array(
'order_id' => array(
'description' => 'The primary identifier for an order.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'order_number' => array(
'description' => 'The order number displayed to the customer.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'revision_id' => array(
'description' => 'The current {commerce_order_revision}.revision_id version identifier.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'type' => array(
'description' => 'The type of this order.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'uid' => array(
'description' => 'The {users}.uid that owns this order.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'mail' => array(
'description' => 'The e-mail address associated with the order.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'status' => array(
'description' => 'The status name of this order.',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
),
'created' => array(
'description' => 'The Unix timestamp when the order was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'changed' => array(
'description' => 'The Unix timestamp when the order was most recently saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'hostname' => array(
'description' => 'The IP address that created this order.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'data' => array(
'type' => 'blob',
'not null' => FALSE,
'size' => 'big',
'serialize' => TRUE,
'description' => 'A serialized array of additional data.',
),
),
'primary key' => array('order_id'),
'unique keys' => array(
'order_number' => array('order_number'),
'revision_id' => array('revision_id'),
),
'foreign keys' => array(
'current_revision' => array(
'table' => 'commerce_order_revision',
'columns'=> array('revision_id' => 'revision_id'),
),
'owner' => array(
'table' => 'users',
'columns' => array('uid' => 'uid'),
),
),
);
$schema['commerce_order_revision'] = array(
'description' => 'Saves information about each saved revision of a {commerce_order}.',
'fields' => array(
'order_id' => array(
'description' => 'The {commerce_order}.order_id of the order this revision belongs to.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'order_number' => array(
'description' => 'The order number displayed to the customer for this revision.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'revision_id' => array(
'description' => 'The primary identifier for this revision.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'revision_uid' => array(
'description' => 'The {users}.uid that owns the order at this revision.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'mail' => array(
'description' => 'The e-mail address associated with the order at this revision.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'status' => array(
'description' => 'The status name of this revision.',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
),
'log' => array(
'description' => 'The log entry explaining the changes in this version.',
'type' => 'text',
'not null' => TRUE,
'size' => 'big',
),
'revision_timestamp' => array(
'description' => 'The Unix timestamp when this revision was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'revision_hostname' => array(
'description' => 'The IP address that created this order.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'data' => array(
'type' => 'blob',
'not null' => FALSE,
'size' => 'big',
'serialize' => TRUE,
'description' => 'A serialized array of additional data.',
),
),
'primary key' => array('revision_id'),
'foreign keys' => array(
'order' => array(
'table' => 'commerce_order',
'columns'=> array('order_id' => 'order_id'),
),
'owner' => array(
'table' => 'users',
'columns' => array('uid' => 'uid'),
),
),
);
return $schema;
}
/**
* Implements hook_uninstall().
*/
function commerce_order_uninstall() {
// Remove customer profile reference fields.
$entity_info = entity_get_info('commerce_customer_profile');
foreach ($entity_info['bundles'] as $type => $data) {
field_delete_field('commerce_customer_' . $type);
}
field_delete_field('commmerce_line_items');
field_delete_field('commerce_order_total');
}
// $Id$
(function ($) {
Drupal.behaviors.orderFieldsetSummaries = {
attach: function (context) {
$('fieldset#edit-order-status', context).drupalSetSummary(function (context) {
// If the status has been changed, indicate the original status.
if ($('#edit-status').val() != $('#edit-status-original').val()) {
return Drupal.t('From @title', { '@title' : Drupal.settings.status_titles[$('#edit-status-original').val()] }) + '<br />' + Drupal.t('To @title', { '@title' : Drupal.settings.status_titles[$('#edit-status').val()] });
}
else {
return Drupal.settings.status_titles[$('#edit-status').val()];
}
});
$('fieldset#edit-user', context).drupalSetSummary(function (context) {
var name = $('#edit-name').val() || Drupal.settings.anonymous,
mail = $('#edit-mail').val();
return mail ?
Drupal.t('Owned by @name', { '@name' : name }) + '<br />' + mail :
Drupal.t('Owned by @name', { '@name': name });
});
$('fieldset#edit-order-history', context).drupalSetSummary(function (context) {
var summary = $('#edit-created', context).val() ?
Drupal.t('Created @date', { '@date' : $('#edit-created').val() }) :
Drupal.t('New order');
// Add the changed date to the summary if it's different from the created.
if ($('#edit-created', context).val() != $('#edit-changed', context).val()) {
summary += '<br />' + Drupal.t('Updated @date', { '@date' : $('#edit-changed').val() });
}
return summary;
});
}
};
})(jQuery);
<?php
// $Id$
/**
* @file
* Defines the core Commerce order entity and API functions to manage orders and
* interact with them.
*/
/**
* Implements hook_entity_info().
*/
function commerce_order_entity_info() {
$return = array(
'commerce_order' => array(
'label' => t('Order', array(), array('context' => 'a drupal commerce order')),
'controller class' => 'CommerceOrderEntityController',
'base table' => 'commerce_order',
'revision table' => 'commerce_order_revision',
'fieldable' => TRUE,
'entity keys' => array(
'id' => 'order_id',
'bundle' => 'type',
'revision' => 'revision_id',
'label' => 'order_number',
),
'bundle keys' => array(
'bundle' => 'type',
),
'bundles' => array(
'commerce_order' => array(
'label' => t('Order', array(), array('context' => 'a drupal commerce order')),
),
),
'load hook' => 'commerce_order_load',
'view modes' => array(
'administrator' => array(
'label' => t('Administrator'),
'custom settings' => FALSE,
),
'customer' => array(
'label' => t('Customer'),
'custom settings' => FALSE,
),
),
'creation callback' => '_commerce_order_create',
'save callback' => 'commerce_order_save',
'deletion callback' => 'commerce_order_delete',
'access callback' => 'commerce_order_access',
'token type' => 'order',
),
);
return $return;
}
/**
* Implements hook_enable().
*/
function commerce_order_enable() {
// Add the line item reference field to the default order type.
commerce_order_configure_order_type();
}
/**
* Implements hook_views_api().
*/
function commerce_order_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'commerce_order') . '/includes/views',
);
}
/**
* Implements hook_permission().
*/
function commerce_order_permission() {
$permissions = array(
'administer orders' => array(
'title' => t('Administer orders'),
'restrict access' => TRUE,
),
'access orders' => array(
'title' => t('Access lists of orders, primarily for viewing and referencing'),
'restrict access' => TRUE,
),
'create orders' => array(
'title' => t('Create orders'),
'restrict access' => TRUE,
),
'edit any order' => array(
'title' => t('Edit any order'),
'restrict access' => TRUE,
),
'edit own orders' => array(
'title' => t('Edit own orders'),
'restrict access' => TRUE,
),
'view own orders' => array(
'title' => t('View own orders'),
),
);
return $permissions;
}
/**
* Implements hook_commerce_checkout_pane_info().
*/
function commerce_order_commerce_checkout_pane_info() {
$checkout_panes = array();
$checkout_panes['account'] = array(
'title' => t('Account information'),
'file' => 'includes/commerce_order.checkout_pane.inc',
'base' => 'commerce_order_account_pane',
'page' => 'checkout',
'weight' => -5,
);
return $checkout_panes;
}
/**
* Implements hook_commerce_order_state_info().
*/
function commerce_order_commerce_order_state_info() {
$order_states = array();
$order_states['canceled'] = array(
'name' => 'canceled',
'title' => t('Canceled'),
'description' => t('Orders in this state have been canceled through some user action.'),
'weight' => -10,
'default_status' => 'canceled',
);
$order_states['pending'] = array(
'name' => 'pending',
'title' => t('Pending'),
'description' => t('Orders in this state have been created and are awaiting further action.'),
'weight' => 0,
'default_status' => 'pending',
);
$order_states['completed'] = array(
'name' => 'complete',
'title' => t('Completed'),
'description' => t('Orders in this state have been completed as far as the customer is concerned.'),
'weight' => 10,
'default_status' => 'complete',
);
return $order_states;
}
/**
* Implements hook_commerce_order_status_info().
*/
function commerce_order_commerce_order_status_info() {
$order_statuses = array();
$order_statuses['canceled'] = array(
'name' => 'canceled',
'title' => t('Canceled'),
'state' => 'canceled',
);
$order_statuses['pending'] = array(
'name' => 'pending',
'title' => t('Pending'),
'state' => 'pending',
);
$order_statuses['processing'] = array(
'name' => 'processing',
'title' => t('Processing'),
'state' => 'pending',
'weight' => 5,
);
$order_statuses['completed'] = array(
'name' => 'completed',
'title' => t('Completed'),
'state' => 'completed',
);
return $order_statuses;
}
/**
* Returns the name of the specified order type or all names keyed by type if no
* type is specified.
*
* For Drupal Commerce 1.0, the decision was made to support order types at the
* database level but not to introduce their complexity into the UI. To that end
* order "types" (i.e. bundles) may only be defined by altering the entity info.
*
* This function merely traverses the bundles array looking for data instead of
* relying on a special hook.
*
* @param $type
* The order type whose name should be returned; corresponds to the bundle key
* in the order entity definition.
*
* @return
* Either the specified name, defaulting to the type itself if the name is not
* found, or an array of all names keyed by type if no type is passed in.
*/
function commerce_order_type_get_name($type = NULL) {
$names = array();
$entity = entity_get_info('commerce_order');
foreach ($entity['bundles'] as $key => $value) {
$names[$key] = $value['label'];
}
if (empty($type)) {
return $names;
}
if (empty($names[$type])) {
return check_plain($type);
}
else {
return $names[$type];
}
}
/**
* Wraps commerce_order_type_get_name() for the Entity module.
*/
function commerce_order_type_options_list() {
return commerce_order_type_get_name();
}
/**
* Returns an initialized order object.
*
* @param $uid
* The uid of the owner of the order.
* @param $status
* Optionally the order status of the new order.
* @param $type
* The type of the order; defaults to the standard 'order' type.
*
* @return
* An order object with all default fields initialized.
*/
function commerce_order_new($uid = 0, $status = '', $type = 'commerce_order') {
// If no status was specified, use the default Pending status.
if (empty($status)) {
$order_state = commerce_order_state_load('pending');
$status = $order_state['default_status'];
}
return entity_get_controller('commerce_order')->create($uid, $status, $type);
}
/**
* Creation callback for the Entity module.
*/
function _commerce_order_create($values = array()) {
// Create a new order owned by the specified user.
$order = commerce_order_new($values['uid']);
unset($values['uid']);
$wrapper = entity_metadata_wrapper('commerce_order', $order);
foreach ($values as $name => $value) {
$wrapper->$name->set($value);
}
return $wrapper->value();
}
/**
* Saves an order.
*
* @param $order
* The full order object to save. If $order->order_id is empty, a new order
* will be created.
*
* @return
* The saved order object.
*/
function commerce_order_save($order) {
return entity_get_controller('commerce_order')->save($order);
}
/**
* Loads an order by ID.
*/
function commerce_order_load($order_id) {
$orders = commerce_order_load_multiple(array($order_id), array());
return $orders ? reset($orders) : FALSE;
}
/**
* Loads an order by number.
*/
function commerce_order_load_by_number($order_number) {
$orders = commerce_order_load_multiple(array(), array('order_number' => $order_number));
return $orders ? reset($orders) : FALSE;
}
/**
* Loads multiple orders by ID or based on a set of matching conditions.
*
* @see entity_load()
*
* @param $order_ids
* An array of order IDs.
* @param $conditions
* An array of conditions on the {commerce_order} table in the form
* 'field' => $value.
* @param $reset
* Whether to reset the internal order loading cache.
*
* @return
* An array of order objects indexed by order_id.
*/
function commerce_order_load_multiple($order_ids = array(), $conditions = array(), $reset = FALSE) {
return entity_load('commerce_order', $order_ids, $conditions, $reset);
}
/**
* Generate an array for rendering the given order.
*
* @param $order
* A fully loaded order object.
* @param $view_mode
* The view mode for displaying the order, 'administrator' or 'customer'.
*
* @return
* An array as expected by drupal_render().
*/
function commerce_order_build_content($order, $view_mode = 'administrator') {
// Populate $order->content with a render() array.
// Remove previously built content, if it exists.
$order->content = array();
// Build fields content.
field_attach_prepare_view('commerce_order', array($order->order_id => $order), $view_mode);
entity_prepare_view('commerce_order', array($order->order_id => $order));
$order->content += field_attach_view('commerce_order', $order, $view_mode);
// Allow modules to make their own additions to the order content.
module_invoke_all('commerce_order_view', $order, $view_mode);
// Remove the content array from the order to avoid duplicate rendering.
$build = $order->content;
unset($order->content);
$build += array(
'#theme' => 'commerce_order',
'#order' => $order,
'#view_mode' => $view_mode,
);
// Allow modules to modify the final build array.
drupal_alter('commerce_order_view', $build);
return $build;
}
/**
* Deletes an order by ID.
*
* @param $order_id
* The ID of the order to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
function commerce_order_delete($order_id) {
return commerce_order_delete_multiple(array($order_id));
}
/**
* Deletes multiple orders by ID.
*
* @param $order_ids
* An array of order IDs to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
function commerce_order_delete_multiple($order_ids) {
return entity_get_controller('commerce_order')->delete($order_ids);
}
/**
* Checks order access for various operations.
*
* @param $op
* The operation being performed. One of 'view', 'update', 'create' or
* 'delete'.
* @param $order
* Optionally an order to check access for.
* @param $account
* The user to check for. Leave it to NULL to check for the current user.
*/
function commerce_order_access($op, $order = NULL, $account = NULL) {
global $user;
$account = isset($account) ? $account : $user;
// Grant access to any operation for any order for administrators.
if (user_access('administer orders', $account)) {
return TRUE;
}
// Grant view access to users with the administrative access orders permission
// or to a user's own orders with the view own orders permission.
if ($op == 'view') {
if (user_access('access orders', $account)) {
return TRUE;
}
elseif (user_access('view own orders', $account) && $order->uid == $account->uid) {
return TRUE;
}
}
if ($op == 'create' && user_access('create orders', $account)) {
return TRUE;
}
if (isset($order) && ($op == 'update' || $op == 'delete')) {
if (user_access('edit any order')) {
return TRUE;
}
// Others either don't have any access or must match the order uid.
if ($account->uid && user_access('edit own orders', $account) && $order->uid == $account->uid) {
return TRUE;
}
}
return FALSE;
}
/**
* Performs token replacement on an order number for valid tokens only.
*
* TODO: This function currently limits acceptable Tokens to Order ID with no
* ability to use Tokens for the Fields attached to the order. That might be
* fine for a core Token replacement, but we should at least open the
* $valid_tokens array up to other modules to enable various Tokens for use.
*
* @param $order_number
* The raw order number string including any tokens as entered.
* @param $order
* An order object used to perform token replacement on the number.
*
* @return
* The number with tokens replaced or FALSE if it included invalid tokens.
*/
function commerce_order_replace_number_tokens($order_number, $order) {
// Build an array of valid order number tokens.
$valid_tokens = array('order-id');
// Ensure that only valid tokens were used.
$invalid_tokens = FALSE;
foreach (token_scan($order_number) as $type => $token) {
if ($type !== 'order') {
$invalid_tokens = TRUE;
}
else {
foreach (array_keys($token) as $value) {
if (!in_array($value, $valid_tokens)) {
$invalid_tokens = TRUE;
}
}
}
}
// Register the error if an invalid token was detected.
if ($invalid_tokens) {
return FALSE;
}
return $order_number;
}
/**
* Validates an order number string for acceptable characters.
*
* @param $order_number
* The order number string to validate.
*
* @return
* TRUE or FALSE indicating whether or not the order number contains valid
* characters.
*/
function commerce_order_validate_number_characters($order_number) {
return preg_match('!^[A-Za-z0-9_-]+$!', $order_number);
}
/**
* Checks to see if a given order number already exists for another order.
*
* @param $order_number
* The string to match against existing order numbers.
* @param $order_id
* The ID of the order the number is for; an empty value represents the number
* is meant for a new order.
*
* @return
* TRUE or FALSE indicating whether or not the number exists for another
* order.
*/
function commerce_order_validate_number_unique($order_number, $order_id) {
// Look for an ID of an order matching the supplied number.
if ($match_id = db_query('SELECT order_id FROM {commerce_order} WHERE order_number = :order_number', array(':order_number' => $order_number))->fetchField()) {
// If this number is supposed to be for a new order or an order other than
// the one that matched...
if (empty($order_id) || $match_id != $order_id) {
return FALSE;
}
}
return TRUE;
}
/**
* Returns an array of all the order states keyed by name.
*
* Order states can only be defined by modules but may have settings overridden
* that are stored in the database (weight and the default status). When this
* function is first called, it will load all the states as defined by
* hook_commerce_order_state_info() and update them based on the data in the
* database. The final array will be cached for subsequent calls.
*/
function commerce_order_states() {
// First check the static cache for an order states array.
$order_states = &drupal_static(__FUNCTION__);
// If it did not exist, fetch the statuses now.
if (empty($order_states)) {
$order_states = module_invoke_all('commerce_order_state_info');
// Give other modules a chance to alter the order states.
drupal_alter('commerce_order_state_info', $order_states);
uasort($order_states, 'drupal_sort_weight');
}
return $order_states;
}
/**
* Resets the cached list of order statuses.
*/
function commerce_order_states_reset() {
$order_statuses = &drupal_static('commerce_order_statuses');
$order_statuses = NULL;
}
/**
* Returns an order state object.
*
* @param $name
* The machine readable name string of the state to return.
*
* @return
* The fully loaded state object or FALSE if not found.
*/
function commerce_order_state_load($name) {
$order_states = commerce_order_states();
if (isset($order_states[$name])) {
return $order_states[$name];
}
return FALSE;
}
/**
* Resets an order state by name to its module defined defaults.
*/
function commerce_order_state_reset($name) {
db_delete('commerce_order_state')
->condition('name', $name)
->execute();
}
/**
* Returns the human readable title of any or all order states.
*
* @param $name
* Optional parameter specifying the name of the order state whose title
* should be return.
*
* @return
* Either an array of all order state titles keyed by name or a string
* containing the human readable title for the specified state. If a state
* is specified that does not exist, this function returns FALSE.
*/
function commerce_order_state_get_title($name = NULL) {
$order_states = commerce_order_states();
// Return a state title if specified and it exists.
if (!empty($name)) {
if (isset($order_states[$name])) {
return $order_states[$name]['title'];
}
else {
// Return FALSE if it does not exist.
return FALSE;
}
}
// Otherwise turn the array values into the status title only.
foreach ($order_states as $key => $value) {
$order_states[$key] = $value['title'];
}
return $order_states;
}
/**
* Wraps commerce_order_state_get_title() for use by the Entity module.
*/
function commerce_order_state_options_list() {
return commerce_order_state_get_title();
}
/**
* Returns an array of some or all of the order statuses keyed by name.
*
* Order statuses can only be defined by modules but may have settings
* overridden that are stored in the database (weight and status). When this
* function is first called, it will load all the statuses as defined by
* hook_commerce_order_status_info() and update them based on the data in the
* database. The final array will be cached for subsequent calls.
*
* @param $conditions
* An array of conditions to filter the returned list by; for example, if you
* specify 'state' => 'cart' in the array, then only order statuses in the
* cart state would be included.
*
* @return
* The array of order status objects, keyed by status name.
*/
function commerce_order_statuses($conditions = array()) {
// First check the static cache for an order statuses array.
$order_statuses = &drupal_static(__FUNCTION__);
// If it did not exist, fetch the statuses now.
if (!isset($order_statuses)) {
$order_statuses = module_invoke_all('commerce_order_status_info');
// Merge in defaults.
foreach ($order_statuses as $name => $order_status) {
// Set some defaults for the checkout pane.
$defaults = array(
'cart' => FALSE,
'weight' => 0,
'status' => TRUE,
);
$order_status += $defaults;
$order_statuses[$name] = $order_status;
}
// Give other modules a chance to alter the order statuses.
drupal_alter('commerce_order_status_info', $order_statuses);
uasort($order_statuses, 'drupal_sort_weight');
}
// Apply conditions to the returned statuses if specified.
if (!empty($conditions)) {
$matching_statuses = array();
foreach ($order_statuses as $name => $order_status) {
// Check the status against the conditions array to determine whether to
// add it to the return array or not.
$valid = TRUE;
foreach ($conditions as $property => $value) {
// If the current value for the specified property on the pane does not
// match the filter value...
if ($order_status[$property] != $value) {
// Do not add it to the temporary array.
$valid = FALSE;
}
}
if ($valid) {
$matching_statuses[$name] = $order_status;
}
}
return $matching_statuses;
}
return $order_statuses;
}
/**
* Resets the cached list of order statuses.
*/
function commerce_order_statuses_reset() {
$order_statuses = &drupal_static('commerce_order_statuses');
$order_statuses = NULL;
}
/**
* Returns an order status object.
*
* @param $name
* The machine readable name string of the status to return.
*
* @return
* The fully loaded status object or FALSE if not found.
*/
function commerce_order_status_load($name) {
$order_statuses = commerce_order_statuses();
if (isset($order_statuses[$name])) {
return $order_statuses[$name];
}
return FALSE;
}
/**
* Resets an order status by name to its module defined defaults.
*/
function commerce_order_status_reset($name) {
db_delete('commerce_order_status')
->condition('name', $name)
->execute();
}
/**
* Returns the human readable title of any or all order statuses.
*
* @param $name
* Optional parameter specifying the name of the order status whose title
* to return.
*
* @return
* Either an array of all order status titles keyed by the status_id or a
* string containing the human readable title for the specified status. If a
* status is specified that does not exist, this function returns FALSE.
*/
function commerce_order_status_get_title($name = NULL) {